data_nexus 0.2.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/.github/workflows/ci.yml +43 -0
- data/.mise.local.toml.example +14 -0
- data/.mise.toml +36 -0
- data/.rubocop.yml +28 -0
- data/README.md +268 -0
- data/Rakefile +8 -0
- data/lib/data_nexus/client.rb +103 -0
- data/lib/data_nexus/collection.rb +120 -0
- data/lib/data_nexus/configuration.rb +74 -0
- data/lib/data_nexus/connection.rb +176 -0
- data/lib/data_nexus/errors.rb +78 -0
- data/lib/data_nexus/resources/member_consents.rb +125 -0
- data/lib/data_nexus/resources/member_enrollments.rb +122 -0
- data/lib/data_nexus/resources/members.rb +117 -0
- data/lib/data_nexus/resources/program_member.rb +134 -0
- data/lib/data_nexus/resources/program_members.rb +78 -0
- data/lib/data_nexus/resources/programs.rb +146 -0
- data/lib/data_nexus/version.rb +5 -0
- data/lib/data_nexus.rb +52 -0
- data/sig/datanexus.rbs +4 -0
- metadata +95 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3a77e46a3dbaca91a51fa4b1eda5f5f5aa267b1f8c7011d21888de892b19e373
|
|
4
|
+
data.tar.gz: 91e3d85cdec6629444eaf4c5a7846be3dbaf876ec00b56cefc12b2bac18b473c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3fe4cf797a07bdff2cbe1f1a8ad7689f4ea2ff29396c75896e01acf05e41fe88c51f8f7965655565b8e7078c5205e1eb3aa31d5a8bcb31e26133189db958fe51
|
|
7
|
+
data.tar.gz: 575e20457eca50ebbaf258b9fad1d86210a78337c8bfa04a535457843ce83a73687e21f5629e28a2bdb755a7592a38e4745839a54bb830ded1bae3a8670bc0c2
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
ruby-version: ["3.0", "3.1", "3.2", "3.4", "4.0"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
22
|
+
uses: ruby/setup-ruby@v1
|
|
23
|
+
with:
|
|
24
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
25
|
+
bundler-cache: true
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: bundle exec rspec
|
|
29
|
+
|
|
30
|
+
lint:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- name: Set up Ruby
|
|
37
|
+
uses: ruby/setup-ruby@v1
|
|
38
|
+
with:
|
|
39
|
+
ruby-version: "3.2"
|
|
40
|
+
bundler-cache: true
|
|
41
|
+
|
|
42
|
+
- name: Run RuboCop
|
|
43
|
+
run: bundle exec rubocop
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copy this file to .mise.local.toml and fill in real values
|
|
2
|
+
# This file is gitignored and should contain your local dev/test credentials
|
|
3
|
+
|
|
4
|
+
[env]
|
|
5
|
+
# The vast majority of these are for testing against a local DataNexus instance and recording VCR cassettes.
|
|
6
|
+
DATANEXUS_API_KEY = "your-real-api-key"
|
|
7
|
+
DATANEXUS_BASE_URL = "https://localhost:4000"
|
|
8
|
+
DATANEXUS_SSL_VERIFY = "false"
|
|
9
|
+
DATANEXUS_TEST_PROGRAM_ID = "your-real-program-id"
|
|
10
|
+
DATANEXUS_TEST_MEMBER_ID = "your-real-member-id"
|
|
11
|
+
DATANEXUS_TEST_CONSENT_ID = "your-real-consent-id"
|
|
12
|
+
DATANEXUS_TEST_ENROLLMENT_ID = "your-real-enrollment-id"
|
|
13
|
+
DATANEXUS_TEST_BORN_ON = "1980-01-15"
|
|
14
|
+
DATANEXUS_TEST_EMPLOYEE_ID = "EMP123"
|
data/.mise.toml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[tools]
|
|
2
|
+
ruby = "3.3"
|
|
3
|
+
|
|
4
|
+
[tasks.setup]
|
|
5
|
+
description = "Install dependencies"
|
|
6
|
+
run = "bundle install"
|
|
7
|
+
|
|
8
|
+
[tasks.test]
|
|
9
|
+
description = "Run all tests"
|
|
10
|
+
run = "bundle exec rspec --exclude-pattern 'spec/integration/**/*'"
|
|
11
|
+
depends = ["setup"]
|
|
12
|
+
|
|
13
|
+
[tasks."test:all"]
|
|
14
|
+
description = "Run all tests including integration"
|
|
15
|
+
run = "bundle exec rspec"
|
|
16
|
+
depends = ["setup"]
|
|
17
|
+
|
|
18
|
+
[tasks."test:integration"]
|
|
19
|
+
description = "Run integration tests only"
|
|
20
|
+
run = "bundle exec rspec spec/integration/"
|
|
21
|
+
depends = ["setup"]
|
|
22
|
+
|
|
23
|
+
[tasks.lint]
|
|
24
|
+
description = "Run rubocop"
|
|
25
|
+
run = "bundle exec rubocop"
|
|
26
|
+
depends = ["setup"]
|
|
27
|
+
|
|
28
|
+
[tasks.fix]
|
|
29
|
+
description = "Run rubocop with auto-fix"
|
|
30
|
+
run = "bundle exec rubocop -a"
|
|
31
|
+
depends = ["setup"]
|
|
32
|
+
|
|
33
|
+
[tasks.console]
|
|
34
|
+
description = "Start IRB console with gem loaded"
|
|
35
|
+
run = "bundle exec bin/console"
|
|
36
|
+
depends = ["setup"]
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
plugins:
|
|
2
|
+
- rubocop-rake
|
|
3
|
+
- rubocop-rspec
|
|
4
|
+
|
|
5
|
+
AllCops:
|
|
6
|
+
TargetRubyVersion: 3.0
|
|
7
|
+
NewCops: enable
|
|
8
|
+
|
|
9
|
+
# Integration tests have different expectations
|
|
10
|
+
RSpec/DescribeClass:
|
|
11
|
+
Exclude:
|
|
12
|
+
- "spec/integration/**/*"
|
|
13
|
+
|
|
14
|
+
RSpec/MultipleExpectations:
|
|
15
|
+
Exclude:
|
|
16
|
+
- "spec/integration/**/*"
|
|
17
|
+
|
|
18
|
+
RSpec/ExampleLength:
|
|
19
|
+
Exclude:
|
|
20
|
+
- "spec/integration/**/*"
|
|
21
|
+
|
|
22
|
+
RSpec/MultipleMemoizedHelpers:
|
|
23
|
+
Exclude:
|
|
24
|
+
- "spec/integration/**/*"
|
|
25
|
+
|
|
26
|
+
Metrics/ParameterLists:
|
|
27
|
+
Exclude:
|
|
28
|
+
- "lib/data_nexus/client.rb"
|
data/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# DataNexus
|
|
2
|
+
|
|
3
|
+
Ruby client for the DataNexus API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install from GitHub (before gem is published):
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'data_nexus', git: 'https://github.com/DartHealth/datanexus-ruby.git', tag: 'v0.1.0'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or from RubyGems (once published):
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem 'data_nexus'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
client = DataNexus::Client.new(
|
|
23
|
+
api_key: ENV['DATANEXUS_API_KEY'],
|
|
24
|
+
base_url: 'https://api.datanexus.com' # optional
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Program Members
|
|
29
|
+
|
|
30
|
+
### List Members
|
|
31
|
+
|
|
32
|
+
Filters are required. Valid combinations:
|
|
33
|
+
- `born_on` + `employee_id`
|
|
34
|
+
- `born_on` + `first_name` + `last_name`
|
|
35
|
+
- `born_on` + `first_name_prefix` + `last_name_prefix`
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
collection = client.programs('program-id').members.list(
|
|
39
|
+
born_on: '1980-01-15',
|
|
40
|
+
employee_id: 'EMP123'
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
collection.data.each do |member|
|
|
44
|
+
puts "#{member[:first_name]} #{member[:last_name]}"
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Search Members
|
|
49
|
+
|
|
50
|
+
Search for members within a program. Returns a bounded result set (max 10 results) with a `more_results` flag indicating if additional matches exist.
|
|
51
|
+
|
|
52
|
+
Valid parameter combinations:
|
|
53
|
+
- `born_on` + `employee_id`
|
|
54
|
+
- `born_on` + `first_name` + `last_name`
|
|
55
|
+
- `born_on` + `first_name` + `last_name` + `employee_id`
|
|
56
|
+
- `born_on` + `first_name_prefix` + `last_name_prefix`
|
|
57
|
+
- `born_on` + `first_name_prefix` + `last_name_prefix` + `employee_id`
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# Search by employee ID and DOB
|
|
61
|
+
result = client.programs('program-id').search_members(
|
|
62
|
+
born_on: '1980-01-15',
|
|
63
|
+
employee_id: 'EMP123'
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
result[:data].each do |member|
|
|
67
|
+
puts "#{member[:first_name]} #{member[:last_name]}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
puts "More results available" if result[:more_results]
|
|
71
|
+
|
|
72
|
+
# Search by name and DOB
|
|
73
|
+
result = client.programs('program-id').search_members(
|
|
74
|
+
born_on: '1980-01-15',
|
|
75
|
+
first_name: 'George',
|
|
76
|
+
last_name: 'Washington'
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Search by name prefix and DOB
|
|
80
|
+
result = client.programs('program-id').search_members(
|
|
81
|
+
born_on: '1980-01-15',
|
|
82
|
+
first_name_prefix: 'G',
|
|
83
|
+
last_name_prefix: 'Was'
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Note: Unlike `list`, `search_members` does not support pagination. It returns up to 10 results with a `more_results` boolean. An `ArgumentError` will be raised if an invalid parameter combination is provided.
|
|
88
|
+
|
|
89
|
+
Note: Depending on your API key, `search_members` may be the only method you have access to. Contact your DataNexus representative for more information about your API key's permissions.
|
|
90
|
+
|
|
91
|
+
### Pagination
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
collection.each_page do |page|
|
|
95
|
+
page.data.each { |member| process(member) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Or iterate all records directly
|
|
99
|
+
collection.each { |member| process(member) }
|
|
100
|
+
|
|
101
|
+
# Manual pagination
|
|
102
|
+
if collection.next_page?
|
|
103
|
+
next_collection = collection.next_page
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Find Member
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
member = client.programs('program-id').members('member-id').find
|
|
111
|
+
puts member[:first_name]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Update Member
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
response = client.programs('program-id').members('member-id').update(
|
|
118
|
+
member: { phone_number: '+15551234567' }
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Household Members
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
household = client.programs('program-id').members('member-id').household
|
|
126
|
+
household.each { |member| puts member[:first_name] }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Member Consents
|
|
130
|
+
|
|
131
|
+
#### Create Consent
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
response = client.programs('program-id').members('member-id').consents.create(
|
|
135
|
+
consent: {
|
|
136
|
+
category: 'sms',
|
|
137
|
+
member_response: true,
|
|
138
|
+
consent_details: { sms_phone_number: '+15558675309' }
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
# program_id is automatically injected
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Find Consent
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
consent = client.programs('program-id').members('member-id').consents.find(123)
|
|
148
|
+
puts consent[:category]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Update Consent
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
response = client.programs('program-id').members('member-id').consents.update(123,
|
|
155
|
+
consent: { member_response: false }
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Delete Consent
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
client.programs('program-id').members('member-id').consents.delete(123)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Member Enrollments
|
|
166
|
+
|
|
167
|
+
#### Create Enrollment
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
response = client.programs('program-id').members('member-id').enrollments.create(
|
|
171
|
+
enrollment: {
|
|
172
|
+
enrolled_at: '2024-01-01T00:00:00Z',
|
|
173
|
+
expires_at: '2025-01-01T00:00:00Z'
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
# program_id is automatically injected
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### Find Enrollment
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
enrollment = client.programs('program-id').members('member-id').enrollments.find(123)
|
|
183
|
+
puts enrollment[:enrolled_at]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### Update Enrollment
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
response = client.programs('program-id').members('member-id').enrollments.update(123,
|
|
190
|
+
enrollment: { expires_at: '2026-01-01T00:00:00Z' }
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### Delete Enrollment
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
client.programs('program-id').members('member-id').enrollments.delete(123)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Top-Level Members
|
|
201
|
+
|
|
202
|
+
You can also access members without a program scope:
|
|
203
|
+
|
|
204
|
+
### List Members
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
collection = client.members.list(
|
|
208
|
+
first_name: 'George',
|
|
209
|
+
last_name: 'Washington',
|
|
210
|
+
born_on: '1976-07-04'
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Filter by program eligibility
|
|
214
|
+
collection = client.members.list(program_id: 'program-uuid')
|
|
215
|
+
|
|
216
|
+
# Filter by update time
|
|
217
|
+
collection = client.members.list(updated_since: '2024-01-01T00:00:00Z')
|
|
218
|
+
|
|
219
|
+
# Pagination
|
|
220
|
+
collection = client.members.list(first: 50, after: 'cursor')
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Find Member
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
member = client.members.find('member-id')
|
|
227
|
+
puts member[:first_name]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Update Member
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
response = client.members.update('member-id',
|
|
234
|
+
member: { phone_number: '+15551234567' }
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Error Handling
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
begin
|
|
242
|
+
client.programs('id').members('id').find
|
|
243
|
+
rescue DataNexus::AuthenticationError
|
|
244
|
+
# 401
|
|
245
|
+
rescue DataNexus::NotFoundError
|
|
246
|
+
# 404
|
|
247
|
+
rescue DataNexus::UnprocessableEntityError
|
|
248
|
+
# 422
|
|
249
|
+
rescue DataNexus::RateLimitError => e
|
|
250
|
+
sleep(e.retry_after)
|
|
251
|
+
rescue DataNexus::APIError => e
|
|
252
|
+
puts "#{e.status}: #{e.message}"
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Development
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
cp .mise.local.toml.example .mise.local.toml
|
|
260
|
+
# Edit .mise.local.toml with your test credentials
|
|
261
|
+
bundle install
|
|
262
|
+
bundle exec rspec
|
|
263
|
+
bundle exec rubocop
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
MIT
|
data/Rakefile
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'configuration'
|
|
4
|
+
require_relative 'connection'
|
|
5
|
+
require_relative 'resources/members'
|
|
6
|
+
require_relative 'resources/programs'
|
|
7
|
+
|
|
8
|
+
module DataNexus
|
|
9
|
+
# Main client for interacting with the DataNexus API
|
|
10
|
+
#
|
|
11
|
+
# @example Create a client with explicit configuration
|
|
12
|
+
# client = DataNexus::Client.new(api_key: "your_api_key")
|
|
13
|
+
#
|
|
14
|
+
# @example Create a client with a configuration object
|
|
15
|
+
# config = DataNexus::Configuration.new(api_key: "your_api_key")
|
|
16
|
+
# client = DataNexus::Client.new(config: config)
|
|
17
|
+
#
|
|
18
|
+
# @example Access program members
|
|
19
|
+
# client.programs("program-uuid").members.list
|
|
20
|
+
# client.programs("program-uuid").members("member-id").find
|
|
21
|
+
#
|
|
22
|
+
class Client
|
|
23
|
+
# @return [Configuration] The client configuration
|
|
24
|
+
attr_reader :config
|
|
25
|
+
|
|
26
|
+
# @return [Connection] The HTTP connection
|
|
27
|
+
attr_reader :connection
|
|
28
|
+
|
|
29
|
+
# Initialize a new Client
|
|
30
|
+
#
|
|
31
|
+
# @param api_key [String, nil] The API key for authentication
|
|
32
|
+
# @param base_url [String, nil] The base URL for the API
|
|
33
|
+
# @param timeout [Integer, nil] Request timeout in seconds
|
|
34
|
+
# @param open_timeout [Integer, nil] Connection open timeout in seconds
|
|
35
|
+
# @param config [Configuration, nil] A pre-built configuration object
|
|
36
|
+
#
|
|
37
|
+
# @raise [ConfigurationError] If no API key is provided
|
|
38
|
+
def initialize(api_key: nil, base_url: nil, timeout: nil, open_timeout: nil, ssl_verify: nil, config: nil)
|
|
39
|
+
@config = config || build_configuration(
|
|
40
|
+
api_key: api_key,
|
|
41
|
+
base_url: base_url,
|
|
42
|
+
timeout: timeout,
|
|
43
|
+
open_timeout: open_timeout,
|
|
44
|
+
ssl_verify: ssl_verify
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
validate_configuration!
|
|
48
|
+
|
|
49
|
+
@connection = Connection.new(@config)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Access top-level member resources
|
|
53
|
+
#
|
|
54
|
+
# @return [Resources::Members] A members resource
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# client.members.list
|
|
58
|
+
# client.members.find("member-id")
|
|
59
|
+
def members
|
|
60
|
+
Resources::Members.new(connection)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Access program-scoped resources
|
|
64
|
+
#
|
|
65
|
+
# @param program_id [String] The program UUID
|
|
66
|
+
# @return [Resources::Programs] A program resource proxy
|
|
67
|
+
#
|
|
68
|
+
# @example
|
|
69
|
+
# client.programs("uuid").members.list
|
|
70
|
+
def programs(program_id)
|
|
71
|
+
Resources::Programs.new(connection, program_id)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Build a configuration from individual parameters
|
|
77
|
+
#
|
|
78
|
+
# @param api_key [String, nil]
|
|
79
|
+
# @param base_url [String, nil]
|
|
80
|
+
# @param timeout [Integer, nil]
|
|
81
|
+
# @param open_timeout [Integer, nil]
|
|
82
|
+
# @param ssl_verify [Boolean, nil]
|
|
83
|
+
# @return [Configuration]
|
|
84
|
+
def build_configuration(api_key:, base_url:, timeout:, open_timeout:, ssl_verify:)
|
|
85
|
+
Configuration.new(
|
|
86
|
+
api_key: api_key,
|
|
87
|
+
base_url: base_url || Configuration::DEFAULT_BASE_URL,
|
|
88
|
+
timeout: timeout || Configuration::DEFAULT_TIMEOUT,
|
|
89
|
+
open_timeout: open_timeout || Configuration::DEFAULT_OPEN_TIMEOUT,
|
|
90
|
+
ssl_verify: ssl_verify.nil? ? Configuration::DEFAULT_SSL_VERIFY : ssl_verify
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Validate that the configuration is complete
|
|
95
|
+
#
|
|
96
|
+
# @raise [ConfigurationError] If the configuration is invalid
|
|
97
|
+
def validate_configuration!
|
|
98
|
+
return if @config.valid?
|
|
99
|
+
|
|
100
|
+
raise ConfigurationError, 'API key is required. Pass api_key: or provide a configured Configuration object.'
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DataNexus
|
|
4
|
+
# Wrapper for paginated API responses
|
|
5
|
+
#
|
|
6
|
+
# Provides convenience methods for accessing data and navigating through pages.
|
|
7
|
+
# The underlying data remains as hashes - this class just adds pagination helpers.
|
|
8
|
+
#
|
|
9
|
+
# @example Accessing data
|
|
10
|
+
# collection = client.programs('uuid').members.list
|
|
11
|
+
# collection.data.each { |member| puts member[:first_name] }
|
|
12
|
+
#
|
|
13
|
+
# @example Manual pagination
|
|
14
|
+
# collection = client.programs('uuid').members.list(first: 50)
|
|
15
|
+
# while collection
|
|
16
|
+
# process(collection.data)
|
|
17
|
+
# collection = collection.next_page
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Block pagination
|
|
21
|
+
# client.programs('uuid').members.list(first: 50).each_page do |page|
|
|
22
|
+
# page.data.each { |member| puts member[:first_name] }
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
class Collection
|
|
26
|
+
# @return [Array<Hash>] The records in this page
|
|
27
|
+
attr_reader :data
|
|
28
|
+
|
|
29
|
+
# @return [String, nil] Cursor for the start of this page
|
|
30
|
+
attr_reader :start_cursor
|
|
31
|
+
|
|
32
|
+
# @return [String, nil] Cursor for the end of this page
|
|
33
|
+
attr_reader :end_cursor
|
|
34
|
+
|
|
35
|
+
# Initialize a new Collection
|
|
36
|
+
#
|
|
37
|
+
# @param response [Hash] The raw API response
|
|
38
|
+
# @param resource [Object] The resource instance that made the request
|
|
39
|
+
# @param params [Hash] The params used for the original request
|
|
40
|
+
def initialize(response, resource:, params:)
|
|
41
|
+
@data = response[:data] || []
|
|
42
|
+
@start_cursor = response[:start_cursor]
|
|
43
|
+
@end_cursor = response[:end_cursor]
|
|
44
|
+
@resource = resource
|
|
45
|
+
@params = params
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Check if there's a next page of results
|
|
49
|
+
#
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
def next_page?
|
|
52
|
+
!end_cursor.nil? && !end_cursor.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Fetch the next page of results
|
|
56
|
+
#
|
|
57
|
+
# @return [Collection, nil] The next page, or nil if no more pages
|
|
58
|
+
def next_page
|
|
59
|
+
return nil unless next_page?
|
|
60
|
+
|
|
61
|
+
@resource.list(**@params, after: end_cursor)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check if there's a previous page of results
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
def previous_page?
|
|
68
|
+
!start_cursor.nil? && !start_cursor.empty?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Fetch the previous page of results
|
|
72
|
+
#
|
|
73
|
+
# @return [Collection, nil] The previous page, or nil if no more pages
|
|
74
|
+
def previous_page
|
|
75
|
+
return nil unless previous_page?
|
|
76
|
+
|
|
77
|
+
@resource.list(**@params, before: start_cursor)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Iterate through all pages starting from this one
|
|
81
|
+
#
|
|
82
|
+
# @yield [Collection] Each page of results
|
|
83
|
+
# @return [Enumerator] If no block given
|
|
84
|
+
def each_page
|
|
85
|
+
return enum_for(:each_page) unless block_given?
|
|
86
|
+
|
|
87
|
+
page = self
|
|
88
|
+
while page
|
|
89
|
+
yield page
|
|
90
|
+
page = page.next_page
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Iterate through all records across all pages
|
|
95
|
+
#
|
|
96
|
+
# @yield [Hash] Each record
|
|
97
|
+
# @return [Enumerator] If no block given
|
|
98
|
+
def each_record(&block)
|
|
99
|
+
return enum_for(:each_record) unless block_given?
|
|
100
|
+
|
|
101
|
+
each_page do |page|
|
|
102
|
+
page.data.each(&block)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
alias each each_record
|
|
107
|
+
|
|
108
|
+
# @return [Boolean] Whether this page has any records
|
|
109
|
+
def empty?
|
|
110
|
+
data.empty?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @return [Integer] Number of records in this page
|
|
114
|
+
def size
|
|
115
|
+
data.size
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
alias length size
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DataNexus
|
|
4
|
+
# Configuration class for storing API credentials and settings
|
|
5
|
+
#
|
|
6
|
+
# @example Configure the client globally
|
|
7
|
+
# DataNexus.configure do |config|
|
|
8
|
+
# config.api_key = "your_api_key"
|
|
9
|
+
# config.base_url = "https://api.datanexus.com"
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# @example Create a configuration instance
|
|
13
|
+
# config = DataNexus::Configuration.new(
|
|
14
|
+
# api_key: "your_api_key",
|
|
15
|
+
# base_url: "https://api.datanexus.com"
|
|
16
|
+
# )
|
|
17
|
+
#
|
|
18
|
+
class Configuration
|
|
19
|
+
# @return [String, nil] The API key for authentication
|
|
20
|
+
attr_accessor :api_key
|
|
21
|
+
|
|
22
|
+
# @return [String] The base URL for the DataNexus API
|
|
23
|
+
attr_accessor :base_url
|
|
24
|
+
|
|
25
|
+
# @return [Integer] Request timeout in seconds
|
|
26
|
+
attr_accessor :timeout
|
|
27
|
+
|
|
28
|
+
# @return [Integer] Connection open timeout in seconds
|
|
29
|
+
attr_accessor :open_timeout
|
|
30
|
+
|
|
31
|
+
# @return [Boolean] Whether to verify SSL certificates
|
|
32
|
+
attr_accessor :ssl_verify
|
|
33
|
+
|
|
34
|
+
# Default base URL for the DataNexus API
|
|
35
|
+
DEFAULT_BASE_URL = 'https://api.datanexus.com'
|
|
36
|
+
|
|
37
|
+
# Default request timeout in seconds
|
|
38
|
+
DEFAULT_TIMEOUT = 30
|
|
39
|
+
|
|
40
|
+
# Default connection open timeout in seconds
|
|
41
|
+
DEFAULT_OPEN_TIMEOUT = 10
|
|
42
|
+
|
|
43
|
+
# Default SSL verification setting
|
|
44
|
+
DEFAULT_SSL_VERIFY = true
|
|
45
|
+
|
|
46
|
+
# Initialize a new Configuration instance
|
|
47
|
+
#
|
|
48
|
+
# @param api_key [String, nil] The API key for authentication
|
|
49
|
+
# @param base_url [String] The base URL for the API
|
|
50
|
+
# @param timeout [Integer] Request timeout in seconds
|
|
51
|
+
# @param open_timeout [Integer] Connection open timeout in seconds
|
|
52
|
+
# @param ssl_verify [Boolean] Whether to verify SSL certificates (set to false for self-signed certs)
|
|
53
|
+
def initialize(
|
|
54
|
+
api_key: nil,
|
|
55
|
+
base_url: DEFAULT_BASE_URL,
|
|
56
|
+
timeout: DEFAULT_TIMEOUT,
|
|
57
|
+
open_timeout: DEFAULT_OPEN_TIMEOUT,
|
|
58
|
+
ssl_verify: DEFAULT_SSL_VERIFY
|
|
59
|
+
)
|
|
60
|
+
@api_key = api_key
|
|
61
|
+
@base_url = base_url
|
|
62
|
+
@timeout = timeout
|
|
63
|
+
@open_timeout = open_timeout
|
|
64
|
+
@ssl_verify = ssl_verify
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check if the configuration has valid credentials
|
|
68
|
+
#
|
|
69
|
+
# @return [Boolean] true if api_key is present
|
|
70
|
+
def valid?
|
|
71
|
+
!api_key.nil? && !api_key.empty?
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|