answerlayer 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 +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/lib/answerlayer/authentication.rb +38 -0
- data/lib/answerlayer/client.rb +66 -0
- data/lib/answerlayer/configuration.rb +52 -0
- data/lib/answerlayer/errors.rb +63 -0
- data/lib/answerlayer/request.rb +144 -0
- data/lib/answerlayer/resources/base.rb +26 -0
- data/lib/answerlayer/resources/connections.rb +25 -0
- data/lib/answerlayer/resources/dashboards.rb +22 -0
- data/lib/answerlayer/resources/identity_broker.rb +19 -0
- data/lib/answerlayer/resources/inquiry.rb +25 -0
- data/lib/answerlayer/resources/query.rb +23 -0
- data/lib/answerlayer/resources/query_results.rb +13 -0
- data/lib/answerlayer/resources/saved_queries.rb +33 -0
- data/lib/answerlayer/resources/semantic.rb +57 -0
- data/lib/answerlayer/responses/api_response.rb +21 -0
- data/lib/answerlayer/responses/download_response.rb +15 -0
- data/lib/answerlayer/responses/response.rb +32 -0
- data/lib/answerlayer/responses/result_envelope.rb +25 -0
- data/lib/answerlayer/responses/sse_parser.rb +45 -0
- data/lib/answerlayer/responses/stream_event.rb +17 -0
- data/lib/answerlayer/responses.rb +8 -0
- data/lib/answerlayer/version.rb +5 -0
- data/lib/answerlayer.rb +37 -0
- metadata +100 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1a954237e60b4bdd0702fc444f5fbb6c4571785e1ea1a1c3d1195a9f72740ef7
|
|
4
|
+
data.tar.gz: 68ba2a049a0c86482e0727d73d33cff07d83773b2757c6223f928138fc466db3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8bac47e068a61c18255a7b89db7dd922696aba691f99b8c89bb643f5feff04ae9dfc93d39f294b5448c70427f597d4358270417a886543fe57a297d1deb3d999
|
|
7
|
+
data.tar.gz: a5d2b7f7b9ba225a27337eb87c493f5c79343eb57ea81ac1019ec6e50fb9f1c87ce364f8a8cd1374ccc59a9a36b95b8c3be7c97858c41b654c0eaddb8e3e7060
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Initial AnswerLayer Ruby SDK release.
|
|
6
|
+
- Added `AnswerLayer::Client` with resources for connections, query execution, inquiry sessions, saved queries, dashboards, query results, semantic layer, and identity broker.
|
|
7
|
+
- Added API-key configuration, default AnswerLayer API base URL, request timeouts, subject header support, and OAuth token exchange support.
|
|
8
|
+
- Added JSON request/response handling, downloads, server-sent event parsing, and lightweight response objects: `ApiResponse`, `ResultEnvelope`, `DownloadResponse`, and `StreamEvent`.
|
|
9
|
+
- Added status-aware SDK errors under `AnswerLayer::Error`, including `ApiError` subclasses with `status`, `body`, and `headers`.
|
|
10
|
+
- Added RSpec coverage using captured response fixtures for documented resource shapes.
|
|
11
|
+
- Added public README, resource, response, authentication, configuration, and error documentation.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AnswerLayer Maintainers
|
|
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,104 @@
|
|
|
1
|
+
# AnswerLayer Ruby SDK
|
|
2
|
+
|
|
3
|
+
The AnswerLayer Ruby SDK is a lightweight client for calling the AnswerLayer API from Ruby applications. It exposes one `AnswerLayer::Client` with resources for connections, query execution, inquiry sessions, saved queries, dashboards, query results, semantic layer, and identity broker.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby 3.1 or newer
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
With Bundler:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem "answerlayer"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then install:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bundle install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or install the gem directly:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
gem install answerlayer
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
Pass your API key directly:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
require "answerlayer"
|
|
35
|
+
|
|
36
|
+
client = AnswerLayer::Client.new(api_key: "your-api-key")
|
|
37
|
+
|
|
38
|
+
connections = client.connections.list
|
|
39
|
+
|
|
40
|
+
result = client.query.execute(
|
|
41
|
+
connection_id: "connection-id",
|
|
42
|
+
query: "SELECT 1 AS ok"
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
Pass your API key directly:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
client = AnswerLayer::Client.new(api_key: "your-api-key")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
See [docs/configuration.md](docs/configuration.md) for timeout, subject header, and advanced configuration options.
|
|
55
|
+
|
|
56
|
+
## Resources
|
|
57
|
+
|
|
58
|
+
Resources group related API methods under the client:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
client.connections
|
|
62
|
+
client.query
|
|
63
|
+
client.inquiry
|
|
64
|
+
client.saved_queries
|
|
65
|
+
client.dashboards
|
|
66
|
+
client.query_results
|
|
67
|
+
client.semantic
|
|
68
|
+
client.identity_broker
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
See [docs/resources.md](docs/resources.md) for method-level documentation.
|
|
72
|
+
|
|
73
|
+
## Responses
|
|
74
|
+
|
|
75
|
+
The SDK returns plain Ruby hashes/arrays for simple flexible responses and lightweight wrappers for common shapes:
|
|
76
|
+
|
|
77
|
+
- `AnswerLayer::ApiResponse`
|
|
78
|
+
- `AnswerLayer::ResultEnvelope`
|
|
79
|
+
- `AnswerLayer::DownloadResponse`
|
|
80
|
+
- `AnswerLayer::StreamEvent`
|
|
81
|
+
|
|
82
|
+
See [docs/responses.md](docs/responses.md).
|
|
83
|
+
|
|
84
|
+
## Errors
|
|
85
|
+
|
|
86
|
+
All SDK errors inherit from `AnswerLayer::Error`, so applications can rescue one base class for AnswerLayer failures:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
begin
|
|
90
|
+
client.query.validate(connection_id: "connection-id", query: "SELECT * FROM table")
|
|
91
|
+
rescue AnswerLayer::Error => error
|
|
92
|
+
warn error.message
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
HTTP response errors inherit from `AnswerLayer::ApiError` and expose `status`, `body`, and `headers`. Status-specific classes are available for common responses such as bad request, unauthorized, not found, rate limit, and server errors.
|
|
97
|
+
|
|
98
|
+
See [docs/errors.md](docs/errors.md).
|
|
99
|
+
|
|
100
|
+
## Examples
|
|
101
|
+
|
|
102
|
+
Examples under `examples/` show common SDK usage:
|
|
103
|
+
|
|
104
|
+
- [examples/basic_usage.rb](examples/basic_usage.rb)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class Authentication
|
|
5
|
+
def initialize(configuration)
|
|
6
|
+
@configuration = configuration
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def apply(headers, mode: :api_key, subject: false)
|
|
10
|
+
@configuration.validate!(auth_mode: mode)
|
|
11
|
+
authenticated = headers.dup
|
|
12
|
+
|
|
13
|
+
case mode
|
|
14
|
+
when :api_key, :oauth
|
|
15
|
+
authenticated["X-API-Key"] = @configuration.api_key
|
|
16
|
+
when :bearer
|
|
17
|
+
authenticated["Authorization"] = "Bearer #{@configuration.bearer_token}"
|
|
18
|
+
when :none
|
|
19
|
+
authenticated
|
|
20
|
+
else
|
|
21
|
+
raise ConfigurationError, "unsupported auth mode: #{mode}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
apply_subject_headers(authenticated) if subject && mode != :bearer
|
|
25
|
+
authenticated
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
def apply_subject_headers(headers)
|
|
30
|
+
headers["X-Subject-Org-ID"] = @configuration.subject_org_id if present?(@configuration.subject_org_id)
|
|
31
|
+
headers["X-Subject-User-ID"] = @configuration.subject_user_id if present?(@configuration.subject_user_id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def present?(value)
|
|
35
|
+
!value.nil? && !value.to_s.strip.empty?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class Client
|
|
5
|
+
DEFAULT_BASE_URL = Configuration::DEFAULT_BASE_URL
|
|
6
|
+
|
|
7
|
+
attr_reader :configuration
|
|
8
|
+
|
|
9
|
+
def initialize(api_key: nil, bearer_token: nil, subject_org_id: nil, subject_user_id: nil, base_url: DEFAULT_BASE_URL, open_timeout: 10, read_timeout: 30, configuration: nil)
|
|
10
|
+
@configuration = configuration || Configuration.new(
|
|
11
|
+
api_key: api_key,
|
|
12
|
+
bearer_token: bearer_token,
|
|
13
|
+
subject_org_id: subject_org_id,
|
|
14
|
+
subject_user_id: subject_user_id,
|
|
15
|
+
base_url: base_url,
|
|
16
|
+
open_timeout: open_timeout,
|
|
17
|
+
read_timeout: read_timeout
|
|
18
|
+
)
|
|
19
|
+
@request = Request.new(configuration: @configuration)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get(path, params: nil)
|
|
23
|
+
request(method: :get, path: path, params: params)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def post(path, body: nil)
|
|
27
|
+
request(method: :post, path: path, body: body)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def request(method:, path:, params: nil, body: nil, headers: {}, auth: :api_key, subject: false, form: nil, multipart: nil, download: false)
|
|
31
|
+
@request.call(method: method, path: path, params: params, body: body, headers: headers, auth: auth, subject: subject, form: form, multipart: multipart, download: download)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def connections
|
|
35
|
+
@connections ||= ConnectionsResource.new(self)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def query
|
|
39
|
+
@query ||= QueryResource.new(self)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def inquiry
|
|
43
|
+
@inquiry ||= InquiryResource.new(self)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def saved_queries
|
|
47
|
+
@saved_queries ||= SavedQueriesResource.new(self)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def dashboards
|
|
51
|
+
@dashboards ||= DashboardsResource.new(self)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def query_results
|
|
55
|
+
@query_results ||= QueryResultsResource.new(self)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def semantic
|
|
59
|
+
@semantic ||= SemanticResource.new(self)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def identity_broker
|
|
63
|
+
@identity_broker ||= IdentityBrokerResource.new(self)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class Configuration
|
|
5
|
+
DEFAULT_BASE_URL = "https://app.answerlayer.io/api/v1"
|
|
6
|
+
|
|
7
|
+
attr_accessor :api_key, :bearer_token, :subject_org_id, :subject_user_id, :base_url, :open_timeout, :read_timeout
|
|
8
|
+
|
|
9
|
+
def initialize(api_key: nil, bearer_token: nil, subject_org_id: nil, subject_user_id: nil, base_url: nil, open_timeout: 10, read_timeout: 30)
|
|
10
|
+
@api_key = explicit_or_env(api_key, "ANSWERLAYER_API_KEY")
|
|
11
|
+
@bearer_token = explicit_or_env(bearer_token, "ANSWERLAYER_BEARER_TOKEN")
|
|
12
|
+
@subject_org_id = subject_org_id
|
|
13
|
+
@subject_user_id = subject_user_id
|
|
14
|
+
@base_url = base_url || DEFAULT_BASE_URL
|
|
15
|
+
@open_timeout = open_timeout
|
|
16
|
+
@read_timeout = read_timeout
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def validate!(auth_mode: :api_key)
|
|
20
|
+
raise ConfigurationError, "base_url is required" if blank?(base_url)
|
|
21
|
+
case auth_mode
|
|
22
|
+
when :api_key, :oauth
|
|
23
|
+
raise ConfigurationError, "api_key is required" if blank?(api_key)
|
|
24
|
+
when :bearer
|
|
25
|
+
raise ConfigurationError, "bearer_token is required" if blank?(bearer_token)
|
|
26
|
+
when :none
|
|
27
|
+
true
|
|
28
|
+
else
|
|
29
|
+
raise ConfigurationError, "unsupported auth mode: #{auth_mode}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def base_uri
|
|
36
|
+
URI(base_url)
|
|
37
|
+
rescue URI::InvalidURIError
|
|
38
|
+
raise ConfigurationError, "base_url must be a valid URL"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def explicit_or_env(value, env_key)
|
|
43
|
+
return value unless blank?(value)
|
|
44
|
+
|
|
45
|
+
ENV[env_key]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def blank?(value)
|
|
49
|
+
value.nil? || value.to_s.strip.empty?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
# Local SDK errors.
|
|
7
|
+
class ConfigurationError < Error; end
|
|
8
|
+
|
|
9
|
+
class RequestError < Error; end
|
|
10
|
+
|
|
11
|
+
class ResponseError < Error; end
|
|
12
|
+
|
|
13
|
+
# HTTP API errors.
|
|
14
|
+
class ApiError < Error
|
|
15
|
+
attr_reader :status, :body, :headers
|
|
16
|
+
|
|
17
|
+
def initialize(message, status:, body: nil, headers: nil)
|
|
18
|
+
@status = status
|
|
19
|
+
@body = body
|
|
20
|
+
@headers = headers
|
|
21
|
+
super(message)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class BadRequestError < ApiError; end
|
|
26
|
+
|
|
27
|
+
class UnauthorizedError < ApiError; end
|
|
28
|
+
|
|
29
|
+
class ForbiddenError < ApiError; end
|
|
30
|
+
|
|
31
|
+
class NotFoundError < ApiError; end
|
|
32
|
+
|
|
33
|
+
class ConflictError < ApiError; end
|
|
34
|
+
|
|
35
|
+
class GoneError < ApiError; end
|
|
36
|
+
|
|
37
|
+
class UnprocessableEntityError < ApiError; end
|
|
38
|
+
|
|
39
|
+
class RateLimitError < ApiError; end
|
|
40
|
+
|
|
41
|
+
class ServerError < ApiError; end
|
|
42
|
+
|
|
43
|
+
# OAuth-shaped API errors.
|
|
44
|
+
class OAuthError < ApiError
|
|
45
|
+
attr_reader :error, :error_description
|
|
46
|
+
|
|
47
|
+
def initialize(message, error: nil, error_description: nil, status: nil, body: nil, headers: nil)
|
|
48
|
+
@error = error
|
|
49
|
+
@error_description = error_description
|
|
50
|
+
super(message, status: status, body: body, headers: headers)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Server-sent event stream errors.
|
|
55
|
+
class StreamError < Error
|
|
56
|
+
attr_reader :event
|
|
57
|
+
|
|
58
|
+
def initialize(message, event: nil)
|
|
59
|
+
@event = event
|
|
60
|
+
super(message)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module AnswerLayer
|
|
8
|
+
class Request
|
|
9
|
+
DEFAULT_HEADERS = {
|
|
10
|
+
"Accept" => "application/json",
|
|
11
|
+
"Content-Type" => "application/json"
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(configuration:, authentication: Authentication.new(configuration))
|
|
15
|
+
@configuration = configuration
|
|
16
|
+
@authentication = authentication
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
FORM_HEADERS = {
|
|
20
|
+
"Accept" => "application/json",
|
|
21
|
+
"Content-Type" => "application/x-www-form-urlencoded"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
MULTIPART_HEADERS = {
|
|
25
|
+
"Accept" => "application/json",
|
|
26
|
+
"Content-Type" => "multipart/form-data"
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
def call(method:, path:, params: nil, body: nil, headers: {}, auth: :api_key, subject: false, form: nil, multipart: nil, download: false)
|
|
30
|
+
uri = build_uri(path, params)
|
|
31
|
+
request = build_request(method, uri, body, headers: headers, auth: auth, subject: subject, form: form, multipart: multipart)
|
|
32
|
+
response = perform(uri, request)
|
|
33
|
+
parsed_response(response, download: download)
|
|
34
|
+
rescue Timeout::Error, Errno::ECONNREFUSED, SocketError => error
|
|
35
|
+
raise RequestError, "request failed: #{error.message}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def build_uri(path, params)
|
|
40
|
+
base = @configuration.base_uri
|
|
41
|
+
uri = base.dup
|
|
42
|
+
request_path = path.to_s.sub(%r{\A/}, "")
|
|
43
|
+
uri.path = if path.to_s.start_with?("/.well-known")
|
|
44
|
+
path.to_s
|
|
45
|
+
else
|
|
46
|
+
base_path = base.path.to_s.sub(%r{/\z}, "")
|
|
47
|
+
[base_path, request_path].reject(&:empty?).join("/")
|
|
48
|
+
end
|
|
49
|
+
uri.query = URI.encode_www_form(params) if params && !params.empty?
|
|
50
|
+
uri
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_request(method, uri, body, headers:, auth:, subject:, form:, multipart:)
|
|
54
|
+
request_class = request_class_for(method)
|
|
55
|
+
request = request_class.new(uri)
|
|
56
|
+
base_headers = if form
|
|
57
|
+
FORM_HEADERS
|
|
58
|
+
elsif multipart
|
|
59
|
+
MULTIPART_HEADERS
|
|
60
|
+
else
|
|
61
|
+
DEFAULT_HEADERS
|
|
62
|
+
end
|
|
63
|
+
@authentication.apply(base_headers.merge(headers), mode: auth, subject: subject).each { |key, value| request[key] = value }
|
|
64
|
+
request.body = URI.encode_www_form(form) if form
|
|
65
|
+
request.body = multipart.inspect if multipart
|
|
66
|
+
request.body = JSON.generate(body) unless body.nil? || form
|
|
67
|
+
request
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def request_class_for(method)
|
|
71
|
+
case method.to_s.downcase
|
|
72
|
+
when "get" then Net::HTTP::Get
|
|
73
|
+
when "post" then Net::HTTP::Post
|
|
74
|
+
when "put" then Net::HTTP::Put
|
|
75
|
+
when "patch" then Net::HTTP::Patch
|
|
76
|
+
when "delete" then Net::HTTP::Delete
|
|
77
|
+
else
|
|
78
|
+
raise RequestError, "unsupported HTTP method: #{method}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def perform(uri, request)
|
|
83
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https", open_timeout: @configuration.open_timeout, read_timeout: @configuration.read_timeout) do |http|
|
|
84
|
+
http.request(request)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def parsed_response(raw_response, download:)
|
|
89
|
+
response = Response.new(status: raw_response.code, headers: raw_response.to_hash, body: raw_response.body)
|
|
90
|
+
raise_error_for(response) unless response.success?
|
|
91
|
+
return download_response(response) if download && !response.json?
|
|
92
|
+
|
|
93
|
+
response.parsed_body
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def download_response(response)
|
|
97
|
+
DownloadResponse.new(
|
|
98
|
+
body: response.body,
|
|
99
|
+
content_type: Array(response.headers["content-type"]).first,
|
|
100
|
+
filename: filename_from(response.headers),
|
|
101
|
+
status: response.status,
|
|
102
|
+
headers: response.headers
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def filename_from(headers)
|
|
107
|
+
disposition = Array(headers["content-disposition"]).first
|
|
108
|
+
disposition&.match(/filename="?([^";]+)"?/) { |match| match[1] }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def raise_error_for(response)
|
|
112
|
+
parsed = response.json? ? safe_parse(response) : nil
|
|
113
|
+
message = parsed && (parsed["detail"] || parsed["error_description"] || parsed["error"])
|
|
114
|
+
message ||= "request failed with status #{response.status}"
|
|
115
|
+
|
|
116
|
+
if parsed && parsed["error"]
|
|
117
|
+
raise OAuthError.new(message, error: parsed["error"], error_description: parsed["error_description"], status: response.status, body: response.body, headers: response.headers)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
error_class = case response.status
|
|
121
|
+
when 400 then BadRequestError
|
|
122
|
+
when 401 then UnauthorizedError
|
|
123
|
+
when 403 then ForbiddenError
|
|
124
|
+
when 404 then NotFoundError
|
|
125
|
+
when 409 then ConflictError
|
|
126
|
+
when 410 then GoneError
|
|
127
|
+
when 422 then UnprocessableEntityError
|
|
128
|
+
when 429 then RateLimitError
|
|
129
|
+
when 500..599 then ServerError
|
|
130
|
+
else RequestError
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
raise RequestError, message if error_class == RequestError
|
|
134
|
+
|
|
135
|
+
raise error_class.new(message, status: response.status, body: response.body, headers: response.headers)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def safe_parse(response)
|
|
139
|
+
response.parsed_body
|
|
140
|
+
rescue ResponseError
|
|
141
|
+
nil
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class Resource
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
def request(**kwargs)
|
|
11
|
+
@client.request(**kwargs)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_api_response(data)
|
|
15
|
+
data.is_a?(ApiResponse) ? data : ApiResponse.new(data || {})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_result_envelope(data)
|
|
19
|
+
data.is_a?(ResultEnvelope) ? data : ResultEnvelope.new(data || {})
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def compact(hash)
|
|
23
|
+
hash.reject { |_key, value| value.nil? }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class ConnectionsResource < Resource
|
|
5
|
+
def list
|
|
6
|
+
request(method: :get, path: "/connections/")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_existing(connection_id:)
|
|
10
|
+
to_api_response(request(method: :post, path: "/connections/#{connection_id}/test_existing"))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def schema(connection_id:)
|
|
14
|
+
request(method: :get, path: "/connections/#{connection_id}/schema")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def upload_csv(file:, name: nil, has_header: nil, delimiter: nil)
|
|
18
|
+
request(method: :post, path: "/csv/upload", multipart: compact(file: file, name: name, has_header: has_header, delimiter: delimiter))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def upload_duckdb(file:, name: nil)
|
|
22
|
+
request(method: :post, path: "/duckdb/upload", multipart: compact(file: file, name: name))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class DashboardsResource < Resource
|
|
5
|
+
def manifest(dashboard_id:)
|
|
6
|
+
request(method: :get, path: "/dashboards/#{dashboard_id}/manifest", subject: true)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def tile_data(dashboard_id:, tile_id:, filters: nil, params: nil, pagination: nil, result_handle: nil)
|
|
10
|
+
to_result_envelope(request(method: :post, path: "/dashboards/#{dashboard_id}/tiles/#{tile_id}/data", body: compact(filters: filters, params: params, pagination: pagination, result_handle: result_handle), subject: true))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def parameters(dashboard_id:, tile_id:)
|
|
14
|
+
to_api_response(request(method: :get, path: "/dashboards/#{dashboard_id}/tiles/#{tile_id}/parameters", subject: true))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update_parameters(dashboard_id:, tile_id:, values:, subject_org_id: nil)
|
|
18
|
+
headers = subject_org_id ? { "X-Subject-Org-ID" => subject_org_id } : {}
|
|
19
|
+
to_api_response(request(method: :put, path: "/dashboards/#{dashboard_id}/tiles/#{tile_id}/parameters", body: { values: values }, headers: headers, subject: true))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class IdentityBrokerResource < Resource
|
|
5
|
+
SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt"
|
|
6
|
+
|
|
7
|
+
def exchange_token(subject_token:, subject_token_type: SUBJECT_TOKEN_TYPE)
|
|
8
|
+
to_api_response(request(method: :post, path: "/oauth/token", auth: :oauth, form: {
|
|
9
|
+
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
10
|
+
subject_token: subject_token,
|
|
11
|
+
subject_token_type: subject_token_type
|
|
12
|
+
}))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def jwks
|
|
16
|
+
to_api_response(request(method: :get, path: "/.well-known/jwks.json", auth: :none))
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class InquiryResource < Resource
|
|
5
|
+
def create_session(connection_id:, model: nil)
|
|
6
|
+
to_api_response(request(method: :post, path: "/inquiry/sessions", body: compact(connection_id: connection_id, model: model)))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def list_sessions
|
|
10
|
+
request(method: :get, path: "/inquiry/sessions")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def session(session_id:)
|
|
14
|
+
request(method: :get, path: "/inquiry/sessions/#{session_id}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def turn_stream(session_id:, user_input:)
|
|
18
|
+
request(method: :post, path: "/inquiry/sessions/#{session_id}", body: { user_input: user_input })
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def turn_sync(session_id:, user_input:)
|
|
22
|
+
to_api_response(request(method: :post, path: "/inquiry/sessions/#{session_id}/sync", body: { user_input: user_input }))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class QueryResource < Resource
|
|
5
|
+
def execute(connection_id:, query:, params: nil, row_limit: nil, timeout: nil)
|
|
6
|
+
request(method: :post, path: "/query/#{connection_id}", body: compact(query: query, params: params, row_limit: row_limit, timeout: timeout))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def validate(connection_id:, query:)
|
|
10
|
+
to_api_response(request(method: :post, path: "/query/#{connection_id}/validate", body: { query: query }))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def export(connection_id:, query:, format: :csv, params: nil, row_limit: nil, timeout: nil)
|
|
14
|
+
request(
|
|
15
|
+
method: :post,
|
|
16
|
+
path: "/query/#{connection_id}/export",
|
|
17
|
+
params: { format: format },
|
|
18
|
+
body: compact(query: query, params: params, row_limit: row_limit, timeout: timeout),
|
|
19
|
+
download: true
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class QueryResultsResource < Resource
|
|
5
|
+
def get(handle:, cursor: nil, limit: nil)
|
|
6
|
+
to_result_envelope(request(method: :get, path: "/query-results/#{handle}", params: compact(cursor: cursor, limit: limit)))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def release(handle:)
|
|
10
|
+
request(method: :delete, path: "/query-results/#{handle}")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class SavedQueriesResource < Resource
|
|
5
|
+
def list
|
|
6
|
+
to_api_response(request(method: :get, path: "/saved-queries"))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create(name:, sql:, connection_id:, description: nil, visibility: nil)
|
|
10
|
+
to_api_response(request(method: :post, path: "/saved-queries", body: compact(name: name, sql: sql, connection_id: connection_id, description: description, visibility: visibility)))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get(saved_query_id:)
|
|
14
|
+
to_api_response(request(method: :get, path: "/saved-queries/#{saved_query_id}"))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def execute(saved_query_id:, params: nil, row_limit: nil, timeout: nil)
|
|
18
|
+
to_result_envelope(request(method: :post, path: "/saved-queries/#{saved_query_id}/execute", body: compact(params: params, row_limit: row_limit, timeout: timeout)))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def update(saved_query_id:, **attributes)
|
|
22
|
+
to_api_response(request(method: :patch, path: "/saved-queries/#{saved_query_id}", body: attributes))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def delete(saved_query_id:)
|
|
26
|
+
request(method: :delete, path: "/saved-queries/#{saved_query_id}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create_from_inquiry_turn(inquiry_turn_id:, name:, description: nil, visibility: nil)
|
|
30
|
+
to_api_response(request(method: :post, path: "/saved-queries/from-inquiry-turn", body: compact(inquiry_turn_id: inquiry_turn_id, name: name, description: description, visibility: visibility)))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class SemanticResource < Resource
|
|
5
|
+
def components(component:, connection_id:)
|
|
6
|
+
to_api_response(request(method: :get, path: "/semantic/#{component}", params: { connection_id: connection_id }))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def jobs
|
|
10
|
+
to_api_response(request(method: :get, path: "/semantic/jobs"))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create_component(component:, attributes:)
|
|
14
|
+
to_api_response(request(method: :post, path: "/semantic/#{component}", body: attributes))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_component(component:, id:)
|
|
18
|
+
to_api_response(request(method: :get, path: "/semantic/#{component}/#{id}"))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def update_component(component:, id:, attributes:)
|
|
22
|
+
to_api_response(request(method: :put, path: "/semantic/#{component}/#{id}", body: attributes))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def delete_component(component:, id:)
|
|
26
|
+
request(method: :delete, path: "/semantic/#{component}/#{id}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def generate_stream(component:, connection_id:, prompt: nil)
|
|
30
|
+
request(method: :post, path: "/semantic/#{component}/generate/stream", body: compact(connection_id: connection_id, prompt: prompt))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_job(connection_id:, component_type:, prompt: nil)
|
|
34
|
+
to_api_response(request(method: :post, path: "/semantic/jobs", body: compact(connection_id: connection_id, component_type: component_type, prompt: prompt)))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def job_stream(job_id:)
|
|
38
|
+
request(method: :get, path: "/semantic/jobs/#{job_id}/stream")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def job_questions(job_id:)
|
|
42
|
+
to_api_response(request(method: :get, path: "/semantic/jobs/#{job_id}/questions"))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def submit_guidance(job_id:, responses:)
|
|
46
|
+
to_api_response(request(method: :post, path: "/semantic/jobs/#{job_id}/guidance", body: { responses: responses }))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def job_status(job_id:)
|
|
50
|
+
to_api_response(request(method: :get, path: "/semantic/jobs/#{job_id}/status"))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def cancel_job(job_id:)
|
|
54
|
+
to_api_response(request(method: :post, path: "/semantic/jobs/#{job_id}/cancel"))
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class ApiResponse
|
|
5
|
+
attr_reader :data, :status, :headers
|
|
6
|
+
|
|
7
|
+
def initialize(data = {}, status: nil, headers: {})
|
|
8
|
+
@data = data
|
|
9
|
+
@status = status
|
|
10
|
+
@headers = headers
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def [](key)
|
|
14
|
+
data[key.to_s] || data[key.to_sym]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_h
|
|
18
|
+
data
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class DownloadResponse
|
|
5
|
+
attr_reader :body, :content_type, :filename, :status, :headers
|
|
6
|
+
|
|
7
|
+
def initialize(body:, content_type: nil, filename: nil, status: nil, headers: {})
|
|
8
|
+
@body = body
|
|
9
|
+
@content_type = content_type
|
|
10
|
+
@filename = filename
|
|
11
|
+
@status = status
|
|
12
|
+
@headers = headers
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module AnswerLayer
|
|
6
|
+
class Response
|
|
7
|
+
attr_reader :status, :headers, :body
|
|
8
|
+
|
|
9
|
+
def initialize(status:, headers:, body:)
|
|
10
|
+
@status = status.to_i
|
|
11
|
+
@headers = headers
|
|
12
|
+
@body = body
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def success?
|
|
16
|
+
status.between?(200, 299)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def json?
|
|
20
|
+
content_type = Array(headers["content-type"] || headers["Content-Type"]).join(";")
|
|
21
|
+
content_type.include?("json") || body.to_s.strip.start_with?("{", "[")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parsed_body
|
|
25
|
+
return nil if body.nil? || body.empty?
|
|
26
|
+
|
|
27
|
+
JSON.parse(body)
|
|
28
|
+
rescue JSON::ParserError => error
|
|
29
|
+
raise ResponseError, "response body was not valid JSON: #{error.message}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class ResultEnvelope < ApiResponse
|
|
5
|
+
def columns
|
|
6
|
+
self["columns"] || []
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def rows
|
|
10
|
+
self["rows"] || []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def next_cursor
|
|
14
|
+
self["next_cursor"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def result_handle
|
|
18
|
+
self["result_handle"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def has_next_page?
|
|
22
|
+
!next_cursor.nil? && !next_cursor.to_s.empty?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module AnswerLayer
|
|
6
|
+
class SSEParser
|
|
7
|
+
def self.parse(stream)
|
|
8
|
+
events = []
|
|
9
|
+
current_type = "message"
|
|
10
|
+
data_lines = []
|
|
11
|
+
|
|
12
|
+
stream.each_line do |line|
|
|
13
|
+
line = line.chomp
|
|
14
|
+
if line.empty?
|
|
15
|
+
events << build_event(current_type, data_lines.join("\n")) unless data_lines.empty?
|
|
16
|
+
current_type = "message"
|
|
17
|
+
data_lines = []
|
|
18
|
+
next
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
field, value = line.split(":", 2)
|
|
22
|
+
value = value ? value.sub(/\A /, "") : ""
|
|
23
|
+
case field
|
|
24
|
+
when "event"
|
|
25
|
+
current_type = value
|
|
26
|
+
when "data"
|
|
27
|
+
data_lines << value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
events << build_event(current_type, data_lines.join("\n")) unless data_lines.empty?
|
|
32
|
+
events
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.build_event(type, raw_data)
|
|
36
|
+
data = JSON.parse(raw_data)
|
|
37
|
+
event = StreamEvent.new(type: type, data: data, raw: raw_data)
|
|
38
|
+
raise StreamError.new(data["message"] || data["error"] || "stream error", event: event) if event.error?
|
|
39
|
+
|
|
40
|
+
event
|
|
41
|
+
rescue JSON::ParserError
|
|
42
|
+
StreamEvent.new(type: type, data: raw_data, raw: raw_data)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnswerLayer
|
|
4
|
+
class StreamEvent
|
|
5
|
+
attr_reader :type, :data, :raw
|
|
6
|
+
|
|
7
|
+
def initialize(type:, data:, raw: nil)
|
|
8
|
+
@type = type.to_s
|
|
9
|
+
@data = data
|
|
10
|
+
@raw = raw
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def error?
|
|
14
|
+
type == "error"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "responses/response"
|
|
4
|
+
require_relative "responses/api_response"
|
|
5
|
+
require_relative "responses/result_envelope"
|
|
6
|
+
require_relative "responses/download_response"
|
|
7
|
+
require_relative "responses/stream_event"
|
|
8
|
+
require_relative "responses/sse_parser"
|
data/lib/answerlayer.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "answerlayer/version"
|
|
4
|
+
require_relative "answerlayer/errors"
|
|
5
|
+
require_relative "answerlayer/configuration"
|
|
6
|
+
require_relative "answerlayer/authentication"
|
|
7
|
+
require_relative "answerlayer/responses"
|
|
8
|
+
require_relative "answerlayer/request"
|
|
9
|
+
require_relative "answerlayer/resources/base"
|
|
10
|
+
require_relative "answerlayer/resources/connections"
|
|
11
|
+
require_relative "answerlayer/resources/query"
|
|
12
|
+
require_relative "answerlayer/resources/inquiry"
|
|
13
|
+
require_relative "answerlayer/resources/saved_queries"
|
|
14
|
+
require_relative "answerlayer/resources/dashboards"
|
|
15
|
+
require_relative "answerlayer/resources/query_results"
|
|
16
|
+
require_relative "answerlayer/resources/semantic"
|
|
17
|
+
require_relative "answerlayer/resources/identity_broker"
|
|
18
|
+
require_relative "answerlayer/client"
|
|
19
|
+
|
|
20
|
+
module AnswerLayer
|
|
21
|
+
class << self
|
|
22
|
+
attr_writer :configuration
|
|
23
|
+
|
|
24
|
+
def configuration
|
|
25
|
+
@configuration ||= Configuration.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def configure
|
|
29
|
+
yield configuration
|
|
30
|
+
configuration
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def client
|
|
34
|
+
Client.new(configuration: configuration)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: answerlayer
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- AnswerLayer Maintainers
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rake
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '13.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '13.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.13'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.13'
|
|
40
|
+
description: A dependency-light Ruby client for AnswerLayer connections, queries,
|
|
41
|
+
inquiry sessions, saved queries, dashboards, query results, semantic-layer APIs,
|
|
42
|
+
and identity broker APIs.
|
|
43
|
+
email:
|
|
44
|
+
- maintainers@example.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- CHANGELOG.md
|
|
50
|
+
- LICENSE.txt
|
|
51
|
+
- README.md
|
|
52
|
+
- lib/answerlayer.rb
|
|
53
|
+
- lib/answerlayer/authentication.rb
|
|
54
|
+
- lib/answerlayer/client.rb
|
|
55
|
+
- lib/answerlayer/configuration.rb
|
|
56
|
+
- lib/answerlayer/errors.rb
|
|
57
|
+
- lib/answerlayer/request.rb
|
|
58
|
+
- lib/answerlayer/resources/base.rb
|
|
59
|
+
- lib/answerlayer/resources/connections.rb
|
|
60
|
+
- lib/answerlayer/resources/dashboards.rb
|
|
61
|
+
- lib/answerlayer/resources/identity_broker.rb
|
|
62
|
+
- lib/answerlayer/resources/inquiry.rb
|
|
63
|
+
- lib/answerlayer/resources/query.rb
|
|
64
|
+
- lib/answerlayer/resources/query_results.rb
|
|
65
|
+
- lib/answerlayer/resources/saved_queries.rb
|
|
66
|
+
- lib/answerlayer/resources/semantic.rb
|
|
67
|
+
- lib/answerlayer/responses.rb
|
|
68
|
+
- lib/answerlayer/responses/api_response.rb
|
|
69
|
+
- lib/answerlayer/responses/download_response.rb
|
|
70
|
+
- lib/answerlayer/responses/response.rb
|
|
71
|
+
- lib/answerlayer/responses/result_envelope.rb
|
|
72
|
+
- lib/answerlayer/responses/sse_parser.rb
|
|
73
|
+
- lib/answerlayer/responses/stream_event.rb
|
|
74
|
+
- lib/answerlayer/version.rb
|
|
75
|
+
homepage: https://github.com/ironguild/answerlayer-ruby
|
|
76
|
+
licenses:
|
|
77
|
+
- MIT
|
|
78
|
+
metadata:
|
|
79
|
+
homepage_uri: https://github.com/ironguild/answerlayer-ruby
|
|
80
|
+
source_code_uri: https://github.com/ironguild/answerlayer-ruby
|
|
81
|
+
changelog_uri: https://github.com/ironguild/answerlayer-ruby/blob/main/CHANGELOG.md
|
|
82
|
+
rubygems_mfa_required: 'true'
|
|
83
|
+
rdoc_options: []
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '3.1'
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
requirements: []
|
|
97
|
+
rubygems_version: 3.6.9
|
|
98
|
+
specification_version: 4
|
|
99
|
+
summary: Ruby API wrapper for the AnswerLayer API.
|
|
100
|
+
test_files: []
|