├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── lint.yml │ └── vulns.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── Taskfile.yml ├── client.go ├── client_test.go ├── doc.go ├── entity ├── date.go ├── date_test.go └── entity.go ├── errcode.go ├── errcode_test.go ├── example_test.go ├── go.mod ├── go.sum ├── marketplace.go ├── marketplace_test.go ├── operation.go ├── query ├── getbrowsenodes.go ├── getbrowsenodes_test.go ├── getitems.go ├── getitems_test.go ├── getvariations.go ├── getvariations_test.go ├── query.go ├── query_test.go ├── request.go ├── resources.go ├── searchitems.go └── searchitems_test.go ├── server.go ├── server_test.go ├── values-datetime.go └── values-datetime_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | indent_style = tab 7 | indent_size = 4 8 | trim_trailing_whitespace = false 9 | insert_final_newline = true 10 | 11 | [*.go] 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [*.yml] 19 | indent_style = space 20 | indent_size = 2 21 | trim_trailing_whitespace = true 22 | 23 | [*.toml] 24 | indent_style = space 25 | indent_size = 2 26 | trim_trailing_whitespace = true 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.go text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 20 * * 0' 16 | 17 | jobs: 18 | CodeQL-Build: 19 | # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest 20 | runs-on: ubuntu-latest 21 | 22 | permissions: 23 | # required for all workflows 24 | security-events: write 25 | 26 | # only required for workflows in private repositories 27 | actions: read 28 | contents: read 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v3 33 | 34 | # Initializes the CodeQL tools for scanning. 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v2 37 | # Override language selection by uncommenting this and choosing your languages 38 | with: 39 | languages: go 40 | 41 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 42 | # If this step fails, then you should remove it and run the build manually (see below). 43 | - name: Autobuild 44 | uses: github/codeql-action/autobuild@v2 45 | 46 | # ℹ️ Command-line programs to run using the OS shell. 47 | # 📚 https://git.io/JvXDl 48 | 49 | # ✏️ If the Autobuild fails above, remove it and uncomment the following 50 | # three lines and modify them (or add more) to build your code if your 51 | # project uses a compiled language 52 | 53 | #- run: | 54 | # make bootstrap 55 | # make release 56 | 57 | - name: Perform CodeQL Analysis 58 | uses: github/codeql-action/analyze@v2 59 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 11 | # pull-requests: read 12 | jobs: 13 | golangci: 14 | name: lint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-go@v3 19 | with: 20 | go-version-file: 'go.mod' 21 | - name: golangci-lint 22 | uses: golangci/golangci-lint-action@v3 23 | with: 24 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 25 | version: latest 26 | 27 | # Optional: working directory, useful for monorepos 28 | # working-directory: somedir 29 | 30 | # Optional: golangci-lint command line arguments. 31 | args: --enable gosec 32 | 33 | # Optional: show only new issues if it's a pull request. The default value is `false`. 34 | # only-new-issues: true 35 | 36 | # Optional: if set to true then the all caching functionality will be complete disabled, 37 | # takes precedence over all other caching options. 38 | # skip-cache: true 39 | 40 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 41 | # skip-pkg-cache: true 42 | 43 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 44 | # skip-build-cache: true 45 | - name: testing 46 | run: go test -shuffle on ./... 47 | - name: install govulncheck 48 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 49 | - name: running govulncheck 50 | run: govulncheck ./... 51 | -------------------------------------------------------------------------------- /.github/workflows/vulns.yml: -------------------------------------------------------------------------------- 1 | name: vulns 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | vulns: 9 | name: Vulnerability scanner 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-go@v3 14 | with: 15 | go-version-file: 'go.mod' 16 | - name: WriteGoList 17 | run: go list -json -m all > go.list 18 | - name: Nancy 19 | uses: sonatype-nexus-community/nancy-github-action@main 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Other files and Folders 15 | *.bak 16 | work/ 17 | .task/ 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.15.x" 5 | 6 | env: 7 | global: 8 | - GO111MODULE=on 9 | 10 | install: 11 | - go mod download 12 | 13 | script: 14 | - go test ./... 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [pa-api] -- APIs for Amazon Product Advertising API v5 by Golang 2 | 3 | [![check vulns](https://github.com/goark/pa-api/workflows/vulns/badge.svg)](https://github.com/goark/pa-api/actions) 4 | [![lint status](https://github.com/goark/pa-api/workflows/lint/badge.svg)](https://github.com/goark/pa-api/actions) 5 | [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/goark/pa-api/master/LICENSE) 6 | [![GitHub release](https://img.shields.io/github/release/goark/pa-api.svg)](https://github.com/goark/pa-api/releases/latest) 7 | 8 | This package is required Go 1.16 or later. 9 | 10 | **Migrated repository to [github.com/goark/pa-api][pa-api]** 11 | 12 | ## Usage 13 | 14 | ### Create PA-API Information 15 | 16 | Default PA-API information. 17 | 18 | ```go 19 | sv := paapi5.New() //Create default server 20 | fmt.Println("Marketplace:", sv.Marketplace()) 21 | fmt.Println("Region:", sv.Region()) 22 | fmt.Println("AcceptLanguage:", sv.AcceptLanguage()) 23 | fmt.Println("URL:", sv.URL(paapi5.GetItems.Path())) 24 | // Output: 25 | // Marketplace: www.amazon.com 26 | // Region: us-east-1 27 | // AcceptLanguage: en_US 28 | // URL: https://webservices.amazon.com/paapi5/getitems 29 | ``` 30 | 31 | PA-API information for Japan region. 32 | 33 | ```go 34 | sv := paapi5.New(paapi5.WithMarketplace(paapi5.LocaleJapan)) //Create server in Japan region 35 | fmt.Println("Marketplace:", sv.Marketplace()) 36 | fmt.Println("Region:", sv.Region()) 37 | fmt.Println("AcceptLanguage:", sv.AcceptLanguage()) 38 | fmt.Println("URL:", sv.URL(paapi5.GetItems.Path())) 39 | // Output: 40 | // Marketplace: www.amazon.co.jp 41 | // Region: us-west-2 42 | // AcceptLanguage: ja_JP 43 | // URL: https://webservices.amazon.co.jp/paapi5/getitems 44 | ``` 45 | 46 | ### Create Client Instance 47 | 48 | Create default client instance. 49 | 50 | ```go 51 | client := paapi5.DefaultClient("mytag-20", "AKIAIOSFODNN7EXAMPLE", "1234567890") //Create default client 52 | fmt.Println("Marketplace:", client.Marketplace()) 53 | // Output: 54 | // Marketplace: www.amazon.com 55 | ``` 56 | 57 | Create client instance for Japan region. 58 | 59 | ```go 60 | //Create client for Janan region 61 | client := paapi5.New( 62 | paapi5.WithMarketplace(paapi5.LocaleJapan), 63 | ).CreateClient( 64 | "mytag-20", 65 | "AKIAIOSFODNN7EXAMPLE", 66 | "1234567890", 67 | paapi5.WithHttpClient(http.DefaultClient), 68 | ) 69 | fmt.Println("Marketplace:", client.Marketplace()) 70 | // Output: 71 | // Marketplace: www.amazon.co.jp 72 | ``` 73 | 74 | ## Sample code 75 | 76 | ### Operation GetItems 77 | 78 | ```go 79 | package main 80 | 81 | import ( 82 | "context" 83 | "fmt" 84 | 85 | paapi5 "github.com/goark/pa-api" 86 | "github.com/goark/pa-api/entity" 87 | "github.com/goark/pa-api/query" 88 | ) 89 | 90 | func main() { 91 | //Create client 92 | client := paapi5.New( 93 | paapi5.WithMarketplace(paapi5.LocaleJapan), 94 | ).CreateClient( 95 | "mytag-20", 96 | "AKIAIOSFODNN7EXAMPLE", 97 | "1234567890", 98 | ) 99 | 100 | //Make query 101 | q := query.NewGetItems( 102 | client.Marketplace(), 103 | client.PartnerTag(), 104 | client.PartnerType(), 105 | ).ASINs([]string{"B07YCM5K55"}).EnableImages().EnableItemInfo().EnableParentASIN() 106 | 107 | //Requet and response 108 | body, err := client.RequestContext(context.Background(), q) 109 | if err != nil { 110 | fmt.Printf("%+v\n", err) 111 | return 112 | } 113 | //io.Copy(os.Stdout, bytes.NewReader(body)) 114 | 115 | //Decode JSON 116 | res, err := entity.DecodeResponse(body) 117 | if err != nil { 118 | fmt.Printf("%+v\n", err) 119 | return 120 | } 121 | fmt.Println(res.String()) 122 | } 123 | ``` 124 | ### Operation GetVariations 125 | 126 | ```go 127 | package main 128 | 129 | import ( 130 | "context" 131 | "fmt" 132 | 133 | paapi5 "github.com/goark/pa-api" 134 | "github.com/goark/pa-api/entity" 135 | "github.com/goark/pa-api/query" 136 | ) 137 | 138 | func main() { 139 | //Create client 140 | client := paapi5.New( 141 | paapi5.WithMarketplace(paapi5.LocaleJapan), 142 | ).CreateClient( 143 | "mytag-20", 144 | "AKIAIOSFODNN7EXAMPLE", 145 | "1234567890", 146 | ) 147 | 148 | //Make query 149 | q := query.NewGetVariations( 150 | client.Marketplace(), 151 | client.PartnerTag(), 152 | client.PartnerType(), 153 | ).ASIN("B07YCM5K55").EnableImages().EnableItemInfo().EnableParentASIN() 154 | 155 | //Request and response 156 | body, err := client.RequestContext(context.Background(), q) 157 | if err != nil { 158 | fmt.Printf("%+v\n", err) 159 | return 160 | } 161 | //io.Copy(os.Stdout, bytes.NewReader(body)) 162 | 163 | //Decode JSON 164 | res, err := entity.DecodeResponse(body) 165 | if err != nil { 166 | fmt.Printf("%+v\n", err) 167 | return 168 | } 169 | fmt.Println(res.String()) 170 | } 171 | ``` 172 | 173 | ### Operation SearchItems 174 | 175 | ```go 176 | package main 177 | 178 | import ( 179 | "context" 180 | "fmt" 181 | 182 | paapi5 "github.com/goark/pa-api" 183 | "github.com/goark/pa-api/entity" 184 | "github.com/goark/pa-api/query" 185 | ) 186 | 187 | func main() { 188 | //Create client 189 | client := paapi5.New( 190 | paapi5.WithMarketplace(paapi5.LocaleJapan), 191 | ).CreateClient( 192 | "mytag-20", 193 | "AKIAIOSFODNN7EXAMPLE", 194 | "1234567890", 195 | ) 196 | 197 | //Make query 198 | q := query.NewSearchItems( 199 | client.Marketplace(), 200 | client.PartnerTag(), 201 | client.PartnerType(), 202 | ).Search(query.Keywords, "数学ガール").EnableImages().EnableItemInfo().EnableParentASIN() 203 | 204 | //Request and response 205 | body, err := client.RequestContext(context.Background(), q) 206 | if err != nil { 207 | fmt.Printf("%+v\n", err) 208 | return 209 | } 210 | //io.Copy(os.Stdout, bytes.NewReader(body)) 211 | 212 | //Decode JSON 213 | res, err := entity.DecodeResponse(body) 214 | if err != nil { 215 | fmt.Printf("%+v\n", err) 216 | return 217 | } 218 | fmt.Println(res.String()) 219 | } 220 | ``` 221 | 222 | ### Operation GetBrowseNodes 223 | 224 | ```go 225 | package main 226 | 227 | import ( 228 | "context" 229 | "fmt" 230 | 231 | paapi5 "github.com/goark/pa-api" 232 | "github.com/goark/pa-api/entity" 233 | "github.com/goark/pa-api/query" 234 | ) 235 | 236 | func main() { 237 | //Create client 238 | client := paapi5.New( 239 | paapi5.WithMarketplace(paapi5.LocaleJapan), 240 | ).CreateClient( 241 | "mytag-20", 242 | "AKIAIOSFODNN7EXAMPLE", 243 | "1234567890", 244 | ) 245 | 246 | //Make query 247 | q := query.NewGetBrowseNodes( 248 | client.Marketplace(), 249 | client.PartnerTag(), 250 | client.PartnerType(), 251 | ).BrowseNodeIds([]string{"3040", "3045"}).EnableBrowseNodes() 252 | 253 | //Request and response 254 | body, err := client.RequestContext(context.Background(), q) 255 | if err != nil { 256 | fmt.Printf("%+v\n", err) 257 | return 258 | } 259 | 260 | //Decode JSON 261 | res, err := entity.DecodeResponse(body) 262 | if err != nil { 263 | fmt.Printf("%+v\n", err) 264 | return 265 | } 266 | fmt.Println(res.String()) 267 | } 268 | ``` 269 | 270 | ## Contributors 271 | 272 | Many thanks for [contributors](https://github.com/goark/pa-api/graphs/contributors "Contributors to goark/pa-api") 273 | 274 | ## Links 275 | 276 | - [Go 言語用 PA-API v5 クライアント・パッケージ — リリース情報 | text.Baldanders.info](https://text.baldanders.info/release/pa-api-v5/) 277 | 278 | [pa-api]: https://github.com/goark/pa-api "goark/pa-api: APIs for Amazon Product Advertising API v5 by Golang" 279 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | tasks: 4 | default: 5 | cmds: 6 | - task: clean 7 | - task: test 8 | - task: nancy 9 | 10 | test: 11 | desc: Test and lint. 12 | cmds: 13 | - go mod verify 14 | - go test -shuffle on ./... 15 | - golangci-lint run --enable gosec --timeout 3m0s ./... 16 | sources: 17 | - ./go.mod 18 | - '**/*.go' 19 | 20 | nancy: 21 | desc: Check vulnerability of external packages with Nancy. 22 | cmds: 23 | - depm list -j | nancy sleuth -n 24 | sources: 25 | - ./go.mod 26 | - '**/*.go' 27 | 28 | clean: 29 | desc: Initialize module and build cache, and remake go.sum file. 30 | cmds: 31 | - go mod tidy -v -go=1.23 32 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/hmac" 7 | "crypto/sha256" 8 | "encoding/hex" 9 | "strings" 10 | "time" 11 | 12 | "github.com/goark/errs" 13 | "github.com/goark/fetch" 14 | ) 15 | 16 | const ( 17 | defaultPartnerType = "Associates" 18 | ) 19 | 20 | // Query interface for Client type 21 | type Query interface { 22 | Operation() Operation 23 | Payload() ([]byte, error) 24 | } 25 | 26 | // Client interface 27 | type Client interface { 28 | Marketplace() string 29 | PartnerTag() string 30 | PartnerType() string 31 | Request(Query) ([]byte, error) 32 | RequestContext(context.Context, Query) ([]byte, error) 33 | } 34 | 35 | // client is http.Client for Aozora API Server 36 | type client struct { 37 | server *Server 38 | client fetch.Client 39 | partnerTag string 40 | accessKey string 41 | secretKey string 42 | } 43 | 44 | // Marketplace returns name of Marketplace parameter for PA-API v5 45 | func (c *client) Marketplace() string { 46 | return c.server.Marketplace() 47 | } 48 | 49 | // PartnerTag returns PartnerTag parameter for PA-API v5 50 | func (c *client) PartnerTag() string { 51 | return c.partnerTag 52 | } 53 | 54 | // PartnerType returns PartnerType parameter for PA-API v5 55 | func (c *client) PartnerType() string { 56 | return defaultPartnerType 57 | } 58 | 59 | // Request method returns response data (JSON format) by PA-APIv5. 60 | func (c *client) Request(q Query) ([]byte, error) { 61 | return c.RequestContext(context.Background(), q) 62 | } 63 | 64 | // RequestContext method returns response data (JSON format) by PA-APIv5. (with context.Context) 65 | func (c *client) RequestContext(ctx context.Context, q Query) ([]byte, error) { 66 | payload, err := q.Payload() 67 | if err != nil { 68 | return nil, errs.Wrap(err, errs.WithContext("Operation", q.Operation().String())) 69 | } 70 | b, err := c.post(ctx, q.Operation(), payload) 71 | if err != nil { 72 | return nil, errs.Wrap(err, errs.WithContext("Operation", q.Operation().String()), errs.WithContext("payload", string(payload))) 73 | } 74 | return b, nil 75 | } 76 | 77 | func (c *client) post(ctx context.Context, cmd Operation, payload []byte) ([]byte, error) { 78 | dt := NewTimeStamp(time.Now()) 79 | u := c.server.URL(cmd.Path()) 80 | hds := newHeaders(c.server, cmd, dt) 81 | sig := c.signiture(c.signedString(hds, payload), hds) 82 | resp, err := c.client.PostWithContext( 83 | ctx, 84 | u, 85 | bytes.NewReader(payload), 86 | fetch.WithRequestHeaderSet("Accept", c.server.Accept()), 87 | fetch.WithRequestHeaderSet("Accept-Language", c.server.AcceptLanguage()), 88 | fetch.WithRequestHeaderSet("Content-Type", c.server.ContentType()), 89 | fetch.WithRequestHeaderSet("Content-Encoding", hds.get("Content-Encoding")), 90 | fetch.WithRequestHeaderSet("Host", hds.get("Host")), 91 | fetch.WithRequestHeaderSet("X-Amz-Date", hds.get("X-Amz-Date")), 92 | fetch.WithRequestHeaderSet("X-Amz-Target", hds.get("X-Amz-Target")), 93 | fetch.WithRequestHeaderSet("Authorization", c.authorization(sig, hds)), 94 | ) 95 | if err != nil { 96 | return nil, errs.Wrap(err, errs.WithContext("url", u.String()), errs.WithContext("payload", string(payload))) 97 | } 98 | body, err := resp.DumpBodyAndClose() 99 | if err != nil { 100 | return nil, errs.Wrap(err, errs.WithContext("url", u.String()), errs.WithContext("payload", string(payload))) 101 | } 102 | return body, nil 103 | } 104 | 105 | func (c *client) authorization(sig string, hds *headers) string { 106 | buf := bytes.Buffer{} 107 | buf.WriteString(c.server.HMACAlgorithm()) 108 | buf.WriteString(" Credential=") 109 | buf.WriteString(strings.Join([]string{c.accessKey, hds.dt.StringDate(), c.server.Region(), c.server.ServiceName(), c.server.AWS4Request()}, "/")) 110 | buf.WriteString(",SignedHeaders=") 111 | buf.WriteString(hds.list()) 112 | buf.WriteString(",Signature=") 113 | buf.WriteString(sig) 114 | return buf.String() 115 | } 116 | 117 | func (c *client) signiture(signed string, hds *headers) string { 118 | dateKey := hmacSHA256([]byte("AWS4"+c.secretKey), []byte(hds.dt.StringDate())) 119 | regionKey := hmacSHA256(dateKey, []byte(c.server.Region())) 120 | serviceKey := hmacSHA256(regionKey, []byte(c.server.ServiceName())) 121 | requestKey := hmacSHA256(serviceKey, []byte(c.server.AWS4Request())) 122 | return hex.EncodeToString(hmacSHA256(requestKey, []byte(signed))) 123 | } 124 | 125 | func (c *client) signedString(hds *headers, payload []byte) string { 126 | return strings.Join( 127 | []string{ 128 | c.server.HMACAlgorithm(), 129 | hds.dt.String(), 130 | strings.Join([]string{hds.dt.StringDate(), c.server.Region(), c.server.ServiceName(), c.server.AWS4Request()}, "/"), 131 | hashedString([]byte(c.canonicalRequest(hds, payload))), 132 | }, 133 | "\n", 134 | ) 135 | } 136 | 137 | func (c *client) canonicalRequest(hds *headers, payload []byte) string { 138 | request := []string{"POST", hds.cmd.Path(), "", hds.values(), "", hds.list(), hashedString(payload)} 139 | return strings.Join(request, "\n") 140 | } 141 | 142 | func hmacSHA256(key, data []byte) []byte { 143 | hasher := hmac.New(sha256.New, key) 144 | _, err := hasher.Write(data) 145 | if err != nil { 146 | return []byte{} 147 | } 148 | return hasher.Sum(nil) 149 | } 150 | 151 | func hashedString(data []byte) string { 152 | sum := sha256.Sum256(data) 153 | return hex.EncodeToString(sum[:]) 154 | } 155 | 156 | type headers struct { 157 | cmd Operation 158 | dt TimeStamp 159 | headers []string 160 | valueList map[string]string 161 | } 162 | 163 | func newHeaders(svr *Server, cmd Operation, dt TimeStamp) *headers { 164 | hds := &headers{cmd: cmd, dt: dt, headers: []string{"content-encoding", "host", "x-amz-date", "x-amz-target"}, valueList: map[string]string{}} 165 | hds.valueList["content-encoding"] = svr.ContentEncoding() 166 | hds.valueList["host"] = svr.HostName() 167 | hds.valueList["x-amz-date"] = dt.String() 168 | hds.valueList["x-amz-target"] = cmd.Target() 169 | return hds 170 | } 171 | 172 | func (h *headers) get(name string) string { 173 | if s, ok := h.valueList[strings.ToLower(name)]; ok { 174 | return s 175 | } 176 | return "" 177 | } 178 | 179 | func (h *headers) list() string { 180 | return strings.Join(h.headers, ";") 181 | } 182 | 183 | func (h *headers) values() string { 184 | list := []string{} 185 | for _, name := range h.headers { 186 | list = append(list, name+":"+h.get(name)) 187 | } 188 | return strings.Join(list, "\n") 189 | } 190 | 191 | /* Copyright 2019-2021 Spiegel 192 | * 193 | * Licensed under the Apache License, Version 2.0 (the "License"); 194 | * you may not use this file except in compliance with the License. 195 | * You may obtain a copy of the License at 196 | * 197 | * http://www.apache.org/licenses/LICENSE-2.0 198 | * 199 | * Unless required by applicable law or agreed to in writing, software 200 | * distributed under the License is distributed on an "AS IS" BASIS, 201 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | * See the License for the specific language governing permissions and 203 | * limitations under the License. 204 | */ 205 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestClient(t *testing.T) { 9 | testCases := []struct { 10 | partnerTag string 11 | accessKey string 12 | secretKey string 13 | marketplace string 14 | partnerType string 15 | date TimeStamp 16 | contentEncoding string 17 | hostName string 18 | xAmzDate string 19 | xAmzTarget string 20 | payload []byte 21 | sigedText string 22 | sig string 23 | authorization string 24 | }{ 25 | { 26 | partnerTag: "mytag-20", 27 | accessKey: "AKIAIOSFODNN7EXAMPLE", 28 | secretKey: "1234567890", 29 | marketplace: DefaultMarketplace.String(), 30 | partnerType: defaultPartnerType, 31 | date: NewTimeStamp(time.Date(2019, time.September, 30, 8, 31, 54, 0, time.UTC)), 32 | contentEncoding: "amz-1.0", 33 | hostName: "webservices.amazon.com", 34 | xAmzDate: "20190930T083154Z", 35 | xAmzTarget: "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.GetItems", 36 | payload: []byte(`{"ItemIds": ["B07YCM5K55"],"Resources": ["Images.Primary.Small","Images.Primary.Medium","Images.Primary.Large","ItemInfo.ByLineInfo","ItemInfo.ContentInfo","ItemInfo.Classifications","ItemInfo.ExternalIds","ItemInfo.ProductInfo","ItemInfo.Title"],"PartnerTag": "mytag-20","PartnerType": "Associates","Marketplace": "www.amazon.com","Operation": "GetItems"}`), 37 | sigedText: "AWS4-HMAC-SHA256\n20190930T083154Z\n20190930/us-east-1/ProductAdvertisingAPI/aws4_request\n00edab8e9f221dd80f01241b85f4526f204ebf49818d678f041e21404a44b8cb", 38 | sig: "717e266b28f02523fc39894f565532bd53fe80b37a9ed1b631b77c50483f8c08", 39 | authorization: "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20190930/us-east-1/ProductAdvertisingAPI/aws4_request,SignedHeaders=content-encoding;host;x-amz-date;x-amz-target,Signature=717e266b28f02523fc39894f565532bd53fe80b37a9ed1b631b77c50483f8c08", 40 | }, 41 | } 42 | for _, tc := range testCases { 43 | c := New().CreateClient(tc.partnerTag, tc.accessKey, tc.secretKey) 44 | if c.Marketplace() != tc.marketplace { 45 | t.Errorf("Client.Marketplace() is \"%v\", want \"%v\"", c.Marketplace(), tc.marketplace) 46 | } 47 | if c.PartnerTag() != tc.partnerTag { 48 | t.Errorf("Client.PartnerTag() is \"%v\", want \"%v\"", c.PartnerTag(), tc.partnerTag) 49 | } 50 | if c.PartnerType() != tc.partnerType { 51 | t.Errorf("Client.PartnerType() is \"%v\", want \"%v\"", c.PartnerType(), tc.partnerType) 52 | } 53 | hds := newHeaders(c.(*client).server, GetItems, tc.date) 54 | if hds.get("Content-Encoding") != tc.contentEncoding { 55 | t.Errorf("headers.get(\"Content-Encoding\") is \"%v\", want \"%v\"", hds.get("Content-Encoding"), tc.contentEncoding) 56 | } 57 | if hds.get("Host") != tc.hostName { 58 | t.Errorf("headers.get(\"Host\") is \"%v\", want \"%v\"", hds.get("Host"), tc.hostName) 59 | } 60 | if hds.get("X-Amz-Date") != tc.xAmzDate { 61 | t.Errorf("headers.get(\"X-Amz-Date\") is \"%v\", want \"%v\"", hds.get("X-Amz-Date"), tc.xAmzDate) 62 | } 63 | if hds.get("X-Amz-Target") != tc.xAmzTarget { 64 | t.Errorf("headers.get(\"X-Amz-Target\") is \"%v\", want \"%v\"", hds.get("X-Amz-Target"), tc.xAmzTarget) 65 | } 66 | str := c.(*client).signedString(hds, tc.payload) 67 | if str != tc.sigedText { 68 | t.Errorf("Client.signedString() is \"%v\", want \"%v\"", str, tc.sigedText) 69 | } 70 | sig := c.(*client).signiture(str, hds) 71 | if sig != tc.sig { 72 | t.Errorf("Client.signiture() is \"%v\", want \"%v\"", sig, tc.sig) 73 | } 74 | auth := c.(*client).authorization(sig, hds) 75 | if auth != tc.authorization { 76 | t.Errorf("Client.authorization() is \"%v\", want \"%v\"", auth, tc.authorization) 77 | } 78 | } 79 | } 80 | 81 | /* Copyright 2019,2020 Spiegel 82 | * 83 | * Licensed under the Apache License, Version 2.0 (the "License"); 84 | * you may not use this file except in compliance with the License. 85 | * You may obtain a copy of the License at 86 | * 87 | * http://www.apache.org/licenses/LICENSE-2.0 88 | * 89 | * Unless required by applicable law or agreed to in writing, software 90 | * distributed under the License is distributed on an "AS IS" BASIS, 91 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 92 | * See the License for the specific language governing permissions and 93 | * limitations under the License. 94 | */ 95 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | //Package paapi5 APIs for Amazon Product Advertising API v5 client 2 | package paapi5 3 | -------------------------------------------------------------------------------- /entity/date.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | 8 | "github.com/goark/errs" 9 | ) 10 | 11 | //Time is wrapper class of time.Time 12 | type Date struct { 13 | time.Time 14 | } 15 | 16 | //NewDate returns Time instance 17 | func NewDate(tm time.Time) Date { 18 | return Date{tm} 19 | } 20 | 21 | var timeTemplate = []string{ 22 | time.RFC3339, 23 | "2006-01T", 24 | "2006-01-02", 25 | "2006-01", 26 | "2006/01/02", 27 | "2006/01", 28 | "2006", 29 | "2006T", 30 | } 31 | 32 | //UnmarshalJSON returns result of Unmarshal for json.Unmarshal() 33 | func (t *Date) UnmarshalJSON(b []byte) error { 34 | s, err := strconv.Unquote(string(b)) 35 | if err != nil { 36 | s = string(b) 37 | } 38 | if len(s) == 0 || strings.ToLower(s) == "null" { 39 | *t = Date{time.Time{}} 40 | return nil 41 | } 42 | var lastErr error 43 | for _, tmplt := range timeTemplate { 44 | if tm, err := time.Parse(tmplt, s); err != nil { 45 | lastErr = errs.Wrap(err, errs.WithContext("time_string", s), errs.WithContext("time_template", tmplt)) 46 | } else { 47 | *t = Date{tm} 48 | return nil 49 | } 50 | } 51 | return lastErr 52 | } 53 | 54 | /* Copyright 2019,2020 Spiegel and contributors 55 | * 56 | * Licensed under the Apache License, Version 2.0 (the "License"); 57 | * you may not use this file except in compliance with the License. 58 | * You may obtain a copy of the License at 59 | * 60 | * http://www.apache.org/licenses/LICENSE-2.0 61 | * 62 | * Unless required by applicable law or agreed to in writing, software 63 | * distributed under the License is distributed on an "AS IS" BASIS, 64 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | * See the License for the specific language governing permissions and 66 | * limitations under the License. 67 | */ 68 | -------------------------------------------------------------------------------- /entity/date_test.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | type ForTestStruct struct { 9 | DateTaken Date `json:"date_taken,omitempty"` 10 | } 11 | 12 | func TestUnmarshal(t *testing.T) { 13 | testCases := []struct { 14 | s string 15 | jsn string 16 | }{ 17 | {s: `{"date_taken": "2005-03-26T15:04:05Z"}`, jsn: `{"date_taken":"2005-03-26T15:04:05Z"}`}, 18 | {s: `{"date_taken": "2005-03T"}`, jsn: `{"date_taken":"2005-03-01T00:00:00Z"}`}, 19 | {s: `{"date_taken": "2005T"}`, jsn: `{"date_taken":"2005-01-01T00:00:00Z"}`}, 20 | {s: `{"date_taken": "2005-03-26"}`, jsn: `{"date_taken":"2005-03-26T00:00:00Z"}`}, 21 | {s: `{"date_taken": "2005-03"}`, jsn: `{"date_taken":"2005-03-01T00:00:00Z"}`}, 22 | {s: `{"date_taken": "2005"}`, jsn: `{"date_taken":"2005-01-01T00:00:00Z"}`}, 23 | {s: `{"date_taken": "2005/03/26"}`, jsn: `{"date_taken":"2005-03-26T00:00:00Z"}`}, 24 | {s: `{"date_taken": "2005/03"}`, jsn: `{"date_taken":"2005-03-01T00:00:00Z"}`}, 25 | {s: `{}`, jsn: `{"date_taken":"0001-01-01T00:00:00Z"}`}, 26 | } 27 | 28 | for _, tc := range testCases { 29 | tst := &ForTestStruct{} 30 | if err := json.Unmarshal([]byte(tc.s), tst); err != nil { 31 | t.Errorf("json.Unmarshal() is \"%v\", want nil.", err) 32 | continue 33 | } 34 | b, err := json.Marshal(tst) 35 | if err != nil { 36 | t.Errorf("json.Marshal() is \"%v\", want nil.", err) 37 | continue 38 | } 39 | str := string(b) 40 | if str != tc.jsn { 41 | t.Errorf("ForTestStruct = \"%v\", want \"%v\".", str, tc.jsn) 42 | } 43 | } 44 | } 45 | 46 | /* Copyright 2019 Spiegel and contributors 47 | * 48 | * Licensed under the Apache License, Version 2.0 (the "License"); 49 | * you may not use this file except in compliance with the License. 50 | * You may obtain a copy of the License at 51 | * 52 | * http://www.apache.org/licenses/LICENSE-2.0 53 | * 54 | * Unless required by applicable law or agreed to in writing, software 55 | * distributed under the License is distributed on an "AS IS" BASIS, 56 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 57 | * See the License for the specific language governing permissions and 58 | * limitations under the License. 59 | */ 60 | -------------------------------------------------------------------------------- /entity/entity.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | "github.com/goark/errs" 8 | ) 9 | 10 | type Image struct { 11 | URL string 12 | Height int 13 | Width int 14 | } 15 | 16 | type GenInfo struct { 17 | DisplayValue string 18 | Label string `json:",omitempty"` 19 | Locale string `json:",omitempty"` 20 | } 21 | 22 | type GenInfoInt struct { 23 | DisplayValue int 24 | Label string `json:",omitempty"` 25 | Locale string `json:",omitempty"` 26 | } 27 | 28 | type GenInfoFloat struct { 29 | DisplayValue float64 30 | Label string `json:",omitempty"` 31 | Locale string `json:",omitempty"` 32 | Unit string `json:",omitempty"` 33 | } 34 | 35 | type GenInfoTime struct { 36 | DisplayValue Date 37 | Label string `json:",omitempty"` 38 | Locale string `json:",omitempty"` 39 | } 40 | 41 | type IdInfo struct { 42 | DisplayValues []string 43 | Label string `json:",omitempty"` 44 | Locale string `json:",omitempty"` 45 | } 46 | 47 | type Ancestor struct { 48 | Id string 49 | DisplayName string 50 | ContextFreeName string 51 | Ancestor *Ancestor `json:",omitempty"` 52 | } 53 | 54 | type ConditionInfo struct { 55 | DisplayValue string `json:",omitempty"` 56 | Label string `json:",omitempty"` 57 | Locale string `json:",omitempty"` 58 | Value string `json:",omitempty"` 59 | SubCondition *ConditionInfo `json:",omitempty"` 60 | } 61 | 62 | type GenPriceInfo struct { 63 | Amount float64 `json:",omitempty"` 64 | Currency string `json:",omitempty"` 65 | DisplayAmount string `json:",omitempty"` 66 | PricePerUnit float64 `json:",omitempty"` 67 | } 68 | 69 | type VariationAttribute struct { 70 | Name string `json:",omitempty"` 71 | Value string `json:",omitempty"` 72 | } 73 | 74 | type Item struct { 75 | ASIN string 76 | ParentASIN string 77 | DetailPageURL string 78 | CustomerReviews *struct { 79 | Count *int `json:",omitempty"` 80 | StarRating *struct { 81 | Value *float64 `json:",omitempty"` 82 | } `json:",omitempty"` 83 | } `json:",omitempty"` 84 | BrowseNodeInfo *struct { 85 | BrowseNodes []struct { 86 | Id string 87 | DisplayName string 88 | ContextFreeName string 89 | IsRoot bool 90 | SalesRank *int `json:",omitempty"` 91 | Ancestor *Ancestor `json:",omitempty"` 92 | WebsiteSalesRank *struct { 93 | DisplayName string 94 | ContextFreeName string 95 | SalesRank int 96 | } `json:",omitempty"` 97 | } `json:",omitempty"` 98 | } `json:",omitempty"` 99 | Images *struct { 100 | Primary *struct { 101 | Large *Image `json:",omitempty"` 102 | Medium *Image `json:",omitempty"` 103 | Small *Image `json:",omitempty"` 104 | } `json:",omitempty"` 105 | Variants []*struct { 106 | Large *Image `json:",omitempty"` 107 | Medium *Image `json:",omitempty"` 108 | Small *Image `json:",omitempty"` 109 | } `json:",omitempty"` 110 | } `json:",omitempty"` 111 | ItemInfo *struct { 112 | ByLineInfo *struct { 113 | Brand *GenInfo `json:",omitempty"` 114 | Manufacturer *GenInfo `json:",omitempty"` 115 | Contributors []struct { 116 | Name string 117 | Locale string 118 | Role string 119 | } 120 | } `json:",omitempty"` 121 | Classifications *struct { 122 | Binding GenInfo 123 | ProductGroup GenInfo 124 | } `json:",omitempty"` 125 | ContentInfo *struct { 126 | Edition *GenInfo `json:",omitempty"` 127 | Languages struct { 128 | DisplayValues []struct { 129 | DisplayValue string 130 | Type string 131 | } 132 | Label string 133 | Locale string 134 | } 135 | PagesCount struct { 136 | DisplayValue int 137 | Label string 138 | Locale string 139 | } 140 | PublicationDate GenInfoTime 141 | } `json:",omitempty"` 142 | ContentRating *struct { 143 | AudienceRating GenInfo 144 | } `json:",omitempty"` 145 | ExternalIds *struct { 146 | EANs *IdInfo `json:",omitempty"` 147 | ISBNs *IdInfo `json:",omitempty"` 148 | UPCs *IdInfo `json:",omitempty"` 149 | } `json:",omitempty"` 150 | Features *IdInfo `json:",omitempty"` 151 | ManufactureInfo *struct { 152 | ItemPartNumber *GenInfo `json:",omitempty"` 153 | Model *GenInfo `json:",omitempty"` 154 | Warranty *GenInfo `json:",omitempty"` 155 | } `json:",omitempty"` 156 | ProductInfo *struct { 157 | Color *GenInfo `json:",omitempty"` 158 | IsAdultProduct struct { 159 | DisplayValue bool 160 | Label string 161 | Locale string 162 | } 163 | ItemDimensions *struct { 164 | Height *GenInfoFloat `json:",omitempty"` 165 | Length *GenInfoFloat `json:",omitempty"` 166 | Weight *GenInfoFloat `json:",omitempty"` 167 | Width *GenInfoFloat `json:",omitempty"` 168 | } `json:",omitempty"` 169 | ReleaseDate *GenInfoTime `json:",omitempty"` 170 | Size *GenInfo `json:",omitempty"` 171 | UnitCount *GenInfoInt `json:",omitempty"` 172 | } `json:",omitempty"` 173 | TechnicalInfo *struct { 174 | Formats IdInfo 175 | } `json:",omitempty"` 176 | Title *GenInfo `json:",omitempty"` 177 | TradeInInfo *struct { 178 | IsEligibleForTradeIn bool 179 | Price struct { 180 | DisplayAmount string 181 | Amount float64 182 | Currency string 183 | } 184 | } `json:",omitempty"` 185 | VariationAttributes []VariationAttribute `json:",omitempty"` 186 | } 187 | Offers *struct { 188 | Listings *[]struct { 189 | Availability *struct { 190 | MaxOrderQuantity int 191 | Message string 192 | MinOrderQuantity int 193 | Type string 194 | } `json:",omitempty"` 195 | Condition *ConditionInfo `json:",omitempty"` 196 | DeliveryInfo *struct { 197 | IsAmazonFulfilled bool `json:",omitempty"` 198 | IsFreeShippingEligible bool `json:",omitempty"` 199 | IsPrimeEligible bool `json:",omitempty"` 200 | } `json:",omitempty"` 201 | ID string `json:"Id"` 202 | IsBuyboxWinner bool 203 | LoyaltyPoints *struct { 204 | Points int 205 | } `json:",omitempty"` 206 | MerchantInfo *struct { 207 | DefaultShippingCountry string 208 | FeedbackCount int 209 | FeedbackRating float64 210 | ID string `json:"Id"` 211 | Name string 212 | } `json:",omitempty"` 213 | Price *struct { 214 | *GenPriceInfo `json:",omitempty"` 215 | Savings *struct { 216 | Amount float64 217 | Currency string 218 | DisplayAmount string 219 | Percentage int 220 | PricePerUnit float64 221 | } `json:",omitempty"` 222 | } `json:",omitempty"` 223 | ProgramEligibility *struct { 224 | IsPrimeExclusive bool 225 | IsPrimePantry bool 226 | } `json:",omitempty"` 227 | Promotions *[]struct { 228 | Amount float64 229 | Currency string 230 | DiscountPercent json.Number 231 | DisplayAmount string 232 | PricePerUnit float64 233 | Type string 234 | } `json:",omitempty"` 235 | SavingBasis *GenPriceInfo `json:",omitempty"` 236 | ViolateMAP bool 237 | } `json:",omitempty"` 238 | Summaries *[]struct { 239 | Condition *ConditionInfo `json:",omitempty"` 240 | HighestPrice *GenPriceInfo `json:",omitempty"` 241 | LowestPrice *GenPriceInfo `json:",omitempty"` 242 | OfferCount int 243 | } `json:",omitempty"` 244 | } `json:",omitempty"` 245 | } 246 | 247 | type Refinement struct { 248 | Id string 249 | DisplayName string 250 | Bins []struct { 251 | Id string 252 | DisplayName string 253 | } `json:",omitempty"` 254 | } 255 | 256 | type Price struct { 257 | DisplayAmount string 258 | Amount float64 259 | Currency string 260 | } 261 | 262 | type VariationDimension struct { 263 | DisplayName string 264 | Name string 265 | Values []string 266 | } 267 | 268 | type Response struct { 269 | Errors []struct { 270 | Code string 271 | Message string 272 | } `json:",omitempty"` 273 | ItemsResult *struct { 274 | Items []Item `json:",omitempty"` 275 | } `json:",omitempty"` 276 | SearchResult *struct { 277 | Items []Item `json:",omitempty"` 278 | SearchRefinements *struct { 279 | SearchIndex *Refinement `json:",omitempty"` 280 | BrowseNode *Refinement `json:",omitempty"` 281 | OtherRefinements []Refinement `json:",omitempty"` 282 | } `json:",omitempty"` 283 | SearchURL string 284 | TotalResultCount int 285 | } `json:",omitempty"` 286 | VariationsResult *struct { 287 | Items []Item `json:",omitempty"` 288 | VariationSummary *struct { 289 | PageCount int 290 | VariationCount int 291 | Price *struct { 292 | HighestPrice *Price `json:",omitempty"` 293 | LowestPrice *Price `json:",omitempty"` 294 | } `json:",omitempty"` 295 | VariationDimensions []VariationDimension `json:",omitempty"` 296 | } `json:",omitempty"` 297 | } `json:",omitempty"` 298 | BrowseNodesResult *struct { 299 | BrowseNodes []*struct { 300 | Ancestor *Ancestor `json:",omitempty"` 301 | Children []*struct { 302 | Id string 303 | DisplayName string 304 | ContextFreeName string 305 | } `json:",omitempty"` 306 | Id string 307 | DisplayName string 308 | ContextFreeName string 309 | IsRoot bool 310 | } `json:",omitempty"` 311 | } `json:",omitempty"` 312 | } 313 | 314 | // DecodeResponse returns array of Response instance from byte buffer 315 | func DecodeResponse(b []byte) (*Response, error) { 316 | rsp := Response{} 317 | if err := json.NewDecoder(bytes.NewReader(b)).Decode(&rsp); err != nil { 318 | return &rsp, errs.Wrap(err, errs.WithContext("JSON", string(b))) 319 | } 320 | return &rsp, nil 321 | } 322 | 323 | // JSON returns JSON data from Response instance 324 | func (r *Response) JSON() ([]byte, error) { 325 | b, err := json.Marshal(r) 326 | return b, errs.Wrap(err) 327 | } 328 | 329 | // Stringer 330 | func (r *Response) String() string { 331 | b, err := r.JSON() 332 | if err != nil { 333 | return "" 334 | } 335 | return string(b) 336 | } 337 | 338 | /* Copyright 2019-2022 Spiegel and contributors 339 | * 340 | * Licensed under the Apache License, Version 2.0 (the "License"); 341 | * you may not use this file except in compliance with the License. 342 | * You may obtain a copy of the License at 343 | * 344 | * http://www.apache.org/licenses/LICENSE-2.0 345 | * 346 | * Unless required by applicable law or agreed to in writing, software 347 | * distributed under the License is distributed on an "AS IS" BASIS, 348 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 349 | * See the License for the specific language governing permissions and 350 | * limitations under the License. 351 | */ 352 | -------------------------------------------------------------------------------- /errcode.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import "fmt" 4 | 5 | //Error is error codes for paapi5 package 6 | type Error int 7 | 8 | const ( 9 | ErrNullPointer Error = iota + 1 //Null reference instance 10 | ErrHTTPStatus //Bad HTTP status 11 | ErrNoData //No response data 12 | ) 13 | 14 | var errMessages = map[Error]string{ 15 | ErrNullPointer: "Null reference instance", 16 | ErrHTTPStatus: "Bad HTTP status", 17 | ErrNoData: "No response data", 18 | } 19 | 20 | //Error method returns error message. 21 | //This method is a implementation of error interface. 22 | func (e Error) Error() string { 23 | if s, ok := errMessages[e]; ok { 24 | return s 25 | } 26 | return fmt.Sprintf("unknown error (%d)", int(e)) 27 | } 28 | 29 | /* Copyright 2019 Spiegel 30 | * 31 | * Licensed under the Apache License, Version 2.0 (the "License"); 32 | * you may not use this file except in compliance with the License. 33 | * You may obtain a copy of the License at 34 | * 35 | * http://www.apache.org/licenses/LICENSE-2.0 36 | * 37 | * Unless required by applicable law or agreed to in writing, software 38 | * distributed under the License is distributed on an "AS IS" BASIS, 39 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 40 | * See the License for the specific language governing permissions and 41 | * limitations under the License. 42 | */ 43 | -------------------------------------------------------------------------------- /errcode_test.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestError(t *testing.T) { 9 | testCases := []struct { 10 | err error 11 | str string 12 | }{ 13 | {err: Error(0), str: "unknown error (0)"}, 14 | {err: ErrNullPointer, str: "Null reference instance"}, 15 | {err: ErrHTTPStatus, str: "Bad HTTP status"}, 16 | {err: ErrNoData, str: "No response data"}, 17 | {err: Error(4), str: "unknown error (4)"}, 18 | } 19 | 20 | for _, tc := range testCases { 21 | errStr := tc.err.Error() 22 | if errStr != tc.str { 23 | t.Errorf("\"%v\" != \"%v\"", errStr, tc.str) 24 | } 25 | fmt.Printf("Info(TestError): %+v\n", tc.err) 26 | } 27 | } 28 | 29 | /* Copyright 2019 Spiegel 30 | * 31 | * Licensed under the Apache License, Version 2.0 (the "License"); 32 | * you may not use this file except in compliance with the License. 33 | * You may obtain a copy of the License at 34 | * 35 | * http://www.apache.org/licenses/LICENSE-2.0 36 | * 37 | * Unless required by applicable law or agreed to in writing, software 38 | * distributed under the License is distributed on an "AS IS" BASIS, 39 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 40 | * See the License for the specific language governing permissions and 41 | * limitations under the License. 42 | */ 43 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package paapi5_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | paapi5 "github.com/goark/pa-api" 9 | ) 10 | 11 | func ExampleServer() { 12 | sv := paapi5.New() //Create default server 13 | fmt.Println("Marketplace:", sv.Marketplace()) 14 | fmt.Println("Region:", sv.Region()) 15 | fmt.Println("AcceptLanguage:", sv.AcceptLanguage()) 16 | fmt.Println("URL:", sv.URL(paapi5.GetItems.Path())) 17 | // Output: 18 | // Marketplace: www.amazon.com 19 | // Region: us-east-1 20 | // AcceptLanguage: en_US 21 | // URL: https://webservices.amazon.com/paapi5/getitems 22 | } 23 | 24 | func ExampleNew() { 25 | sv := paapi5.New(paapi5.WithMarketplace(paapi5.LocaleJapan)) //Create server in Japan region 26 | fmt.Println("Marketplace:", sv.Marketplace()) 27 | fmt.Println("Region:", sv.Region()) 28 | fmt.Println("AcceptLanguage:", sv.AcceptLanguage()) 29 | fmt.Println("URL:", sv.URL(paapi5.GetItems.Path())) 30 | // Output: 31 | // Marketplace: www.amazon.co.jp 32 | // Region: us-west-2 33 | // AcceptLanguage: ja_JP 34 | // URL: https://webservices.amazon.co.jp/paapi5/getitems 35 | } 36 | 37 | func ExampleDefaultClient() { 38 | client := paapi5.DefaultClient("mytag-20", "AKIAIOSFODNN7EXAMPLE", "1234567890") //Create default client 39 | fmt.Println("Marketplace:", client.Marketplace()) 40 | // Output: 41 | // Marketplace: www.amazon.com 42 | } 43 | 44 | func ExampleClient() { 45 | //Create client for Janan region 46 | client := paapi5.New( 47 | paapi5.WithMarketplace(paapi5.LocaleJapan), 48 | ).CreateClient( 49 | "mytag-20", 50 | "AKIAIOSFODNN7EXAMPLE", 51 | "1234567890", 52 | paapi5.WithContext(context.Background()), 53 | paapi5.WithHttpClient(http.DefaultClient), 54 | ) 55 | fmt.Println("Marketplace:", client.Marketplace()) 56 | // Output: 57 | // Marketplace: www.amazon.co.jp 58 | } 59 | 60 | /* Copyright 2019 Spiegel 61 | * 62 | * Licensed under the Apache License, Version 2.0 (the "License"); 63 | * you may not use this file except in compliance with the License. 64 | * You may obtain a copy of the License at 65 | * 66 | * http://www.apache.org/licenses/LICENSE-2.0 67 | * 68 | * Unless required by applicable law or agreed to in writing, software 69 | * distributed under the License is distributed on an "AS IS" BASIS, 70 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 71 | * See the License for the specific language governing permissions and 72 | * limitations under the License. 73 | */ 74 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/goark/pa-api 2 | 3 | go 1.23 4 | 5 | toolchain go1.23.1 6 | 7 | require ( 8 | github.com/goark/errs v1.3.2 9 | github.com/goark/fetch v0.4.1 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/goark/errs v1.3.2 h1:ifccNe1aK7Xezt4XVYwHUqalmnfhuphnEvh3FshCReQ= 2 | github.com/goark/errs v1.3.2/go.mod h1:ZsQucxaDFVfSB8I99j4bxkDRfNOrlKINwg72QMuRWKw= 3 | github.com/goark/fetch v0.4.1 h1:Y59g9sAdgqjPS7UADdLIoQGRxJE1WMo5ixlJi/ZcCfc= 4 | github.com/goark/fetch v0.4.1/go.mod h1:umZxLIJHa15J8EQSp5zft1dHDv0VGwmQL6pOfaJ60FY= 5 | -------------------------------------------------------------------------------- /marketplace.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | // Marketplace is interface class of locale information. 4 | type Marketplace interface { 5 | String() string 6 | HostName() string 7 | Region() string 8 | Language() string 9 | } 10 | 11 | // MarketplaceEnum is enumeration of locale information. 12 | type MarketplaceEnum int 13 | 14 | const ( 15 | LocaleUnknown MarketplaceEnum = iota //Unknown local 16 | LocaleAustralia //Australia 17 | LocaleBrazil //Brazil 18 | LocaleCanada //Canada 19 | LocaleEgypt //Egypt 20 | LocaleFrance //France 21 | LocaleGermany //Germany 22 | LocaleIndia //India 23 | LocaleItaly //Italy 24 | LocaleJapan //Japan 25 | LocaleMexico //Mexico 26 | LocaleNetherlands //Netherlands 27 | LocalePoland //Poland 28 | LocaleSingapore //Singapore 29 | LocaleSaudiArabia //SaudiArabia 30 | LocaleSpain //Spain 31 | LocaleSweden //Sweden 32 | LocaleTurkey //Turkey 33 | LocaleUnitedArabEmirates //United Arab Emirates 34 | LocaleUnitedKingdom //United Kingdom 35 | LocaleUnitedStates //United States 36 | DefaultMarketplace = LocaleUnitedStates 37 | ) 38 | 39 | var marketplaceMap = map[MarketplaceEnum]string{ 40 | LocaleAustralia: "www.amazon.com.au", //Australia 41 | LocaleBrazil: "www.amazon.com.br", //Brazil 42 | LocaleCanada: "www.amazon.ca", //Canada 43 | LocaleEgypt: "www.amazon.eg", //Egypt 44 | LocaleFrance: "www.amazon.fr", //France 45 | LocaleGermany: "www.amazon.de", //Germany 46 | LocaleIndia: "www.amazon.in", //India 47 | LocaleItaly: "www.amazon.it", //Italy 48 | LocaleJapan: "www.amazon.co.jp", //Japan 49 | LocaleMexico: "www.amazon.com.mx", //Mexico 50 | LocaleNetherlands: "www.amazon.nl", //Netherlands 51 | LocalePoland: "www.amazon.pl", //Poland 52 | LocaleSingapore: "www.amazon.sg", //Singapore 53 | LocaleSaudiArabia: "www.amazon.sa", //SaudiArabia 54 | LocaleSpain: "www.amazon.es", //Spain 55 | LocaleSweden: "www.amazon.se", //Sweden 56 | LocaleTurkey: "www.amazon.com.tr", //Turkey 57 | LocaleUnitedArabEmirates: "www.amazon.ae", //United Arab Emirates 58 | LocaleUnitedKingdom: "www.amazon.co.uk", //United Kingdom 59 | LocaleUnitedStates: "www.amazon.com", //United States 60 | } 61 | 62 | var hostMap = map[MarketplaceEnum]string{ 63 | LocaleAustralia: "webservices.amazon.com.au", //Australia 64 | LocaleBrazil: "webservices.amazon.com.br", //Brazil 65 | LocaleCanada: "webservices.amazon.ca", //Canada 66 | LocaleEgypt: "webservices.amazon.eg", //Egypt 67 | LocaleFrance: "webservices.amazon.fr", //France 68 | LocaleGermany: "webservices.amazon.de", //Germany 69 | LocaleIndia: "webservices.amazon.in", //India 70 | LocaleItaly: "webservices.amazon.it", //Italy 71 | LocaleJapan: "webservices.amazon.co.jp", //Japan 72 | LocaleMexico: "webservices.amazon.com.mx", //Mexico 73 | LocaleNetherlands: "webservices.amazon.nl", //Netherlands 74 | LocalePoland: "webservices.amazon.pl", //Poland 75 | LocaleSingapore: "webservices.amazon.sg", //Singapore 76 | LocaleSaudiArabia: "webservices.amazon.sa", //SaudiArabia 77 | LocaleSpain: "webservices.amazon.es", //Spain 78 | LocaleSweden: "webservices.amazon.se", //Sweden 79 | LocaleTurkey: "webservices.amazon.com.tr", //Turkey 80 | LocaleUnitedArabEmirates: "webservices.amazon.ae", //United Arab Emirates 81 | LocaleUnitedKingdom: "webservices.amazon.co.uk", //United Kingdom 82 | LocaleUnitedStates: "webservices.amazon.com", //United States 83 | } 84 | 85 | var regionMap = map[MarketplaceEnum]string{ 86 | LocaleAustralia: "us-west-2", //Australia 87 | LocaleBrazil: "us-east-1", //Brazil 88 | LocaleCanada: "us-east-1", //Canada 89 | LocaleEgypt: "us-west-1", //Egypt 90 | LocaleFrance: "eu-west-1", //France 91 | LocaleGermany: "eu-west-1", //Germany 92 | LocaleIndia: "eu-west-1", //India 93 | LocaleItaly: "eu-west-1", //Italy 94 | LocaleJapan: "us-west-2", //Japan 95 | LocaleMexico: "us-east-1", //Mexico 96 | LocaleNetherlands: "eu-west-1", //Netherlands 97 | LocalePoland: "eu-west-1", //Poland 98 | LocaleSingapore: "us-west-2", //Singapore 99 | LocaleSaudiArabia: "eu-west-1", //SaudiArabia 100 | LocaleSpain: "eu-west-1", //Spain 101 | LocaleSweden: "eu-west-1", //Sweden 102 | LocaleTurkey: "eu-west-1", //Turkey 103 | LocaleUnitedArabEmirates: "eu-west-1", //United Arab Emirates 104 | LocaleUnitedKingdom: "eu-west-1", //United Kingdom 105 | LocaleUnitedStates: "us-east-1", //United States 106 | } 107 | 108 | var languageMap = map[MarketplaceEnum]string{ 109 | LocaleAustralia: "en_AU", //Australia 110 | LocaleBrazil: "pt_BR", //Brazil 111 | LocaleCanada: "en_CA", //Canada 112 | LocaleEgypt: "ar_EG", //Egypt 113 | LocaleFrance: "fr_FR", //France 114 | LocaleGermany: "de_DE", //Germany 115 | LocaleIndia: "en_IN", //India 116 | LocaleItaly: "it_IT", //Italy 117 | LocaleJapan: "ja_JP", //Japan 118 | LocaleMexico: "es_MX", //Mexico 119 | LocaleNetherlands: "nl_NL", //Netherlands 120 | LocalePoland: "pl_PL", //Poland 121 | LocaleSingapore: "en_SG", //Singapore 122 | LocaleSaudiArabia: "en_AE", //SaudiArabia 123 | LocaleSpain: "es_ES", //Spain 124 | LocaleSweden: "sv_SE", //Sweden 125 | LocaleTurkey: "tr_TR", //Turkey 126 | LocaleUnitedArabEmirates: "en_AE", //United Arab Emirates 127 | LocaleUnitedKingdom: "en_GB", //United Kingdom 128 | LocaleUnitedStates: "en_US", //United States 129 | } 130 | 131 | // MarketplaceOf function returns Marketplace instance from service domain. 132 | func MarketplaceOf(s string) Marketplace { 133 | for k, v := range marketplaceMap { 134 | if s == v { 135 | return k 136 | } 137 | } 138 | return LocaleUnknown 139 | } 140 | 141 | // String returns marketplace name of Marketplace. 142 | func (m MarketplaceEnum) String() string { 143 | if s, ok := marketplaceMap[m]; ok { 144 | return s 145 | } 146 | return marketplaceMap[DefaultMarketplace] 147 | } 148 | 149 | // HostName returns hostname of Marketplace. 150 | func (m MarketplaceEnum) HostName() string { 151 | if s, ok := hostMap[m]; ok { 152 | return s 153 | } 154 | return hostMap[LocaleUnitedStates] 155 | } 156 | 157 | // Region returns region name of Marketplace. 158 | func (m MarketplaceEnum) Region() string { 159 | if s, ok := regionMap[m]; ok { 160 | return s 161 | } 162 | return regionMap[DefaultMarketplace] 163 | } 164 | 165 | // Language returns language name of Marketplace. 166 | func (m MarketplaceEnum) Language() string { 167 | if s, ok := languageMap[m]; ok { 168 | return s 169 | } 170 | return languageMap[DefaultMarketplace] 171 | } 172 | 173 | /* Copyright 2019-2021 Spiegel 174 | * 175 | * Licensed under the Apache License, Version 2.0 (the "License"); 176 | * you may not use this file except in compliance with the License. 177 | * You may obtain a copy of the License at 178 | * 179 | * http://www.apache.org/licenses/LICENSE-2.0 180 | * 181 | * Unless required by applicable law or agreed to in writing, software 182 | * distributed under the License is distributed on an "AS IS" BASIS, 183 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 184 | * See the License for the specific language governing permissions and 185 | * limitations under the License. 186 | */ 187 | -------------------------------------------------------------------------------- /marketplace_test.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import "testing" 4 | 5 | func TestMarketplace(t *testing.T) { 6 | testCases := []struct { 7 | name string 8 | marketplace Marketplace 9 | str string 10 | hostName string 11 | region string 12 | language string 13 | }{ 14 | {name: "www.amazon.com.au", marketplace: LocaleAustralia, str: "www.amazon.com.au", hostName: "webservices.amazon.com.au", region: "us-west-2", language: "en_AU"}, 15 | {name: "www.amazon.com.br", marketplace: LocaleBrazil, str: "www.amazon.com.br", hostName: "webservices.amazon.com.br", region: "us-east-1", language: "pt_BR"}, 16 | {name: "www.amazon.ca", marketplace: LocaleCanada, str: "www.amazon.ca", hostName: "webservices.amazon.ca", region: "us-east-1", language: "en_CA"}, 17 | {name: "www.amazon.eg", marketplace: LocaleEgypt, str: "www.amazon.eg", hostName: "webservices.amazon.eg", region: "us-west-1", language: "ar_EG"}, 18 | {name: "www.amazon.fr", marketplace: LocaleFrance, str: "www.amazon.fr", hostName: "webservices.amazon.fr", region: "eu-west-1", language: "fr_FR"}, 19 | {name: "www.amazon.de", marketplace: LocaleGermany, str: "www.amazon.de", hostName: "webservices.amazon.de", region: "eu-west-1", language: "de_DE"}, 20 | {name: "www.amazon.in", marketplace: LocaleIndia, str: "www.amazon.in", hostName: "webservices.amazon.in", region: "eu-west-1", language: "en_IN"}, 21 | {name: "www.amazon.it", marketplace: LocaleItaly, str: "www.amazon.it", hostName: "webservices.amazon.it", region: "eu-west-1", language: "it_IT"}, 22 | {name: "www.amazon.co.jp", marketplace: LocaleJapan, str: "www.amazon.co.jp", hostName: "webservices.amazon.co.jp", region: "us-west-2", language: "ja_JP"}, 23 | {name: "www.amazon.com.mx", marketplace: LocaleMexico, str: "www.amazon.com.mx", hostName: "webservices.amazon.com.mx", region: "us-east-1", language: "es_MX"}, 24 | {name: "www.amazon.nl", marketplace: LocaleNetherlands, str: "www.amazon.nl", hostName: "webservices.amazon.nl", region: "eu-west-1", language: "nl_NL"}, 25 | {name: "www.amazon.pl", marketplace: LocalePoland, str: "www.amazon.pl", hostName: "webservices.amazon.pl", region: "eu-west-1", language: "pl_PL"}, 26 | {name: "www.amazon.sg", marketplace: LocaleSingapore, str: "www.amazon.sg", hostName: "webservices.amazon.sg", region: "us-west-2", language: "en_SG"}, 27 | {name: "www.amazon.sa", marketplace: LocaleSaudiArabia, str: "www.amazon.sa", hostName: "webservices.amazon.sa", region: "eu-west-1", language: "en_AE"}, 28 | {name: "www.amazon.es", marketplace: LocaleSpain, str: "www.amazon.es", hostName: "webservices.amazon.es", region: "eu-west-1", language: "es_ES"}, 29 | {name: "www.amazon.se", marketplace: LocaleSweden, str: "www.amazon.se", hostName: "webservices.amazon.se", region: "eu-west-1", language: "sv_SE"}, 30 | {name: "www.amazon.com.tr", marketplace: LocaleTurkey, str: "www.amazon.com.tr", hostName: "webservices.amazon.com.tr", region: "eu-west-1", language: "tr_TR"}, 31 | {name: "www.amazon.ae", marketplace: LocaleUnitedArabEmirates, str: "www.amazon.ae", hostName: "webservices.amazon.ae", region: "eu-west-1", language: "en_AE"}, 32 | {name: "www.amazon.co.uk", marketplace: LocaleUnitedKingdom, str: "www.amazon.co.uk", hostName: "webservices.amazon.co.uk", region: "eu-west-1", language: "en_GB"}, 33 | {name: "www.amazon.com", marketplace: LocaleUnitedStates, str: "www.amazon.com", hostName: "webservices.amazon.com", region: "us-east-1", language: "en_US"}, 34 | {name: "foo.bar", marketplace: LocaleUnknown, str: "www.amazon.com", hostName: "webservices.amazon.com", region: "us-east-1", language: "en_US"}, 35 | } 36 | for _, tc := range testCases { 37 | m := MarketplaceOf(tc.name) 38 | if m != tc.marketplace { 39 | t.Errorf("\"%v\" is \"%v\", want \"%v\"", tc.name, m, tc.marketplace) 40 | } 41 | if m.String() != tc.str { 42 | t.Errorf("Marketplace.String() is \"%v\", want \"%v\"", m.String(), tc.str) 43 | } 44 | if m.HostName() != tc.hostName { 45 | t.Errorf("Marketplace.HostName() is \"%v\", want \"%v\"", m.HostName(), tc.hostName) 46 | } 47 | if m.Region() != tc.region { 48 | t.Errorf("Marketplace.Region() is \"%v\", want \"%v\"", m.Region(), tc.region) 49 | } 50 | if m.Language() != tc.language { 51 | t.Errorf("Marketplace.Language() is \"%v\", want \"%v\"", m.Language(), tc.language) 52 | } 53 | } 54 | } 55 | 56 | /* Copyright 2019 Spiegel 57 | * 58 | * Licensed under the Apache License, Version 2.0 (the "License"); 59 | * you may not use this file except in compliance with the License. 60 | * You may obtain a copy of the License at 61 | * 62 | * http://www.apache.org/licenses/LICENSE-2.0 63 | * 64 | * Unless required by applicable law or agreed to in writing, software 65 | * distributed under the License is distributed on an "AS IS" BASIS, 66 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 67 | * See the License for the specific language governing permissions and 68 | * limitations under the License. 69 | */ 70 | -------------------------------------------------------------------------------- /operation.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import ( 4 | "encoding/json" 5 | "path" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | //Operation is enumeration of PA-API operation 11 | type Operation int 12 | 13 | var _ json.Marshaler = Operation(0) //Operation type is compatible with json.Marshaler interface 14 | var _ json.Unmarshaler = (*Operation)(nil) //Operation type is compatible with json.Unmarshaler interface 15 | 16 | const ( 17 | NullOperation Operation = iota //Unknown 18 | GetVariations //GetVariations 19 | GetItems //GetItems 20 | SearchItems //SearchItems 21 | GetBrowseNodes //GetBrowseNodes 22 | ) 23 | 24 | var nameMap = map[Operation]string{ 25 | GetVariations: "GetVariations", 26 | GetItems: "GetItems", 27 | SearchItems: "SearchItems", 28 | GetBrowseNodes: "GetBrowseNodes", 29 | } 30 | 31 | //String method is a implementation of fmt.Stringer interface. 32 | func (c Operation) String() string { 33 | if s, ok := nameMap[c]; ok { 34 | return s 35 | } 36 | return "" 37 | } 38 | 39 | //Path method returns URL path of PA-API operation 40 | func (c Operation) Path() string { 41 | cmd := c.String() 42 | if len(cmd) == 0 { 43 | return "" 44 | } 45 | return path.Join("/paapi5", strings.ToLower(cmd)) 46 | } 47 | 48 | //Target method returns taget name of PA-API operation 49 | func (c Operation) Target() string { 50 | cmd := c.String() 51 | if len(cmd) == 0 { 52 | return "" 53 | } 54 | return strings.Join([]string{"com.amazon.paapi5.v1.ProductAdvertisingAPIv1", cmd}, ".") 55 | } 56 | 57 | //UnmarshalJSON method implements json.Unmarshaler interface. 58 | func (c *Operation) UnmarshalJSON(b []byte) error { 59 | s := string(b) 60 | if ss, err := strconv.Unquote(s); err == nil { 61 | s = ss 62 | } 63 | for k, v := range nameMap { 64 | if s == v { 65 | *c = k 66 | } 67 | } 68 | return nil 69 | } 70 | 71 | //MarshalJSON method implements the json.Marshaler interface. 72 | func (c Operation) MarshalJSON() ([]byte, error) { 73 | return []byte(strconv.Quote(c.String())), nil 74 | } 75 | 76 | /* Copyright 2019-2022 Spiegel 77 | * 78 | * Licensed under the Apache License, Version 2.0 (the "License"); 79 | * you may not use this file except in compliance with the License. 80 | * You may obtain a copy of the License at 81 | * 82 | * http://www.apache.org/licenses/LICENSE-2.0 83 | * 84 | * Unless required by applicable law or agreed to in writing, software 85 | * distributed under the License is distributed on an "AS IS" BASIS, 86 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 87 | * See the License for the specific language governing permissions and 88 | * limitations under the License. 89 | */ 90 | -------------------------------------------------------------------------------- /query/getbrowsenodes.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | paapi5 "github.com/goark/pa-api" 5 | ) 6 | 7 | //GetItems type is embedded Query for GetItems operation in PA-API v5 8 | type GetBrowseNodes struct { 9 | Query 10 | } 11 | 12 | var _ paapi5.Query = (*GetItems)(nil) //GetItems is compatible with paapi5.Query interface 13 | 14 | //New creates new GetBrowseNodes instance 15 | func NewGetBrowseNodes(marketplace, partnerTag, partnerType string) *GetBrowseNodes { 16 | q := &GetBrowseNodes{*(New(paapi5.GetBrowseNodes))} 17 | q.Request(Marketplace, marketplace).Request(PartnerTag, partnerTag).Request(PartnerType, partnerType) 18 | return q 19 | } 20 | 21 | var requestsOfGetBrowseNodes = []RequestFilter{ 22 | BrowseNodeIds, 23 | LanguagesOfPreference, 24 | Marketplace, 25 | PartnerTag, 26 | PartnerType, 27 | } 28 | 29 | //RequestFilters adds RequestFilter to Query instance 30 | func (q *GetBrowseNodes) Request(request RequestFilter, value interface{}) *GetBrowseNodes { 31 | if request.findIn(requestsOfGetBrowseNodes) { 32 | q.With().RequestFilters(RequestMap{request: value}) 33 | } 34 | return q 35 | } 36 | 37 | //BrowseNodeIds sets ItemIds in GetItems instance 38 | func (q *GetBrowseNodes) BrowseNodeIds(itms []string) *GetBrowseNodes { 39 | return q.Request(BrowseNodeIds, itms) 40 | } 41 | 42 | //EnableBrowseNodes sets the resource of EnableBrowseNodes 43 | func (q *GetBrowseNodes) EnableBrowseNodes() *GetBrowseNodes { 44 | q.With().BrowseNodes() 45 | return q 46 | } 47 | 48 | /* Copyright 2022 Spiegel and contributors 49 | * 50 | * Licensed under the Apache License, Version 2.0 (the "License"); 51 | * you may not use this file except in compliance with the License. 52 | * You may obtain a copy of the License at 53 | * 54 | * http://www.apache.org/licenses/LICENSE-2.0 55 | * 56 | * Unless required by applicable law or agreed to in writing, software 57 | * distributed under the License is distributed on an "AS IS" BASIS, 58 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 59 | * See the License for the specific language governing permissions and 60 | * limitations under the License. 61 | */ 62 | -------------------------------------------------------------------------------- /query/getbrowsenodes_test.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetBrowseNodes(t *testing.T) { 8 | testCases := []struct { 9 | q *GetBrowseNodes 10 | str string 11 | }{ 12 | {q: NewGetBrowseNodes("foo.bar", "mytag-20", "Associates"), str: `{"Operation":"GetBrowseNodes","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 13 | {q: NewGetBrowseNodes("foo.bar", "mytag-20", "Associates").BrowseNodeIds([]string{"123"}), str: `{"Operation":"GetBrowseNodes","BrowseNodeIds":["123"],"Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 14 | } 15 | for _, tc := range testCases { 16 | if str := tc.q.String(); str != tc.str { 17 | t.Errorf("BrowseNodes.String() is \"%v\", want \"%v\"", str, tc.str) 18 | } 19 | } 20 | } 21 | 22 | func TestRequestFiltersInGetBrowseNodes(t *testing.T) { 23 | testCases := []struct { 24 | q *GetBrowseNodes 25 | str string 26 | }{ 27 | {q: NewGetBrowseNodes("", "", ""), str: `{"Operation":"GetBrowseNodes"}`}, 28 | {q: NewGetBrowseNodes("", "", "").Request(Actor, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 29 | {q: NewGetBrowseNodes("", "", "").Request(Artist, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 30 | {q: NewGetBrowseNodes("", "", "").Request(Availability, "Available"), str: `{"Operation":"GetBrowseNodes"}`}, 31 | {q: NewGetBrowseNodes("", "", "").Request(Author, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 32 | {q: NewGetBrowseNodes("", "", "").Request(Brand, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 33 | {q: NewGetBrowseNodes("", "", "").Request(BrowseNodeID, "123"), str: `{"Operation":"GetBrowseNodes"}`}, 34 | {q: NewGetBrowseNodes("", "", "").Request(Condition, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 35 | {q: NewGetBrowseNodes("", "", "").Request(Condition, "Any"), str: `{"Operation":"GetBrowseNodes"}`}, 36 | {q: NewGetBrowseNodes("", "", "").Request(Condition, "New"), str: `{"Operation":"GetBrowseNodes"}`}, 37 | {q: NewGetBrowseNodes("", "", "").Request(Condition, "Used"), str: `{"Operation":"GetBrowseNodes"}`}, 38 | {q: NewGetBrowseNodes("", "", "").Request(Condition, "Collectible"), str: `{"Operation":"GetBrowseNodes"}`}, 39 | {q: NewGetBrowseNodes("", "", "").Request(Condition, "Refurbished"), str: `{"Operation":"GetBrowseNodes"}`}, 40 | {q: NewGetBrowseNodes("", "", "").Request(CurrencyOfPreference, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 41 | {q: NewGetBrowseNodes("", "", "").Request(DeliveryFlags, "AmazonGlobal"), str: `{"Operation":"GetBrowseNodes"}`}, 42 | {q: NewGetBrowseNodes("", "", "").Request(ItemIds, "4900900028"), str: `{"Operation":"GetBrowseNodes"}`}, 43 | {q: NewGetBrowseNodes("", "", "").Request(ItemIdType, "ASIN"), str: `{"Operation":"GetBrowseNodes"}`}, 44 | {q: NewGetBrowseNodes("", "", "").Request(ItemCount, 1), str: `{"Operation":"GetBrowseNodes"}`}, 45 | {q: NewGetBrowseNodes("", "", "").Request(ItemPage, 1), str: `{"Operation":"GetBrowseNodes"}`}, 46 | {q: NewGetBrowseNodes("", "", "").Request(ItemPage, 1), str: `{"Operation":"GetBrowseNodes"}`}, 47 | {q: NewGetBrowseNodes("", "", "").Request(Keywords, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 48 | {q: NewGetBrowseNodes("", "", "").Request(BrowseNodeIds, "123"), str: `{"Operation":"GetBrowseNodes","BrowseNodeIds":["123"]}`}, 49 | {q: NewGetBrowseNodes("", "", "").Request(BrowseNodeIds, []string{"123", "456"}), str: `{"Operation":"GetBrowseNodes","BrowseNodeIds":["123","456"]}`}, 50 | {q: NewGetBrowseNodes("", "", "").Request(LanguagesOfPreference, "foo"), str: `{"Operation":"GetBrowseNodes","LanguagesOfPreference":["foo"]}`}, 51 | {q: NewGetBrowseNodes("", "", "").Request(LanguagesOfPreference, []string{"foo", "bar"}), str: `{"Operation":"GetBrowseNodes","LanguagesOfPreference":["foo","bar"]}`}, 52 | {q: NewGetBrowseNodes("", "", "").Request(Marketplace, "foo.bar"), str: `{"Operation":"GetBrowseNodes","Marketplace":"foo.bar"}`}, 53 | {q: NewGetBrowseNodes("", "", "").Request(MaxPrice, 1), str: `{"Operation":"GetBrowseNodes"}`}, 54 | {q: NewGetBrowseNodes("", "", "").Request(Merchant, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 55 | {q: NewGetBrowseNodes("", "", "").Request(Merchant, "All"), str: `{"Operation":"GetBrowseNodes"}`}, 56 | {q: NewGetBrowseNodes("", "", "").Request(Merchant, "Amazon"), str: `{"Operation":"GetBrowseNodes"}`}, 57 | {q: NewGetBrowseNodes("", "", "").Request(MinPrice, 1), str: `{"Operation":"GetBrowseNodes"}`}, 58 | {q: NewGetBrowseNodes("", "", "").Request(MinReviewsRating, 1), str: `{"Operation":"GetBrowseNodes"}`}, 59 | {q: NewGetBrowseNodes("", "", "").Request(MinSavingPercent, 1), str: `{"Operation":"GetBrowseNodes"}`}, 60 | {q: NewGetBrowseNodes("", "", "").Request(OfferCount, -1), str: `{"Operation":"GetBrowseNodes"}`}, 61 | {q: NewGetBrowseNodes("", "", "").Request(OfferCount, 0), str: `{"Operation":"GetBrowseNodes"}`}, 62 | {q: NewGetBrowseNodes("", "", "").Request(OfferCount, 1), str: `{"Operation":"GetBrowseNodes"}`}, 63 | {q: NewGetBrowseNodes("", "", "").Request(OfferCount, 123), str: `{"Operation":"GetBrowseNodes"}`}, 64 | {q: NewGetBrowseNodes("", "", "").Request(PartnerTag, "foo"), str: `{"Operation":"GetBrowseNodes","PartnerTag":"foo"}`}, 65 | {q: NewGetBrowseNodes("", "", "").Request(PartnerType, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 66 | {q: NewGetBrowseNodes("", "", "").Request(PartnerType, "Associates"), str: `{"Operation":"GetBrowseNodes","PartnerType":"Associates"}`}, 67 | {q: NewGetBrowseNodes("", "", "").Request(Properties, map[string]string{"foo": "bar"}), str: `{"Operation":"GetBrowseNodes"}`}, 68 | {q: NewGetBrowseNodes("", "", "").Request(SearchIndex, "All"), str: `{"Operation":"GetBrowseNodes"}`}, 69 | {q: NewGetBrowseNodes("", "", "").Request(SortBy, "AvgCustomerReviews"), str: `{"Operation":"GetBrowseNodes"}`}, 70 | {q: NewGetBrowseNodes("", "", "").Request(Title, "foo"), str: `{"Operation":"GetBrowseNodes"}`}, 71 | } 72 | 73 | for _, tc := range testCases { 74 | if str := tc.q.String(); str != tc.str { 75 | t.Errorf("BrowseNodes.String() is \"%v\", want \"%v\"", str, tc.str) 76 | } 77 | } 78 | } 79 | 80 | func TestResourcesInGetBrowseNodes(t *testing.T) { 81 | testCases := []struct { 82 | q *GetBrowseNodes 83 | str string 84 | }{ 85 | {q: NewGetBrowseNodes("", "", "").EnableBrowseNodes(), str: `{"Operation":"GetBrowseNodes","Resources":["BrowseNodes.Ancestor","BrowseNodes.Children"]}`}, 86 | } 87 | 88 | for _, tc := range testCases { 89 | if str := tc.q.String(); str != tc.str { 90 | t.Errorf("Query.String() is \"%v\", want \"%v\"", str, tc.str) 91 | } 92 | } 93 | } 94 | 95 | /* Copyright 2022 Spiegel and contributors 96 | * 97 | * Licensed under the Apache License, Version 2.0 (the "License"); 98 | * you may not use this file except in compliance with the License. 99 | * You may obtain a copy of the License at 100 | * 101 | * http://www.apache.org/licenses/LICENSE-2.0 102 | * 103 | * Unless required by applicable law or agreed to in writing, software 104 | * distributed under the License is distributed on an "AS IS" BASIS, 105 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 106 | * See the License for the specific language governing permissions and 107 | * limitations under the License. 108 | */ 109 | -------------------------------------------------------------------------------- /query/getitems.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | paapi5 "github.com/goark/pa-api" 5 | ) 6 | 7 | //GetItems type is embedded Query for GetItems operation in PA-API v5 8 | type GetItems struct { 9 | Query 10 | } 11 | 12 | var _ paapi5.Query = (*GetItems)(nil) //GetItems is compatible with paapi5.Query interface 13 | 14 | //New creates new GetItems instance 15 | func NewGetItems(marketplace, partnerTag, partnerType string) *GetItems { 16 | q := &GetItems{*(New(paapi5.GetItems))} 17 | q.Request(Marketplace, marketplace).Request(PartnerTag, partnerTag).Request(PartnerType, partnerType) 18 | return q 19 | } 20 | 21 | var requestsOfGetItems = []RequestFilter{ 22 | Condition, 23 | CurrencyOfPreference, 24 | ItemIdType, 25 | ItemIds, 26 | LanguagesOfPreference, 27 | Marketplace, 28 | Merchant, 29 | OfferCount, 30 | PartnerTag, 31 | PartnerType, 32 | } 33 | 34 | //RequestFilters adds RequestFilter to Query instance 35 | func (q *GetItems) Request(request RequestFilter, value interface{}) *GetItems { 36 | if request.findIn(requestsOfGetItems) { 37 | q.With().RequestFilters(RequestMap{request: value}) 38 | } 39 | return q 40 | } 41 | 42 | //ASINs sets ItemIds in GetItems instance 43 | func (q *GetItems) ASINs(itms []string) *GetItems { 44 | return q.Request(ItemIds, itms).Request(ItemIdType, "ASIN") 45 | } 46 | 47 | //EnableBrowseNodeInfo sets the resource of BrowseNodeInfo 48 | func (q *GetItems) EnableBrowseNodeInfo() *GetItems { 49 | q.With().BrowseNodeInfo() 50 | return q 51 | } 52 | 53 | //EnableImages sets the resource of Images 54 | func (q *GetItems) EnableImages() *GetItems { 55 | q.With().Images() 56 | return q 57 | } 58 | 59 | //EnableItemInfo sets the resource of ItemInfo 60 | func (q *GetItems) EnableItemInfo() *GetItems { 61 | q.With().ItemInfo() 62 | return q 63 | } 64 | 65 | //EnableOffers sets the resource of Offers 66 | func (q *GetItems) EnableOffers() *GetItems { 67 | q.With().Offers() 68 | return q 69 | } 70 | 71 | //EnableParentASIN sets the resource of ParentASIN 72 | func (q *GetItems) EnableParentASIN() *GetItems { 73 | q.With().ParentASIN() 74 | return q 75 | } 76 | 77 | //EnableCustomerReviews sets the resource of CustomerReviews 78 | func (q *GetItems) EnableCustomerReviews() *GetItems { 79 | q.With().CustomerReviews() 80 | return q 81 | } 82 | 83 | /* Copyright 2019 Spiegel and contributors 84 | * 85 | * Licensed under the Apache License, Version 2.0 (the "License"); 86 | * you may not use this file except in compliance with the License. 87 | * You may obtain a copy of the License at 88 | * 89 | * http://www.apache.org/licenses/LICENSE-2.0 90 | * 91 | * Unless required by applicable law or agreed to in writing, software 92 | * distributed under the License is distributed on an "AS IS" BASIS, 93 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 94 | * See the License for the specific language governing permissions and 95 | * limitations under the License. 96 | */ 97 | -------------------------------------------------------------------------------- /query/getitems_test.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetItems(t *testing.T) { 8 | testCases := []struct { 9 | q *GetItems 10 | str string 11 | }{ 12 | {q: NewGetItems("foo.bar", "mytag-20", "Associates"), str: `{"Operation":"GetItems","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 13 | {q: NewGetItems("foo.bar", "mytag-20", "Associates").ASINs([]string{"4900900028"}), str: `{"Operation":"GetItems","ItemIds":["4900900028"],"ItemIdType":"ASIN","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 14 | } 15 | for _, tc := range testCases { 16 | if str := tc.q.String(); str != tc.str { 17 | t.Errorf("GetItems.String() is \"%v\", want \"%v\"", str, tc.str) 18 | } 19 | } 20 | } 21 | 22 | func TestRequestFiltersInGetItems(t *testing.T) { 23 | testCases := []struct { 24 | q *GetItems 25 | str string 26 | }{ 27 | {q: NewGetItems("", "", ""), str: `{"Operation":"GetItems"}`}, 28 | {q: NewGetItems("", "", "").Request(Actor, "foo"), str: `{"Operation":"GetItems"}`}, 29 | {q: NewGetItems("", "", "").Request(Artist, "foo"), str: `{"Operation":"GetItems"}`}, 30 | {q: NewGetItems("", "", "").Request(Availability, "Available"), str: `{"Operation":"GetItems"}`}, 31 | {q: NewGetItems("", "", "").Request(Author, "foo"), str: `{"Operation":"GetItems"}`}, 32 | {q: NewGetItems("", "", "").Request(Brand, "foo"), str: `{"Operation":"GetItems"}`}, 33 | {q: NewGetItems("", "", "").Request(BrowseNodeID, "123"), str: `{"Operation":"GetItems"}`}, 34 | {q: NewGetItems("", "", "").Request(Condition, "foo"), str: `{"Operation":"GetItems"}`}, 35 | {q: NewGetItems("", "", "").Request(Condition, "Any"), str: `{"Operation":"GetItems","Condition":"Any"}`}, 36 | {q: NewGetItems("", "", "").Request(Condition, "New"), str: `{"Operation":"GetItems","Condition":"New"}`}, 37 | {q: NewGetItems("", "", "").Request(Condition, "Used"), str: `{"Operation":"GetItems","Condition":"Used"}`}, 38 | {q: NewGetItems("", "", "").Request(Condition, "Collectible"), str: `{"Operation":"GetItems","Condition":"Collectible"}`}, 39 | {q: NewGetItems("", "", "").Request(Condition, "Refurbished"), str: `{"Operation":"GetItems","Condition":"Refurbished"}`}, 40 | {q: NewGetItems("", "", "").Request(CurrencyOfPreference, "foo"), str: `{"Operation":"GetItems","CurrencyOfPreference":"foo"}`}, 41 | {q: NewGetItems("", "", "").Request(DeliveryFlags, "AmazonGlobal"), str: `{"Operation":"GetItems"}`}, 42 | {q: NewGetItems("", "", "").Request(ItemIds, "4900900028"), str: `{"Operation":"GetItems","ItemIds":["4900900028"]}`}, 43 | {q: NewGetItems("", "", "").Request(ItemIdType, "ASIN"), str: `{"Operation":"GetItems","ItemIdType":"ASIN"}`}, 44 | {q: NewGetItems("", "", "").Request(ItemCount, 1), str: `{"Operation":"GetItems"}`}, 45 | {q: NewGetItems("", "", "").Request(ItemPage, 1), str: `{"Operation":"GetItems"}`}, 46 | {q: NewGetItems("", "", "").Request(ItemPage, 1), str: `{"Operation":"GetItems"}`}, 47 | {q: NewGetItems("", "", "").Request(Keywords, "foo"), str: `{"Operation":"GetItems"}`}, 48 | {q: NewGetItems("", "", "").Request(BrowseNodeIds, "123"), str: `{"Operation":"GetItems"}`}, 49 | {q: NewGetItems("", "", "").Request(BrowseNodeIds, []string{"123", "456"}), str: `{"Operation":"GetItems"}`}, 50 | {q: NewGetItems("", "", "").Request(LanguagesOfPreference, "foo"), str: `{"Operation":"GetItems","LanguagesOfPreference":["foo"]}`}, 51 | {q: NewGetItems("", "", "").Request(LanguagesOfPreference, []string{"foo", "bar"}), str: `{"Operation":"GetItems","LanguagesOfPreference":["foo","bar"]}`}, 52 | {q: NewGetItems("", "", "").Request(Marketplace, "foo.bar"), str: `{"Operation":"GetItems","Marketplace":"foo.bar"}`}, 53 | {q: NewGetItems("", "", "").Request(MaxPrice, 1), str: `{"Operation":"GetItems"}`}, 54 | {q: NewGetItems("", "", "").Request(Merchant, "foo"), str: `{"Operation":"GetItems"}`}, 55 | {q: NewGetItems("", "", "").Request(Merchant, "All"), str: `{"Operation":"GetItems","Merchant":"All"}`}, 56 | {q: NewGetItems("", "", "").Request(Merchant, "Amazon"), str: `{"Operation":"GetItems","Merchant":"Amazon"}`}, 57 | {q: NewGetItems("", "", "").Request(MinPrice, 1), str: `{"Operation":"GetItems"}`}, 58 | {q: NewGetItems("", "", "").Request(MinReviewsRating, 1), str: `{"Operation":"GetItems"}`}, 59 | {q: NewGetItems("", "", "").Request(MinSavingPercent, 1), str: `{"Operation":"GetItems"}`}, 60 | {q: NewGetItems("", "", "").Request(OfferCount, -1), str: `{"Operation":"GetItems"}`}, 61 | {q: NewGetItems("", "", "").Request(OfferCount, 0), str: `{"Operation":"GetItems"}`}, 62 | {q: NewGetItems("", "", "").Request(OfferCount, 1), str: `{"Operation":"GetItems","OfferCount":1}`}, 63 | {q: NewGetItems("", "", "").Request(OfferCount, 123), str: `{"Operation":"GetItems","OfferCount":123}`}, 64 | {q: NewGetItems("", "", "").Request(PartnerTag, "foo"), str: `{"Operation":"GetItems","PartnerTag":"foo"}`}, 65 | {q: NewGetItems("", "", "").Request(PartnerType, "foo"), str: `{"Operation":"GetItems"}`}, 66 | {q: NewGetItems("", "", "").Request(PartnerType, "Associates"), str: `{"Operation":"GetItems","PartnerType":"Associates"}`}, 67 | {q: NewGetItems("", "", "").Request(Properties, map[string]string{"foo": "bar"}), str: `{"Operation":"GetItems"}`}, 68 | {q: NewGetItems("", "", "").Request(SearchIndex, "All"), str: `{"Operation":"GetItems"}`}, 69 | {q: NewGetItems("", "", "").Request(SortBy, "AvgCustomerReviews"), str: `{"Operation":"GetItems"}`}, 70 | {q: NewGetItems("", "", "").Request(Title, "foo"), str: `{"Operation":"GetItems"}`}, 71 | } 72 | 73 | for _, tc := range testCases { 74 | if str := tc.q.String(); str != tc.str { 75 | t.Errorf("GetItems.String() is \"%v\", want \"%v\"", str, tc.str) 76 | } 77 | } 78 | } 79 | 80 | func TestResourcesInGetItems(t *testing.T) { 81 | testCases := []struct { 82 | q *GetItems 83 | str string 84 | }{ 85 | {q: NewGetItems("", "", "").EnableBrowseNodeInfo(), str: `{"Operation":"GetItems","Resources":["BrowseNodeInfo.BrowseNodes","BrowseNodeInfo.BrowseNodes.Ancestor","BrowseNodeInfo.BrowseNodes.SalesRank","BrowseNodeInfo.WebsiteSalesRank"]}`}, 86 | {q: NewGetItems("", "", "").EnableImages(), str: `{"Operation":"GetItems","Resources":["Images.Primary.Small","Images.Primary.Medium","Images.Primary.Large","Images.Variants.Small","Images.Variants.Medium","Images.Variants.Large"]}`}, 87 | {q: NewGetItems("", "", "").EnableItemInfo(), str: `{"Operation":"GetItems","Resources":["ItemInfo.ByLineInfo","ItemInfo.ContentInfo","ItemInfo.ContentRating","ItemInfo.Classifications","ItemInfo.ExternalIds","ItemInfo.Features","ItemInfo.ManufactureInfo","ItemInfo.ProductInfo","ItemInfo.TechnicalInfo","ItemInfo.Title","ItemInfo.TradeInInfo"]}`}, 88 | {q: NewGetItems("", "", "").EnableOffers(), str: `{"Operation":"GetItems","Resources":["Offers.Listings.Availability.MaxOrderQuantity","Offers.Listings.Availability.Message","Offers.Listings.Availability.MinOrderQuantity","Offers.Listings.Availability.Type","Offers.Listings.Condition","Offers.Listings.Condition.SubCondition","Offers.Listings.DeliveryInfo.IsAmazonFulfilled","Offers.Listings.DeliveryInfo.IsFreeShippingEligible","Offers.Listings.DeliveryInfo.IsPrimeEligible","Offers.Listings.DeliveryInfo.ShippingCharges","Offers.Listings.IsBuyBoxWinner","Offers.Listings.LoyaltyPoints.Points","Offers.Listings.MerchantInfo","Offers.Listings.Price","Offers.Listings.ProgramEligibility.IsPrimeExclusive","Offers.Listings.ProgramEligibility.IsPrimePantry","Offers.Listings.Promotions","Offers.Listings.SavingBasis","Offers.Summaries.HighestPrice","Offers.Summaries.LowestPrice","Offers.Summaries.OfferCount"]}`}, 89 | {q: NewGetItems("", "", "").EnableParentASIN(), str: `{"Operation":"GetItems","Resources":["ParentASIN"]}`}, 90 | {q: NewGetItems("", "", "").EnableCustomerReviews(), str: `{"Operation":"GetItems","Resources":["CustomerReviews.Count","CustomerReviews.StarRating"]}`}, 91 | } 92 | 93 | for _, tc := range testCases { 94 | if str := tc.q.String(); str != tc.str { 95 | t.Errorf("Query.String() is \"%v\", want \"%v\"", str, tc.str) 96 | } 97 | } 98 | } 99 | 100 | /* Copyright 2019-2022 Spiegel and contributors 101 | * 102 | * Licensed under the Apache License, Version 2.0 (the "License"); 103 | * you may not use this file except in compliance with the License. 104 | * You may obtain a copy of the License at 105 | * 106 | * http://www.apache.org/licenses/LICENSE-2.0 107 | * 108 | * Unless required by applicable law or agreed to in writing, software 109 | * distributed under the License is distributed on an "AS IS" BASIS, 110 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 111 | * See the License for the specific language governing permissions and 112 | * limitations under the License. 113 | */ 114 | -------------------------------------------------------------------------------- /query/getvariations.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | paapi5 "github.com/goark/pa-api" 5 | ) 6 | 7 | // GetVariations type is embedded Query for GetVariations operation in PA-API v5 8 | type GetVariations struct { 9 | Query 10 | } 11 | 12 | var _ paapi5.Query = (*GetVariations)(nil) //GetVariations is compatible with paapi5.Query interface 13 | 14 | // New creates new GetVariations instance 15 | func NewGetVariations(marketplace, partnerTag, partnerType string) *GetVariations { 16 | q := &GetVariations{*(New(paapi5.GetVariations))} 17 | q.Request(Marketplace, marketplace).Request(PartnerTag, partnerTag).Request(PartnerType, partnerType) 18 | return q 19 | } 20 | 21 | var requestsOfGetVariations = []RequestFilter{ 22 | ASIN, 23 | Condition, 24 | CurrencyOfPreference, 25 | LanguagesOfPreference, 26 | Marketplace, 27 | Merchant, 28 | OfferCount, 29 | PartnerTag, 30 | PartnerType, 31 | VariationCount, 32 | VariationPage, 33 | } 34 | 35 | // RequestFilters adds RequestFilter to Query instance 36 | func (q *GetVariations) Request(request RequestFilter, value interface{}) *GetVariations { 37 | if request.findIn(requestsOfGetVariations) { 38 | q.With().RequestFilters(RequestMap{request: value}) 39 | } 40 | return q 41 | } 42 | 43 | // ASIN sets ASIN in GetVariations instance 44 | func (q *GetVariations) ASIN(itm string) *GetVariations { 45 | return q.Request(ASIN, itm) 46 | } 47 | 48 | // EnableBrowseNodeInfo sets the resource of BrowseNodeInfo 49 | func (q *GetVariations) EnableBrowseNodeInfo() *GetVariations { 50 | q.With().BrowseNodeInfo() 51 | return q 52 | } 53 | 54 | // EnableImages sets the resource of Images 55 | func (q *GetVariations) EnableImages() *GetVariations { 56 | q.With().Images() 57 | return q 58 | } 59 | 60 | // EnableItemInfo sets the resource of ItemInfo 61 | func (q *GetVariations) EnableItemInfo() *GetVariations { 62 | q.With().ItemInfo() 63 | return q 64 | } 65 | 66 | // EnableOffers sets the resource of Offers 67 | func (q *GetVariations) EnableOffers() *GetVariations { 68 | q.With().Offers() 69 | return q 70 | } 71 | 72 | // EnableVariationSummary sets the resource of VariationSummary 73 | func (q *GetVariations) EnableVariationSummary() *GetVariations { 74 | q.With().VariationSummary() 75 | return q 76 | } 77 | 78 | /* Copyright 2019 Spiegel and contributors 79 | * 80 | * Licensed under the Apache License, Version 2.0 (the "License"); 81 | * you may not use this file except in compliance with the License. 82 | * You may obtain a copy of the License at 83 | * 84 | * http://www.apache.org/licenses/LICENSE-2.0 85 | * 86 | * Unless required by applicable law or agreed to in writing, software 87 | * distributed under the License is distributed on an "AS IS" BASIS, 88 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 89 | * See the License for the specific language governing permissions and 90 | * limitations under the License. 91 | */ 92 | -------------------------------------------------------------------------------- /query/getvariations_test.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetVariations(t *testing.T) { 8 | testCases := []struct { 9 | q *GetVariations 10 | str string 11 | }{ 12 | {q: NewGetVariations("foo.bar", "mytag-20", "Associates"), str: `{"Operation":"GetVariations","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 13 | {q: NewGetVariations("foo.bar", "mytag-20", "Associates").ASIN("4900900028"), str: `{"Operation":"GetVariations","ASIN":"4900900028","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 14 | } 15 | for _, tc := range testCases { 16 | if str := tc.q.String(); str != tc.str { 17 | t.Errorf("GetVariations.String() is \"%v\", want \"%v\"", str, tc.str) 18 | } 19 | } 20 | } 21 | 22 | func TestRequestFiltersInGetVariations(t *testing.T) { 23 | testCases := []struct { 24 | q *GetVariations 25 | str string 26 | }{ 27 | {q: NewGetVariations("", "", ""), str: `{"Operation":"GetVariations"}`}, 28 | {q: NewGetVariations("", "", "").Request(Actor, "foo"), str: `{"Operation":"GetVariations"}`}, 29 | {q: NewGetVariations("", "", "").Request(Artist, "foo"), str: `{"Operation":"GetVariations"}`}, 30 | {q: NewGetVariations("", "", "").Request(ASIN, "4900900028"), str: `{"Operation":"GetVariations","ASIN":"4900900028"}`}, 31 | {q: NewGetVariations("", "", "").Request(Availability, "Available"), str: `{"Operation":"GetVariations"}`}, 32 | {q: NewGetVariations("", "", "").Request(Author, "foo"), str: `{"Operation":"GetVariations"}`}, 33 | {q: NewGetVariations("", "", "").Request(Brand, "foo"), str: `{"Operation":"GetVariations"}`}, 34 | {q: NewGetVariations("", "", "").Request(BrowseNodeID, "123"), str: `{"Operation":"GetVariations"}`}, 35 | {q: NewGetVariations("", "", "").Request(Condition, "foo"), str: `{"Operation":"GetVariations"}`}, 36 | {q: NewGetVariations("", "", "").Request(Condition, "Any"), str: `{"Operation":"GetVariations","Condition":"Any"}`}, 37 | {q: NewGetVariations("", "", "").Request(Condition, "New"), str: `{"Operation":"GetVariations","Condition":"New"}`}, 38 | {q: NewGetVariations("", "", "").Request(Condition, "Used"), str: `{"Operation":"GetVariations","Condition":"Used"}`}, 39 | {q: NewGetVariations("", "", "").Request(Condition, "Collectible"), str: `{"Operation":"GetVariations","Condition":"Collectible"}`}, 40 | {q: NewGetVariations("", "", "").Request(Condition, "Refurbished"), str: `{"Operation":"GetVariations","Condition":"Refurbished"}`}, 41 | {q: NewGetVariations("", "", "").Request(CurrencyOfPreference, "foo"), str: `{"Operation":"GetVariations","CurrencyOfPreference":"foo"}`}, 42 | {q: NewGetVariations("", "", "").Request(DeliveryFlags, "AmazonGlobal"), str: `{"Operation":"GetVariations"}`}, 43 | {q: NewGetVariations("", "", "").Request(ItemIds, "4900900028"), str: `{"Operation":"GetVariations"}`}, 44 | {q: NewGetVariations("", "", "").Request(ItemIdType, "ASIN"), str: `{"Operation":"GetVariations"}`}, 45 | {q: NewGetVariations("", "", "").Request(ItemCount, 1), str: `{"Operation":"GetVariations"}`}, 46 | {q: NewGetVariations("", "", "").Request(ItemPage, 1), str: `{"Operation":"GetVariations"}`}, 47 | {q: NewGetVariations("", "", "").Request(ItemPage, 1), str: `{"Operation":"GetVariations"}`}, 48 | {q: NewGetVariations("", "", "").Request(Keywords, "foo"), str: `{"Operation":"GetVariations"}`}, 49 | {q: NewGetVariations("", "", "").Request(BrowseNodeIds, "123"), str: `{"Operation":"GetVariations"}`}, 50 | {q: NewGetVariations("", "", "").Request(BrowseNodeIds, []string{"123", "456"}), str: `{"Operation":"GetVariations"}`}, 51 | {q: NewGetVariations("", "", "").Request(LanguagesOfPreference, "foo"), str: `{"Operation":"GetVariations","LanguagesOfPreference":["foo"]}`}, 52 | {q: NewGetVariations("", "", "").Request(LanguagesOfPreference, []string{"foo", "bar"}), str: `{"Operation":"GetVariations","LanguagesOfPreference":["foo","bar"]}`}, 53 | {q: NewGetVariations("", "", "").Request(Marketplace, "foo.bar"), str: `{"Operation":"GetVariations","Marketplace":"foo.bar"}`}, 54 | {q: NewGetVariations("", "", "").Request(MaxPrice, 1), str: `{"Operation":"GetVariations"}`}, 55 | {q: NewGetVariations("", "", "").Request(Merchant, "foo"), str: `{"Operation":"GetVariations"}`}, 56 | {q: NewGetVariations("", "", "").Request(Merchant, "All"), str: `{"Operation":"GetVariations","Merchant":"All"}`}, 57 | {q: NewGetVariations("", "", "").Request(Merchant, "Amazon"), str: `{"Operation":"GetVariations","Merchant":"Amazon"}`}, 58 | {q: NewGetVariations("", "", "").Request(MinPrice, 1), str: `{"Operation":"GetVariations"}`}, 59 | {q: NewGetVariations("", "", "").Request(MinReviewsRating, 1), str: `{"Operation":"GetVariations"}`}, 60 | {q: NewGetVariations("", "", "").Request(MinSavingPercent, 1), str: `{"Operation":"GetVariations"}`}, 61 | {q: NewGetVariations("", "", "").Request(OfferCount, -1), str: `{"Operation":"GetVariations"}`}, 62 | {q: NewGetVariations("", "", "").Request(OfferCount, 0), str: `{"Operation":"GetVariations"}`}, 63 | {q: NewGetVariations("", "", "").Request(OfferCount, 1), str: `{"Operation":"GetVariations","OfferCount":1}`}, 64 | {q: NewGetVariations("", "", "").Request(OfferCount, 123), str: `{"Operation":"GetVariations","OfferCount":123}`}, 65 | {q: NewGetVariations("", "", "").Request(PartnerTag, "foo"), str: `{"Operation":"GetVariations","PartnerTag":"foo"}`}, 66 | {q: NewGetVariations("", "", "").Request(PartnerType, "foo"), str: `{"Operation":"GetVariations"}`}, 67 | {q: NewGetVariations("", "", "").Request(PartnerType, "Associates"), str: `{"Operation":"GetVariations","PartnerType":"Associates"}`}, 68 | {q: NewGetVariations("", "", "").Request(Properties, map[string]string{"foo": "bar"}), str: `{"Operation":"GetVariations"}`}, 69 | {q: NewGetVariations("", "", "").Request(SearchIndex, "All"), str: `{"Operation":"GetVariations"}`}, 70 | {q: NewGetVariations("", "", "").Request(SortBy, "AvgCustomerReviews"), str: `{"Operation":"GetVariations"}`}, 71 | {q: NewGetVariations("", "", "").Request(Title, "foo"), str: `{"Operation":"GetVariations"}`}, 72 | {q: NewGetVariations("", "", "").Request(VariationCount, 5), str: `{"Operation":"GetVariations","VariationCount":5}`}, 73 | {q: NewGetVariations("", "", "").Request(VariationCount, 11), str: `{"Operation":"GetVariations"}`}, 74 | {q: NewGetVariations("", "", "").Request(VariationCount, -1), str: `{"Operation":"GetVariations"}`}, 75 | {q: NewGetVariations("", "", "").Request(VariationPage, 2), str: `{"Operation":"GetVariations","VariationPage":2}`}, 76 | {q: NewGetVariations("", "", "").Request(VariationPage, 0), str: `{"Operation":"GetVariations"}`}, 77 | } 78 | 79 | for _, tc := range testCases { 80 | if str := tc.q.String(); str != tc.str { 81 | t.Errorf("GetVariations.String() is \"%v\", want \"%v\"", str, tc.str) 82 | } 83 | } 84 | } 85 | 86 | func TestResourcesInGetVariations(t *testing.T) { 87 | testCases := []struct { 88 | q *GetVariations 89 | str string 90 | }{ 91 | {q: NewGetVariations("", "", "").EnableBrowseNodeInfo(), str: `{"Operation":"GetVariations","Resources":["BrowseNodeInfo.BrowseNodes","BrowseNodeInfo.BrowseNodes.Ancestor","BrowseNodeInfo.BrowseNodes.SalesRank","BrowseNodeInfo.WebsiteSalesRank"]}`}, 92 | {q: NewGetVariations("", "", "").EnableImages(), str: `{"Operation":"GetVariations","Resources":["Images.Primary.Small","Images.Primary.Medium","Images.Primary.Large","Images.Variants.Small","Images.Variants.Medium","Images.Variants.Large"]}`}, 93 | {q: NewGetVariations("", "", "").EnableItemInfo(), str: `{"Operation":"GetVariations","Resources":["ItemInfo.ByLineInfo","ItemInfo.ContentInfo","ItemInfo.ContentRating","ItemInfo.Classifications","ItemInfo.ExternalIds","ItemInfo.Features","ItemInfo.ManufactureInfo","ItemInfo.ProductInfo","ItemInfo.TechnicalInfo","ItemInfo.Title","ItemInfo.TradeInInfo"]}`}, 94 | {q: NewGetVariations("", "", "").EnableOffers(), str: `{"Operation":"GetVariations","Resources":["Offers.Listings.Availability.MaxOrderQuantity","Offers.Listings.Availability.Message","Offers.Listings.Availability.MinOrderQuantity","Offers.Listings.Availability.Type","Offers.Listings.Condition","Offers.Listings.Condition.SubCondition","Offers.Listings.DeliveryInfo.IsAmazonFulfilled","Offers.Listings.DeliveryInfo.IsFreeShippingEligible","Offers.Listings.DeliveryInfo.IsPrimeEligible","Offers.Listings.DeliveryInfo.ShippingCharges","Offers.Listings.IsBuyBoxWinner","Offers.Listings.LoyaltyPoints.Points","Offers.Listings.MerchantInfo","Offers.Listings.Price","Offers.Listings.ProgramEligibility.IsPrimeExclusive","Offers.Listings.ProgramEligibility.IsPrimePantry","Offers.Listings.Promotions","Offers.Listings.SavingBasis","Offers.Summaries.HighestPrice","Offers.Summaries.LowestPrice","Offers.Summaries.OfferCount"]}`}, 95 | {q: NewGetVariations("", "", "").EnableVariationSummary(), str: `{"Operation":"GetVariations","Resources":["VariationSummary.Price.HighestPrice","VariationSummary.Price.LowestPrice","VariationSummary.VariationDimension"]}`}, 96 | } 97 | 98 | for _, tc := range testCases { 99 | if str := tc.q.String(); str != tc.str { 100 | t.Errorf("Query.String() is \"%v\", want \"%v\"", str, tc.str) 101 | } 102 | } 103 | } 104 | 105 | /* Copyright 2019-2022 Spiegel and contributors 106 | * 107 | * Licensed under the Apache License, Version 2.0 (the "License"); 108 | * you may not use this file except in compliance with the License. 109 | * You may obtain a copy of the License at 110 | * 111 | * http://www.apache.org/licenses/LICENSE-2.0 112 | * 113 | * Unless required by applicable law or agreed to in writing, software 114 | * distributed under the License is distributed on an "AS IS" BASIS, 115 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 116 | * See the License for the specific language governing permissions and 117 | * limitations under the License. 118 | */ 119 | -------------------------------------------------------------------------------- /query/query.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/goark/errs" 7 | paapi5 "github.com/goark/pa-api" 8 | ) 9 | 10 | // Query is a query data class for PA-API v5 11 | type Query struct { 12 | OpeCode paapi5.Operation `json:"Operation"` 13 | request 14 | Resources []string `json:",omitempty"` 15 | enableResources map[resource]bool 16 | } 17 | 18 | var _ paapi5.Query = (*Query)(nil) //Query is compatible with paapi5.Query interface 19 | 20 | // New creates a new Query instance 21 | func New(opeCode paapi5.Operation) *Query { 22 | return &Query{OpeCode: opeCode, enableResources: map[resource]bool{}} 23 | } 24 | 25 | // Operation returns the type of the PA-API operation 26 | func (q *Query) Operation() paapi5.Operation { 27 | if q == nil { 28 | return paapi5.NullOperation 29 | } 30 | return q.OpeCode 31 | } 32 | 33 | // Payload defines the resources to be returned 34 | func (q *Query) Payload() ([]byte, error) { 35 | if q == nil { 36 | return nil, errs.Wrap(paapi5.ErrNullPointer) 37 | } 38 | q.Resources = []string{} 39 | for r, flag := range q.enableResources { 40 | if flag { 41 | q.Resources = append(q.Resources, r.Strings()...) 42 | } 43 | } 44 | b, err := json.Marshal(q) 45 | if err != nil { 46 | return nil, errs.Wrap(err) 47 | } 48 | return b, nil 49 | } 50 | 51 | // Stringer interface 52 | func (q *Query) String() string { 53 | b, err := q.Payload() 54 | if err != nil { 55 | return "" 56 | } 57 | return string(b) 58 | } 59 | 60 | // With returns this instance 61 | func (q *Query) With() *Query { 62 | if q == nil { 63 | q = New(paapi5.NullOperation) 64 | } 65 | return q 66 | } 67 | 68 | // RequestMap is mapping data for RequestFilter 69 | type RequestMap map[RequestFilter]interface{} 70 | 71 | // RequestFilters adds RequestFilter to Query instance 72 | func (q *Query) RequestFilters(requests ...RequestMap) *Query { 73 | for _, request := range requests { 74 | for name, value := range request { 75 | q.mapFilter(name, value) 76 | } 77 | } 78 | return q 79 | } 80 | 81 | // BrowseNodeInfo sets the resource of BrowseNodeInfo 82 | func (q *Query) BrowseNodeInfo() *Query { 83 | q.enableResources[resourceBrowseNodeInfo] = true 84 | return q 85 | } 86 | 87 | // Images sets the resource of Images 88 | func (q *Query) Images() *Query { 89 | q.enableResources[resourceImages] = true 90 | return q 91 | } 92 | 93 | // ItemInfo sets the resource of ItemInfo 94 | func (q *Query) ItemInfo() *Query { 95 | q.enableResources[resourceItemInfo] = true 96 | return q 97 | } 98 | 99 | // Offers sets the resource of Offers 100 | func (q *Query) Offers() *Query { 101 | q.enableResources[resourceOffers] = true 102 | return q 103 | } 104 | 105 | // SearchRefinements sets the resource of SearchRefinements 106 | func (q *Query) SearchRefinements() *Query { 107 | q.enableResources[resourceSearchRefinements] = true 108 | return q 109 | } 110 | 111 | // ParentASIN sets the resource of ParentASIN 112 | func (q *Query) ParentASIN() *Query { 113 | q.enableResources[resourceParentASIN] = true 114 | return q 115 | } 116 | 117 | // CustomerReviews sets the resource of CustomerReviews resource 118 | func (q *Query) CustomerReviews() *Query { 119 | q.enableResources[resourceCustomerReviews] = true 120 | return q 121 | } 122 | 123 | // BrowseNodes sets the resource of BrowseNodes resource 124 | func (q *Query) BrowseNodes() *Query { 125 | q.enableResources[resourceBrowseNodes] = true 126 | return q 127 | } 128 | 129 | // VariationSummary sets the resource of VariationSummary resource 130 | func (q *Query) VariationSummary() *Query { 131 | q.enableResources[resourceVariationSummary] = true 132 | return q 133 | } 134 | 135 | /* Copyright 2019-2022 Spiegel and contributors 136 | * 137 | * Licensed under the Apache License, Version 2.0 (the "License"); 138 | * you may not use this file except in compliance with the License. 139 | * You may obtain a copy of the License at 140 | * 141 | * http://www.apache.org/licenses/LICENSE-2.0 142 | * 143 | * Unless required by applicable law or agreed to in writing, software 144 | * distributed under the License is distributed on an "AS IS" BASIS, 145 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 146 | * See the License for the specific language governing permissions and 147 | * limitations under the License. 148 | */ 149 | -------------------------------------------------------------------------------- /query/query_test.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | paapi5 "github.com/goark/pa-api" 8 | ) 9 | 10 | func TestNilQuery(t *testing.T) { 11 | testCases := []struct { 12 | q *Query 13 | op paapi5.Operation 14 | err error 15 | jsn string 16 | }{ 17 | {q: nil, op: paapi5.NullOperation, err: paapi5.ErrNullPointer, jsn: ""}, 18 | {q: (*Query)(nil), op: paapi5.NullOperation, err: paapi5.ErrNullPointer, jsn: ""}, 19 | {q: (*Query)(nil).With(), op: paapi5.NullOperation, err: nil, jsn: `{"Operation":""}`}, 20 | {q: New(paapi5.GetItems), op: paapi5.GetItems, err: nil, jsn: `{"Operation":"GetItems"}`}, 21 | } 22 | 23 | for _, tc := range testCases { 24 | if op := tc.q.Operation(); op != tc.op { 25 | t.Errorf("Query.Operation() is \"%v\", want \"%v\"", op, tc.op) 26 | 27 | } 28 | if b, err := tc.q.Payload(); !errors.Is(err, tc.err) { 29 | t.Errorf("Query.Payload() is \"%v\", want \"%v\"", err, tc.err) 30 | } else if string(b) != tc.jsn { 31 | t.Errorf("Query.Payload() is \"%v\", want \"%v\"", string(b), tc.jsn) 32 | } 33 | } 34 | } 35 | 36 | func TestRequestFilters(t *testing.T) { 37 | empty := (*Query)(nil) 38 | testCases := []struct { 39 | q *Query 40 | str string 41 | }{ 42 | {q: empty, str: ""}, 43 | {q: empty.With(), str: `{"Operation":""}`}, 44 | {q: empty.With().RequestFilters(), str: `{"Operation":""}`}, 45 | {q: empty.With().RequestFilters(RequestMap{RequestFilter(0): "foo"}), str: `{"Operation":""}`}, 46 | {q: empty.With().RequestFilters(RequestMap{Actor: "foo"}), str: `{"Operation":"","Actor":"foo"}`}, 47 | {q: empty.With().RequestFilters(RequestMap{Artist: "foo"}), str: `{"Operation":"","Artist":"foo"}`}, 48 | {q: empty.With().RequestFilters(RequestMap{ASIN: "4900900028"}), str: `{"Operation":"","ASIN":"4900900028"}`}, 49 | {q: empty.With().RequestFilters(RequestMap{Availability: "foo"}), str: `{"Operation":""}`}, 50 | {q: empty.With().RequestFilters(RequestMap{Availability: "Available"}), str: `{"Operation":"","Availability":"Available"}`}, 51 | {q: empty.With().RequestFilters(RequestMap{Availability: "IncludeOutOfStock"}), str: `{"Operation":"","Availability":"IncludeOutOfStock"}`}, 52 | {q: empty.With().RequestFilters(RequestMap{Author: "foo"}), str: `{"Operation":"","Author":"foo"}`}, 53 | {q: empty.With().RequestFilters(RequestMap{Brand: "foo"}), str: `{"Operation":"","Brand":"foo"}`}, 54 | {q: empty.With().RequestFilters(RequestMap{BrowseNodeID: "foo"}), str: `{"Operation":""}`}, 55 | {q: empty.With().RequestFilters(RequestMap{BrowseNodeID: "123"}), str: `{"Operation":"","BrowseNodeId":"123"}`}, 56 | {q: empty.With().RequestFilters(RequestMap{Condition: "foo"}), str: `{"Operation":""}`}, 57 | {q: empty.With().RequestFilters(RequestMap{Condition: "Any"}), str: `{"Operation":"","Condition":"Any"}`}, 58 | {q: empty.With().RequestFilters(RequestMap{Condition: "New"}), str: `{"Operation":"","Condition":"New"}`}, 59 | {q: empty.With().RequestFilters(RequestMap{Condition: "Used"}), str: `{"Operation":"","Condition":"Used"}`}, 60 | {q: empty.With().RequestFilters(RequestMap{Condition: "Collectible"}), str: `{"Operation":"","Condition":"Collectible"}`}, 61 | {q: empty.With().RequestFilters(RequestMap{Condition: "Refurbished"}), str: `{"Operation":"","Condition":"Refurbished"}`}, 62 | {q: empty.With().RequestFilters(RequestMap{CurrencyOfPreference: "foo"}), str: `{"Operation":"","CurrencyOfPreference":"foo"}`}, 63 | {q: empty.With().RequestFilters(RequestMap{DeliveryFlags: "foo"}), str: `{"Operation":""}`}, 64 | {q: empty.With().RequestFilters(RequestMap{DeliveryFlags: "AmazonGlobal"}), str: `{"Operation":"","DeliveryFlags":["AmazonGlobal"]}`}, 65 | {q: empty.With().RequestFilters(RequestMap{DeliveryFlags: []string{"AmazonGlobal", "FreeShipping", "FulfilledByAmazon", "Prime"}}), str: `{"Operation":"","DeliveryFlags":["AmazonGlobal","FreeShipping","FulfilledByAmazon","Prime"]}`}, 66 | {q: empty.With().RequestFilters(RequestMap{ItemIds: "4900900028", ItemIdType: "ASIN"}), str: `{"Operation":"","ItemIds":["4900900028"],"ItemIdType":"ASIN"}`}, 67 | {q: empty.With().RequestFilters(RequestMap{ItemIds: "4900900028"}, RequestMap{ItemIdType: "ASIN"}), str: `{"Operation":"","ItemIds":["4900900028"],"ItemIdType":"ASIN"}`}, 68 | {q: empty.With().RequestFilters(RequestMap{ItemCount: -1}), str: `{"Operation":""}`}, 69 | {q: empty.With().RequestFilters(RequestMap{ItemCount: 0}), str: `{"Operation":""}`}, 70 | {q: empty.With().RequestFilters(RequestMap{ItemCount: 1}), str: `{"Operation":"","ItemCount":1}`}, 71 | {q: empty.With().RequestFilters(RequestMap{ItemCount: 10}), str: `{"Operation":"","ItemCount":10}`}, 72 | {q: empty.With().RequestFilters(RequestMap{ItemCount: 11}), str: `{"Operation":""}`}, 73 | {q: empty.With().RequestFilters(RequestMap{ItemPage: -1}), str: `{"Operation":""}`}, 74 | {q: empty.With().RequestFilters(RequestMap{ItemPage: 0}), str: `{"Operation":""}`}, 75 | {q: empty.With().RequestFilters(RequestMap{ItemPage: 1}), str: `{"Operation":"","ItemPage":1}`}, 76 | {q: empty.With().RequestFilters(RequestMap{ItemPage: 10}), str: `{"Operation":"","ItemPage":10}`}, 77 | {q: empty.With().RequestFilters(RequestMap{ItemPage: 11}), str: `{"Operation":""}`}, 78 | {q: empty.With().RequestFilters(RequestMap{Keywords: "foo"}), str: `{"Operation":"","Keywords":"foo"}`}, 79 | {q: empty.With().RequestFilters(RequestMap{BrowseNodeIds: "123"}), str: `{"Operation":"","BrowseNodeIds":["123"]}`}, 80 | {q: empty.With().RequestFilters(RequestMap{BrowseNodeIds: []string{"123", "456"}}), str: `{"Operation":"","BrowseNodeIds":["123","456"]}`}, 81 | {q: empty.With().RequestFilters(RequestMap{LanguagesOfPreference: "foo"}), str: `{"Operation":"","LanguagesOfPreference":["foo"]}`}, 82 | {q: empty.With().RequestFilters(RequestMap{LanguagesOfPreference: []string{"foo", "bar"}}), str: `{"Operation":"","LanguagesOfPreference":["foo","bar"]}`}, 83 | {q: empty.With().RequestFilters(RequestMap{Marketplace: "foo.bar"}), str: `{"Operation":"","Marketplace":"foo.bar"}`}, 84 | {q: empty.With().RequestFilters(RequestMap{MaxPrice: -1}), str: `{"Operation":""}`}, 85 | {q: empty.With().RequestFilters(RequestMap{MaxPrice: 0}), str: `{"Operation":""}`}, 86 | {q: empty.With().RequestFilters(RequestMap{MaxPrice: 1}), str: `{"Operation":"","MaxPrice":1}`}, 87 | {q: empty.With().RequestFilters(RequestMap{MaxPrice: 123}), str: `{"Operation":"","MaxPrice":123}`}, 88 | {q: empty.With().RequestFilters(RequestMap{Merchant: "foo"}), str: `{"Operation":""}`}, 89 | {q: empty.With().RequestFilters(RequestMap{Merchant: "All"}), str: `{"Operation":"","Merchant":"All"}`}, 90 | {q: empty.With().RequestFilters(RequestMap{Merchant: "Amazon"}), str: `{"Operation":"","Merchant":"Amazon"}`}, 91 | {q: empty.With().RequestFilters(RequestMap{MinPrice: -1}), str: `{"Operation":""}`}, 92 | {q: empty.With().RequestFilters(RequestMap{MinPrice: 0}), str: `{"Operation":""}`}, 93 | {q: empty.With().RequestFilters(RequestMap{MinPrice: 1}), str: `{"Operation":"","MinPrice":1}`}, 94 | {q: empty.With().RequestFilters(RequestMap{MinPrice: 123}), str: `{"Operation":"","MinPrice":123}`}, 95 | {q: empty.With().RequestFilters(RequestMap{MinReviewsRating: -1}), str: `{"Operation":""}`}, 96 | {q: empty.With().RequestFilters(RequestMap{MinReviewsRating: 0}), str: `{"Operation":""}`}, 97 | {q: empty.With().RequestFilters(RequestMap{MinReviewsRating: 1}), str: `{"Operation":"","MinReviewsRating":1}`}, 98 | {q: empty.With().RequestFilters(RequestMap{MinReviewsRating: 4}), str: `{"Operation":"","MinReviewsRating":4}`}, 99 | {q: empty.With().RequestFilters(RequestMap{MinReviewsRating: 5}), str: `{"Operation":""}`}, 100 | {q: empty.With().RequestFilters(RequestMap{MinSavingPercent: -1}), str: `{"Operation":""}`}, 101 | {q: empty.With().RequestFilters(RequestMap{MinSavingPercent: 0}), str: `{"Operation":""}`}, 102 | {q: empty.With().RequestFilters(RequestMap{MinSavingPercent: 1}), str: `{"Operation":"","MinSavingPercent":1}`}, 103 | {q: empty.With().RequestFilters(RequestMap{MinSavingPercent: 99}), str: `{"Operation":"","MinSavingPercent":99}`}, 104 | {q: empty.With().RequestFilters(RequestMap{MinSavingPercent: 100}), str: `{"Operation":""}`}, 105 | {q: empty.With().RequestFilters(RequestMap{OfferCount: -1}), str: `{"Operation":""}`}, 106 | {q: empty.With().RequestFilters(RequestMap{OfferCount: 0}), str: `{"Operation":""}`}, 107 | {q: empty.With().RequestFilters(RequestMap{OfferCount: 1}), str: `{"Operation":"","OfferCount":1}`}, 108 | {q: empty.With().RequestFilters(RequestMap{OfferCount: 123}), str: `{"Operation":"","OfferCount":123}`}, 109 | {q: empty.With().RequestFilters(RequestMap{PartnerTag: "foo"}), str: `{"Operation":"","PartnerTag":"foo"}`}, 110 | {q: empty.With().RequestFilters(RequestMap{PartnerType: "foo"}), str: `{"Operation":""}`}, 111 | {q: empty.With().RequestFilters(RequestMap{PartnerType: "Associates"}), str: `{"Operation":"","PartnerType":"Associates"}`}, 112 | {q: empty.With().RequestFilters(RequestMap{Properties: map[string]string{"foo": "bar"}}), str: `{"Operation":"","Properties":{"foo":"bar"}}`}, 113 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "foo"}), str: `{"Operation":""}`}, 114 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "All"}), str: `{"Operation":"","SearchIndex":"All"}`}, 115 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "AmazonVideo"}), str: `{"Operation":"","SearchIndex":"AmazonVideo"}`}, 116 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Apparel"}), str: `{"Operation":"","SearchIndex":"Apparel"}`}, 117 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Appliances"}), str: `{"Operation":"","SearchIndex":"Appliances"}`}, 118 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "ArtsAndCrafts"}), str: `{"Operation":"","SearchIndex":"ArtsAndCrafts"}`}, 119 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Automotive"}), str: `{"Operation":"","SearchIndex":"Automotive"}`}, 120 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Baby"}), str: `{"Operation":"","SearchIndex":"Baby"}`}, 121 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Beauty"}), str: `{"Operation":"","SearchIndex":"Beauty"}`}, 122 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Books"}), str: `{"Operation":"","SearchIndex":"Books"}`}, 123 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Classical"}), str: `{"Operation":"","SearchIndex":"Classical"}`}, 124 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Collectibles"}), str: `{"Operation":"","SearchIndex":"Collectibles"}`}, 125 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Computers"}), str: `{"Operation":"","SearchIndex":"Computers"}`}, 126 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "DigitalMusic"}), str: `{"Operation":"","SearchIndex":"DigitalMusic"}`}, 127 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Electronics"}), str: `{"Operation":"","SearchIndex":"Electronics"}`}, 128 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "EverythingElse"}), str: `{"Operation":"","SearchIndex":"EverythingElse"}`}, 129 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Fashion"}), str: `{"Operation":"","SearchIndex":"Fashion"}`}, 130 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "FashionBaby"}), str: `{"Operation":"","SearchIndex":"FashionBaby"}`}, 131 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "FashionBoys"}), str: `{"Operation":"","SearchIndex":"FashionBoys"}`}, 132 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "FashionGirls"}), str: `{"Operation":"","SearchIndex":"FashionGirls"}`}, 133 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "FashionMen"}), str: `{"Operation":"","SearchIndex":"FashionMen"}`}, 134 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "FashionWomen"}), str: `{"Operation":"","SearchIndex":"FashionWomen"}`}, 135 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "GardenAndOutdoor"}), str: `{"Operation":"","SearchIndex":"GardenAndOutdoor"}`}, 136 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "GiftCards"}), str: `{"Operation":"","SearchIndex":"GiftCards"}`}, 137 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "GroceryAndGourmetFood"}), str: `{"Operation":"","SearchIndex":"GroceryAndGourmetFood"}`}, 138 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Handmade"}), str: `{"Operation":"","SearchIndex":"Handmade"}`}, 139 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "HealthPersonalCare"}), str: `{"Operation":"","SearchIndex":"HealthPersonalCare"}`}, 140 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "HomeAndKitchen"}), str: `{"Operation":"","SearchIndex":"HomeAndKitchen"}`}, 141 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Industrial"}), str: `{"Operation":"","SearchIndex":"Industrial"}`}, 142 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Jewelry"}), str: `{"Operation":"","SearchIndex":"Jewelry"}`}, 143 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "KindleStore"}), str: `{"Operation":"","SearchIndex":"KindleStore"}`}, 144 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "LocalServices"}), str: `{"Operation":"","SearchIndex":"LocalServices"}`}, 145 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Luggage"}), str: `{"Operation":"","SearchIndex":"Luggage"}`}, 146 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "LuxuryBeauty"}), str: `{"Operation":"","SearchIndex":"LuxuryBeauty"}`}, 147 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Magazines"}), str: `{"Operation":"","SearchIndex":"Magazines"}`}, 148 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "MobileAndAccessories"}), str: `{"Operation":"","SearchIndex":"MobileAndAccessories"}`}, 149 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "MobileApps"}), str: `{"Operation":"","SearchIndex":"MobileApps"}`}, 150 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "MoviesAndTV"}), str: `{"Operation":"","SearchIndex":"MoviesAndTV"}`}, 151 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Music"}), str: `{"Operation":"","SearchIndex":"Music"}`}, 152 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "MusicalInstruments"}), str: `{"Operation":"","SearchIndex":"MusicalInstruments"}`}, 153 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "OfficeProducts"}), str: `{"Operation":"","SearchIndex":"OfficeProducts"}`}, 154 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "PetSupplies"}), str: `{"Operation":"","SearchIndex":"PetSupplies"}`}, 155 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Photo"}), str: `{"Operation":"","SearchIndex":"Photo"}`}, 156 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Shoes"}), str: `{"Operation":"","SearchIndex":"Shoes"}`}, 157 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Software"}), str: `{"Operation":"","SearchIndex":"Software"}`}, 158 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "SportsAndOutdoors"}), str: `{"Operation":"","SearchIndex":"SportsAndOutdoors"}`}, 159 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "ToolsAndHomeImprovement"}), str: `{"Operation":"","SearchIndex":"ToolsAndHomeImprovement"}`}, 160 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "ToysAndGames"}), str: `{"Operation":"","SearchIndex":"ToysAndGames"}`}, 161 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "VHS"}), str: `{"Operation":"","SearchIndex":"VHS"}`}, 162 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "VideoGames"}), str: `{"Operation":"","SearchIndex":"VideoGames"}`}, 163 | {q: empty.With().RequestFilters(RequestMap{SearchIndex: "Watches"}), str: `{"Operation":"","SearchIndex":"Watches"}`}, 164 | {q: empty.With().RequestFilters(RequestMap{SortBy: "foo"}), str: `{"Operation":""}`}, 165 | {q: empty.With().RequestFilters(RequestMap{SortBy: "AvgCustomerReviews"}), str: `{"Operation":"","SortBy":"AvgCustomerReviews"}`}, 166 | {q: empty.With().RequestFilters(RequestMap{SortBy: "Featured"}), str: `{"Operation":"","SortBy":"Featured"}`}, 167 | {q: empty.With().RequestFilters(RequestMap{SortBy: "NewestArrivals"}), str: `{"Operation":"","SortBy":"NewestArrivals"}`}, 168 | {q: empty.With().RequestFilters(RequestMap{SortBy: "Price:HighToLow"}), str: `{"Operation":"","SortBy":"Price:HighToLow"}`}, 169 | {q: empty.With().RequestFilters(RequestMap{SortBy: "Price:LowToHigh"}), str: `{"Operation":"","SortBy":"Price:LowToHigh"}`}, 170 | {q: empty.With().RequestFilters(RequestMap{SortBy: "Relevance"}), str: `{"Operation":"","SortBy":"Relevance"}`}, 171 | {q: empty.With().RequestFilters(RequestMap{Title: "foo"}), str: `{"Operation":"","Title":"foo"}`}, 172 | } 173 | 174 | for _, tc := range testCases { 175 | if str := tc.q.String(); str != tc.str { 176 | t.Errorf("Query.String() is \"%v\", want \"%v\"", str, tc.str) 177 | } 178 | } 179 | } 180 | 181 | func TestResources(t *testing.T) { 182 | empty := (*Query)(nil) 183 | testCases := []struct { 184 | q *Query 185 | str string 186 | }{ 187 | {q: empty.With().BrowseNodeInfo(), str: `{"Operation":"","Resources":["BrowseNodeInfo.BrowseNodes","BrowseNodeInfo.BrowseNodes.Ancestor","BrowseNodeInfo.BrowseNodes.SalesRank","BrowseNodeInfo.WebsiteSalesRank"]}`}, 188 | {q: empty.With().Images(), str: `{"Operation":"","Resources":["Images.Primary.Small","Images.Primary.Medium","Images.Primary.Large","Images.Variants.Small","Images.Variants.Medium","Images.Variants.Large"]}`}, 189 | {q: empty.With().ItemInfo(), str: `{"Operation":"","Resources":["ItemInfo.ByLineInfo","ItemInfo.ContentInfo","ItemInfo.ContentRating","ItemInfo.Classifications","ItemInfo.ExternalIds","ItemInfo.Features","ItemInfo.ManufactureInfo","ItemInfo.ProductInfo","ItemInfo.TechnicalInfo","ItemInfo.Title","ItemInfo.TradeInInfo"]}`}, 190 | {q: empty.With().Offers(), str: `{"Operation":"","Resources":["Offers.Listings.Availability.MaxOrderQuantity","Offers.Listings.Availability.Message","Offers.Listings.Availability.MinOrderQuantity","Offers.Listings.Availability.Type","Offers.Listings.Condition","Offers.Listings.Condition.SubCondition","Offers.Listings.DeliveryInfo.IsAmazonFulfilled","Offers.Listings.DeliveryInfo.IsFreeShippingEligible","Offers.Listings.DeliveryInfo.IsPrimeEligible","Offers.Listings.DeliveryInfo.ShippingCharges","Offers.Listings.IsBuyBoxWinner","Offers.Listings.LoyaltyPoints.Points","Offers.Listings.MerchantInfo","Offers.Listings.Price","Offers.Listings.ProgramEligibility.IsPrimeExclusive","Offers.Listings.ProgramEligibility.IsPrimePantry","Offers.Listings.Promotions","Offers.Listings.SavingBasis","Offers.Summaries.HighestPrice","Offers.Summaries.LowestPrice","Offers.Summaries.OfferCount"]}`}, 191 | {q: empty.With().SearchRefinements(), str: `{"Operation":"","Resources":["SearchRefinements"]}`}, 192 | {q: empty.With().ParentASIN(), str: `{"Operation":"","Resources":["ParentASIN"]}`}, 193 | } 194 | 195 | for _, tc := range testCases { 196 | if str := tc.q.String(); str != tc.str { 197 | t.Errorf("Query.String() is \"%v\", want \"%v\"", str, tc.str) 198 | } 199 | } 200 | } 201 | 202 | /* Copyright 2019 Spiegel and contributors 203 | * 204 | * Licensed under the Apache License, Version 2.0 (the "License"); 205 | * you may not use this file except in compliance with the License. 206 | * You may obtain a copy of the License at 207 | * 208 | * http://www.apache.org/licenses/LICENSE-2.0 209 | * 210 | * Unless required by applicable law or agreed to in writing, software 211 | * distributed under the License is distributed on an "AS IS" BASIS, 212 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 213 | * See the License for the specific language governing permissions and 214 | * limitations under the License. 215 | */ 216 | -------------------------------------------------------------------------------- /query/request.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import "strconv" 4 | 5 | // RequestFilter signals the types of filters to use 6 | type RequestFilter int 7 | 8 | // Constants for RequestFilter 9 | const ( 10 | Actor RequestFilter = iota + 1 11 | Artist 12 | ASIN 13 | Author 14 | Availability 15 | Brand 16 | BrowseNodeID 17 | Condition 18 | CurrencyOfPreference 19 | DeliveryFlags 20 | ItemIds 21 | ItemIdType 22 | ItemCount 23 | ItemPage 24 | Keywords 25 | BrowseNodeIds 26 | LanguagesOfPreference 27 | Marketplace 28 | MaxPrice 29 | Merchant 30 | MinPrice 31 | MinReviewsRating 32 | MinSavingPercent 33 | OfferCount 34 | PartnerTag 35 | PartnerType 36 | Properties 37 | SearchIndex 38 | SortBy 39 | Title 40 | VariationCount 41 | VariationPage 42 | ) 43 | 44 | func (f RequestFilter) findIn(list []RequestFilter) bool { 45 | for _, elm := range list { 46 | if f == elm { 47 | return true 48 | } 49 | } 50 | return false 51 | } 52 | 53 | // available (valid) filter parameters 54 | var ( 55 | validationMap = map[RequestFilter][]string{ 56 | Availability: {"Available", "IncludeOutOfStock"}, 57 | Condition: {"Any", "New", "Used", "Collectible", "Refurbished"}, 58 | DeliveryFlags: {"AmazonGlobal", "FreeShipping", "FulfilledByAmazon", "Prime"}, 59 | ItemIdType: {"ASIN"}, 60 | Merchant: {"All", "Amazon"}, 61 | PartnerType: {"Associates"}, 62 | SearchIndex: {"All", "AmazonVideo", "Apparel", "Appliances", "ArtsAndCrafts", "Automotive", "Baby", "Beauty", "Books", "Classical", "Collectibles", "Computers", "DigitalMusic", "Electronics", "EverythingElse", "Fashion", "FashionBaby", "FashionBoys", "FashionGirls", "FashionMen", "FashionWomen", "GardenAndOutdoor", "GiftCards", "GroceryAndGourmetFood", "Handmade", "HealthPersonalCare", "HomeAndKitchen", "Industrial", "Jewelry", "KindleStore", "LocalServices", "Luggage", "LuxuryBeauty", "Magazines", "MobileAndAccessories", "MobileApps", "MoviesAndTV", "Music", "MusicalInstruments", "OfficeProducts", "PetSupplies", "Photo", "Shoes", "Software", "SportsAndOutdoors", "ToolsAndHomeImprovement", "ToysAndGames", "VHS", "VideoGames", "Watches"}, 63 | SortBy: {"AvgCustomerReviews", "Featured", "NewestArrivals", "Price:HighToLow", "Price:LowToHigh", "Relevance"}, 64 | } 65 | ) 66 | 67 | // isVlidString methos checks if the given parameter is valid for the chosen filter option 68 | func (f RequestFilter) isVlidString(value string) bool { 69 | switch f { 70 | case BrowseNodeID, BrowseNodeIds: 71 | if _, err := strconv.ParseInt(value, 10, 64); err == nil { 72 | return true 73 | } 74 | case Availability, Condition, DeliveryFlags, ItemIdType, Merchant, PartnerType, SearchIndex, SortBy: 75 | for _, param := range validationMap[f] { 76 | if value == param { 77 | return true 78 | } 79 | } 80 | default: 81 | if len(value) > 0 { 82 | return true 83 | } 84 | } 85 | return false 86 | } 87 | 88 | // request is the private and anonymously imported struct, which selects the filters to be used 89 | type request struct { 90 | Actor string `json:",omitempty"` 91 | Artist string `json:",omitempty"` 92 | ASIN string `json:",omitempty"` 93 | Availability string `json:",omitempty"` 94 | Author string `json:",omitempty"` 95 | Brand string `json:",omitempty"` 96 | BrowseNodeID string `json:"BrowseNodeId,omitempty"` 97 | Condition string `json:",omitempty"` 98 | CurrencyOfPreference string `json:",omitempty"` 99 | DeliveryFlags []string `json:",omitempty"` 100 | ItemIds []string `json:",omitempty"` 101 | ItemIdType string `json:",omitempty"` 102 | ItemCount int `json:",omitempty"` 103 | ItemPage int `json:",omitempty"` 104 | Keywords string `json:",omitempty"` 105 | BrowseNodeIds []string `json:",omitempty"` 106 | LanguagesOfPreference []string `json:",omitempty"` 107 | Marketplace string `json:",omitempty"` 108 | MaxPrice int `json:",omitempty"` 109 | Merchant string `json:",omitempty"` 110 | MinPrice int `json:",omitempty"` 111 | MinReviewsRating int `json:",omitempty"` 112 | MinSavingPercent int `json:",omitempty"` 113 | OfferCount int `json:",omitempty"` 114 | PartnerTag string `json:",omitempty"` 115 | PartnerType string `json:",omitempty"` 116 | Properties map[string]string `json:",omitempty"` 117 | SearchIndex string `json:",omitempty"` 118 | SortBy string `json:",omitempty"` 119 | Title string `json:",omitempty"` 120 | VariationCount int `json:",omitempty"` 121 | VariationPage int `json:",omitempty"` 122 | } 123 | 124 | // mapFilter is a helper function for (*filters).WithFilters 125 | // This function does not check, if the filters to be used match the chosen searchParam/searchType (Actor, Artist etc.pp.) 126 | // TODO: - reduce nesting 127 | func (r *request) mapFilter(filter RequestFilter, filterValue interface{}) { 128 | switch filter { 129 | case Actor: 130 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 131 | r.Actor = param 132 | } 133 | case Artist: 134 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 135 | r.Artist = param 136 | } 137 | case ASIN: 138 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 139 | r.ASIN = param 140 | } 141 | case Availability: 142 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 143 | r.Availability = param 144 | } 145 | case Author: 146 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 147 | r.Author = param 148 | } 149 | case Brand: 150 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 151 | r.Brand = param 152 | } 153 | case BrowseNodeID: 154 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 155 | r.BrowseNodeID = param 156 | } 157 | case Condition: 158 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 159 | r.Condition = param 160 | } 161 | case CurrencyOfPreference: 162 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 163 | r.CurrencyOfPreference = param 164 | } 165 | case DeliveryFlags: 166 | switch v := filterValue.(type) { 167 | case []string: 168 | r.DeliveryFlags = []string{} 169 | for _, param := range v { 170 | if filter.isVlidString(param) { 171 | r.DeliveryFlags = append(r.DeliveryFlags, param) 172 | } 173 | } 174 | case string: 175 | if filter.isVlidString(v) { 176 | r.DeliveryFlags = []string{v} 177 | } 178 | } 179 | case ItemIds: 180 | switch v := filterValue.(type) { 181 | case []string: 182 | r.ItemIds = []string{} 183 | for _, param := range v { 184 | if filter.isVlidString(param) { 185 | r.ItemIds = append(r.ItemIds, param) 186 | } 187 | } 188 | case string: 189 | if filter.isVlidString(v) { 190 | r.ItemIds = []string{v} 191 | } 192 | } 193 | case ItemIdType: 194 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 195 | r.ItemIdType = param 196 | } 197 | case ItemCount: 198 | if count, ok := filterValue.(int); ok && 0 < count && count < 11 { 199 | r.ItemCount = count 200 | } 201 | case ItemPage: 202 | if page, ok := filterValue.(int); ok && 0 < page && page < 11 { 203 | r.ItemPage = page 204 | } 205 | case Keywords: 206 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 207 | r.Keywords = param 208 | } 209 | case BrowseNodeIds: 210 | switch v := filterValue.(type) { 211 | case []string: 212 | r.BrowseNodeIds = []string{} 213 | for _, param := range v { 214 | if filter.isVlidString(param) { 215 | r.BrowseNodeIds = append(r.BrowseNodeIds, param) 216 | } 217 | } 218 | case string: 219 | if filter.isVlidString(v) { 220 | r.BrowseNodeIds = []string{v} 221 | } 222 | } 223 | case LanguagesOfPreference: 224 | switch v := filterValue.(type) { 225 | case []string: 226 | r.LanguagesOfPreference = []string{} 227 | for _, param := range v { 228 | if filter.isVlidString(param) { 229 | r.LanguagesOfPreference = append(r.LanguagesOfPreference, param) 230 | } 231 | } 232 | case string: 233 | if filter.isVlidString(v) { 234 | r.LanguagesOfPreference = []string{v} 235 | } 236 | } 237 | case Marketplace: 238 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 239 | r.Marketplace = param 240 | } 241 | case MaxPrice: // Yet, here is not further check if the given price is meaningful (it is assumed to already be the lowest currency denomination, e.g 3241 => 31.41) 242 | if price, ok := filterValue.(int); ok && price > 0 { 243 | r.MaxPrice = price 244 | } 245 | case Merchant: 246 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 247 | r.Merchant = param 248 | } 249 | case MinPrice: // Yet, here is not further check if the given price is meaningful (it is assumed to already be the lowest currency denomination, e.g 3241 => 31.41) 250 | if price, ok := filterValue.(int); ok && price > 0 { 251 | r.MinPrice = price 252 | } 253 | case MinReviewsRating: 254 | if minRating, ok := filterValue.(int); ok && 0 < minRating && minRating < 5 { 255 | r.MinReviewsRating = minRating 256 | } 257 | case MinSavingPercent: 258 | if minSaving, ok := filterValue.(int); ok && 0 < minSaving && minSaving < 100 { 259 | r.MinSavingPercent = minSaving 260 | } 261 | case OfferCount: 262 | if oCount, ok := filterValue.(int); ok && oCount > 0 { 263 | r.OfferCount = oCount 264 | } 265 | case PartnerTag: 266 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 267 | r.PartnerTag = param 268 | } 269 | case PartnerType: 270 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 271 | r.PartnerType = param 272 | } 273 | case Properties: 274 | if params, ok := filterValue.(map[string]string); ok && len(params) > 0 { 275 | r.Properties = params 276 | } 277 | case SearchIndex: 278 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 279 | r.SearchIndex = param 280 | } 281 | case SortBy: 282 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 283 | r.SortBy = param 284 | } 285 | case Title: 286 | if param, ok := filterValue.(string); ok && filter.isVlidString(param) { 287 | r.Title = param 288 | } 289 | case VariationCount: 290 | if count, ok := filterValue.(int); ok && 0 < count && count < 11 { 291 | r.VariationCount = count 292 | } 293 | case VariationPage: 294 | if count, ok := filterValue.(int); ok && 0 < count { 295 | r.VariationPage = count 296 | } 297 | } 298 | 299 | } 300 | 301 | /* Copyright 2019-2022 Spiegel and contributors 302 | * 303 | * Licensed under the Apache License, Version 2.0 (the "License"); 304 | * you may not use this file except in compliance with the License. 305 | * You may obtain a copy of the License at 306 | * 307 | * http://www.apache.org/licenses/LICENSE-2.0 308 | * 309 | * Unless required by applicable law or agreed to in writing, software 310 | * distributed under the License is distributed on an "AS IS" BASIS, 311 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 312 | * See the License for the specific language governing permissions and 313 | * limitations under the License. 314 | */ 315 | -------------------------------------------------------------------------------- /query/resources.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | type resource int 4 | 5 | const ( 6 | resourceBrowseNodeInfo resource = 1 + iota //BrowseNodeInfo resource 7 | resourceImages //Images resource 8 | resourceItemInfo //ItemInfo resource 9 | resourceOffers //Offers resource 10 | resourceSearchRefinements //SearchRefinements resource 11 | resourceParentASIN //ParentASIN resource 12 | resourceCustomerReviews //CustomerReviews resource 13 | resourceBrowseNodes //BrowseNodes resource 14 | resourceVariationSummary //VariationSummary resource 15 | ) 16 | 17 | var ( 18 | //BrowseNodeInfo resource 19 | resourcesBrowseNodeInfo = []string{ 20 | "BrowseNodeInfo.BrowseNodes", 21 | "BrowseNodeInfo.BrowseNodes.Ancestor", 22 | "BrowseNodeInfo.BrowseNodes.SalesRank", 23 | "BrowseNodeInfo.WebsiteSalesRank", 24 | } 25 | //Images resource 26 | resourcesImages = []string{ 27 | "Images.Primary.Small", 28 | "Images.Primary.Medium", 29 | "Images.Primary.Large", 30 | "Images.Variants.Small", 31 | "Images.Variants.Medium", 32 | "Images.Variants.Large", 33 | } 34 | //ItemInfo resource 35 | resourcesItemInfo = []string{ 36 | "ItemInfo.ByLineInfo", 37 | "ItemInfo.ContentInfo", 38 | "ItemInfo.ContentRating", 39 | "ItemInfo.Classifications", 40 | "ItemInfo.ExternalIds", 41 | "ItemInfo.Features", 42 | "ItemInfo.ManufactureInfo", 43 | "ItemInfo.ProductInfo", 44 | "ItemInfo.TechnicalInfo", 45 | "ItemInfo.Title", 46 | "ItemInfo.TradeInInfo", 47 | } 48 | //Offers resource 49 | resourcesOffers = []string{ 50 | "Offers.Listings.Availability.MaxOrderQuantity", 51 | "Offers.Listings.Availability.Message", 52 | "Offers.Listings.Availability.MinOrderQuantity", 53 | "Offers.Listings.Availability.Type", 54 | "Offers.Listings.Condition", 55 | "Offers.Listings.Condition.SubCondition", 56 | "Offers.Listings.DeliveryInfo.IsAmazonFulfilled", 57 | "Offers.Listings.DeliveryInfo.IsFreeShippingEligible", 58 | "Offers.Listings.DeliveryInfo.IsPrimeEligible", 59 | "Offers.Listings.DeliveryInfo.ShippingCharges", 60 | "Offers.Listings.IsBuyBoxWinner", 61 | "Offers.Listings.LoyaltyPoints.Points", 62 | "Offers.Listings.MerchantInfo", 63 | "Offers.Listings.Price", 64 | "Offers.Listings.ProgramEligibility.IsPrimeExclusive", 65 | "Offers.Listings.ProgramEligibility.IsPrimePantry", 66 | "Offers.Listings.Promotions", 67 | "Offers.Listings.SavingBasis", 68 | "Offers.Summaries.HighestPrice", 69 | "Offers.Summaries.LowestPrice", 70 | "Offers.Summaries.OfferCount", 71 | } 72 | //SearchRefinements resource 73 | resourcesSearchRefinements = []string{ 74 | "SearchRefinements", 75 | } 76 | //ParentASIN resource 77 | resourcesParentASIN = []string{ 78 | "ParentASIN", 79 | } 80 | //CustomerReviews resource 81 | resourcesCustomerReviews = []string{ 82 | "CustomerReviews.Count", 83 | "CustomerReviews.StarRating", 84 | } 85 | //BrowseNodes resource 86 | resourcesBrowseNodes = []string{ 87 | "BrowseNodes.Ancestor", 88 | "BrowseNodes.Children", 89 | } 90 | 91 | //VariationSummary resource 92 | resourcesVariationSummary = []string{ 93 | "VariationSummary.Price.HighestPrice", 94 | "VariationSummary.Price.LowestPrice", 95 | "VariationSummary.VariationDimension", 96 | } 97 | 98 | resourcesMap = map[resource][]string{ 99 | resourceBrowseNodeInfo: resourcesBrowseNodeInfo, //BrowseNodeInfo resource 100 | resourceImages: resourcesImages, //Images resource 101 | resourceItemInfo: resourcesItemInfo, //ItemInfo resource 102 | resourceOffers: resourcesOffers, //Offers resource 103 | resourceSearchRefinements: resourcesSearchRefinements, //SearchRefinements resource 104 | resourceParentASIN: resourcesParentASIN, //ParentASIN resource 105 | resourceCustomerReviews: resourcesCustomerReviews, //CustomerReviews resource 106 | resourceBrowseNodes: resourcesBrowseNodes, //BrowseNodes resource 107 | resourceVariationSummary: resourcesVariationSummary, //VariationSummary resource 108 | } 109 | ) 110 | 111 | func (r resource) Strings() []string { 112 | if ss, ok := resourcesMap[r]; ok { 113 | return ss 114 | } 115 | return []string{} 116 | } 117 | 118 | /* Copyright 2019-2022 Spiegel and contributors 119 | * 120 | * Licensed under the Apache License, Version 2.0 (the "License"); 121 | * you may not use this file except in compliance with the License. 122 | * You may obtain a copy of the License at 123 | * 124 | * http://www.apache.org/licenses/LICENSE-2.0 125 | * 126 | * Unless required by applicable law or agreed to in writing, software 127 | * distributed under the License is distributed on an "AS IS" BASIS, 128 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 129 | * See the License for the specific language governing permissions and 130 | * limitations under the License. 131 | */ 132 | -------------------------------------------------------------------------------- /query/searchitems.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | paapi5 "github.com/goark/pa-api" 5 | ) 6 | 7 | //SearchItems type is embedded Query for SearchItems operation in PA-API v5 8 | type SearchItems struct { 9 | Query 10 | } 11 | 12 | var _ paapi5.Query = (*SearchItems)(nil) //SearchItems is compatible with paapi5.Query interface 13 | 14 | //NewSearchItems creates a new SearchItems instance 15 | func NewSearchItems(marketplace, partnerTag, partnerType string) *SearchItems { 16 | q := &SearchItems{*(New(paapi5.SearchItems))} 17 | q.Request(Marketplace, marketplace).Request(PartnerTag, partnerTag).Request(PartnerType, partnerType) 18 | return q 19 | } 20 | 21 | var ( 22 | requestsOfSearchItems = []RequestFilter{ 23 | Actor, 24 | Artist, 25 | Author, 26 | Availability, 27 | Brand, 28 | BrowseNodeID, 29 | Condition, 30 | CurrencyOfPreference, 31 | DeliveryFlags, 32 | ItemCount, 33 | ItemPage, 34 | Keywords, 35 | LanguagesOfPreference, 36 | Marketplace, 37 | MaxPrice, 38 | Merchant, 39 | MinPrice, 40 | MinReviewsRating, 41 | MinSavingPercent, 42 | OfferCount, 43 | PartnerTag, 44 | PartnerType, 45 | Properties, 46 | SearchIndex, 47 | SortBy, 48 | Title, 49 | } 50 | searchTypes = []RequestFilter{Actor, Artist, Author, Brand, Keywords, Title} 51 | ) 52 | 53 | //Request adds RequestFilter to Query instance 54 | func (q *SearchItems) Request(request RequestFilter, value interface{}) *SearchItems { 55 | if request.findIn(requestsOfSearchItems) { 56 | q.With().RequestFilters(RequestMap{request: value}) 57 | } 58 | return q 59 | } 60 | 61 | //Search is a generic search query funtion to obtain informations from the "SearchItems"-operation 62 | func (q *SearchItems) Search(searchType RequestFilter, searchParam string) *SearchItems { 63 | if searchType.findIn(searchTypes) { 64 | return q.Request(searchType, searchParam) 65 | } 66 | return q 67 | } 68 | 69 | //EnableBrowseNodeInfo sets the enableBrowseNodeInfo flag in SearchItems instance 70 | func (q *SearchItems) EnableBrowseNodeInfo() *SearchItems { 71 | q.With().BrowseNodeInfo() 72 | return q 73 | } 74 | 75 | //EnableImages sets the enableImages flag in SearchItems instance 76 | func (q *SearchItems) EnableImages() *SearchItems { 77 | q.With().Images() 78 | return q 79 | } 80 | 81 | //EnableItemInfo sets the enableItemInfo flag in SearchItems instance 82 | func (q *SearchItems) EnableItemInfo() *SearchItems { 83 | q.With().ItemInfo() 84 | return q 85 | } 86 | 87 | //EnableOffers sets the enableOffers flag in SearchItems instance 88 | func (q *SearchItems) EnableOffers() *SearchItems { 89 | q.With().Offers() 90 | return q 91 | } 92 | 93 | //EnableSearchRefinements sets the enableOffers flag in SearchItems instance 94 | func (q *SearchItems) EnableSearchRefinements() *SearchItems { 95 | q.With().SearchRefinements() 96 | return q 97 | } 98 | 99 | //EnableParentASIN sets the enableParentASIN flag in SearchItems instance 100 | func (q *SearchItems) EnableParentASIN() *SearchItems { 101 | q.With().ParentASIN() 102 | return q 103 | } 104 | 105 | //EnableCustomerReviews sets the enableCustomerReviews flag in SearchItems instance 106 | func (q *SearchItems) EnableCustomerReviews() *SearchItems { 107 | q.With().CustomerReviews() 108 | return q 109 | } 110 | 111 | /* Copyright 2019 Spiegel and contributors 112 | * 113 | * Licensed under the Apache License, Version 2.0 (the "License"); 114 | * you may not use this file except in compliance with the License. 115 | * You may obtain a copy of the License at 116 | * 117 | * http://www.apache.org/licenses/LICENSE-2.0 118 | * 119 | * Unless required by applicable law or agreed to in writing, software 120 | * distributed under the License is distributed on an "AS IS" BASIS, 121 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 122 | * See the License for the specific language governing permissions and 123 | * limitations under the License. 124 | */ 125 | -------------------------------------------------------------------------------- /query/searchitems_test.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import "testing" 4 | 5 | func TestSearchItems(t *testing.T) { 6 | testCases := []struct { 7 | q *SearchItems 8 | str string 9 | }{ 10 | {q: NewSearchItems("foo.bar", "mytag-20", "Associates"), str: `{"Operation":"SearchItems","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 11 | {q: NewSearchItems("foo.bar", "mytag-20", "Associates").Search(ItemIds, "foo"), str: `{"Operation":"SearchItems","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 12 | {q: NewSearchItems("foo.bar", "mytag-20", "Associates").Search(Actor, "foo"), str: `{"Operation":"SearchItems","Actor":"foo","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 13 | {q: NewSearchItems("foo.bar", "mytag-20", "Associates").Search(Artist, "foo"), str: `{"Operation":"SearchItems","Artist":"foo","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 14 | {q: NewSearchItems("foo.bar", "mytag-20", "Associates").Search(Author, "foo"), str: `{"Operation":"SearchItems","Author":"foo","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 15 | {q: NewSearchItems("foo.bar", "mytag-20", "Associates").Search(Brand, "foo"), str: `{"Operation":"SearchItems","Brand":"foo","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 16 | {q: NewSearchItems("foo.bar", "mytag-20", "Associates").Search(Keywords, "foo"), str: `{"Operation":"SearchItems","Keywords":"foo","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates"}`}, 17 | {q: NewSearchItems("foo.bar", "mytag-20", "Associates").Search(Title, "foo"), str: `{"Operation":"SearchItems","Marketplace":"foo.bar","PartnerTag":"mytag-20","PartnerType":"Associates","Title":"foo"}`}, 18 | } 19 | for _, tc := range testCases { 20 | if str := tc.q.String(); str != tc.str { 21 | t.Errorf("SearchItems.String() is \"%v\", want \"%v\"", str, tc.str) 22 | } 23 | } 24 | } 25 | 26 | func TestRequestInSearchItems(t *testing.T) { 27 | testCases := []struct { 28 | q *SearchItems 29 | str string 30 | }{ 31 | {q: NewSearchItems("", "", ""), str: `{"Operation":"SearchItems"}`}, 32 | {q: NewSearchItems("", "", "").Request(Actor, "foo"), str: `{"Operation":"SearchItems","Actor":"foo"}`}, 33 | {q: NewSearchItems("", "", "").Request(Artist, "foo"), str: `{"Operation":"SearchItems","Artist":"foo"}`}, 34 | {q: NewSearchItems("", "", "").Request(Availability, "foo"), str: `{"Operation":"SearchItems"}`}, 35 | {q: NewSearchItems("", "", "").Request(Availability, "Available"), str: `{"Operation":"SearchItems","Availability":"Available"}`}, 36 | {q: NewSearchItems("", "", "").Request(Availability, "IncludeOutOfStock"), str: `{"Operation":"SearchItems","Availability":"IncludeOutOfStock"}`}, 37 | {q: NewSearchItems("", "", "").Request(Author, "foo"), str: `{"Operation":"SearchItems","Author":"foo"}`}, 38 | {q: NewSearchItems("", "", "").Request(Brand, "foo"), str: `{"Operation":"SearchItems","Brand":"foo"}`}, 39 | {q: NewSearchItems("", "", "").Request(BrowseNodeID, "foo"), str: `{"Operation":"SearchItems"}`}, 40 | {q: NewSearchItems("", "", "").Request(BrowseNodeID, "123"), str: `{"Operation":"SearchItems","BrowseNodeId":"123"}`}, 41 | {q: NewSearchItems("", "", "").Request(Condition, "foo"), str: `{"Operation":"SearchItems"}`}, 42 | {q: NewSearchItems("", "", "").Request(Condition, "Any"), str: `{"Operation":"SearchItems","Condition":"Any"}`}, 43 | {q: NewSearchItems("", "", "").Request(Condition, "New"), str: `{"Operation":"SearchItems","Condition":"New"}`}, 44 | {q: NewSearchItems("", "", "").Request(Condition, "Used"), str: `{"Operation":"SearchItems","Condition":"Used"}`}, 45 | {q: NewSearchItems("", "", "").Request(Condition, "Collectible"), str: `{"Operation":"SearchItems","Condition":"Collectible"}`}, 46 | {q: NewSearchItems("", "", "").Request(Condition, "Refurbished"), str: `{"Operation":"SearchItems","Condition":"Refurbished"}`}, 47 | {q: NewSearchItems("", "", "").Request(CurrencyOfPreference, "foo"), str: `{"Operation":"SearchItems","CurrencyOfPreference":"foo"}`}, 48 | {q: NewSearchItems("", "", "").Request(DeliveryFlags, "foo"), str: `{"Operation":"SearchItems"}`}, 49 | {q: NewSearchItems("", "", "").Request(DeliveryFlags, "AmazonGlobal"), str: `{"Operation":"SearchItems","DeliveryFlags":["AmazonGlobal"]}`}, 50 | {q: NewSearchItems("", "", "").Request(DeliveryFlags, []string{"AmazonGlobal", "FreeShipping", "FulfilledByAmazon", "Prime"}), str: `{"Operation":"SearchItems","DeliveryFlags":["AmazonGlobal","FreeShipping","FulfilledByAmazon","Prime"]}`}, 51 | {q: NewSearchItems("", "", "").Request(ItemIds, "4900900028"), str: `{"Operation":"SearchItems"}`}, 52 | {q: NewSearchItems("", "", "").Request(ItemIdType, "ASIN"), str: `{"Operation":"SearchItems"}`}, 53 | {q: NewSearchItems("", "", "").Request(ItemCount, -1), str: `{"Operation":"SearchItems"}`}, 54 | {q: NewSearchItems("", "", "").Request(ItemCount, 0), str: `{"Operation":"SearchItems"}`}, 55 | {q: NewSearchItems("", "", "").Request(ItemCount, 1), str: `{"Operation":"SearchItems","ItemCount":1}`}, 56 | {q: NewSearchItems("", "", "").Request(ItemCount, 10), str: `{"Operation":"SearchItems","ItemCount":10}`}, 57 | {q: NewSearchItems("", "", "").Request(ItemCount, 11), str: `{"Operation":"SearchItems"}`}, 58 | {q: NewSearchItems("", "", "").Request(ItemPage, -1), str: `{"Operation":"SearchItems"}`}, 59 | {q: NewSearchItems("", "", "").Request(ItemPage, 0), str: `{"Operation":"SearchItems"}`}, 60 | {q: NewSearchItems("", "", "").Request(ItemPage, 1), str: `{"Operation":"SearchItems","ItemPage":1}`}, 61 | {q: NewSearchItems("", "", "").Request(ItemPage, 10), str: `{"Operation":"SearchItems","ItemPage":10}`}, 62 | {q: NewSearchItems("", "", "").Request(ItemPage, 11), str: `{"Operation":"SearchItems"}`}, 63 | {q: NewSearchItems("", "", "").Request(Keywords, "foo"), str: `{"Operation":"SearchItems","Keywords":"foo"}`}, 64 | {q: NewSearchItems("", "", "").Request(BrowseNodeIds, "123"), str: `{"Operation":"SearchItems"}`}, 65 | {q: NewSearchItems("", "", "").Request(BrowseNodeIds, []string{"123", "456"}), str: `{"Operation":"SearchItems"}`}, 66 | {q: NewSearchItems("", "", "").Request(LanguagesOfPreference, "foo"), str: `{"Operation":"SearchItems","LanguagesOfPreference":["foo"]}`}, 67 | {q: NewSearchItems("", "", "").Request(LanguagesOfPreference, []string{"foo", "bar"}), str: `{"Operation":"SearchItems","LanguagesOfPreference":["foo","bar"]}`}, 68 | {q: NewSearchItems("", "", "").Request(Marketplace, "foo.bar"), str: `{"Operation":"SearchItems","Marketplace":"foo.bar"}`}, 69 | {q: NewSearchItems("", "", "").Request(MaxPrice, -1), str: `{"Operation":"SearchItems"}`}, 70 | {q: NewSearchItems("", "", "").Request(MaxPrice, 0), str: `{"Operation":"SearchItems"}`}, 71 | {q: NewSearchItems("", "", "").Request(MaxPrice, 1), str: `{"Operation":"SearchItems","MaxPrice":1}`}, 72 | {q: NewSearchItems("", "", "").Request(MaxPrice, 123), str: `{"Operation":"SearchItems","MaxPrice":123}`}, 73 | {q: NewSearchItems("", "", "").Request(Merchant, "foo"), str: `{"Operation":"SearchItems"}`}, 74 | {q: NewSearchItems("", "", "").Request(Merchant, "All"), str: `{"Operation":"SearchItems","Merchant":"All"}`}, 75 | {q: NewSearchItems("", "", "").Request(Merchant, "Amazon"), str: `{"Operation":"SearchItems","Merchant":"Amazon"}`}, 76 | {q: NewSearchItems("", "", "").Request(MinPrice, -1), str: `{"Operation":"SearchItems"}`}, 77 | {q: NewSearchItems("", "", "").Request(MinPrice, 0), str: `{"Operation":"SearchItems"}`}, 78 | {q: NewSearchItems("", "", "").Request(MinPrice, 1), str: `{"Operation":"SearchItems","MinPrice":1}`}, 79 | {q: NewSearchItems("", "", "").Request(MinPrice, 123), str: `{"Operation":"SearchItems","MinPrice":123}`}, 80 | {q: NewSearchItems("", "", "").Request(MinReviewsRating, -1), str: `{"Operation":"SearchItems"}`}, 81 | {q: NewSearchItems("", "", "").Request(MinReviewsRating, 0), str: `{"Operation":"SearchItems"}`}, 82 | {q: NewSearchItems("", "", "").Request(MinReviewsRating, 1), str: `{"Operation":"SearchItems","MinReviewsRating":1}`}, 83 | {q: NewSearchItems("", "", "").Request(MinReviewsRating, 4), str: `{"Operation":"SearchItems","MinReviewsRating":4}`}, 84 | {q: NewSearchItems("", "", "").Request(MinReviewsRating, 5), str: `{"Operation":"SearchItems"}`}, 85 | {q: NewSearchItems("", "", "").Request(MinSavingPercent, -1), str: `{"Operation":"SearchItems"}`}, 86 | {q: NewSearchItems("", "", "").Request(MinSavingPercent, 0), str: `{"Operation":"SearchItems"}`}, 87 | {q: NewSearchItems("", "", "").Request(MinSavingPercent, 1), str: `{"Operation":"SearchItems","MinSavingPercent":1}`}, 88 | {q: NewSearchItems("", "", "").Request(MinSavingPercent, 99), str: `{"Operation":"SearchItems","MinSavingPercent":99}`}, 89 | {q: NewSearchItems("", "", "").Request(MinSavingPercent, 100), str: `{"Operation":"SearchItems"}`}, 90 | {q: NewSearchItems("", "", "").Request(OfferCount, -1), str: `{"Operation":"SearchItems"}`}, 91 | {q: NewSearchItems("", "", "").Request(OfferCount, 0), str: `{"Operation":"SearchItems"}`}, 92 | {q: NewSearchItems("", "", "").Request(OfferCount, 1), str: `{"Operation":"SearchItems","OfferCount":1}`}, 93 | {q: NewSearchItems("", "", "").Request(OfferCount, 123), str: `{"Operation":"SearchItems","OfferCount":123}`}, 94 | {q: NewSearchItems("", "", "").Request(PartnerTag, "foo"), str: `{"Operation":"SearchItems","PartnerTag":"foo"}`}, 95 | {q: NewSearchItems("", "", "").Request(PartnerType, "foo"), str: `{"Operation":"SearchItems"}`}, 96 | {q: NewSearchItems("", "", "").Request(PartnerType, "Associates"), str: `{"Operation":"SearchItems","PartnerType":"Associates"}`}, 97 | {q: NewSearchItems("", "", "").Request(Properties, map[string]string{"foo": "bar"}), str: `{"Operation":"SearchItems","Properties":{"foo":"bar"}}`}, 98 | {q: NewSearchItems("", "", "").Request(SearchIndex, "foo"), str: `{"Operation":"SearchItems"}`}, 99 | {q: NewSearchItems("", "", "").Request(SearchIndex, "All"), str: `{"Operation":"SearchItems","SearchIndex":"All"}`}, 100 | {q: NewSearchItems("", "", "").Request(SearchIndex, "AmazonVideo"), str: `{"Operation":"SearchItems","SearchIndex":"AmazonVideo"}`}, 101 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Apparel"), str: `{"Operation":"SearchItems","SearchIndex":"Apparel"}`}, 102 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Appliances"), str: `{"Operation":"SearchItems","SearchIndex":"Appliances"}`}, 103 | {q: NewSearchItems("", "", "").Request(SearchIndex, "ArtsAndCrafts"), str: `{"Operation":"SearchItems","SearchIndex":"ArtsAndCrafts"}`}, 104 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Automotive"), str: `{"Operation":"SearchItems","SearchIndex":"Automotive"}`}, 105 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Baby"), str: `{"Operation":"SearchItems","SearchIndex":"Baby"}`}, 106 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Beauty"), str: `{"Operation":"SearchItems","SearchIndex":"Beauty"}`}, 107 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Books"), str: `{"Operation":"SearchItems","SearchIndex":"Books"}`}, 108 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Classical"), str: `{"Operation":"SearchItems","SearchIndex":"Classical"}`}, 109 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Collectibles"), str: `{"Operation":"SearchItems","SearchIndex":"Collectibles"}`}, 110 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Computers"), str: `{"Operation":"SearchItems","SearchIndex":"Computers"}`}, 111 | {q: NewSearchItems("", "", "").Request(SearchIndex, "DigitalMusic"), str: `{"Operation":"SearchItems","SearchIndex":"DigitalMusic"}`}, 112 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Electronics"), str: `{"Operation":"SearchItems","SearchIndex":"Electronics"}`}, 113 | {q: NewSearchItems("", "", "").Request(SearchIndex, "EverythingElse"), str: `{"Operation":"SearchItems","SearchIndex":"EverythingElse"}`}, 114 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Fashion"), str: `{"Operation":"SearchItems","SearchIndex":"Fashion"}`}, 115 | {q: NewSearchItems("", "", "").Request(SearchIndex, "FashionBaby"), str: `{"Operation":"SearchItems","SearchIndex":"FashionBaby"}`}, 116 | {q: NewSearchItems("", "", "").Request(SearchIndex, "FashionBoys"), str: `{"Operation":"SearchItems","SearchIndex":"FashionBoys"}`}, 117 | {q: NewSearchItems("", "", "").Request(SearchIndex, "FashionGirls"), str: `{"Operation":"SearchItems","SearchIndex":"FashionGirls"}`}, 118 | {q: NewSearchItems("", "", "").Request(SearchIndex, "FashionMen"), str: `{"Operation":"SearchItems","SearchIndex":"FashionMen"}`}, 119 | {q: NewSearchItems("", "", "").Request(SearchIndex, "FashionWomen"), str: `{"Operation":"SearchItems","SearchIndex":"FashionWomen"}`}, 120 | {q: NewSearchItems("", "", "").Request(SearchIndex, "GardenAndOutdoor"), str: `{"Operation":"SearchItems","SearchIndex":"GardenAndOutdoor"}`}, 121 | {q: NewSearchItems("", "", "").Request(SearchIndex, "GiftCards"), str: `{"Operation":"SearchItems","SearchIndex":"GiftCards"}`}, 122 | {q: NewSearchItems("", "", "").Request(SearchIndex, "GroceryAndGourmetFood"), str: `{"Operation":"SearchItems","SearchIndex":"GroceryAndGourmetFood"}`}, 123 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Handmade"), str: `{"Operation":"SearchItems","SearchIndex":"Handmade"}`}, 124 | {q: NewSearchItems("", "", "").Request(SearchIndex, "HealthPersonalCare"), str: `{"Operation":"SearchItems","SearchIndex":"HealthPersonalCare"}`}, 125 | {q: NewSearchItems("", "", "").Request(SearchIndex, "HomeAndKitchen"), str: `{"Operation":"SearchItems","SearchIndex":"HomeAndKitchen"}`}, 126 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Industrial"), str: `{"Operation":"SearchItems","SearchIndex":"Industrial"}`}, 127 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Jewelry"), str: `{"Operation":"SearchItems","SearchIndex":"Jewelry"}`}, 128 | {q: NewSearchItems("", "", "").Request(SearchIndex, "KindleStore"), str: `{"Operation":"SearchItems","SearchIndex":"KindleStore"}`}, 129 | {q: NewSearchItems("", "", "").Request(SearchIndex, "LocalServices"), str: `{"Operation":"SearchItems","SearchIndex":"LocalServices"}`}, 130 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Luggage"), str: `{"Operation":"SearchItems","SearchIndex":"Luggage"}`}, 131 | {q: NewSearchItems("", "", "").Request(SearchIndex, "LuxuryBeauty"), str: `{"Operation":"SearchItems","SearchIndex":"LuxuryBeauty"}`}, 132 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Magazines"), str: `{"Operation":"SearchItems","SearchIndex":"Magazines"}`}, 133 | {q: NewSearchItems("", "", "").Request(SearchIndex, "MobileAndAccessories"), str: `{"Operation":"SearchItems","SearchIndex":"MobileAndAccessories"}`}, 134 | {q: NewSearchItems("", "", "").Request(SearchIndex, "MobileApps"), str: `{"Operation":"SearchItems","SearchIndex":"MobileApps"}`}, 135 | {q: NewSearchItems("", "", "").Request(SearchIndex, "MoviesAndTV"), str: `{"Operation":"SearchItems","SearchIndex":"MoviesAndTV"}`}, 136 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Music"), str: `{"Operation":"SearchItems","SearchIndex":"Music"}`}, 137 | {q: NewSearchItems("", "", "").Request(SearchIndex, "MusicalInstruments"), str: `{"Operation":"SearchItems","SearchIndex":"MusicalInstruments"}`}, 138 | {q: NewSearchItems("", "", "").Request(SearchIndex, "OfficeProducts"), str: `{"Operation":"SearchItems","SearchIndex":"OfficeProducts"}`}, 139 | {q: NewSearchItems("", "", "").Request(SearchIndex, "PetSupplies"), str: `{"Operation":"SearchItems","SearchIndex":"PetSupplies"}`}, 140 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Photo"), str: `{"Operation":"SearchItems","SearchIndex":"Photo"}`}, 141 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Shoes"), str: `{"Operation":"SearchItems","SearchIndex":"Shoes"}`}, 142 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Software"), str: `{"Operation":"SearchItems","SearchIndex":"Software"}`}, 143 | {q: NewSearchItems("", "", "").Request(SearchIndex, "SportsAndOutdoors"), str: `{"Operation":"SearchItems","SearchIndex":"SportsAndOutdoors"}`}, 144 | {q: NewSearchItems("", "", "").Request(SearchIndex, "ToolsAndHomeImprovement"), str: `{"Operation":"SearchItems","SearchIndex":"ToolsAndHomeImprovement"}`}, 145 | {q: NewSearchItems("", "", "").Request(SearchIndex, "ToysAndGames"), str: `{"Operation":"SearchItems","SearchIndex":"ToysAndGames"}`}, 146 | {q: NewSearchItems("", "", "").Request(SearchIndex, "VHS"), str: `{"Operation":"SearchItems","SearchIndex":"VHS"}`}, 147 | {q: NewSearchItems("", "", "").Request(SearchIndex, "VideoGames"), str: `{"Operation":"SearchItems","SearchIndex":"VideoGames"}`}, 148 | {q: NewSearchItems("", "", "").Request(SearchIndex, "Watches"), str: `{"Operation":"SearchItems","SearchIndex":"Watches"}`}, 149 | {q: NewSearchItems("", "", "").Request(SortBy, "foo"), str: `{"Operation":"SearchItems"}`}, 150 | {q: NewSearchItems("", "", "").Request(SortBy, "AvgCustomerReviews"), str: `{"Operation":"SearchItems","SortBy":"AvgCustomerReviews"}`}, 151 | {q: NewSearchItems("", "", "").Request(SortBy, "Featured"), str: `{"Operation":"SearchItems","SortBy":"Featured"}`}, 152 | {q: NewSearchItems("", "", "").Request(SortBy, "NewestArrivals"), str: `{"Operation":"SearchItems","SortBy":"NewestArrivals"}`}, 153 | {q: NewSearchItems("", "", "").Request(SortBy, "Price:HighToLow"), str: `{"Operation":"SearchItems","SortBy":"Price:HighToLow"}`}, 154 | {q: NewSearchItems("", "", "").Request(SortBy, "Price:LowToHigh"), str: `{"Operation":"SearchItems","SortBy":"Price:LowToHigh"}`}, 155 | {q: NewSearchItems("", "", "").Request(SortBy, "Relevance"), str: `{"Operation":"SearchItems","SortBy":"Relevance"}`}, 156 | {q: NewSearchItems("", "", "").Request(Title, "foo"), str: `{"Operation":"SearchItems","Title":"foo"}`}, 157 | } 158 | 159 | for _, tc := range testCases { 160 | if str := tc.q.String(); str != tc.str { 161 | t.Errorf("Query.String() is \"%v\", want \"%v\"", str, tc.str) 162 | } 163 | } 164 | } 165 | 166 | func TestResourcesInSearchItems(t *testing.T) { 167 | testCases := []struct { 168 | q *SearchItems 169 | str string 170 | }{ 171 | {q: NewSearchItems("", "", "").EnableBrowseNodeInfo(), str: `{"Operation":"SearchItems","Resources":["BrowseNodeInfo.BrowseNodes","BrowseNodeInfo.BrowseNodes.Ancestor","BrowseNodeInfo.BrowseNodes.SalesRank","BrowseNodeInfo.WebsiteSalesRank"]}`}, 172 | {q: NewSearchItems("", "", "").EnableImages(), str: `{"Operation":"SearchItems","Resources":["Images.Primary.Small","Images.Primary.Medium","Images.Primary.Large","Images.Variants.Small","Images.Variants.Medium","Images.Variants.Large"]}`}, 173 | {q: NewSearchItems("", "", "").EnableItemInfo(), str: `{"Operation":"SearchItems","Resources":["ItemInfo.ByLineInfo","ItemInfo.ContentInfo","ItemInfo.ContentRating","ItemInfo.Classifications","ItemInfo.ExternalIds","ItemInfo.Features","ItemInfo.ManufactureInfo","ItemInfo.ProductInfo","ItemInfo.TechnicalInfo","ItemInfo.Title","ItemInfo.TradeInInfo"]}`}, 174 | {q: NewSearchItems("", "", "").EnableOffers(), str: `{"Operation":"SearchItems","Resources":["Offers.Listings.Availability.MaxOrderQuantity","Offers.Listings.Availability.Message","Offers.Listings.Availability.MinOrderQuantity","Offers.Listings.Availability.Type","Offers.Listings.Condition","Offers.Listings.Condition.SubCondition","Offers.Listings.DeliveryInfo.IsAmazonFulfilled","Offers.Listings.DeliveryInfo.IsFreeShippingEligible","Offers.Listings.DeliveryInfo.IsPrimeEligible","Offers.Listings.DeliveryInfo.ShippingCharges","Offers.Listings.IsBuyBoxWinner","Offers.Listings.LoyaltyPoints.Points","Offers.Listings.MerchantInfo","Offers.Listings.Price","Offers.Listings.ProgramEligibility.IsPrimeExclusive","Offers.Listings.ProgramEligibility.IsPrimePantry","Offers.Listings.Promotions","Offers.Listings.SavingBasis","Offers.Summaries.HighestPrice","Offers.Summaries.LowestPrice","Offers.Summaries.OfferCount"]}`}, 175 | {q: NewSearchItems("", "", "").EnableSearchRefinements(), str: `{"Operation":"SearchItems","Resources":["SearchRefinements"]}`}, 176 | {q: NewSearchItems("", "", "").EnableParentASIN(), str: `{"Operation":"SearchItems","Resources":["ParentASIN"]}`}, 177 | {q: NewSearchItems("", "", "").EnableCustomerReviews(), str: `{"Operation":"SearchItems","Resources":["CustomerReviews.Count","CustomerReviews.StarRating"]}`}, 178 | } 179 | 180 | for _, tc := range testCases { 181 | if str := tc.q.String(); str != tc.str { 182 | t.Errorf("Query.String() is \"%v\", want \"%v\"", str, tc.str) 183 | } 184 | } 185 | } 186 | 187 | /* Copyright 2019-2022 Spiegel and contributors 188 | * 189 | * Licensed under the Apache License, Version 2.0 (the "License"); 190 | * you may not use this file except in compliance with the License. 191 | * You may obtain a copy of the License at 192 | * 193 | * http://www.apache.org/licenses/LICENSE-2.0 194 | * 195 | * Unless required by applicable law or agreed to in writing, software 196 | * distributed under the License is distributed on an "AS IS" BASIS, 197 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 198 | * See the License for the specific language governing permissions and 199 | * limitations under the License. 200 | */ 201 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/url" 7 | 8 | "github.com/goark/fetch" 9 | ) 10 | 11 | const ( 12 | defaultScheme = "https" 13 | defaultAccept = "application/json, text/javascript" 14 | defaultContentType = "application/json; charset=UTF-8" 15 | defaultHMACAlgorithm = "AWS4-HMAC-SHA256" 16 | defaultServiceName = "ProductAdvertisingAPI" 17 | defaultContentEncoding = "amz-1.0" 18 | defaultAWS4Request = "aws4_request" 19 | ) 20 | 21 | //Server type is a implementation of PA-API service. 22 | type Server struct { 23 | scheme string 24 | marketplace Marketplace 25 | language string 26 | } 27 | 28 | //ServerOptFunc type is self-referential function type for New functions. (functional options pattern) 29 | type ServerOptFunc func(*Server) 30 | 31 | //New function returns an Server instance with options. 32 | func New(opts ...ServerOptFunc) *Server { 33 | server := &Server{scheme: defaultScheme, marketplace: DefaultMarketplace, language: ""} 34 | for _, opt := range opts { 35 | opt(server) 36 | } 37 | return server 38 | } 39 | 40 | //WithMarketplace function returns ServerOptFunc function value. 41 | //This function is used in New functions that represents Marketplace data. 42 | func WithMarketplace(marketplace Marketplace) ServerOptFunc { 43 | return func(s *Server) { 44 | if s != nil { 45 | s.marketplace = marketplace 46 | } 47 | } 48 | } 49 | 50 | //WithLanguage function returns ServerOptFunc function value. 51 | //This function is used in New functions that represents Accept-Language parameter. 52 | func WithLanguage(language string) ServerOptFunc { 53 | return func(s *Server) { 54 | if s != nil { 55 | s.language = language 56 | } 57 | } 58 | } 59 | 60 | //URL method returns url of service server information for PA-API v5. 61 | func (s *Server) URL(path string) *url.URL { 62 | if s == nil { 63 | s = New() 64 | } 65 | return &url.URL{Scheme: s.scheme, Host: s.HostName(), Path: path} 66 | } 67 | 68 | //Marketplace method returns marketplace name for PA-API v5. 69 | func (s *Server) Marketplace() string { 70 | if s == nil { 71 | s = New() 72 | } 73 | return s.marketplace.String() 74 | } 75 | 76 | //HostName method returns hostname for PA-API v5. 77 | func (s *Server) HostName() string { 78 | if s == nil { 79 | s = New() 80 | } 81 | return s.marketplace.HostName() 82 | } 83 | 84 | //Region method returns region name for PA-API v5 85 | func (s *Server) Region() string { 86 | if s == nil { 87 | s = New() 88 | } 89 | return s.marketplace.Region() 90 | } 91 | 92 | //Accept method returns Accept parameter for PA-API v5 93 | func (s *Server) Accept() string { 94 | return defaultAccept 95 | } 96 | 97 | //AcceptLanguage method returns Accept-Language parameter for PA-API v5 98 | func (s *Server) AcceptLanguage() string { 99 | if s == nil { 100 | s = New() 101 | } 102 | if len(s.language) > 0 { 103 | return s.language 104 | } 105 | return s.marketplace.Language() //default language 106 | } 107 | 108 | //ContentType method returns Content-Type parameter for PA-API v5 109 | func (s *Server) ContentType() string { 110 | return defaultContentType 111 | } 112 | 113 | //HMACAlgorithm method returns HMAC-Algorithm parameter for PA-API v5 114 | func (s *Server) HMACAlgorithm() string { 115 | return defaultHMACAlgorithm 116 | } 117 | 118 | //ServiceName method returns ServiceName parameter for PA-API v5 119 | func (s *Server) ServiceName() string { 120 | return defaultServiceName 121 | } 122 | 123 | //AWS4Request method returns AWS4Request parameter for PA-API v5 124 | func (s *Server) AWS4Request() string { 125 | return defaultAWS4Request 126 | } 127 | 128 | //ContentEncoding method returns Content-Encoding parameter for PA-API v5 129 | func (s *Server) ContentEncoding() string { 130 | return defaultContentEncoding 131 | } 132 | 133 | //ClientOptFunc type is self-referential function type for Server.CreateClient method. (functional options pattern) 134 | type ClientOptFunc func(*client) 135 | 136 | //CreateClient method returns an Client instance with associate-tag, access-key, secret-key, and other options. 137 | func (s *Server) CreateClient(associateTag, accessKey, secretKey string, opts ...ClientOptFunc) Client { 138 | if s == nil { 139 | s = New() 140 | } 141 | cli := &client{ 142 | server: s, 143 | client: nil, 144 | partnerTag: associateTag, 145 | accessKey: accessKey, 146 | secretKey: secretKey, 147 | } 148 | for _, opt := range opts { 149 | opt(cli) 150 | } 151 | if cli.client == nil { 152 | cli.client = fetch.New() 153 | } 154 | return cli 155 | } 156 | 157 | //WithContext is dummy function. Because this function is deprecated. 158 | func WithContext(ctx context.Context) ClientOptFunc { 159 | return func(c *client) {} 160 | } 161 | 162 | //WithHttpClient function returns ClientOptFunc function value. 163 | //This function is used in Server.CreateClient method that represents http.Client. 164 | func WithHttpClient(hc *http.Client) ClientOptFunc { 165 | return func(c *client) { 166 | if c != nil { 167 | c.client = fetch.New(fetch.WithHTTPClient(hc)) 168 | } 169 | } 170 | } 171 | 172 | //DefaultClient function returns an default Client instance with associate-tag, access-key, and secret-key parameters. 173 | func DefaultClient(associateTag, accessKey, secretKey string) Client { 174 | return New().CreateClient(associateTag, accessKey, secretKey) 175 | } 176 | 177 | /* Copyright 2019-2021 Spiegel and contributors 178 | * 179 | * Licensed under the Apache License, Version 2.0 (the "License"); 180 | * you may not use this file except in compliance with the License. 181 | * You may obtain a copy of the License at 182 | * 183 | * http://www.apache.org/licenses/LICENSE-2.0 184 | * 185 | * Unless required by applicable law or agreed to in writing, software 186 | * distributed under the License is distributed on an "AS IS" BASIS, 187 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 188 | * See the License for the specific language governing permissions and 189 | * limitations under the License. 190 | */ 191 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import "testing" 4 | 5 | func TestServer(t *testing.T) { 6 | testCases := []struct { 7 | sv *Server 8 | marketplace string 9 | hostName string 10 | region string 11 | accept string 12 | acceptLanguage string 13 | contentType string 14 | hmacAlgorithm string 15 | serviceName string 16 | aws4Request string 17 | contentEncoding string 18 | url string 19 | }{ 20 | {sv: (*Server)(nil), marketplace: "www.amazon.com", hostName: "webservices.amazon.com", region: "us-east-1", accept: defaultAccept, acceptLanguage: "en_US", contentType: defaultContentType, hmacAlgorithm: defaultHMACAlgorithm, serviceName: defaultServiceName, contentEncoding: defaultContentEncoding, aws4Request: defaultAWS4Request, url: "https://webservices.amazon.com/paapi5/getitems"}, 21 | {sv: New(WithMarketplace(LocaleJapan)), marketplace: "www.amazon.co.jp", hostName: "webservices.amazon.co.jp", region: "us-west-2", accept: defaultAccept, acceptLanguage: "ja_JP", contentType: defaultContentType, hmacAlgorithm: defaultHMACAlgorithm, serviceName: defaultServiceName, contentEncoding: defaultContentEncoding, aws4Request: defaultAWS4Request, url: "https://webservices.amazon.co.jp/paapi5/getitems"}, 22 | } 23 | for _, tc := range testCases { 24 | if tc.sv.Marketplace() != tc.marketplace { 25 | t.Errorf("Server.Marketplace() is \"%v\", want \"%v\"", tc.sv.Marketplace(), tc.marketplace) 26 | } 27 | if tc.sv.HostName() != tc.hostName { 28 | t.Errorf("Server.HostName() is \"%v\", want \"%v\"", tc.sv.HostName(), tc.hostName) 29 | } 30 | if tc.sv.Region() != tc.region { 31 | t.Errorf("Server.Region() is \"%v\", want \"%v\"", tc.sv.Region(), tc.region) 32 | } 33 | if tc.sv.Accept() != tc.accept { 34 | t.Errorf("Server.Accept() is \"%v\", want \"%v\"", tc.sv.Accept(), tc.accept) 35 | } 36 | if tc.sv.AcceptLanguage() != tc.acceptLanguage { 37 | t.Errorf("Server.AcceptLanguage() is \"%v\", want \"%v\"", tc.sv.AcceptLanguage(), tc.acceptLanguage) 38 | } 39 | if tc.sv.ContentType() != tc.contentType { 40 | t.Errorf("Server.ContentType() is \"%v\", want \"%v\"", tc.sv.ContentType(), tc.contentType) 41 | } 42 | if tc.sv.HMACAlgorithm() != tc.hmacAlgorithm { 43 | t.Errorf("Server.HMACAlgorithm() is \"%v\", want \"%v\"", tc.sv.HMACAlgorithm(), tc.hmacAlgorithm) 44 | } 45 | if tc.sv.ServiceName() != tc.serviceName { 46 | t.Errorf("Server.ServiceName() is \"%v\", want \"%v\"", tc.sv.ServiceName(), tc.serviceName) 47 | } 48 | if tc.sv.AWS4Request() != tc.aws4Request { 49 | t.Errorf("Server.AWS4Request() is \"%v\", want \"%v\"", tc.sv.AWS4Request(), tc.aws4Request) 50 | } 51 | if tc.sv.ContentEncoding() != tc.contentEncoding { 52 | t.Errorf("Server.ContentEncoding() is \"%v\", want \"%v\"", tc.sv.ContentEncoding(), tc.contentEncoding) 53 | } 54 | url := tc.sv.URL(GetItems.Path()).String() 55 | if url != tc.url { 56 | t.Errorf("Server.URL() is \"%v\", want \"%v\"", url, tc.url) 57 | } 58 | } 59 | } 60 | 61 | /* Copyright 2019 Spiegel and contributors 62 | * 63 | * Licensed under the Apache License, Version 2.0 (the "License"); 64 | * you may not use this file except in compliance with the License. 65 | * You may obtain a copy of the License at 66 | * 67 | * http://www.apache.org/licenses/LICENSE-2.0 68 | * 69 | * Unless required by applicable law or agreed to in writing, software 70 | * distributed under the License is distributed on an "AS IS" BASIS, 71 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 72 | * See the License for the specific language governing permissions and 73 | * limitations under the License. 74 | */ 75 | -------------------------------------------------------------------------------- /values-datetime.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import "time" 4 | 5 | //TimeStamp is wrapper class of time.Time 6 | type TimeStamp struct { 7 | time.Time 8 | } 9 | 10 | //NewTimeStamp returns TimeStamp instance 11 | func NewTimeStamp(tm time.Time) TimeStamp { 12 | return TimeStamp{tm} 13 | } 14 | 15 | func (t TimeStamp) StringDate() string { 16 | return t.UTC().Format("20060102") 17 | } 18 | 19 | func (t TimeStamp) String() string { 20 | return t.UTC().Format("20060102T150405Z") 21 | } 22 | 23 | /* Copyright 2019 Spiegel 24 | * 25 | * Licensed under the Apache License, Version 2.0 (the "License"); 26 | * you may not use this file except in compliance with the License. 27 | * You may obtain a copy of the License at 28 | * 29 | * http://www.apache.org/licenses/LICENSE-2.0 30 | * 31 | * Unless required by applicable law or agreed to in writing, software 32 | * distributed under the License is distributed on an "AS IS" BASIS, 33 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | * See the License for the specific language governing permissions and 35 | * limitations under the License. 36 | */ 37 | -------------------------------------------------------------------------------- /values-datetime_test.go: -------------------------------------------------------------------------------- 1 | package paapi5 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestTimeStamp(t *testing.T) { 9 | testCases := []struct { 10 | tm time.Time 11 | str string 12 | strDate string 13 | }{ 14 | {tm: time.Time{}, str: "00010101T000000Z", strDate: "00010101"}, 15 | {tm: time.Date(2019, time.September, 30, 8, 31, 54, 0, time.UTC), str: "20190930T083154Z", strDate: "20190930"}, 16 | {tm: time.Date(2019, time.September, 30, 8, 31, 54, 0, time.FixedZone("JST", 9*60*60)), str: "20190929T233154Z", strDate: "20190929"}, 17 | } 18 | for _, tc := range testCases { 19 | dt := NewTimeStamp(tc.tm) 20 | str := dt.String() 21 | if str != tc.str { 22 | t.Errorf("TimeStamp.String() = \"%v\", want \"%v\".", str, tc.str) 23 | } 24 | strDate := dt.StringDate() 25 | if strDate != tc.strDate { 26 | t.Errorf("TimeStamp.StringDate() = \"%v\", want \"%v\".", strDate, tc.strDate) 27 | } 28 | } 29 | } 30 | 31 | /* Copyright 2019 Spiegel 32 | * 33 | * Licensed under the Apache License, Version 2.0 (the "License"); 34 | * you may not use this file except in compliance with the License. 35 | * You may obtain a copy of the License at 36 | * 37 | * http://www.apache.org/licenses/LICENSE-2.0 38 | * 39 | * Unless required by applicable law or agreed to in writing, software 40 | * distributed under the License is distributed on an "AS IS" BASIS, 41 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | * See the License for the specific language governing permissions and 43 | * limitations under the License. 44 | */ 45 | --------------------------------------------------------------------------------