adadiamonds 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +21 -0
- data/README.md +112 -0
- data/lib/adadiamonds/client.rb +295 -0
- data/lib/adadiamonds/version.rb +6 -0
- data/lib/adadiamonds.rb +22 -0
- metadata +99 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0a6bdae7ac7a99d8a70ee8800eec58b6692a35c63784cad85e75b835398857b6
|
|
4
|
+
data.tar.gz: 47f5cc02e53554e5326781a9645914403b4d47f0b8a464c4896459fccf2b43c1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 669e2711d23848bf585a0a6b3f6f6e26f8f5114e4759b5f7cee63a185164c9731d83f8583b09c6796f630b9de11f69fc4a616bc6bd7a4b2b68c9043b99098a3e
|
|
7
|
+
data.tar.gz: 37e2e2ed149551590cfa04ae8cc62e4d6d8765c3633ae2f05490b111619064134449f640300297024da48bac9e72b1070746d3076d58b87f1a2e2e27ad66a0b2
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- First release: `AdaDiamonds::Client` with one method per endpoint (`diamonds`, `diamond`, `engagement_rings`, `engagement_ring`, `jewelry`, `jewelry_item`, `knowledge_base`, `article`, `showrooms`, `request_consultation`, `create_key`, `batch`, `ask`), generic `get`/`post`, `AdaDiamonds::Error`, and `last_rate_limit`.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ada Diamonds, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# adadiamonds
|
|
2
|
+
|
|
3
|
+
Official Ruby client for the [Ada Diamonds](https://www.adadiamonds.com) API: live lab grown diamond inventory, engagement ring settings, fine jewelry, showrooms, and the diamond buying guides.
|
|
4
|
+
|
|
5
|
+
Standard library only (`net/http`, `json`). Ruby 2.6 or newer.
|
|
6
|
+
|
|
7
|
+
- Documentation: https://www.adadiamonds.com/developers
|
|
8
|
+
- REST reference: https://www.adadiamonds.com/developers/api
|
|
9
|
+
- OpenAPI 3.1: https://www.adadiamonds.com/openapi.json
|
|
10
|
+
- MCP server (for tool use instead of HTTP): https://www.adadiamonds.com/mcp
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
gem install adadiamonds
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
or in a Gemfile:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem "adadiamonds"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Reading the catalog needs no account and no API key (120 requests per minute). An API key raises that to 600 and unlocks the write endpoints. Every price is in US dollars.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
### Search live diamond inventory
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require "adadiamonds"
|
|
32
|
+
|
|
33
|
+
client = AdaDiamonds::Client.new # anonymous
|
|
34
|
+
page = client.diamonds(shape: "Oval", min_carat: 1, max_price: 4000, sort: "price_asc")
|
|
35
|
+
page.each do |stone|
|
|
36
|
+
puts [stone["carat"], stone["shape"], stone["price"]].join(" ")
|
|
37
|
+
end
|
|
38
|
+
p client.last_rate_limit # #<struct limit=120, remaining=119, reset=58, policy="...">
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Quote a complete ring
|
|
42
|
+
|
|
43
|
+
An engagement ring is a setting plus a loose diamond, priced separately. Setting prices never include the center stone.
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
setting = client.engagement_rings(shape: "Oval", type: "Solitaire", limit: 1).first
|
|
47
|
+
stone = client.diamonds(shape: "Oval", min_carat: 1.5, max_carat: 1.6, sort: "price_asc", limit: 1).first
|
|
48
|
+
puts setting["priceFrom"] + stone["price"] # US dollars
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Authenticate and write
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
key = AdaDiamonds::Client.new.create_key("my-agent", env: "sandbox")
|
|
55
|
+
client = AdaDiamonds::Client.new(api_key: key["api_key"]) # 600 requests/min
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
client.request_consultation(
|
|
59
|
+
{ "email" => "test@example.com", "topic" => "engagement_ring" },
|
|
60
|
+
idempotency_key: "consult-42" # a retry cannot create two requests
|
|
61
|
+
)
|
|
62
|
+
rescue AdaDiamonds::Error => e
|
|
63
|
+
puts [e.status, e.code, e.description, e.retry_after].inspect
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Sandbox keys (`ada_test_*`) return well-formed responses and contact nobody.
|
|
68
|
+
|
|
69
|
+
### Ask a question (NLWeb)
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
answer = client.ask("oval lab diamond engagement ring under $6,000")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Anything else
|
|
76
|
+
|
|
77
|
+
Every method maps to one endpoint under `/api/v1`. For endpoints without a helper, `get` and `post` take a path relative to `/api/v1` (or absolute on the site) and return the decoded JSON:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
bands = client.get("/jewelry", category: "wedding-bands")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Methods
|
|
84
|
+
|
|
85
|
+
| Method | Endpoint |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `diamonds`, `diamond` | `GET /api/v1/diamonds`, `GET /api/v1/diamonds/{id}` |
|
|
88
|
+
| `engagement_rings`, `engagement_ring` | `GET /api/v1/engagement-rings`, `.../{slug}` |
|
|
89
|
+
| `jewelry`, `jewelry_item` | `GET /api/v1/jewelry`, `.../{slug}` |
|
|
90
|
+
| `knowledge_base`, `article` | `GET /api/v1/knowledge-base`, `.../{slug}` |
|
|
91
|
+
| `showrooms` | `GET /api/v1/showrooms` |
|
|
92
|
+
| `request_consultation` | `POST /api/v1/consultations` |
|
|
93
|
+
| `create_key` | `POST /api/v1/keys` |
|
|
94
|
+
| `batch` | `POST /api/v1/batch` (up to 20 GETs in one round trip) |
|
|
95
|
+
| `ask` | `GET /ask` (NLWeb) |
|
|
96
|
+
|
|
97
|
+
List methods return an `AdaDiamonds::Page` (`data`, `pagination`, `total`, `has_more?`, and `Enumerable`). Errors are `AdaDiamonds::Error` with `status`, `code`, `description`, `documentation_url`, and `retry_after`. Every response's `RateLimit-*` headers are available from `last_rate_limit`; pace yourself rather than retrying into a 429.
|
|
98
|
+
|
|
99
|
+
## Etiquette
|
|
100
|
+
|
|
101
|
+
Identify your agent with `AdaDiamonds::Client.new(user_agent: "my-agent/1.0 (+https://example.com)")`. Bulk scraping of the HTML catalog is not permitted; this API and `POST /api/v1/exports` are the supported way to read it. Diamond inventory changes continuously; re-check a stone before quoting it.
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
bundle install
|
|
107
|
+
bundle exec rake test
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT. Copyright Ada Diamonds, Inc.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AdaDiamonds
|
|
4
|
+
# Production host.
|
|
5
|
+
DEFAULT_BASE_URL = "https://www.adadiamonds.com"
|
|
6
|
+
# REST API version this client targets.
|
|
7
|
+
API_VERSION = "v1"
|
|
8
|
+
USER_AGENT = "ada-diamonds-ruby/#{VERSION} (+https://www.adadiamonds.com/developers)"
|
|
9
|
+
|
|
10
|
+
# Site-root endpoints that do not live under /api/v1.
|
|
11
|
+
ROOT_PATHS = ["/ask", "/mcp", "/openapi.json", "/llms.txt"].freeze
|
|
12
|
+
|
|
13
|
+
# A non-2xx response, decoded from the API's JSON error body: a stable
|
|
14
|
+
# machine-readable +code+, a human-readable +description+ that says what to
|
|
15
|
+
# do next, and the HTTP +status+. +documentation_url+ and +retry_after+
|
|
16
|
+
# (seconds) are carried when the API sends them.
|
|
17
|
+
class Error < StandardError
|
|
18
|
+
attr_reader :status, :code, :description, :documentation_url, :retry_after, :body
|
|
19
|
+
|
|
20
|
+
def initialize(status:, code:, description: "", documentation_url: nil, retry_after: 0, body: {})
|
|
21
|
+
@status = status
|
|
22
|
+
@code = code
|
|
23
|
+
@description = description
|
|
24
|
+
@documentation_url = documentation_url
|
|
25
|
+
@retry_after = retry_after
|
|
26
|
+
@body = body
|
|
27
|
+
super(description.empty? ? "#{status} #{code}" : "#{status} #{code}: #{description}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The RateLimit-* headers from the most recent response. Counters are per
|
|
32
|
+
# 60 second window; treat them as a pacing signal.
|
|
33
|
+
RateLimit = Struct.new(:limit, :remaining, :reset, :policy, keyword_init: true)
|
|
34
|
+
|
|
35
|
+
# One page of a list response: catalog items plus pagination. Items are
|
|
36
|
+
# plain hashes because the catalog schema evolves faster than a client
|
|
37
|
+
# release; the field names match the OpenAPI document exactly.
|
|
38
|
+
class Page
|
|
39
|
+
include Enumerable
|
|
40
|
+
|
|
41
|
+
attr_reader :object, :currency, :data, :pagination
|
|
42
|
+
|
|
43
|
+
def initialize(body)
|
|
44
|
+
body ||= {}
|
|
45
|
+
@object = body["object"]
|
|
46
|
+
@currency = body["currency"]
|
|
47
|
+
@data = body["data"] || []
|
|
48
|
+
@pagination = body["pagination"] || {}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def each(&block)
|
|
52
|
+
@data.each(&block)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def total
|
|
56
|
+
@pagination["total"]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def limit
|
|
60
|
+
@pagination["limit"]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def offset
|
|
64
|
+
@pagination["offset"]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def has_more?
|
|
68
|
+
@pagination["has_more"] == true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Calls the Ada Diamonds REST API. Every public method maps to exactly one
|
|
73
|
+
# endpoint; anything the client does can be reproduced with curl.
|
|
74
|
+
class Client
|
|
75
|
+
attr_reader :base_url, :api_key, :user_agent, :timeout, :last_rate_limit, :last_headers
|
|
76
|
+
|
|
77
|
+
# +api_key+: an ada_live_* / ada_test_* key or an OAuth access token (nil
|
|
78
|
+
# means anonymous). +base_url+ points at another host (a staging
|
|
79
|
+
# deployment, a proxy). +user_agent+ identifies your agent.
|
|
80
|
+
def initialize(api_key: nil, base_url: DEFAULT_BASE_URL, user_agent: USER_AGENT, timeout: 30)
|
|
81
|
+
@api_key = api_key
|
|
82
|
+
@base_url = base_url.sub(%r{/+\z}, "")
|
|
83
|
+
@user_agent = user_agent
|
|
84
|
+
@timeout = timeout
|
|
85
|
+
@last_rate_limit = nil
|
|
86
|
+
@last_headers = {}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------- catalog
|
|
90
|
+
|
|
91
|
+
# Searches available loose lab grown diamonds. Filters: shape, min_carat,
|
|
92
|
+
# max_carat, min_price, max_price, color, clarity, cut, sort (price_asc |
|
|
93
|
+
# price_desc | carat_asc | carat_desc), limit, offset.
|
|
94
|
+
def diamonds(params = {})
|
|
95
|
+
Page.new(get("/diamonds", params))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Fetches one stone by id (the catalog id or grading report number).
|
|
99
|
+
def diamond(id)
|
|
100
|
+
get("/diamonds/#{escape(id)}")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Searches ring settings. Filters: shape, style, type, min_price,
|
|
104
|
+
# max_price, q, limit, offset. Setting prices exclude the center stone.
|
|
105
|
+
def engagement_rings(params = {})
|
|
106
|
+
Page.new(get("/engagement-rings", params))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Fetches one setting by slug.
|
|
110
|
+
def engagement_ring(slug)
|
|
111
|
+
get("/engagement-rings/#{escape(slug)}")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Searches wedding bands, earrings, necklaces, bracelets, and fashion
|
|
115
|
+
# rings. Filters: category, type, shape, min_price, max_price, q, limit, offset.
|
|
116
|
+
def jewelry(params = {})
|
|
117
|
+
Page.new(get("/jewelry", params))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Fetches one jewelry product by slug.
|
|
121
|
+
def jewelry_item(slug)
|
|
122
|
+
get("/jewelry/#{escape(slug)}")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Lists the buying guides and articles. +query+ may be nil.
|
|
126
|
+
def knowledge_base(query = nil, params = {})
|
|
127
|
+
merged = params.dup
|
|
128
|
+
merged[:q] = query if query && !query.empty?
|
|
129
|
+
Page.new(get("/knowledge-base", merged))
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Fetches one article by slug, with its body as markdown.
|
|
133
|
+
def article(slug)
|
|
134
|
+
get("/knowledge-base/#{escape(slug)}")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Lists showroom locations, hours, and remote consultation options.
|
|
138
|
+
def showrooms
|
|
139
|
+
get("/showrooms")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# ----------------------------------------------------------------- writes
|
|
143
|
+
|
|
144
|
+
# Asks a jeweler to contact the customer. Requires the appointments:write
|
|
145
|
+
# scope (any API key). Sandbox keys return a well-formed response and
|
|
146
|
+
# contact nobody. Pass an +idempotency_key+ so a retried call cannot
|
|
147
|
+
# create two requests.
|
|
148
|
+
def request_consultation(payload, idempotency_key: nil)
|
|
149
|
+
headers = idempotency_key ? { "Idempotency-Key" => idempotency_key } : {}
|
|
150
|
+
request(:post, "/consultations", body: payload, headers: headers)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Issues a self-serve API key. +env+ is "sandbox" or "production". The key
|
|
154
|
+
# is returned once, in the api_key field; only a hash is stored.
|
|
155
|
+
def create_key(name, env: "sandbox")
|
|
156
|
+
post("/keys", { name: name, env: env })
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Runs up to 20 GET requests in one round trip via POST /api/v1/batch.
|
|
160
|
+
# Each request is a hash with id, method, path, and an optional query.
|
|
161
|
+
# The response carries one responses entry per request, in order, each
|
|
162
|
+
# with its own id, status, and body.
|
|
163
|
+
def batch(requests)
|
|
164
|
+
post("/batch", { requests: requests })
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# ------------------------------------------------------------------ nlweb
|
|
168
|
+
|
|
169
|
+
# Sends a natural-language question to the NLWeb /ask endpoint and returns
|
|
170
|
+
# the JSON answer (products and guides with citations).
|
|
171
|
+
def ask(query, params = {})
|
|
172
|
+
get("/ask", params.merge(query: query))
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# ---------------------------------------------------------------- generic
|
|
176
|
+
|
|
177
|
+
# GET any path relative to /api/v1 ("/diamonds") or absolute on the site
|
|
178
|
+
# ("/ask", "/api/acp/...") and decode the JSON response.
|
|
179
|
+
def get(path, params = {})
|
|
180
|
+
request(:get, path, params: params)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# POST a JSON body to any path and decode the JSON response.
|
|
184
|
+
def post(path, body = nil)
|
|
185
|
+
request(:post, path, body: body)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Sends one request. Returns the decoded JSON (nil for an empty body) or
|
|
189
|
+
# raises AdaDiamonds::Error for a non-2xx response.
|
|
190
|
+
def request(method, path, params: {}, body: nil, headers: {})
|
|
191
|
+
uri = URI(resolve(path))
|
|
192
|
+
query = encode_params(params)
|
|
193
|
+
uri.query = [uri.query, query].compact.reject(&:empty?).join("&") unless query.empty?
|
|
194
|
+
|
|
195
|
+
klass = method.to_s.upcase == "POST" ? Net::HTTP::Post : Net::HTTP::Get
|
|
196
|
+
req = klass.new(uri)
|
|
197
|
+
req["Accept"] = "application/json"
|
|
198
|
+
req["User-Agent"] = user_agent
|
|
199
|
+
req["Authorization"] = "Bearer #{api_key}" if api_key && !api_key.empty?
|
|
200
|
+
headers.each { |k, v| req[k] = v }
|
|
201
|
+
if body
|
|
202
|
+
req["Content-Type"] = "application/json"
|
|
203
|
+
req.body = JSON.generate(body)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
207
|
+
http.use_ssl = uri.scheme == "https"
|
|
208
|
+
http.open_timeout = timeout
|
|
209
|
+
http.read_timeout = timeout
|
|
210
|
+
res = http.request(req)
|
|
211
|
+
|
|
212
|
+
remember(res)
|
|
213
|
+
raw = res.body.to_s
|
|
214
|
+
status = res.code.to_i
|
|
215
|
+
raise decode_error(status, raw, res) unless (200..299).cover?(status)
|
|
216
|
+
return nil if status == 204 || raw.strip.empty?
|
|
217
|
+
|
|
218
|
+
JSON.parse(raw)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
private
|
|
222
|
+
|
|
223
|
+
def escape(value)
|
|
224
|
+
URI.encode_www_form_component(value.to_s).gsub("+", "%20")
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def resolve(path)
|
|
228
|
+
path = path.to_s
|
|
229
|
+
return path if path.start_with?("http://", "https://")
|
|
230
|
+
|
|
231
|
+
path = "/#{path}" unless path.start_with?("/")
|
|
232
|
+
bare = path.split("?", 2).first
|
|
233
|
+
return "#{base_url}#{path}" if path.start_with?("/api/") || ROOT_PATHS.include?(bare)
|
|
234
|
+
|
|
235
|
+
"#{base_url}/api/#{API_VERSION}#{path}"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Builds a query string with deterministic key order. Nil and empty-string
|
|
239
|
+
# values are skipped, so a caller can pass optional filters without pruning
|
|
240
|
+
# them first. Arrays are repeated as multiple values of the same key.
|
|
241
|
+
def encode_params(params)
|
|
242
|
+
return "" if params.nil? || params.empty?
|
|
243
|
+
|
|
244
|
+
pairs = []
|
|
245
|
+
params.keys.map(&:to_s).sort.each do |key|
|
|
246
|
+
value = params.fetch(key.to_sym) { params[key] }
|
|
247
|
+
Array(value).each do |item|
|
|
248
|
+
next if item.nil?
|
|
249
|
+
|
|
250
|
+
text = item.to_s
|
|
251
|
+
next if text.empty?
|
|
252
|
+
|
|
253
|
+
pairs << [key, text]
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
URI.encode_www_form(pairs)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def remember(res)
|
|
260
|
+
@last_headers = res.each_header.to_h
|
|
261
|
+
to_i = ->(name) { res[name].to_s.strip.to_i }
|
|
262
|
+
@last_rate_limit = RateLimit.new(
|
|
263
|
+
limit: to_i.call("RateLimit-Limit"),
|
|
264
|
+
remaining: to_i.call("RateLimit-Remaining"),
|
|
265
|
+
reset: to_i.call("RateLimit-Reset"),
|
|
266
|
+
policy: res["RateLimit-Policy"].to_s
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def decode_error(status, raw, res)
|
|
271
|
+
code = "http_#{status}"
|
|
272
|
+
description = ""
|
|
273
|
+
documentation_url = nil
|
|
274
|
+
retry_after = 0
|
|
275
|
+
body = {}
|
|
276
|
+
parsed = begin
|
|
277
|
+
raw.empty? ? nil : JSON.parse(raw)
|
|
278
|
+
rescue JSON::ParserError
|
|
279
|
+
nil
|
|
280
|
+
end
|
|
281
|
+
if parsed.is_a?(Hash)
|
|
282
|
+
body = parsed
|
|
283
|
+
code = parsed["error"] if parsed["error"].is_a?(String) && !parsed["error"].empty?
|
|
284
|
+
description = parsed["error_description"] if parsed["error_description"].is_a?(String)
|
|
285
|
+
documentation_url = parsed["documentation_url"] if parsed["documentation_url"].is_a?(String)
|
|
286
|
+
retry_after = parsed["retry_after_seconds"].to_i if parsed["retry_after_seconds"].is_a?(Numeric)
|
|
287
|
+
elsif !raw.empty?
|
|
288
|
+
description = raw[0, 500]
|
|
289
|
+
end
|
|
290
|
+
retry_after = res["Retry-After"].to_s.strip.to_i if retry_after.zero? && res["Retry-After"]
|
|
291
|
+
Error.new(status: status, code: code, description: description, documentation_url: documentation_url,
|
|
292
|
+
retry_after: retry_after, body: body)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
data/lib/adadiamonds.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Official Ruby client for the Ada Diamonds API: live lab grown diamond
|
|
4
|
+
# inventory, engagement ring settings, fine jewelry, showrooms, and the
|
|
5
|
+
# diamond buying guides.
|
|
6
|
+
#
|
|
7
|
+
# Reading the catalog needs no account and no API key (120 requests per
|
|
8
|
+
# minute). An API key or OAuth access token raises the limit to 600 and
|
|
9
|
+
# unlocks the write endpoints. Every price is in US dollars.
|
|
10
|
+
#
|
|
11
|
+
# client = AdaDiamonds::Client.new
|
|
12
|
+
# page = client.diamonds(shape: "Oval", min_carat: 1, max_price: 4000)
|
|
13
|
+
# page.data.each { |stone| puts [stone["carat"], stone["shape"], stone["price"]].join(" ") }
|
|
14
|
+
#
|
|
15
|
+
# Documentation: https://www.adadiamonds.com/developers
|
|
16
|
+
# OpenAPI: https://www.adadiamonds.com/openapi.json
|
|
17
|
+
require "json"
|
|
18
|
+
require "net/http"
|
|
19
|
+
require "uri"
|
|
20
|
+
|
|
21
|
+
require_relative "adadiamonds/version"
|
|
22
|
+
require_relative "adadiamonds/client"
|
metadata
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: adadiamonds
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ada Diamonds
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: minitest
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '5.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: webrick
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.7'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.7'
|
|
55
|
+
description: Search live lab grown diamond inventory, engagement ring settings, fine
|
|
56
|
+
jewelry, showrooms, and the diamond buying guides at adadiamonds.com. Standard library
|
|
57
|
+
only; no account or API key needed to read the catalog.
|
|
58
|
+
email:
|
|
59
|
+
- it@adadiamonds.com
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- CHANGELOG.md
|
|
65
|
+
- LICENSE
|
|
66
|
+
- README.md
|
|
67
|
+
- lib/adadiamonds.rb
|
|
68
|
+
- lib/adadiamonds/client.rb
|
|
69
|
+
- lib/adadiamonds/version.rb
|
|
70
|
+
homepage: https://www.adadiamonds.com/developers
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata:
|
|
74
|
+
homepage_uri: https://www.adadiamonds.com/developers
|
|
75
|
+
documentation_uri: https://www.adadiamonds.com/developers/cli
|
|
76
|
+
source_code_uri: https://github.com/adadiamonds/ada-ruby
|
|
77
|
+
changelog_uri: https://github.com/adadiamonds/ada-ruby/blob/main/CHANGELOG.md
|
|
78
|
+
bug_tracker_uri: https://github.com/adadiamonds/ada-ruby/issues
|
|
79
|
+
rubygems_mfa_required: 'true'
|
|
80
|
+
post_install_message:
|
|
81
|
+
rdoc_options: []
|
|
82
|
+
require_paths:
|
|
83
|
+
- lib
|
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '2.6'
|
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
requirements: []
|
|
95
|
+
rubygems_version: 3.0.3.1
|
|
96
|
+
signing_key:
|
|
97
|
+
specification_version: 4
|
|
98
|
+
summary: Official Ruby client for the Ada Diamonds API
|
|
99
|
+
test_files: []
|