clicksign-ruby-sdk 0.1.1 → 0.1.4
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 +4 -4
- data/README.md +249 -29
- data/lib/clicksign/client.rb +8 -40
- data/lib/clicksign/configuration.rb +1 -1
- data/lib/clicksign/instrumentation.rb +5 -2
- data/lib/clicksign/json_api/bulk_operations_client.rb +30 -20
- data/lib/clicksign/json_api/parser.rb +3 -1
- data/lib/clicksign/request_instrumentation.rb +57 -0
- data/lib/clicksign/resource.rb +63 -16
- data/lib/clicksign/retry_backoff.rb +22 -0
- data/lib/clicksign/version.rb +1 -1
- data/lib/clicksign.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2fa45c00d6994b4009e9888eb982da569f1657c692b7e83a5db76c69067ebc2
|
|
4
|
+
data.tar.gz: e74c86dab5a02752fd9bb3a388cf54c78ae90e9a376cdac163bf5dca090fd5e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86b4b41f6607d89ea58786225502c699902523d05ff72f60d983449fc9a318d794ab1c164ce1d475a073ea868d22ee65da53f7e36c61cb477e28e4cea7fa86f4
|
|
7
|
+
data.tar.gz: 484d16275cda5bf7e85413dcc3383776988574de052d6715a7d08e591d54701b13e3dd52a69c9a000977f8d51cc07a7590968050c6456b35cec48c914086a737
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Cliente Ruby oficial para a [API v3 da Clicksign](https://developers.clicksign.c
|
|
|
9
9
|
|
|
10
10
|
**Requisitos:** Ruby >= 3.0 · dependências de runtime: apenas biblioteca padrão (`net/http`, `json`).
|
|
11
11
|
|
|
12
|
-
**Documentação
|
|
12
|
+
**Documentação:** [índice `docs/`](docs/) · [Workflow](docs/WORKFLOW.md) · [Cookbook](docs/cookbook/) · [Troubleshooting](docs/TROUBLESHOOTING.md) · [Arquitetura](docs/ARCHITECTURE.md) · [Observabilidade](docs/OBSERVABILITY.md) · [SPEC](docs/SPEC.md) · API: [Sandbox](https://sandbox.clicksign.com/api/v3) · [Produção](https://app.clicksign.com/api/v3)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -17,17 +17,26 @@ Cliente Ruby oficial para a [API v3 da Clicksign](https://developers.clicksign.c
|
|
|
17
17
|
|
|
18
18
|
- [Instalação](#instalação)
|
|
19
19
|
- [Configuração](#configuração)
|
|
20
|
+
- [Multi-conta e cliente instanciável](#multi-conta-e-cliente-instantiável)
|
|
21
|
+
- [Timeouts, retry e instrumentação](#timeouts-retry-e-instrumentação)
|
|
20
22
|
- [Início rápido](#início-rápido)
|
|
21
23
|
- [Fluxo de assinatura (notarial)](#fluxo-de-assinatura-notarial)
|
|
22
24
|
- [Filtros, ordenação e paginação](#filtros-ordenação-e-paginação)
|
|
23
25
|
- [Outros recursos](#outros-recursos)
|
|
24
26
|
- [Tratamento de erros](#tratamento-de-erros)
|
|
25
27
|
- [Ambientes](#ambientes)
|
|
28
|
+
- [Limitações e produção](#limitações-e-produção)
|
|
26
29
|
- [Desenvolvimento](#desenvolvimento)
|
|
27
30
|
- [Licença](#licença)
|
|
28
31
|
|
|
29
32
|
> **Exemplo passo a passo:** [`docs/WORKFLOW.md`](docs/WORKFLOW.md) — fluxo completo de envelope → documento → signatário → requisitos → ativação → notificação.
|
|
30
33
|
|
|
34
|
+
> **Cookbook (receitas por cenário):** [`docs/cookbook/`](docs/cookbook/) — [retries](docs/cookbook/01-retries.md), [bulk requirements](docs/cookbook/02-bulk-requirements.md), [webhooks](docs/cookbook/03-webhooks.md), [vários clientes](docs/cookbook/04-multi-client.md), [list vs filter](docs/cookbook/07-list-and-filter.md), [limitações de produção](docs/cookbook/08-production-limitations.md).
|
|
35
|
+
|
|
36
|
+
> **Troubleshooting:** [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) — sintoma → causa → correção (erros HTTP, multi-tenant, bulk parcial, webhooks).
|
|
37
|
+
|
|
38
|
+
> **Arquitetura e observabilidade:** [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) · [`docs/OBSERVABILITY.md`](docs/OBSERVABILITY.md)
|
|
39
|
+
|
|
31
40
|
---
|
|
32
41
|
|
|
33
42
|
## Instalação
|
|
@@ -56,21 +65,38 @@ gem install clicksign-ruby-sdk
|
|
|
56
65
|
|
|
57
66
|
## Configuração
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
A forma mais simples é a configuração global no boot da aplicação (initializer, `config/initializers/clicksign.rb`, script, etc.):
|
|
60
69
|
|
|
61
70
|
```ruby
|
|
62
71
|
require 'clicksign'
|
|
63
72
|
|
|
64
73
|
Clicksign.configure do |c|
|
|
65
74
|
c.api_key = ENV.fetch('CLICKSIGN_API_KEY')
|
|
66
|
-
c.
|
|
75
|
+
c.environment = :sandbox # ou :production — define base_url automaticamente
|
|
76
|
+
# c.base_url = '...' # opcional: sobrescreve o URL do ambiente
|
|
77
|
+
c.open_timeout = 2 # segundos (padrão)
|
|
78
|
+
c.read_timeout = 10
|
|
79
|
+
c.write_timeout = 10
|
|
80
|
+
c.max_retries = 0 # retentativas automáticas (ver seção abaixo)
|
|
67
81
|
end
|
|
68
82
|
```
|
|
69
83
|
|
|
84
|
+
| Opção | Padrão | Descrição |
|
|
85
|
+
|-------|--------|-----------|
|
|
86
|
+
| `api_key` | — | Token da API (obrigatório) |
|
|
87
|
+
| `environment` | — | `:sandbox` ou `:production` (atalho para `base_url`) |
|
|
88
|
+
| `base_url` | produção | URL completa da API v3 |
|
|
89
|
+
| `open_timeout` | `2` | Timeout de conexão (s) |
|
|
90
|
+
| `read_timeout` | `10` | Timeout de leitura (s) |
|
|
91
|
+
| `write_timeout` | `10` | Timeout de escrita (s) |
|
|
92
|
+
| `max_retries` | `0` | Retentativas em erros transitórios |
|
|
93
|
+
|
|
70
94
|
A API usa o header `Authorization: <seu-token>` **sem** o prefixo `Bearer`.
|
|
71
95
|
|
|
72
96
|
> **Segurança:** não commite tokens no código. Use variáveis de ambiente ou cofre de secrets (Rails credentials, etc.).
|
|
73
97
|
|
|
98
|
+
> **Multi-conta / multi-tenant:** se cada requisição pode usar credenciais diferentes (SaaS, workers por cliente), prefira [`Clicksign::Services`](#multi-conta-e-cliente-instantiável) em vez da config global.
|
|
99
|
+
|
|
74
100
|
Para testar interativamente no console da gem:
|
|
75
101
|
|
|
76
102
|
```bash
|
|
@@ -79,6 +105,126 @@ CLICKSIGN_API_KEY=seu-token bundle exec ruby bin/console
|
|
|
79
105
|
|
|
80
106
|
---
|
|
81
107
|
|
|
108
|
+
## Multi-conta e cliente instanciável
|
|
109
|
+
|
|
110
|
+
Além da config global, a gem oferece clientes isolados por contexto — útil em apps multi-tenant, jobs Sidekiq com token por conta e testes paralelos.
|
|
111
|
+
|
|
112
|
+
### `Clicksign::Services` (recomendado para resources)
|
|
113
|
+
|
|
114
|
+
Encapsula um `Clicksign::Client` e roteia todas as chamadas de `Clicksign::Resources::*` dentro do bloco `use`:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
conta_a = Clicksign::Services.new(
|
|
118
|
+
api_key: ENV['CLICKSIGN_TOKEN_CONTA_A'],
|
|
119
|
+
environment: :production,
|
|
120
|
+
max_retries: 2
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
conta_b = Clicksign::Services.new(
|
|
124
|
+
api_key: ENV['CLICKSIGN_TOKEN_CONTA_B'],
|
|
125
|
+
environment: :sandbox
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
conta_a.use do
|
|
129
|
+
Clicksign::Resources::Notarial::Envelope.create(name: 'Contrato A')
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
conta_b.use do
|
|
133
|
+
Clicksign::Resources::Notarial::Envelope.filter(status: 'draft').to_a
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
O client ativo fica em `Thread.current` durante o bloco; blocos aninhados restauram o client externo ao sair. Fora de `use`, os resources voltam a usar `Clicksign.client` (config global).
|
|
138
|
+
|
|
139
|
+
Em Rails, um padrão comum é resolver o service no controller e executar a lógica dentro de `use`:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
class EnvelopesController < ApplicationController
|
|
143
|
+
def create
|
|
144
|
+
current_tenant.clicksign_service.use do
|
|
145
|
+
envelope = Clicksign::Resources::Notarial::Envelope.create(envelope_params)
|
|
146
|
+
render json: { id: envelope.id }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `Clicksign::Client` (HTTP direto)
|
|
153
|
+
|
|
154
|
+
Para chamadas JSON:API de baixo nível sem passar pelos resources:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
client = Clicksign::Client.new(
|
|
158
|
+
api_key: ENV['CLICKSIGN_API_KEY'],
|
|
159
|
+
base_url: 'https://sandbox.clicksign.com/api/v3',
|
|
160
|
+
open_timeout: 2,
|
|
161
|
+
read_timeout: 30,
|
|
162
|
+
max_retries: 3
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
response = client.get('/envelopes', params: { 'filter[status]' => 'draft' })
|
|
166
|
+
client.post('/envelopes', body: { data: { type: 'envelopes', attributes: { name: 'Novo' } } })
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| Abordagem | Quando usar |
|
|
170
|
+
|-----------|-------------|
|
|
171
|
+
| `Clicksign.configure` | App single-tenant; initializer único |
|
|
172
|
+
| `Clicksign::Services#use` | Multi-conta; token por request/job |
|
|
173
|
+
| `Clicksign::Client.new` | Controle fino do HTTP ou integração customizada |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Timeouts, retry e instrumentação
|
|
178
|
+
|
|
179
|
+
### Timeouts
|
|
180
|
+
|
|
181
|
+
Configuráveis globalmente (`Clicksign.configure`), por `Services` ou diretamente em `Client.new`. Timeouts de rede disparam `Clicksign::TimeoutError` (retryable quando `max_retries > 0`).
|
|
182
|
+
|
|
183
|
+
### Retry automático
|
|
184
|
+
|
|
185
|
+
Com `max_retries > 0`, o client reexecuta a requisição em erros **transitórios**:
|
|
186
|
+
|
|
187
|
+
- `Clicksign::TimeoutError`
|
|
188
|
+
- `Clicksign::RateLimitError`
|
|
189
|
+
- `Clicksign::ServerError` (5xx)
|
|
190
|
+
|
|
191
|
+
Backoff exponencial com **full jitter** (espera aleatória entre `0` e o teto da tentativa: `0,5s`, `1s`, `2s`… até **30s**), para evitar thundering herd quando muitos clientes falham ao mesmo tempo. Após esgotar as retentativas, a exceção original é relançada.
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
Clicksign.configure do |c|
|
|
195
|
+
c.api_key = ENV['CLICKSIGN_API_KEY']
|
|
196
|
+
c.environment = :production
|
|
197
|
+
c.max_retries = 3
|
|
198
|
+
end
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Operações em lote (`BulkRequirement`) usam o mesmo `max_retries` e os mesmos hooks de instrumentação via `Clicksign.bulk_operations_client` (retry automático só em timeout).
|
|
202
|
+
|
|
203
|
+
### Instrumentação
|
|
204
|
+
|
|
205
|
+
Registre callbacks para observabilidade (logs, métricas, APM). Callbacks não propagam exceções — falhas internas são ignoradas para não afetar a requisição.
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
Clicksign.on_request do |event|
|
|
209
|
+
# event: :method, :path, :status, :duration_ms, :attempt
|
|
210
|
+
Rails.logger.info "[Clicksign] #{event[:method]} #{event[:path]} → #{event[:status]} (#{event[:duration_ms]}ms)"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
Clicksign.on_retry do |event|
|
|
214
|
+
# event: :method, :path, :attempt, :max_retries, :error, :wait_ms
|
|
215
|
+
Rails.logger.warn "[Clicksign] retry #{event[:attempt]}/#{event[:max_retries]} em #{event[:wait_ms]}ms"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
Clicksign.on_error do |event|
|
|
219
|
+
# event: :method, :path, :error, :status, :duration_ms
|
|
220
|
+
Sentry.capture_exception(event[:error])
|
|
221
|
+
end
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Eventos publicados: `:request` (toda tentativa, sucesso ou erro HTTP), `:retry` (antes de cada retentativa), `:error` (quando uma exceção é lançada).
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
82
228
|
## Início rápido
|
|
83
229
|
|
|
84
230
|
Listar envelopes em rascunho e criar um novo:
|
|
@@ -329,23 +475,40 @@ Event.create_for_document(
|
|
|
329
475
|
|
|
330
476
|
## Filtros, ordenação e paginação
|
|
331
477
|
|
|
332
|
-
|
|
478
|
+
### `list` vs `filter`
|
|
479
|
+
|
|
480
|
+
| Método | Retorno | Uso |
|
|
481
|
+
|--------|---------|-----|
|
|
482
|
+
| `Resource.list` | `Array` | Primeira página da collection, **sem** filtros na chain |
|
|
483
|
+
| `Resource.filter(...)` | `QueryProxy` | Filtros, ordenação, paginação, includes — termine com `.to_a`, `.first`, `.auto_paging_each`, etc. |
|
|
484
|
+
|
|
485
|
+
`list` **não** aceita argumentos. Para filtrar: `Envelope.filter(status: 'draft').to_a` (não `Envelope.list(status: 'draft')`).
|
|
486
|
+
|
|
487
|
+
Guia completo: [`docs/cookbook/07-list-and-filter.md`](docs/cookbook/07-list-and-filter.md).
|
|
488
|
+
|
|
489
|
+
```ruby
|
|
490
|
+
# Sem filtros — retorna Array imediatamente
|
|
491
|
+
Webhook.list
|
|
492
|
+
|
|
493
|
+
# Com filtros ou chain — começa em filter
|
|
494
|
+
Envelope.filter(status: 'draft').to_a
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Chain de consulta
|
|
333
498
|
|
|
334
499
|
```ruby
|
|
335
500
|
Envelope
|
|
336
501
|
.filter(status: 'running', name: 'Contrato')
|
|
502
|
+
.with_includes('folder') # sideload JSON:API (.include('folder') também funciona)
|
|
337
503
|
.order('-created')
|
|
338
504
|
.page(1)
|
|
339
505
|
.per(20)
|
|
340
506
|
.to_a
|
|
341
507
|
|
|
342
|
-
# Atalho quando há poucos filtros
|
|
343
508
|
Template.filter(name: 'NDA padrão').first
|
|
344
509
|
|
|
345
|
-
|
|
346
|
-
Envelope.
|
|
347
|
-
Envelope.order('-created').last # mais antigo
|
|
348
|
-
Envelope.filter(status: 'draft').count # total de rascunhos
|
|
510
|
+
Envelope.order('-created').first
|
|
511
|
+
Envelope.filter(status: 'draft').count
|
|
349
512
|
```
|
|
350
513
|
|
|
351
514
|
Atributos dos objetos retornados são acessados como métodos ou por chave string:
|
|
@@ -364,6 +527,8 @@ Envelope.filter(status: 'running').map(&:name)
|
|
|
364
527
|
Envelope.order('-created').select { |e| e.auto_close }
|
|
365
528
|
```
|
|
366
529
|
|
|
530
|
+
Percorrer **todas** as páginas automaticamente (`auto_paging_each`, `each_page`, `auto_paging`): a gem segue `links.next` da JSON:API quando presente; se a API não enviar `links`, usa heurística por `page[size]`.
|
|
531
|
+
|
|
367
532
|
---
|
|
368
533
|
|
|
369
534
|
## Outros recursos
|
|
@@ -372,8 +537,8 @@ Envelope.order('-created').select { |e| e.auto_close }
|
|
|
372
537
|
|---------|--------|---------|
|
|
373
538
|
| Webhook | `Clicksign::Resources::Webhook` | `Webhook.create(endpoint: 'https://...', events: ['envelope.completed'], status: 'active')` |
|
|
374
539
|
| Pasta | `Clicksign::Resources::Folder` | `Folder.create(name: 'Contratos 2026', folder_id: pai&.id)` |
|
|
375
|
-
| Template | `Clicksign::Resources::Template` | `Template.list` · `Template.list_template_fields(id)` |
|
|
376
|
-
| Usuário | `Clicksign::Resources::User` | `User.me` · `User.filter(email: '...')` |
|
|
540
|
+
| Template | `Clicksign::Resources::Template` | `Template.list` · `Template.filter(name: '...')` · `Template.list_template_fields(id)` |
|
|
541
|
+
| Usuário | `Clicksign::Resources::User` | `User.me` · `User.list` · `User.filter(email: '...')` |
|
|
377
542
|
| Membership | `Clicksign::Resources::Membership` | `Membership.create(role: 'member', user_id: user.id)` |
|
|
378
543
|
| Grupo | `Clicksign::Resources::Group` | `Group.add_users(group_id, [user.id])` |
|
|
379
544
|
| ACL pasta/grupo | `Clicksign::Resources::AccessControlList` | `AccessControlList.create(folder_id:, group_id:)` |
|
|
@@ -453,15 +618,22 @@ AccessControlList.destroy(folder_id: folder.id, group_id: group.id)
|
|
|
453
618
|
|
|
454
619
|
Erros HTTP são convertidos em exceções antes de chegar ao seu código:
|
|
455
620
|
|
|
456
|
-
| HTTP | Exceção |
|
|
457
|
-
|
|
458
|
-
| 401, 403 | `Clicksign::AuthenticationError` |
|
|
459
|
-
| 404 | `Clicksign::NotFoundError` |
|
|
460
|
-
| 400, 422 | `Clicksign::ValidationError` |
|
|
461
|
-
| 409 | `Clicksign::ConflictError` |
|
|
462
|
-
| 429 | `Clicksign::RateLimitError` |
|
|
463
|
-
| 5xx | `Clicksign::ServerError` |
|
|
464
|
-
| Timeout / conexão | `Clicksign::TimeoutError` |
|
|
621
|
+
| HTTP | Exceção | `retryable?` |
|
|
622
|
+
|------|---------|--------------|
|
|
623
|
+
| 401, 403 | `Clicksign::AuthenticationError` | não |
|
|
624
|
+
| 404 | `Clicksign::NotFoundError` | não |
|
|
625
|
+
| 400, 422 | `Clicksign::ValidationError` | não |
|
|
626
|
+
| 409 | `Clicksign::ConflictError` | não |
|
|
627
|
+
| 429 | `Clicksign::RateLimitError` | sim |
|
|
628
|
+
| 5xx | `Clicksign::ServerError` | sim |
|
|
629
|
+
| Timeout / conexão | `Clicksign::TimeoutError` | sim |
|
|
630
|
+
|
|
631
|
+
Todas herdam de `Clicksign::Error` e expõem metadados úteis para debug:
|
|
632
|
+
|
|
633
|
+
- `status_code` — código HTTP da resposta
|
|
634
|
+
- `request_id` — quando enviado pela API
|
|
635
|
+
- `response_body` — corpo JSON da resposta de erro
|
|
636
|
+
- `response_headers` — headers da resposta (`RateLimitError` também expõe `rate_limit_remaining` e `rate_limit_reset`)
|
|
465
637
|
|
|
466
638
|
Exemplo:
|
|
467
639
|
|
|
@@ -472,6 +644,9 @@ rescue Clicksign::NotFoundError
|
|
|
472
644
|
puts 'Envelope não encontrado'
|
|
473
645
|
rescue Clicksign::ValidationError => e
|
|
474
646
|
puts "Dados inválidos: #{e.message}"
|
|
647
|
+
puts e.response_body
|
|
648
|
+
rescue Clicksign::RateLimitError => e
|
|
649
|
+
puts "Aguarde reset em #{e.rate_limit_reset}" if e.retryable?
|
|
475
650
|
rescue Clicksign::AuthenticationError
|
|
476
651
|
puts 'Verifique CLICKSIGN_API_KEY'
|
|
477
652
|
end
|
|
@@ -479,28 +654,62 @@ end
|
|
|
479
654
|
|
|
480
655
|
Operações em lote (`BulkRequirement`) podem retornar falhas **por slot** em `response.failures` sem lançar exceção, quando a API responde com `atomic:results` parcial.
|
|
481
656
|
|
|
657
|
+
Guia detalhado: [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
|
|
658
|
+
|
|
482
659
|
---
|
|
483
660
|
|
|
484
661
|
## Ambientes
|
|
485
662
|
|
|
486
|
-
| Ambiente | `base_url` |
|
|
487
|
-
|
|
488
|
-
| Sandbox | `https://sandbox.clicksign.com/api/v3` |
|
|
489
|
-
| Produção | `https://app.clicksign.com/api/v3` |
|
|
663
|
+
| Ambiente | Símbolo | `base_url` |
|
|
664
|
+
|----------|---------|------------|
|
|
665
|
+
| Sandbox | `:sandbox` | `https://sandbox.clicksign.com/api/v3` |
|
|
666
|
+
| Produção | `:production` | `https://app.clicksign.com/api/v3` |
|
|
490
667
|
|
|
491
|
-
O padrão
|
|
668
|
+
O padrão em `Clicksign::Configuration` é **produção**. Para desenvolvimento, use o atalho `environment` (equivalente a definir `base_url`):
|
|
492
669
|
|
|
493
670
|
```ruby
|
|
494
671
|
Clicksign.configure do |c|
|
|
495
|
-
c.api_key
|
|
496
|
-
c.
|
|
672
|
+
c.api_key = ENV['CLICKSIGN_API_KEY']
|
|
673
|
+
c.environment = :sandbox
|
|
497
674
|
end
|
|
675
|
+
|
|
676
|
+
# Ou em multi-conta:
|
|
677
|
+
service = Clicksign::Services.new(
|
|
678
|
+
api_key: ENV['CLICKSIGN_API_KEY'],
|
|
679
|
+
environment: :sandbox
|
|
680
|
+
)
|
|
498
681
|
```
|
|
499
682
|
|
|
683
|
+
Também é possível passar `base_url` manualmente quando precisar de um endpoint customizado (proxy, mock, etc.).
|
|
684
|
+
|
|
500
685
|
Gere tokens de API no painel da Clicksign do ambiente correspondente.
|
|
501
686
|
|
|
502
687
|
---
|
|
503
688
|
|
|
689
|
+
## Limitações e produção
|
|
690
|
+
|
|
691
|
+
Design **stdlib-only** (`net/http`) — sem dependências de runtime extras. Duas limitações importantes em alta carga ou runtimes modernos:
|
|
692
|
+
|
|
693
|
+
### Sem connection pool
|
|
694
|
+
|
|
695
|
+
Cada request abre e fecha uma conexão TCP (via `Net::HTTP.start`). Não há reutilização persistente entre chamadas.
|
|
696
|
+
|
|
697
|
+
- **OK** para jobs sequenciais, integrações moderadas e a maioria dos apps Rails.
|
|
698
|
+
- **Atenção** em Puma com muitas threads e várias chamadas Clicksign por request: overhead de handshake/TLS pode virar gargalo antes do rate limit da API.
|
|
699
|
+
|
|
700
|
+
Mitigações: menos round-trips (`BulkRequirement`, batch na app), filas (Sidekiq), cache de leitura. Detalhes: [`docs/cookbook/08-production-limitations.md`](docs/cookbook/08-production-limitations.md).
|
|
701
|
+
|
|
702
|
+
### `Thread.current` e Fibers
|
|
703
|
+
|
|
704
|
+
`Clicksign::Services#use` armazena o client em `Thread.current[:clicksign_client]`. Resources usam esse client dentro do bloco.
|
|
705
|
+
|
|
706
|
+
- **Compatível:** Puma (thread por request), Sidekiq, scripts.
|
|
707
|
+
- **Incompatível** com propagar contexto em **Fibers** (Falcon, async-ruby): o client do `use` pode não estar visível no Fiber que chama `Envelope.create`.
|
|
708
|
+
|
|
709
|
+
Mitigações: `Clicksign.configure` por processo (single-tenant), `Clicksign::Client.new` explícito no seu contexto async, ou evitar `Services` em stacks fiberizadas.
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
504
713
|
## Desenvolvimento
|
|
505
714
|
|
|
506
715
|
Clone o repositório e instale dependências de desenvolvimento:
|
|
@@ -515,17 +724,28 @@ bundle exec rspec
|
|
|
515
724
|
| `CLICKSIGN_API_KEY` | Token para testes contra sandbox (opcional) |
|
|
516
725
|
| `CLICKSIGN_API_BASE_URL` | URL da API (padrão sandbox nos specs de integração legados) |
|
|
517
726
|
|
|
518
|
-
A suíte
|
|
727
|
+
A suíte usa **WebMock** e não exige rede.
|
|
519
728
|
|
|
520
729
|
Estrutura relevante:
|
|
521
730
|
|
|
522
731
|
```
|
|
523
732
|
lib/clicksign/
|
|
524
|
-
|
|
733
|
+
retry_backoff.rb # Exponential backoff com full jitter
|
|
734
|
+
client.rb # HTTP (GET, POST, PATCH, DELETE), retry, timeouts
|
|
735
|
+
services.rb # Cliente por contexto (multi-conta via #use)
|
|
736
|
+
configuration.rb # Config global, environment, timeouts, retry
|
|
737
|
+
request_instrumentation.rb # Hooks compartilhados Client + BulkOperationsClient
|
|
738
|
+
instrumentation.rb # Eventos :request, :retry, :error
|
|
525
739
|
resource.rb # CRUD base, filtros, nested lists
|
|
526
740
|
resources/notarial/ # Envelope, Document, Signer, Requirement, ...
|
|
527
741
|
json_api/ # Serializer, Parser, bulk operations
|
|
528
742
|
docs/SPEC.md # mapa completo de resources e rotas
|
|
743
|
+
docs/WORKFLOW.md # fluxo notarial ponta a ponta
|
|
744
|
+
docs/README.md # índice da documentação
|
|
745
|
+
docs/cookbook/ # receitas: retries, bulk, webhooks, multi-cliente
|
|
746
|
+
docs/TROUBLESHOOTING.md # diagnóstico e erros comuns
|
|
747
|
+
docs/ARCHITECTURE.md # diagramas e camadas
|
|
748
|
+
docs/OBSERVABILITY.md # logs, métricas, OpenTelemetry
|
|
529
749
|
```
|
|
530
750
|
|
|
531
751
|
---
|
data/lib/clicksign/client.rb
CHANGED
|
@@ -6,6 +6,8 @@ require 'json'
|
|
|
6
6
|
|
|
7
7
|
module Clicksign
|
|
8
8
|
class Client
|
|
9
|
+
include RequestInstrumentation
|
|
10
|
+
|
|
9
11
|
HEADERS = {
|
|
10
12
|
'Content-Type' => 'application/vnd.api+json',
|
|
11
13
|
'Accept' => 'application/vnd.api+json',
|
|
@@ -68,24 +70,16 @@ module Clicksign
|
|
|
68
70
|
Clicksign::ServerError => e
|
|
69
71
|
raise unless e.retryable? && attempts <= @max_retries
|
|
70
72
|
|
|
71
|
-
delay =
|
|
72
|
-
|
|
73
|
-
method: request.method.downcase.to_sym,
|
|
74
|
-
path: resource_path(uri),
|
|
75
|
-
attempt: attempts,
|
|
76
|
-
max_retries: @max_retries,
|
|
77
|
-
error: e,
|
|
78
|
-
wait_ms: (delay * 1000).round,
|
|
79
|
-
})
|
|
73
|
+
delay = RetryBackoff.delay(attempts)
|
|
74
|
+
publish_retry(request, uri, attempts, e, delay)
|
|
80
75
|
sleep(delay)
|
|
81
76
|
retry
|
|
82
77
|
end
|
|
83
78
|
end
|
|
84
79
|
|
|
85
80
|
def execute_once(request, uri, attempt: 1)
|
|
86
|
-
start
|
|
87
|
-
context =
|
|
88
|
-
attempt: attempt }
|
|
81
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
82
|
+
context = request_context(request, uri, attempt)
|
|
89
83
|
response = http_request(request, uri)
|
|
90
84
|
handle_response(response, context, start)
|
|
91
85
|
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED => e
|
|
@@ -102,42 +96,16 @@ module Clicksign
|
|
|
102
96
|
end
|
|
103
97
|
|
|
104
98
|
def handle_response(response, context, start)
|
|
105
|
-
duration =
|
|
106
|
-
status = response.code.to_i
|
|
99
|
+
_response, status, duration = publish_http_outcome(response, context, start)
|
|
107
100
|
begin
|
|
108
101
|
ErrorHandler.call(response)
|
|
109
102
|
rescue Error => e
|
|
110
|
-
|
|
111
|
-
publish_event(:error, context, error: e, status: status, duration_ms: duration)
|
|
103
|
+
publish_http_error(context, e, status, duration)
|
|
112
104
|
raise
|
|
113
105
|
end
|
|
114
|
-
publish_event(:request, context, status: status, duration_ms: duration)
|
|
115
106
|
return nil if response.body.nil? || response.body.empty?
|
|
116
107
|
|
|
117
108
|
JSON.parse(response.body)
|
|
118
109
|
end
|
|
119
|
-
|
|
120
|
-
def handle_network_error(error, context, duration)
|
|
121
|
-
err = TimeoutError.new(error.message)
|
|
122
|
-
publish_event(:error, context, error: err, status: nil, duration_ms: duration)
|
|
123
|
-
raise err, error.message, error.backtrace
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def publish_event(type, context, extra)
|
|
127
|
-
Instrumentation.publish(type, context.merge(extra))
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def elapsed_ms(start)
|
|
131
|
-
((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(1)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def resource_path(uri)
|
|
135
|
-
base = URI.parse(@base_url).path
|
|
136
|
-
uri.path.delete_prefix(base)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def backoff_delay(attempt)
|
|
140
|
-
[0.5 * (2**(attempt - 1)), 30].min
|
|
141
|
-
end
|
|
142
110
|
end
|
|
143
111
|
end
|
|
@@ -18,8 +18,11 @@ module Clicksign
|
|
|
18
18
|
def publish(event, payload)
|
|
19
19
|
@callbacks[event].each do |cb|
|
|
20
20
|
cb.call(payload)
|
|
21
|
-
rescue StandardError
|
|
22
|
-
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
Clicksign.configuration.logger&.warn(
|
|
23
|
+
"[Clicksign] instrumentation callback error (#{event}): " \
|
|
24
|
+
"#{e.class}: #{e.message}",
|
|
25
|
+
)
|
|
23
26
|
end
|
|
24
27
|
end
|
|
25
28
|
|
|
@@ -7,6 +7,8 @@ require 'json'
|
|
|
7
7
|
module Clicksign
|
|
8
8
|
module JsonApi
|
|
9
9
|
class BulkOperationsClient
|
|
10
|
+
include RequestInstrumentation
|
|
11
|
+
|
|
10
12
|
HEADERS = {
|
|
11
13
|
'Content-Type' => 'application/vnd.api+json',
|
|
12
14
|
'Accept' => 'application/vnd.api+json',
|
|
@@ -23,23 +25,13 @@ module Clicksign
|
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def post(path, body:)
|
|
26
|
-
response = perform_post(path, body)
|
|
27
|
-
parsed = parse_response_body(response) || {}
|
|
28
|
-
|
|
29
|
-
return parsed if parsed.key?('atomic:results')
|
|
30
|
-
|
|
31
|
-
ErrorHandler.call(response)
|
|
32
|
-
parsed
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def perform_post(path, body)
|
|
38
28
|
uri = build_uri(path)
|
|
39
29
|
request = build_request(uri, body)
|
|
40
30
|
execute_with_retry(request, uri)
|
|
41
31
|
end
|
|
42
32
|
|
|
33
|
+
private
|
|
34
|
+
|
|
43
35
|
def build_request(uri, body)
|
|
44
36
|
request = Net::HTTP::Post.new(uri, headers)
|
|
45
37
|
request.body = body.to_json
|
|
@@ -50,19 +42,38 @@ module Clicksign
|
|
|
50
42
|
attempts = 0
|
|
51
43
|
begin
|
|
52
44
|
attempts += 1
|
|
53
|
-
|
|
45
|
+
execute_once(request, uri, attempt: attempts)
|
|
54
46
|
rescue Clicksign::TimeoutError => e
|
|
55
47
|
raise unless e.retryable? && attempts <= @max_retries
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
delay = RetryBackoff.delay(attempts)
|
|
50
|
+
publish_retry(request, uri, attempts, e, delay)
|
|
51
|
+
sleep(delay)
|
|
58
52
|
retry
|
|
59
53
|
end
|
|
60
54
|
end
|
|
61
55
|
|
|
62
|
-
def
|
|
63
|
-
|
|
56
|
+
def execute_once(request, uri, attempt: 1)
|
|
57
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
58
|
+
context = request_context(request, uri, attempt)
|
|
59
|
+
response = http_post(request, uri)
|
|
60
|
+
_response, status, duration = publish_http_outcome(response, context, start)
|
|
61
|
+
handle_bulk_body(response, context, status, duration)
|
|
64
62
|
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED => e
|
|
65
|
-
|
|
63
|
+
handle_network_error(e, context, start)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def handle_bulk_body(response, context, status, duration)
|
|
67
|
+
parsed = parse_response_body(response) || {}
|
|
68
|
+
return parsed if parsed.key?('atomic:results')
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
ErrorHandler.call(response)
|
|
72
|
+
rescue Error => e
|
|
73
|
+
publish_http_error(context, e, status, duration)
|
|
74
|
+
raise
|
|
75
|
+
end
|
|
76
|
+
parsed
|
|
66
77
|
end
|
|
67
78
|
|
|
68
79
|
def http_post(request, uri)
|
|
@@ -70,9 +81,8 @@ module Clicksign
|
|
|
70
81
|
use_ssl: uri.scheme == 'https',
|
|
71
82
|
open_timeout: @open_timeout,
|
|
72
83
|
read_timeout: @read_timeout,
|
|
73
|
-
write_timeout: @write_timeout
|
|
74
|
-
|
|
75
|
-
end
|
|
84
|
+
write_timeout: @write_timeout,
|
|
85
|
+
&proc { |http| http.request(request) })
|
|
76
86
|
end
|
|
77
87
|
|
|
78
88
|
def headers
|
|
@@ -15,7 +15,9 @@ module Clicksign
|
|
|
15
15
|
.select { |item| item.is_a?(Hash) && item['type'] }
|
|
16
16
|
.map { |item| build(item) }
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
links = raw['links'] if raw.key?('links')
|
|
19
|
+
|
|
20
|
+
{ data: data, included: included, links: links }
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def self.build(item)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clicksign
|
|
4
|
+
module RequestInstrumentation
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def publish_event(type, context, extra)
|
|
8
|
+
Instrumentation.publish(type, context.merge(extra))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def elapsed_ms(start)
|
|
12
|
+
((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(1)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def resource_path(uri)
|
|
16
|
+
base = URI.parse(@base_url).path
|
|
17
|
+
uri.path.delete_prefix(base)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def request_context(request, uri, attempt)
|
|
21
|
+
{
|
|
22
|
+
method: request.method.downcase.to_sym,
|
|
23
|
+
path: resource_path(uri),
|
|
24
|
+
attempt: attempt,
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def publish_retry(request, uri, attempt, error, delay)
|
|
29
|
+
Instrumentation.publish(:retry, {
|
|
30
|
+
method: request.method.downcase.to_sym,
|
|
31
|
+
path: resource_path(uri),
|
|
32
|
+
attempt: attempt,
|
|
33
|
+
max_retries: @max_retries,
|
|
34
|
+
error: error,
|
|
35
|
+
wait_ms: (delay * 1000).round,
|
|
36
|
+
})
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def handle_network_error(error, context, start)
|
|
40
|
+
duration = elapsed_ms(start)
|
|
41
|
+
err = TimeoutError.new(error.message)
|
|
42
|
+
publish_event(:error, context, error: err, status: nil, duration_ms: duration)
|
|
43
|
+
raise err, error.message, error.backtrace
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def publish_http_outcome(response, context, start)
|
|
47
|
+
duration = elapsed_ms(start)
|
|
48
|
+
status = response.code.to_i
|
|
49
|
+
publish_event(:request, context, status: status, duration_ms: duration)
|
|
50
|
+
[response, status, duration]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def publish_http_error(context, error, status, duration)
|
|
54
|
+
publish_event(:error, context, error: error, status: status, duration_ms: duration)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/clicksign/resource.rb
CHANGED
|
@@ -15,11 +15,15 @@ module Clicksign
|
|
|
15
15
|
self
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def
|
|
18
|
+
def with_includes(*types)
|
|
19
19
|
@builder.include(*types)
|
|
20
20
|
self
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def include(*types)
|
|
24
|
+
with_includes(*types)
|
|
25
|
+
end
|
|
26
|
+
|
|
23
27
|
def order(field)
|
|
24
28
|
@builder.order(field)
|
|
25
29
|
self
|
|
@@ -90,10 +94,10 @@ module Clicksign
|
|
|
90
94
|
@endpoint || "/#{resource_type}"
|
|
91
95
|
end
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
# Returns the first page with no query chain.
|
|
98
|
+
# Use +filter+ for filters, sort, pagination.
|
|
99
|
+
def list
|
|
100
|
+
fetch_list({})
|
|
97
101
|
end
|
|
98
102
|
|
|
99
103
|
def retrieve(id)
|
|
@@ -118,10 +122,28 @@ module Clicksign
|
|
|
118
122
|
QueryProxy.new(self, JsonApi::QueryBuilder.new.filter(**params))
|
|
119
123
|
end
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
# JSON:API sideload — use +with_includes+;
|
|
126
|
+
# +include+ also accepts +Module+ for Ruby mixins.
|
|
127
|
+
def with_includes(*types)
|
|
128
|
+
validate_jsonapi_include_types!(types)
|
|
122
129
|
QueryProxy.new(self, JsonApi::QueryBuilder.new.include(*types))
|
|
123
130
|
end
|
|
124
131
|
|
|
132
|
+
def include(*types)
|
|
133
|
+
modules, jsonapi = types.partition { |t| t.is_a?(Module) }
|
|
134
|
+
if modules.any? && jsonapi.any?
|
|
135
|
+
raise ArgumentError,
|
|
136
|
+
'cannot mix Module with JSON:API ' \
|
|
137
|
+
'include types — use with_includes for sideload'
|
|
138
|
+
end
|
|
139
|
+
if modules.any?
|
|
140
|
+
modules.each { |mod| super(mod) }
|
|
141
|
+
return self
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
with_includes(*types)
|
|
145
|
+
end
|
|
146
|
+
|
|
125
147
|
def order(field)
|
|
126
148
|
QueryProxy.new(self, JsonApi::QueryBuilder.new.order(field))
|
|
127
149
|
end
|
|
@@ -164,6 +186,17 @@ module Clicksign
|
|
|
164
186
|
Thread.current[:clicksign_client] || Clicksign.client
|
|
165
187
|
end
|
|
166
188
|
|
|
189
|
+
def validate_jsonapi_include_types!(types)
|
|
190
|
+
raise ArgumentError, 'at least one include type is required' if types.empty?
|
|
191
|
+
|
|
192
|
+
invalid = types.reject { |t| t.is_a?(String) || t.is_a?(Symbol) }
|
|
193
|
+
return if invalid.empty?
|
|
194
|
+
|
|
195
|
+
raise ArgumentError,
|
|
196
|
+
'JSON:API include types must be String or Symbol, ' \
|
|
197
|
+
"got: #{invalid.map(&:class).uniq.join(', ')}"
|
|
198
|
+
end
|
|
199
|
+
|
|
167
200
|
private
|
|
168
201
|
|
|
169
202
|
def fetch_list(params)
|
|
@@ -178,17 +211,30 @@ module Clicksign
|
|
|
178
211
|
page = 1
|
|
179
212
|
|
|
180
213
|
loop do
|
|
181
|
-
raw
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
214
|
+
raw = client.get(endpoint,
|
|
215
|
+
params: base.merge('page[number]' => page,
|
|
216
|
+
'page[size]' => per))
|
|
217
|
+
parsed = JsonApi::Parser.parse(raw)
|
|
218
|
+
items = parsed[:data].map { |item| build_instance(item) }
|
|
185
219
|
yield items
|
|
186
|
-
break
|
|
220
|
+
break unless more_pages?(parsed, items, per)
|
|
187
221
|
|
|
188
222
|
page += 1
|
|
189
223
|
end
|
|
190
224
|
end
|
|
191
225
|
|
|
226
|
+
def more_pages?(parsed, items, per)
|
|
227
|
+
links = parsed[:links]
|
|
228
|
+
unless links.nil?
|
|
229
|
+
next_link = links['next']
|
|
230
|
+
return false if next_link.nil? || next_link.to_s.empty?
|
|
231
|
+
|
|
232
|
+
return true
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
items.size >= per
|
|
236
|
+
end
|
|
237
|
+
|
|
192
238
|
def build_instance(data, parent_id: nil)
|
|
193
239
|
instance = allocate
|
|
194
240
|
instance.send(:load_data, data, parent_id: parent_id)
|
|
@@ -196,6 +242,8 @@ module Clicksign
|
|
|
196
242
|
end
|
|
197
243
|
|
|
198
244
|
def infer_resource_type
|
|
245
|
+
return 'resources' if name.nil? || name.empty?
|
|
246
|
+
|
|
199
247
|
"#{name.split('::').last
|
|
200
248
|
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
201
249
|
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
@@ -239,11 +287,10 @@ module Clicksign
|
|
|
239
287
|
|
|
240
288
|
def method_missing(name, *args, &block)
|
|
241
289
|
key = name.to_s.delete_suffix('=')
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
end
|
|
290
|
+
return super unless @_attributes&.key?(key)
|
|
291
|
+
|
|
292
|
+
@_attributes[key] = args.first if name.to_s.end_with?('=')
|
|
293
|
+
@_attributes[key]
|
|
247
294
|
end
|
|
248
295
|
|
|
249
296
|
def respond_to_missing?(name, include_private = false)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clicksign
|
|
4
|
+
module RetryBackoff
|
|
5
|
+
BASE_SECONDS = 0.5
|
|
6
|
+
CAP_SECONDS = 30.0
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def ceiling(attempt)
|
|
11
|
+
[BASE_SECONDS * (2**(attempt - 1)), CAP_SECONDS].min.to_f
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Full jitter: uniform in [0, ceiling) to spread retries and avoid thundering herd.
|
|
15
|
+
def delay(attempt, rng: Random)
|
|
16
|
+
max = ceiling(attempt)
|
|
17
|
+
return 0.0 if max <= 0
|
|
18
|
+
|
|
19
|
+
rng.rand(max)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/clicksign/version.rb
CHANGED
data/lib/clicksign.rb
CHANGED
|
@@ -9,6 +9,8 @@ require_relative 'clicksign/configuration'
|
|
|
9
9
|
require_relative 'clicksign/errors'
|
|
10
10
|
require_relative 'clicksign/error_handler'
|
|
11
11
|
require_relative 'clicksign/instrumentation'
|
|
12
|
+
require_relative 'clicksign/request_instrumentation'
|
|
13
|
+
require_relative 'clicksign/retry_backoff'
|
|
12
14
|
require_relative 'clicksign/webhook'
|
|
13
15
|
require_relative 'clicksign/json_api/query_builder'
|
|
14
16
|
require_relative 'clicksign/json_api/serializer'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clicksign-ruby-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Clicksign
|
|
@@ -30,6 +30,7 @@ files:
|
|
|
30
30
|
- lib/clicksign/json_api/parser.rb
|
|
31
31
|
- lib/clicksign/json_api/query_builder.rb
|
|
32
32
|
- lib/clicksign/json_api/serializer.rb
|
|
33
|
+
- lib/clicksign/request_instrumentation.rb
|
|
33
34
|
- lib/clicksign/resource.rb
|
|
34
35
|
- lib/clicksign/resources/acceptance_term/whatsapp.rb
|
|
35
36
|
- lib/clicksign/resources/access_control_list.rb
|
|
@@ -49,6 +50,7 @@ files:
|
|
|
49
50
|
- lib/clicksign/resources/template_field.rb
|
|
50
51
|
- lib/clicksign/resources/user.rb
|
|
51
52
|
- lib/clicksign/resources/webhook.rb
|
|
53
|
+
- lib/clicksign/retry_backoff.rb
|
|
52
54
|
- lib/clicksign/services.rb
|
|
53
55
|
- lib/clicksign/version.rb
|
|
54
56
|
- lib/clicksign/webhook.rb
|