api_particulier 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 +23 -0
- data/LICENSE +21 -0
- data/README.md +66 -0
- data/lib/api_particulier/client.rb +76 -0
- data/lib/api_particulier/commons/auth/bearer_token.rb +21 -0
- data/lib/api_particulier/commons/auth/strategy.rb +13 -0
- data/lib/api_particulier/commons/client_base.rb +128 -0
- data/lib/api_particulier/commons/configuration.rb +101 -0
- data/lib/api_particulier/commons/errors.rb +84 -0
- data/lib/api_particulier/commons/middleware/authentication.rb +49 -0
- data/lib/api_particulier/commons/middleware/envelope.rb +31 -0
- data/lib/api_particulier/commons/middleware/error_handler.rb +106 -0
- data/lib/api_particulier/commons/middleware/logging.rb +70 -0
- data/lib/api_particulier/commons/middleware/rate_limit.rb +17 -0
- data/lib/api_particulier/commons/rate_limit.rb +54 -0
- data/lib/api_particulier/commons/response.rb +32 -0
- data/lib/api_particulier/commons/siren.rb +37 -0
- data/lib/api_particulier/commons/siret.rb +37 -0
- data/lib/api_particulier/commons/user_agent.rb +16 -0
- data/lib/api_particulier/commons/version.rb +7 -0
- data/lib/api_particulier/commons.rb +23 -0
- data/lib/api_particulier/resources/ants.rb +28 -0
- data/lib/api_particulier/resources/cnous.rb +65 -0
- data/lib/api_particulier/resources/dsnj.rb +42 -0
- data/lib/api_particulier/resources/dss.rb +238 -0
- data/lib/api_particulier/resources/france_travail.rb +42 -0
- data/lib/api_particulier/resources/gip_mds.rb +42 -0
- data/lib/api_particulier/resources/men.rb +34 -0
- data/lib/api_particulier/resources/mesri.rb +56 -0
- data/lib/api_particulier/resources/sdh.rb +28 -0
- data/lib/api_particulier/version.rb +3 -0
- data/lib/api_particulier.rb +4 -0
- metadata +107 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 69af0dca4424823c2fa2cbaba141f4ccde38d45309ce3f4a878e3ae6105728aa
|
|
4
|
+
data.tar.gz: 95b4443df163a996ca72416060d212b69fc2efc4dc00981699aa3d486e285993
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d5e1f0af171ee5808171f3e0d13b5cae816d0932422ba7eb5b083b148c339e31f2d9f513f0e874d71246c92062a463a2c9faac8c6d4143b9385685e7817da2b9
|
|
7
|
+
data.tar.gz: 6c98f422268ae3cecf8f9e56296a1b3df56e5fa499e5b31f541b0e6c1598e55587d8ac5ad963275c87da12f9dd4689c5e7afd7de966a7ad8514e6601cb046ba9
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `api_particulier` (Ruby) are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/) and the project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Initial release — conforms to `clients/SPECS.md` §1–§20.
|
|
11
|
+
- `production` / `staging` environments with `base_url` override.
|
|
12
|
+
- `BearerToken` auth strategy with a pluggable `Auth::Strategy` seam.
|
|
13
|
+
- Client-level `default_params` with per-call override for `recipient`
|
|
14
|
+
(Particulier does not require `context` / `object`).
|
|
15
|
+
- Local SIRET / SIREN validation before any HTTP call.
|
|
16
|
+
- `Response` value object (`data`, `links`, `meta`, `raw`, `http_status`,
|
|
17
|
+
`headers`, `rate_limit`).
|
|
18
|
+
- Full JSON:API exception hierarchy matching `clients/SPECS.md` §6.
|
|
19
|
+
- `RateLimit-*` header parsing and `retry_after` on `RateLimitError`.
|
|
20
|
+
- Opt-in retry middleware via `faraday-retry`.
|
|
21
|
+
- 9 resource modules scaffolded from the OpenAPI spec, grouped by provider.
|
|
22
|
+
- Logging middleware redacts query strings by default (PII protection).
|
|
23
|
+
- `examples/{basic,error_handling,retry}.rb`.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DINUM (Direction Interministérielle du Numérique)
|
|
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,66 @@
|
|
|
1
|
+
# api_particulier
|
|
2
|
+
|
|
3
|
+
Ruby client for [API Particulier v3](https://particulier.api.gouv.fr). Conforms
|
|
4
|
+
to [`clients/SPECS.md`](../../SPECS.md).
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
# Gemfile
|
|
10
|
+
gem 'api_particulier'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
client = ApiParticulier::Client.new(
|
|
17
|
+
token: ENV['API_PARTICULIER_TOKEN'],
|
|
18
|
+
environment: :staging, # or :production (default)
|
|
19
|
+
default_params: { recipient: '13002526500013' }
|
|
20
|
+
)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
ENV vars: `API_PARTICULIER_TOKEN`, `API_PARTICULIER_ENV`,
|
|
24
|
+
`API_PARTICULIER_BASE_URL`.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
response = client.ants.extrait_immatriculation_vehicule(immatriculation: 'AA-123-BB')
|
|
30
|
+
response.data # => { "titulaire" => { ... } }
|
|
31
|
+
response.rate_limit.remaining
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Low-level escape hatch: `client.get(path, params: {...})`.
|
|
35
|
+
|
|
36
|
+
## Error handling
|
|
37
|
+
|
|
38
|
+
See the entreprise README and `clients/SPECS.md` §6 — same hierarchy applies
|
|
39
|
+
under `ApiParticulier::Commons::*`.
|
|
40
|
+
|
|
41
|
+
## Testing
|
|
42
|
+
|
|
43
|
+
No embedded mock mode. Stub with WebMock:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
stub_request(:get, %r{https://staging\.particulier\.api\.gouv\.fr/v3/ants/.+})
|
|
47
|
+
.with(headers: { 'Authorization' => 'Bearer test' })
|
|
48
|
+
.to_return(status: 200,
|
|
49
|
+
headers: { 'Content-Type' => 'application/json' },
|
|
50
|
+
body: { data: {}, links: {}, meta: {} }.to_json)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
PII notice: the default logger redacts the query string on Particulier
|
|
54
|
+
requests, since query params carry personal data (names, DOB, INE).
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
Shared code comes from `clients/ruby/commons/`; resources are scaffolded from
|
|
59
|
+
the OpenAPI spec:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
clients/ruby/bin/sync_commons
|
|
63
|
+
clients/ruby/bin/scaffold_resources --api particulier
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
CI checks both are in sync.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require_relative 'commons'
|
|
2
|
+
|
|
3
|
+
# <scaffold:requires:begin>
|
|
4
|
+
require_relative 'resources/ants'
|
|
5
|
+
require_relative 'resources/cnous'
|
|
6
|
+
require_relative 'resources/dsnj'
|
|
7
|
+
require_relative 'resources/dss'
|
|
8
|
+
require_relative 'resources/france_travail'
|
|
9
|
+
require_relative 'resources/gip_mds'
|
|
10
|
+
require_relative 'resources/men'
|
|
11
|
+
require_relative 'resources/mesri'
|
|
12
|
+
require_relative 'resources/sdh'
|
|
13
|
+
# <scaffold:requires:end>
|
|
14
|
+
|
|
15
|
+
module ApiParticulier
|
|
16
|
+
BASE_URLS = {
|
|
17
|
+
Commons::Configuration::PRODUCTION => 'https://particulier.api.gouv.fr',
|
|
18
|
+
Commons::Configuration::STAGING => 'https://staging.particulier.api.gouv.fr'
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
class Client < Commons::ClientBase
|
|
22
|
+
REQUIRED_PARAMS = %i[recipient].freeze
|
|
23
|
+
SIRET_PARAMS = %i[recipient].freeze
|
|
24
|
+
|
|
25
|
+
def initialize(token: nil, environment: nil, default_params: {}, base_url: nil, auth_strategy: nil, **opts)
|
|
26
|
+
env_token = token || ENV.fetch('API_PARTICULIER_TOKEN', nil)
|
|
27
|
+
env_env = (environment || ENV.fetch('API_PARTICULIER_ENV', :production)).to_sym
|
|
28
|
+
|
|
29
|
+
config = Commons::Configuration.new(
|
|
30
|
+
base_urls: BASE_URLS,
|
|
31
|
+
token: env_token,
|
|
32
|
+
auth_strategy: auth_strategy,
|
|
33
|
+
environment: env_env,
|
|
34
|
+
base_url: base_url || ENV.fetch('API_PARTICULIER_BASE_URL', nil),
|
|
35
|
+
default_params: default_params,
|
|
36
|
+
user_agent: opts[:user_agent] || Commons::UserAgent.build(product: 'api-particulier-ruby', version: VERSION),
|
|
37
|
+
open_timeout: opts[:open_timeout] || Commons::Configuration::DEFAULT_OPEN_TIMEOUT,
|
|
38
|
+
read_timeout: opts[:read_timeout] || Commons::Configuration::DEFAULT_READ_TIMEOUT,
|
|
39
|
+
retry: opts[:retry],
|
|
40
|
+
logger: opts[:logger],
|
|
41
|
+
adapter: opts[:adapter]
|
|
42
|
+
)
|
|
43
|
+
super(config, product: :particulier)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# <scaffold:resources:begin>
|
|
47
|
+
def ants
|
|
48
|
+
@ants ||= Resources::Ants.new(self)
|
|
49
|
+
end
|
|
50
|
+
def cnous
|
|
51
|
+
@cnous ||= Resources::Cnous.new(self)
|
|
52
|
+
end
|
|
53
|
+
def dsnj
|
|
54
|
+
@dsnj ||= Resources::Dsnj.new(self)
|
|
55
|
+
end
|
|
56
|
+
def dss
|
|
57
|
+
@dss ||= Resources::Dss.new(self)
|
|
58
|
+
end
|
|
59
|
+
def france_travail
|
|
60
|
+
@france_travail ||= Resources::FranceTravail.new(self)
|
|
61
|
+
end
|
|
62
|
+
def gip_mds
|
|
63
|
+
@gip_mds ||= Resources::GipMds.new(self)
|
|
64
|
+
end
|
|
65
|
+
def men
|
|
66
|
+
@men ||= Resources::Men.new(self)
|
|
67
|
+
end
|
|
68
|
+
def mesri
|
|
69
|
+
@mesri ||= Resources::Mesri.new(self)
|
|
70
|
+
end
|
|
71
|
+
def sdh
|
|
72
|
+
@sdh ||= Resources::Sdh.new(self)
|
|
73
|
+
end
|
|
74
|
+
# <scaffold:resources:end>
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# DO NOT EDIT — generated from clients/ruby/commons/ (source digest: 903f3b3aca1a59a6cb1a53ab98f72c365486fc1f).
|
|
3
|
+
# Regenerate via clients/ruby/bin/sync_commons.
|
|
4
|
+
|
|
5
|
+
require_relative 'strategy'
|
|
6
|
+
|
|
7
|
+
module ApiParticulier::Commons
|
|
8
|
+
module Auth
|
|
9
|
+
class BearerToken < Strategy
|
|
10
|
+
def initialize(token)
|
|
11
|
+
raise ArgumentError, 'token must be a non-empty string' if token.nil? || token.to_s.strip.empty?
|
|
12
|
+
|
|
13
|
+
@token = token.to_s
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def apply(request)
|
|
17
|
+
request.headers['Authorization'] = "Bearer #{@token}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# DO NOT EDIT — generated from clients/ruby/commons/ (source digest: 903f3b3aca1a59a6cb1a53ab98f72c365486fc1f).
|
|
3
|
+
# Regenerate via clients/ruby/bin/sync_commons.
|
|
4
|
+
|
|
5
|
+
module ApiParticulier::Commons
|
|
6
|
+
module Auth
|
|
7
|
+
class Strategy
|
|
8
|
+
def apply(request)
|
|
9
|
+
raise NotImplementedError, "#{self.class} must implement #apply(request)"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# DO NOT EDIT — generated from clients/ruby/commons/ (source digest: 903f3b3aca1a59a6cb1a53ab98f72c365486fc1f).
|
|
3
|
+
# Regenerate via clients/ruby/bin/sync_commons.
|
|
4
|
+
|
|
5
|
+
require 'faraday'
|
|
6
|
+
begin
|
|
7
|
+
require 'faraday/retry'
|
|
8
|
+
rescue LoadError
|
|
9
|
+
# faraday-retry is optional; the :retry middleware is only used when the
|
|
10
|
+
# consumer opts in.
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require_relative 'middleware/authentication'
|
|
14
|
+
require_relative 'middleware/logging'
|
|
15
|
+
require_relative 'middleware/rate_limit'
|
|
16
|
+
require_relative 'middleware/error_handler'
|
|
17
|
+
require_relative 'middleware/envelope'
|
|
18
|
+
require_relative 'response'
|
|
19
|
+
require_relative 'siret'
|
|
20
|
+
require_relative 'errors'
|
|
21
|
+
|
|
22
|
+
module ApiParticulier::Commons
|
|
23
|
+
class ClientBase
|
|
24
|
+
attr_reader :configuration
|
|
25
|
+
|
|
26
|
+
REQUIRED_PARAMS = %i[recipient context object].freeze
|
|
27
|
+
SIRET_PARAMS = %i[recipient].freeze
|
|
28
|
+
|
|
29
|
+
def initialize(configuration, product:)
|
|
30
|
+
@configuration = configuration
|
|
31
|
+
@product = product
|
|
32
|
+
@connection = build_connection
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def get(path, params: {}, headers: {})
|
|
36
|
+
merged = merge_params(params)
|
|
37
|
+
validate_required!(merged)
|
|
38
|
+
validate_sirets!(merged)
|
|
39
|
+
|
|
40
|
+
response = @connection.get(path, clean(merged), headers)
|
|
41
|
+
build_response(response)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def merge_params(params)
|
|
47
|
+
defaults = @configuration.default_params.transform_keys(&:to_s)
|
|
48
|
+
defaults.merge((params || {}).transform_keys(&:to_s))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def required_params_for(_params)
|
|
52
|
+
self.class::REQUIRED_PARAMS
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def siret_params_for(_params)
|
|
56
|
+
self.class::SIRET_PARAMS
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validate_required!(params)
|
|
60
|
+
required_params_for(params).each do |key|
|
|
61
|
+
next unless blank?(params[key.to_s])
|
|
62
|
+
|
|
63
|
+
raise MissingParameterError, "required parameter #{key.inspect} is missing"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def validate_sirets!(params)
|
|
68
|
+
siret_params_for(params).each do |key|
|
|
69
|
+
value = params[key.to_s]
|
|
70
|
+
next if value.nil?
|
|
71
|
+
|
|
72
|
+
Siret.validate!(value, parameter: key)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def blank?(value)
|
|
77
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def clean(params)
|
|
81
|
+
params.reject { |_, v| v.nil? }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def build_response(response)
|
|
85
|
+
Response.new(
|
|
86
|
+
raw: response.body,
|
|
87
|
+
http_status: response.status,
|
|
88
|
+
headers: response.headers,
|
|
89
|
+
rate_limit: response.env[Middleware::RateLimitParser::ENV_KEY]
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_connection
|
|
94
|
+
cfg = @configuration
|
|
95
|
+
Faraday.new(url: cfg.base_url) do |conn|
|
|
96
|
+
conn.options.open_timeout = cfg.open_timeout
|
|
97
|
+
conn.options.timeout = cfg.read_timeout
|
|
98
|
+
|
|
99
|
+
conn.headers['User-Agent'] = cfg.user_agent if cfg.user_agent
|
|
100
|
+
conn.headers['Accept'] = 'application/json'
|
|
101
|
+
|
|
102
|
+
if cfg.retry && defined?(Faraday::Retry)
|
|
103
|
+
conn.request :retry,
|
|
104
|
+
max: cfg.retry.fetch(:max, 2),
|
|
105
|
+
retry_statuses: cfg.retry.fetch(:on_status, [429, 502, 503]),
|
|
106
|
+
methods: %i[get],
|
|
107
|
+
interval: cfg.retry.fetch(:interval, 0.5),
|
|
108
|
+
backoff_factor: cfg.retry.fetch(:backoff_factor, 2),
|
|
109
|
+
exceptions: [
|
|
110
|
+
ApiParticulier::Commons::RateLimitError,
|
|
111
|
+
ApiParticulier::Commons::ProviderError,
|
|
112
|
+
ApiParticulier::Commons::ProviderUnavailableError,
|
|
113
|
+
ApiParticulier::Commons::TransportError
|
|
114
|
+
]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
conn.use Middleware::Authentication, auth_strategy: cfg.auth_strategy
|
|
118
|
+
conn.use Middleware::Logging, logger: cfg.logger, redact_query: @product == :particulier
|
|
119
|
+
|
|
120
|
+
conn.use Middleware::RateLimitParser
|
|
121
|
+
conn.use Middleware::ErrorHandler
|
|
122
|
+
conn.use Middleware::Envelope
|
|
123
|
+
|
|
124
|
+
conn.adapter(cfg.adapter || Faraday.default_adapter)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# DO NOT EDIT — generated from clients/ruby/commons/ (source digest: 903f3b3aca1a59a6cb1a53ab98f72c365486fc1f).
|
|
3
|
+
# Regenerate via clients/ruby/bin/sync_commons.
|
|
4
|
+
|
|
5
|
+
require_relative 'auth/bearer_token'
|
|
6
|
+
|
|
7
|
+
module ApiParticulier::Commons
|
|
8
|
+
class Configuration
|
|
9
|
+
PRODUCTION = :production
|
|
10
|
+
STAGING = :staging
|
|
11
|
+
ENVIRONMENTS = [PRODUCTION, STAGING].freeze
|
|
12
|
+
|
|
13
|
+
DEFAULT_OPEN_TIMEOUT = 5
|
|
14
|
+
DEFAULT_READ_TIMEOUT = 30
|
|
15
|
+
|
|
16
|
+
attr_reader :base_url,
|
|
17
|
+
:environment,
|
|
18
|
+
:auth_strategy,
|
|
19
|
+
:default_params,
|
|
20
|
+
:open_timeout,
|
|
21
|
+
:read_timeout,
|
|
22
|
+
:retry,
|
|
23
|
+
:logger,
|
|
24
|
+
:user_agent,
|
|
25
|
+
:adapter
|
|
26
|
+
|
|
27
|
+
def initialize(
|
|
28
|
+
base_urls:,
|
|
29
|
+
token: nil,
|
|
30
|
+
auth_strategy: nil,
|
|
31
|
+
environment: PRODUCTION,
|
|
32
|
+
base_url: nil,
|
|
33
|
+
default_params: {},
|
|
34
|
+
open_timeout: DEFAULT_OPEN_TIMEOUT,
|
|
35
|
+
read_timeout: DEFAULT_READ_TIMEOUT,
|
|
36
|
+
retry: nil,
|
|
37
|
+
logger: nil,
|
|
38
|
+
user_agent: nil,
|
|
39
|
+
adapter: nil
|
|
40
|
+
)
|
|
41
|
+
@base_urls = base_urls
|
|
42
|
+
resolved_env = resolve_environment(environment)
|
|
43
|
+
@environment = resolved_env
|
|
44
|
+
@explicit_base_url = !base_url.nil?
|
|
45
|
+
@base_url = base_url || base_urls.fetch(resolved_env)
|
|
46
|
+
@auth_strategy = auth_strategy || build_bearer_strategy(token)
|
|
47
|
+
@default_params = default_params.freeze
|
|
48
|
+
@open_timeout = open_timeout
|
|
49
|
+
@read_timeout = read_timeout
|
|
50
|
+
@retry = binding.local_variable_get(:retry)
|
|
51
|
+
@logger = logger
|
|
52
|
+
@user_agent = user_agent
|
|
53
|
+
@adapter = adapter
|
|
54
|
+
freeze
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def with(**overrides)
|
|
58
|
+
self.class.new(**current_attrs.merge(overrides))
|
|
59
|
+
end
|
|
60
|
+
alias copy with
|
|
61
|
+
|
|
62
|
+
def production?
|
|
63
|
+
environment == PRODUCTION
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def staging?
|
|
67
|
+
environment == STAGING
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def current_attrs
|
|
73
|
+
{
|
|
74
|
+
base_urls: @base_urls,
|
|
75
|
+
auth_strategy: auth_strategy,
|
|
76
|
+
environment: environment,
|
|
77
|
+
base_url: @explicit_base_url ? base_url : nil,
|
|
78
|
+
default_params: default_params,
|
|
79
|
+
open_timeout: open_timeout,
|
|
80
|
+
read_timeout: read_timeout,
|
|
81
|
+
retry: @retry,
|
|
82
|
+
logger: logger,
|
|
83
|
+
user_agent: user_agent,
|
|
84
|
+
adapter: adapter
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def resolve_environment(value)
|
|
89
|
+
env = value.to_sym
|
|
90
|
+
return env if ENVIRONMENTS.include?(env)
|
|
91
|
+
|
|
92
|
+
raise ArgumentError, "environment must be one of #{ENVIRONMENTS.inspect}; got #{value.inspect}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def build_bearer_strategy(token)
|
|
96
|
+
return nil if token.nil?
|
|
97
|
+
|
|
98
|
+
Auth::BearerToken.new(token)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# DO NOT EDIT — generated from clients/ruby/commons/ (source digest: 903f3b3aca1a59a6cb1a53ab98f72c365486fc1f).
|
|
3
|
+
# Regenerate via clients/ruby/bin/sync_commons.
|
|
4
|
+
|
|
5
|
+
module ApiParticulier::Commons
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
attr_reader :http_status, :errors, :method, :url
|
|
8
|
+
|
|
9
|
+
def initialize(message = nil, http_status: nil, errors: [], method: nil, url: nil)
|
|
10
|
+
super(message || default_message(http_status, errors))
|
|
11
|
+
@http_status = http_status
|
|
12
|
+
@errors = errors || []
|
|
13
|
+
@method = method
|
|
14
|
+
@url = url
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def first_error
|
|
18
|
+
errors.first || {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def first_error_code
|
|
22
|
+
first_error['code'] || first_error[:code]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def first_error_title
|
|
26
|
+
first_error['title'] || first_error[:title]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def first_error_detail
|
|
30
|
+
first_error['detail'] || first_error[:detail]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def first_error_source
|
|
34
|
+
first_error['source'] || first_error[:source]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def first_error_meta
|
|
38
|
+
first_error['meta'] || first_error[:meta] || {}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def default_message(http_status, errors)
|
|
44
|
+
first = (errors || []).first || {}
|
|
45
|
+
title = first['title'] || first[:title]
|
|
46
|
+
detail = first['detail'] || first[:detail]
|
|
47
|
+
parts = [http_status, title, detail].compact
|
|
48
|
+
parts.empty? ? self.class.name : parts.join(' — ')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class ClientError < Error; end
|
|
53
|
+
class AuthenticationError < ClientError; end
|
|
54
|
+
class AuthorizationError < ClientError; end
|
|
55
|
+
class NotFoundError < ClientError; end
|
|
56
|
+
class ConflictError < ClientError; end
|
|
57
|
+
class ValidationError < ClientError; end
|
|
58
|
+
|
|
59
|
+
class RateLimitError < ClientError
|
|
60
|
+
attr_reader :retry_after
|
|
61
|
+
|
|
62
|
+
def initialize(message = nil, retry_after: nil, **kwargs)
|
|
63
|
+
super(message, **kwargs)
|
|
64
|
+
@retry_after = retry_after
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class ServerError < Error; end
|
|
69
|
+
class ProviderError < ServerError
|
|
70
|
+
attr_reader :retry_after
|
|
71
|
+
|
|
72
|
+
def initialize(message = nil, retry_after: nil, **kwargs)
|
|
73
|
+
super(message, **kwargs)
|
|
74
|
+
@retry_after = retry_after
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
class ProviderUnavailableError < ServerError; end
|
|
78
|
+
|
|
79
|
+
class TransportError < Error; end
|
|
80
|
+
|
|
81
|
+
class InvalidSiretError < ArgumentError; end
|
|
82
|
+
class InvalidSirenError < ArgumentError; end
|
|
83
|
+
class MissingParameterError < ArgumentError; end
|
|
84
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# DO NOT EDIT — generated from clients/ruby/commons/ (source digest: 903f3b3aca1a59a6cb1a53ab98f72c365486fc1f).
|
|
3
|
+
# Regenerate via clients/ruby/bin/sync_commons.
|
|
4
|
+
|
|
5
|
+
require 'faraday'
|
|
6
|
+
|
|
7
|
+
module ApiParticulier::Commons
|
|
8
|
+
module Middleware
|
|
9
|
+
class Authentication < Faraday::Middleware
|
|
10
|
+
def initialize(app, auth_strategy:)
|
|
11
|
+
super(app)
|
|
12
|
+
@auth_strategy = auth_strategy
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def on_request(env)
|
|
16
|
+
return if @auth_strategy.nil?
|
|
17
|
+
|
|
18
|
+
request = RequestWrapper.new(env)
|
|
19
|
+
begin
|
|
20
|
+
@auth_strategy.apply(request)
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
raise ApiParticulier::Commons::AuthenticationError.new(
|
|
23
|
+
"auth strategy raised: #{e.message}",
|
|
24
|
+
method: env.method,
|
|
25
|
+
url: env.url.to_s
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class RequestWrapper
|
|
31
|
+
def initialize(env)
|
|
32
|
+
@env = env
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def headers
|
|
36
|
+
@env.request_headers
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def method
|
|
40
|
+
@env.method
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def url
|
|
44
|
+
@env.url
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# DO NOT EDIT — generated from clients/ruby/commons/ (source digest: 903f3b3aca1a59a6cb1a53ab98f72c365486fc1f).
|
|
3
|
+
# Regenerate via clients/ruby/bin/sync_commons.
|
|
4
|
+
|
|
5
|
+
require 'faraday'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module ApiParticulier::Commons
|
|
9
|
+
module Middleware
|
|
10
|
+
class Envelope < Faraday::Middleware
|
|
11
|
+
def on_complete(env)
|
|
12
|
+
body = env.body
|
|
13
|
+
return if body.nil? || body.is_a?(Hash) || body.is_a?(Array)
|
|
14
|
+
return unless body.is_a?(String) && !body.empty?
|
|
15
|
+
|
|
16
|
+
parsed =
|
|
17
|
+
begin
|
|
18
|
+
JSON.parse(body)
|
|
19
|
+
rescue JSON::ParserError
|
|
20
|
+
raise ApiParticulier::Commons::TransportError.new(
|
|
21
|
+
"invalid JSON body: #{body[0, 200]}",
|
|
22
|
+
method: env.method,
|
|
23
|
+
url: env.url.to_s
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
env.body = parsed
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|