sorted-travel 0.1.1
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/README.md +28 -0
- data/bin/sorted-travel +6 -0
- data/lib/sorted_travel/cli.rb +80 -0
- data/lib/sorted_travel/client.rb +164 -0
- data/lib/sorted_travel/version.rb +6 -0
- data/lib/sorted_travel.rb +8 -0
- metadata +54 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5d9bfa8d4533c0e406b5b22e215cd77fc14d56680f6ad5d6d70c680b02cc7ddb
|
|
4
|
+
data.tar.gz: 73b1eb43b52c5c3fca221ed04e67e074d7287bea6214134e737d6519e0bdf5fb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8a3cf8bb19139741a2dd313fcc0ef2f54a9cd0a25665913fc0173ddb1bdb07a93597ea10a3c081a436987c3fc9c725e9eac55671d0bc5c2d3853b7ddacc400bf
|
|
7
|
+
data.tar.gz: faae7eea35289cfcc448d065a2bf907545d32a28328c69e10f593098d7b64e631d153a7b4a657bf6c87597df308876a1729d763b244bfada050d8346880f1364
|
data/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# sorted-travel Ruby SDK
|
|
2
|
+
|
|
3
|
+
Official Ruby client and CLI for the [Sorted Travel](https://sorted.travel) REST API.
|
|
4
|
+
|
|
5
|
+
- Homepage: [https://sorted.travel](https://sorted.travel)
|
|
6
|
+
- Developer portal: [https://sorted.travel/developers](https://sorted.travel/developers)
|
|
7
|
+
- SDK guide: [https://sorted.travel/sdks.md](https://sorted.travel/sdks.md)
|
|
8
|
+
- Repository: [https://github.com/a-l-e-x-k/sorted-travel-sdk](https://github.com/a-l-e-x-k/sorted-travel-sdk)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
gem install sorted-travel
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
require "sorted_travel"
|
|
20
|
+
|
|
21
|
+
client = SortedTravel::Client.new
|
|
22
|
+
client.status
|
|
23
|
+
client.list_destinations(limit: 20)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Destination catalog, status, sandbox, and jobs are zero-auth. Pass `api_key:` or set `SORTED_TRAVEL_API_KEY` only when a host requires Bearer auth.
|
|
27
|
+
|
|
28
|
+
Interactive ranking, weather, and visa still go through MCP at `https://sorted.travel/mcp`.
|
data/bin/sorted-travel
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "optparse"
|
|
5
|
+
|
|
6
|
+
module SortedTravel
|
|
7
|
+
# Command-line entry point for the Sorted Travel REST SDK.
|
|
8
|
+
module CLI
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# Run the sorted-travel CLI and return an exit code.
|
|
12
|
+
def run(argv = ARGV)
|
|
13
|
+
options = { api_key: nil, base_url: nil }
|
|
14
|
+
parser = OptionParser.new do |opts|
|
|
15
|
+
opts.banner = "Usage: sorted-travel [options] COMMAND"
|
|
16
|
+
opts.on("--base-url URL", "API origin (default https://sorted.travel or SORTED_TRAVEL_BASE_URL)") do |value|
|
|
17
|
+
options[:base_url] = value
|
|
18
|
+
end
|
|
19
|
+
opts.on("--api-key KEY", "Optional Bearer token (default SORTED_TRAVEL_API_KEY)") do |value|
|
|
20
|
+
options[:api_key] = value
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
positional = parser.order(argv)
|
|
25
|
+
parser.parse!(argv)
|
|
26
|
+
command = positional.shift
|
|
27
|
+
if command.nil?
|
|
28
|
+
warn parser
|
|
29
|
+
return 2
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
client = Client.new(api_key: options[:api_key], base_url: options[:base_url])
|
|
33
|
+
|
|
34
|
+
case command
|
|
35
|
+
when "status"
|
|
36
|
+
print_json(client.status)
|
|
37
|
+
when "sandbox"
|
|
38
|
+
print_json(client.sandbox)
|
|
39
|
+
when "api-key"
|
|
40
|
+
print_json(client.create_api_key)
|
|
41
|
+
when "destinations"
|
|
42
|
+
destination_options = { cursor: nil, limit: nil }
|
|
43
|
+
OptionParser.new do |opts|
|
|
44
|
+
opts.on("--cursor VALUE") { |value| destination_options[:cursor] = value }
|
|
45
|
+
opts.on("--limit INTEGER", Integer) { |value| destination_options[:limit] = value }
|
|
46
|
+
end.parse!(positional)
|
|
47
|
+
print_json(client.list_destinations(**destination_options))
|
|
48
|
+
when "create-job"
|
|
49
|
+
operation = positional.shift
|
|
50
|
+
if operation.nil?
|
|
51
|
+
warn "missing operation"
|
|
52
|
+
return 2
|
|
53
|
+
end
|
|
54
|
+
job_options = {}
|
|
55
|
+
OptionParser.new do |opts|
|
|
56
|
+
opts.on("--limit INTEGER", Integer) { |value| job_options[:limit] = value }
|
|
57
|
+
end.parse!(positional)
|
|
58
|
+
print_json(client.create_job(operation, **job_options))
|
|
59
|
+
when "job"
|
|
60
|
+
job_id = positional.shift
|
|
61
|
+
if job_id.nil?
|
|
62
|
+
warn "missing job_id"
|
|
63
|
+
return 2
|
|
64
|
+
end
|
|
65
|
+
print_json(client.get_job(job_id))
|
|
66
|
+
else
|
|
67
|
+
warn "unknown command #{command}"
|
|
68
|
+
return 2
|
|
69
|
+
end
|
|
70
|
+
0
|
|
71
|
+
rescue OptionParser::ParseError => err
|
|
72
|
+
warn err.message
|
|
73
|
+
2
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def print_json(value)
|
|
77
|
+
puts JSON.pretty_generate(value)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module SortedTravel
|
|
8
|
+
# Base error for this SDK.
|
|
9
|
+
class SortedTravelError < StandardError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Returned for non-2xx HTTP responses.
|
|
13
|
+
class APIError < SortedTravelError
|
|
14
|
+
attr_reader :status, :body
|
|
15
|
+
|
|
16
|
+
# Store HTTP status and parsed response body.
|
|
17
|
+
def initialize(status, body)
|
|
18
|
+
@status = status
|
|
19
|
+
@body = body
|
|
20
|
+
super("HTTP #{status}: #{truncate_body(body)}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def truncate_body(value, limit = 300)
|
|
26
|
+
text = value.is_a?(String) ? value : JSON.generate(value)
|
|
27
|
+
return text if text.length <= limit
|
|
28
|
+
|
|
29
|
+
"#{text[0, limit - 1]}…"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Thin client for the Sorted Travel REST API.
|
|
34
|
+
class Client
|
|
35
|
+
DEFAULT_BASE_URL = "https://sorted.travel"
|
|
36
|
+
DEFAULT_TIMEOUT = 30.0
|
|
37
|
+
USER_AGENT = "sorted-travel-ruby/#{SortedTravel::VERSION} (+https://sorted.travel)"
|
|
38
|
+
|
|
39
|
+
# Build a client from options and optional environment variables.
|
|
40
|
+
def initialize(api_key: nil, base_url: nil, timeout: DEFAULT_TIMEOUT, transport: nil, env: ENV)
|
|
41
|
+
@api_key = api_key || env["SORTED_TRAVEL_API_KEY"]
|
|
42
|
+
configured_base = base_url || env["SORTED_TRAVEL_BASE_URL"] || DEFAULT_BASE_URL
|
|
43
|
+
@base_url = configured_base.sub(%r{/+\z}, "")
|
|
44
|
+
@timeout = timeout
|
|
45
|
+
@transport = transport || method(:default_transport)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# GET /api/v1/status.
|
|
49
|
+
def status
|
|
50
|
+
get("/api/v1/status")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# GET /sandbox.
|
|
54
|
+
def sandbox
|
|
55
|
+
get("/sandbox")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# GET /api/v1/destinations with cursor pagination.
|
|
59
|
+
def list_destinations(cursor: nil, limit: nil)
|
|
60
|
+
params = {}
|
|
61
|
+
params["cursor"] = cursor unless cursor.nil?
|
|
62
|
+
params["limit"] = limit unless limit.nil?
|
|
63
|
+
get("/api/v1/destinations", params)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# POST /api/v1/api-keys.
|
|
67
|
+
def create_api_key
|
|
68
|
+
post("/api/v1/api-keys", {})
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# POST /api/v1/jobs.
|
|
72
|
+
def create_job(operation, **body)
|
|
73
|
+
post("/api/v1/jobs", { operation: operation, **body })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# GET /api/v1/jobs/{job_id}.
|
|
77
|
+
def get_job(job_id)
|
|
78
|
+
encoded_job_id = URI.encode_www_form_component(job_id)
|
|
79
|
+
get("/api/v1/jobs/#{encoded_job_id}")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# GET a host-relative REST path.
|
|
83
|
+
def get(path, params = {})
|
|
84
|
+
request("GET", path, params: params)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# POST JSON to a host-relative REST path.
|
|
88
|
+
def post(path, body)
|
|
89
|
+
request("POST", path, body: body)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def request(method, path, params: {}, body: nil)
|
|
95
|
+
raise ArgumentError, "REST paths must start with '/'" unless path.start_with?("/")
|
|
96
|
+
|
|
97
|
+
url = @base_url + path
|
|
98
|
+
unless params.empty?
|
|
99
|
+
query = URI.encode_www_form(stringify_params(params))
|
|
100
|
+
url = "#{url}?#{query}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
headers = {
|
|
104
|
+
"User-Agent" => USER_AGENT,
|
|
105
|
+
"Accept" => "application/json",
|
|
106
|
+
}
|
|
107
|
+
encoded_body = nil
|
|
108
|
+
unless body.nil?
|
|
109
|
+
headers["Content-Type"] = "application/json"
|
|
110
|
+
encoded_body = JSON.generate(body)
|
|
111
|
+
end
|
|
112
|
+
headers["Authorization"] = "Bearer #{@api_key}" if @api_key
|
|
113
|
+
|
|
114
|
+
status_code, _content_type, text = @transport.call(
|
|
115
|
+
url: url,
|
|
116
|
+
method: method,
|
|
117
|
+
headers: headers,
|
|
118
|
+
body: encoded_body,
|
|
119
|
+
timeout: @timeout,
|
|
120
|
+
)
|
|
121
|
+
value = parse_body(text)
|
|
122
|
+
raise APIError.new(status_code, value) if status_code < 200 || status_code >= 300
|
|
123
|
+
|
|
124
|
+
value
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def stringify_params(params)
|
|
128
|
+
params.transform_values do |value|
|
|
129
|
+
case value
|
|
130
|
+
when true
|
|
131
|
+
"true"
|
|
132
|
+
when false
|
|
133
|
+
"false"
|
|
134
|
+
else
|
|
135
|
+
value.to_s
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_body(text)
|
|
141
|
+
return text if text.nil? || text.empty?
|
|
142
|
+
|
|
143
|
+
JSON.parse(text)
|
|
144
|
+
rescue JSON::ParserError
|
|
145
|
+
text
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def default_transport(url:, method:, headers:, body:, timeout:)
|
|
149
|
+
uri = URI(url)
|
|
150
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
151
|
+
http.use_ssl = uri.scheme == "https"
|
|
152
|
+
http.open_timeout = timeout
|
|
153
|
+
http.read_timeout = timeout
|
|
154
|
+
|
|
155
|
+
request_class = Net::HTTP.const_get(method.capitalize)
|
|
156
|
+
http_request = request_class.new(uri)
|
|
157
|
+
headers.each { |key, value| http_request[key] = value }
|
|
158
|
+
http_request.body = body if body
|
|
159
|
+
|
|
160
|
+
response = http.request(http_request)
|
|
161
|
+
[response.code.to_i, response["Content-Type"], response.body]
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sorted-travel
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sorted Travel
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-20 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Official Ruby SDK and CLI for the Sorted Travel REST API.
|
|
14
|
+
email:
|
|
15
|
+
- support@sorted.travel
|
|
16
|
+
executables:
|
|
17
|
+
- sorted-travel
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- README.md
|
|
22
|
+
- bin/sorted-travel
|
|
23
|
+
- lib/sorted_travel.rb
|
|
24
|
+
- lib/sorted_travel/cli.rb
|
|
25
|
+
- lib/sorted_travel/client.rb
|
|
26
|
+
- lib/sorted_travel/version.rb
|
|
27
|
+
homepage: https://sorted.travel
|
|
28
|
+
licenses:
|
|
29
|
+
- MIT
|
|
30
|
+
metadata:
|
|
31
|
+
homepage_uri: https://sorted.travel
|
|
32
|
+
source_code_uri: https://github.com/a-l-e-x-k/sorted-travel-sdk
|
|
33
|
+
documentation_uri: https://sorted.travel/sdks.md
|
|
34
|
+
rubygems_mfa_required: 'true'
|
|
35
|
+
post_install_message:
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
require_paths:
|
|
38
|
+
- lib
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: 3.1.0
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubygems_version: 3.5.22
|
|
51
|
+
signing_key:
|
|
52
|
+
specification_version: 4
|
|
53
|
+
summary: Official Ruby SDK and CLI for the Sorted Travel REST API.
|
|
54
|
+
test_files: []
|