dni_peru 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/.env.example +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +42 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +322 -0
- data/Rakefile +10 -0
- data/TEST_COVERAGE.md +122 -0
- data/dni_peru.gemspec +34 -0
- data/examples/basic_usage.rb +103 -0
- data/examples/rails_integration.rb +133 -0
- data/examples/test_config.rb +103 -0
- data/lib/dni_peru/client.rb +46 -0
- data/lib/dni_peru/configuration.rb +19 -0
- data/lib/dni_peru/errors.rb +10 -0
- data/lib/dni_peru/providers/api_peru_dev.rb +33 -0
- data/lib/dni_peru/providers/apis_net_pe.rb +32 -0
- data/lib/dni_peru/providers/base.rb +52 -0
- data/lib/dni_peru/providers/decolecta.rb +32 -0
- data/lib/dni_peru/response.rb +51 -0
- data/lib/dni_peru/version.rb +3 -0
- data/lib/dni_peru.rb +24 -0
- metadata +99 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7847ab9a63151aa066c6e6132fb8c153607a992011a09029718bb7de1f4aca2e
|
|
4
|
+
data.tar.gz: b68a9e93403b81cb5add28774122308fcbeb046de481378a5fd6793139fa1034
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ab9be7bab4c723630871fc454df03c13ba5e17b8c2ba9233b1ec6ce6b6628fc3c4e7a6a150f4b334deb20cd3d44cacb41d0419f6875e7f088e670ea999131dca
|
|
7
|
+
data.tar.gz: 0ec1fefcc683bcd0af7a048d2c4203e9e5b3464b78ebd97c2739520233cd8870c1320bd458740bb37904493113246f88d278e8a02f3071ca8dc5d871a8d23e0c
|
data/.env.example
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Decolecta API Configuration
|
|
2
|
+
# Get your API key from: https://decolecta.com/profile/
|
|
3
|
+
DECOLECTA_API_KEY=your_decolecta_api_key_here
|
|
4
|
+
|
|
5
|
+
# ApiPeruDev API Configuration (optional alternative provider)
|
|
6
|
+
# Get your API key from: https://apiperu.dev/
|
|
7
|
+
API_PERU_DEV_KEY=your_apiperu_dev_key_here
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 2.7
|
|
3
|
+
NewCops: enable
|
|
4
|
+
Exclude:
|
|
5
|
+
- 'vendor/**/*'
|
|
6
|
+
- 'bin/**/*'
|
|
7
|
+
|
|
8
|
+
Style/StringLiterals:
|
|
9
|
+
Enabled: true
|
|
10
|
+
EnforcedStyle: double_quotes
|
|
11
|
+
|
|
12
|
+
Style/StringLiteralsInInterpolation:
|
|
13
|
+
Enabled: true
|
|
14
|
+
EnforcedStyle: double_quotes
|
|
15
|
+
|
|
16
|
+
Style/Documentation:
|
|
17
|
+
Enabled: false
|
|
18
|
+
|
|
19
|
+
Metrics/BlockLength:
|
|
20
|
+
Exclude:
|
|
21
|
+
- 'spec/**/*'
|
|
22
|
+
- '*.gemspec'
|
|
23
|
+
|
|
24
|
+
Metrics/MethodLength:
|
|
25
|
+
Exclude:
|
|
26
|
+
- 'examples/**/*'
|
|
27
|
+
|
|
28
|
+
Metrics/AbcSize:
|
|
29
|
+
Exclude:
|
|
30
|
+
- 'examples/**/*'
|
|
31
|
+
|
|
32
|
+
Metrics/CyclomaticComplexity:
|
|
33
|
+
Max: 10
|
|
34
|
+
|
|
35
|
+
Metrics/PerceivedComplexity:
|
|
36
|
+
Max: 10
|
|
37
|
+
|
|
38
|
+
Layout/LineLength:
|
|
39
|
+
Max: 120
|
|
40
|
+
|
|
41
|
+
Style/FrozenStringLiteralComment:
|
|
42
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2025-11-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Initial release
|
|
7
|
+
- Support for multiple API providers
|
|
8
|
+
- Built-in providers: Decolecta (decolecta.com) and ApiPeruDev (apiperu.dev)
|
|
9
|
+
- Decolecta as default provider (official RENIEC data source)
|
|
10
|
+
- Configuration system with API key management
|
|
11
|
+
- Comprehensive error handling
|
|
12
|
+
- Response normalization across providers
|
|
13
|
+
- DNI validation (8-digit format)
|
|
14
|
+
- Configurable timeouts and automatic retries
|
|
15
|
+
- Full documentation and usage examples
|
|
16
|
+
- RSpec test suite with WebMock
|
|
17
|
+
- Rails integration guide
|
|
18
|
+
|
|
19
|
+
### Security
|
|
20
|
+
- HTTPS-only connections
|
|
21
|
+
- Bearer token authentication
|
|
22
|
+
- API key validation
|
|
23
|
+
- Backend-only access (CORS disabled)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ruben Paz
|
|
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,322 @@
|
|
|
1
|
+
# DniPeru
|
|
2
|
+
|
|
3
|
+
A Ruby gem that provides a unified interface to query DNI (Documento Nacional de Identidad) information from multiple Peruvian API providers.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'dni_peru'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle install
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install dni_peru
|
|
20
|
+
|
|
21
|
+
## Getting Started
|
|
22
|
+
|
|
23
|
+
### 1. Get Your API Key
|
|
24
|
+
|
|
25
|
+
To use this gem, you need to register and get an API key from Decolecta:
|
|
26
|
+
|
|
27
|
+
1. Visit https://decolecta.com/profile/
|
|
28
|
+
2. Sign up for a free account
|
|
29
|
+
3. Generate your API token
|
|
30
|
+
4. Copy the token for configuration
|
|
31
|
+
|
|
32
|
+
### 2. Store Your API Key Securely
|
|
33
|
+
|
|
34
|
+
**For Rails applications**, add to your credentials:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
EDITOR="code --wait" rails credentials:edit
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Add:
|
|
41
|
+
```yaml
|
|
42
|
+
decolecta:
|
|
43
|
+
api_key: your_api_key_here
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**For other applications**, use environment variables:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export DECOLECTA_API_KEY="your_api_key_here"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or use a `.env` file with the `dotenv` gem.
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
Configure the gem with your preferred settings and API keys:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
DniPeru.configure do |config|
|
|
60
|
+
# Set the default provider (optional, defaults to :decolecta)
|
|
61
|
+
config.default_provider = :decolecta
|
|
62
|
+
|
|
63
|
+
# Set timeout in seconds (optional, defaults to 30)
|
|
64
|
+
config.timeout = 30
|
|
65
|
+
|
|
66
|
+
# Set API keys for different providers
|
|
67
|
+
config.set_api_key(:decolecta, ENV['DECOLECTA_API_KEY'])
|
|
68
|
+
config.set_api_key(:api_peru_dev, ENV['API_PERU_DEV_KEY'])
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Quick Start
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
require 'dni_peru'
|
|
76
|
+
|
|
77
|
+
# Configure with your API key
|
|
78
|
+
DniPeru.configure do |config|
|
|
79
|
+
config.set_api_key(:decolecta, 'your-api-key')
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Query a DNI
|
|
83
|
+
response = DniPeru.query('12345678')
|
|
84
|
+
|
|
85
|
+
if response.success?
|
|
86
|
+
puts "Nombre: #{response.nombre_completo}"
|
|
87
|
+
# => "Nombre: DOE SMITH, JOHN"
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
### Basic Usage
|
|
94
|
+
|
|
95
|
+
Query a DNI using the default provider:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
response = DniPeru.query('12345678')
|
|
99
|
+
|
|
100
|
+
if response.success?
|
|
101
|
+
puts response.nombre_completo
|
|
102
|
+
# => "DOE SMITH, JOHN"
|
|
103
|
+
|
|
104
|
+
puts response.nombres
|
|
105
|
+
# => "JOHN"
|
|
106
|
+
|
|
107
|
+
puts response.apellido_paterno
|
|
108
|
+
# => "DOE"
|
|
109
|
+
|
|
110
|
+
puts response.apellido_materno
|
|
111
|
+
# => "SMITH"
|
|
112
|
+
|
|
113
|
+
puts response.dni
|
|
114
|
+
# => "12345678"
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Using a Specific Provider
|
|
119
|
+
|
|
120
|
+
You can specify which provider to use for a query:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# Using decolecta.com (recommended)
|
|
124
|
+
response = DniPeru.query('12345678', provider: :decolecta)
|
|
125
|
+
|
|
126
|
+
# Using apiperu.dev
|
|
127
|
+
response = DniPeru.query('12345678', provider: :api_peru_dev)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Using the Client Directly
|
|
131
|
+
|
|
132
|
+
For more control, you can create a client instance:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
client = DniPeru::Client.new(provider: :decolecta)
|
|
136
|
+
response = client.query('12345678')
|
|
137
|
+
|
|
138
|
+
puts response.to_h
|
|
139
|
+
# => {
|
|
140
|
+
# dni: "12345678",
|
|
141
|
+
# nombres: "JOHN",
|
|
142
|
+
# apellido_paterno: "DOE",
|
|
143
|
+
# apellido_materno: "SMITH",
|
|
144
|
+
# nombre_completo: "DOE SMITH, JOHN",
|
|
145
|
+
# provider: "Decolecta"
|
|
146
|
+
# }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Error Handling
|
|
150
|
+
|
|
151
|
+
The gem raises specific errors for different scenarios:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
begin
|
|
155
|
+
response = DniPeru.query('12345678')
|
|
156
|
+
rescue DniPeru::InvalidDniError => e
|
|
157
|
+
puts "Invalid DNI format: #{e.message}"
|
|
158
|
+
rescue DniPeru::NotFoundError => e
|
|
159
|
+
puts "DNI not found: #{e.message}"
|
|
160
|
+
rescue DniPeru::UnauthorizedError => e
|
|
161
|
+
puts "Invalid API key: #{e.message}"
|
|
162
|
+
rescue DniPeru::RateLimitError => e
|
|
163
|
+
puts "Rate limit exceeded: #{e.message}"
|
|
164
|
+
rescue DniPeru::ConnectionError => e
|
|
165
|
+
puts "Connection failed: #{e.message}"
|
|
166
|
+
rescue DniPeru::ApiError => e
|
|
167
|
+
puts "API error: #{e.message}"
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Supported Providers
|
|
172
|
+
|
|
173
|
+
### Decolecta (Recommended)
|
|
174
|
+
|
|
175
|
+
Provider identifier: `:decolecta`
|
|
176
|
+
|
|
177
|
+
- Website: https://decolecta.com/
|
|
178
|
+
- Documentation: https://decolecta.gitbook.io/docs
|
|
179
|
+
- Registration: https://decolecta.com/profile/
|
|
180
|
+
- **Requires API key** (register for free)
|
|
181
|
+
- Data source: RENIEC (official government registry)
|
|
182
|
+
- Security: HTTPS only, Bearer token authentication
|
|
183
|
+
- Features: DNI, RUC, Exchange rates
|
|
184
|
+
- Status monitor: https://status.apis.net.pe/
|
|
185
|
+
- Configuration: `config.set_api_key(:decolecta, 'your-api-key')`
|
|
186
|
+
|
|
187
|
+
**API Endpoint:**
|
|
188
|
+
```bash
|
|
189
|
+
curl -H 'Accept: application/json' -H "Authorization: Bearer YOUR_TOKEN" \
|
|
190
|
+
'https://api.decolecta.com/v1/reniec/dni?numero=12345678'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### apiperu.dev
|
|
194
|
+
|
|
195
|
+
Provider identifier: `:api_peru_dev`
|
|
196
|
+
|
|
197
|
+
- Website: https://apiperu.dev/
|
|
198
|
+
- Requires API key
|
|
199
|
+
- Free tier: 100 queries/month
|
|
200
|
+
- Data source: SUNAT reduced registry and public sources
|
|
201
|
+
- Configuration: `config.set_api_key(:api_peru_dev, 'your-key')`
|
|
202
|
+
|
|
203
|
+
## Rails Integration
|
|
204
|
+
|
|
205
|
+
### Initializer
|
|
206
|
+
|
|
207
|
+
Create an initializer file `config/initializers/dni_peru.rb`:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
DniPeru.configure do |config|
|
|
211
|
+
config.default_provider = :decolecta
|
|
212
|
+
config.timeout = 30
|
|
213
|
+
config.set_api_key(:decolecta, Rails.application.credentials.dig(:decolecta, :api_key))
|
|
214
|
+
config.set_api_key(:api_peru_dev, Rails.application.credentials.dig(:api_peru_dev, :api_key))
|
|
215
|
+
end
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Usage in Models
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
class User < ApplicationRecord
|
|
222
|
+
validates :dni, presence: true, length: { is: 8 }
|
|
223
|
+
|
|
224
|
+
def fetch_dni_info
|
|
225
|
+
begin
|
|
226
|
+
response = DniPeru.query(self.dni)
|
|
227
|
+
update(
|
|
228
|
+
first_name: response.nombres,
|
|
229
|
+
paternal_surname: response.apellido_paterno,
|
|
230
|
+
maternal_surname: response.apellido_materno
|
|
231
|
+
)
|
|
232
|
+
rescue DniPeru::Error => e
|
|
233
|
+
errors.add(:dni, "Could not verify DNI: #{e.message}")
|
|
234
|
+
false
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Usage in Controllers
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
class UsersController < ApplicationController
|
|
244
|
+
def verify_dni
|
|
245
|
+
@response = DniPeru.query(params[:dni])
|
|
246
|
+
render json: @response.to_h
|
|
247
|
+
rescue DniPeru::Error => e
|
|
248
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Response Object
|
|
254
|
+
|
|
255
|
+
The `Response` object provides the following attributes:
|
|
256
|
+
|
|
257
|
+
- `dni` - The DNI number
|
|
258
|
+
- `nombres` - First/given names
|
|
259
|
+
- `apellido_paterno` - Paternal surname
|
|
260
|
+
- `apellido_materno` - Maternal surname
|
|
261
|
+
- `nombre_completo` - Full name in format "PATERNAL MATERNAL, NAMES"
|
|
262
|
+
- `provider` - The provider used for the query
|
|
263
|
+
- `raw_data` - Raw data from the API response
|
|
264
|
+
|
|
265
|
+
Methods:
|
|
266
|
+
- `success?` - Returns true if the query was successful
|
|
267
|
+
- `to_h` - Returns a hash representation of the response
|
|
268
|
+
|
|
269
|
+
## Development
|
|
270
|
+
|
|
271
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
272
|
+
|
|
273
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
274
|
+
|
|
275
|
+
### Testing Your Configuration
|
|
276
|
+
|
|
277
|
+
Test your API configuration quickly:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
export DECOLECTA_API_KEY='your-api-key'
|
|
281
|
+
ruby examples/test_config.rb 12345678
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Examples
|
|
285
|
+
|
|
286
|
+
The `examples/` directory contains practical examples:
|
|
287
|
+
|
|
288
|
+
- `examples/basic_usage.rb` - Basic gem usage examples
|
|
289
|
+
- `examples/rails_integration.rb` - Rails integration patterns (services, controllers, models, jobs)
|
|
290
|
+
- `examples/test_config.rb` - Configuration testing script
|
|
291
|
+
|
|
292
|
+
## Testing
|
|
293
|
+
|
|
294
|
+
Run the test suite:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
bundle exec rspec
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Run with coverage:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
COVERAGE=true bundle exec rspec
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Contributing
|
|
307
|
+
|
|
308
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rubenpazch/dni-peru.
|
|
309
|
+
|
|
310
|
+
1. Fork it
|
|
311
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
312
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
313
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
314
|
+
5. Create a new Pull Request
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
319
|
+
|
|
320
|
+
## Code of Conduct
|
|
321
|
+
|
|
322
|
+
Everyone interacting in the DniPeru project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/rubenpazch/dni-peru/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/TEST_COVERAGE.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Test Coverage Report
|
|
2
|
+
|
|
3
|
+
## Test Suite Structure
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
spec/
|
|
7
|
+
├── client_spec.rb # Client initialization and query logic
|
|
8
|
+
├── configuration_spec.rb # Configuration and API key management
|
|
9
|
+
├── dni_peru_spec.rb # Main module interface
|
|
10
|
+
├── errors_spec.rb # Error classes and hierarchy
|
|
11
|
+
├── response_spec.rb # Response parsing and formatting
|
|
12
|
+
└── providers/
|
|
13
|
+
├── base_spec.rb # Base provider functionality
|
|
14
|
+
├── decolecta_spec.rb # Decolecta provider integration
|
|
15
|
+
└── api_peru_dev_spec.rb # ApiPeruDev provider integration
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Coverage Areas
|
|
19
|
+
|
|
20
|
+
### ✅ Core Functionality
|
|
21
|
+
- [x] **DniPeru Module** - Main interface and delegation
|
|
22
|
+
- [x] **Configuration** - Default settings, API key storage
|
|
23
|
+
- [x] **Client** - Provider selection, DNI validation, error wrapping
|
|
24
|
+
- [x] **Response** - Data parsing, normalization, full name generation
|
|
25
|
+
- [x] **Errors** - All error types and hierarchy
|
|
26
|
+
|
|
27
|
+
### ✅ Providers
|
|
28
|
+
- [x] **Base Provider** - Connection setup, response parsing, error handling
|
|
29
|
+
- [x] **Decolecta** - API integration, authentication, data normalization
|
|
30
|
+
- [x] **ApiPeruDev** - API integration, nested data handling
|
|
31
|
+
|
|
32
|
+
### ✅ Test Scenarios
|
|
33
|
+
|
|
34
|
+
#### Configuration Tests
|
|
35
|
+
- Default provider and timeout settings
|
|
36
|
+
- API key storage and retrieval
|
|
37
|
+
- Multiple provider support
|
|
38
|
+
- Key overwriting
|
|
39
|
+
|
|
40
|
+
#### Client Tests
|
|
41
|
+
- Provider initialization
|
|
42
|
+
- DNI format validation (empty, short, invalid characters)
|
|
43
|
+
- Integer to string conversion
|
|
44
|
+
- Provider delegation
|
|
45
|
+
- Faraday error wrapping
|
|
46
|
+
- Default configuration fallback
|
|
47
|
+
|
|
48
|
+
#### Provider Tests
|
|
49
|
+
- API authentication (with/without keys)
|
|
50
|
+
- Successful responses (200)
|
|
51
|
+
- Error responses (401, 404, 429, 500)
|
|
52
|
+
- Data normalization
|
|
53
|
+
- Connection configuration
|
|
54
|
+
- Response building
|
|
55
|
+
|
|
56
|
+
#### Response Tests
|
|
57
|
+
- Symbol and string key parsing
|
|
58
|
+
- Different key formats (camelCase, snake_case)
|
|
59
|
+
- Full name generation
|
|
60
|
+
- Success validation
|
|
61
|
+
- Hash conversion
|
|
62
|
+
- Raw data preservation
|
|
63
|
+
|
|
64
|
+
#### Error Tests
|
|
65
|
+
- Error inheritance hierarchy
|
|
66
|
+
- Custom error messages
|
|
67
|
+
- Error catching patterns
|
|
68
|
+
- API vs non-API error distinction
|
|
69
|
+
|
|
70
|
+
## Running Tests
|
|
71
|
+
|
|
72
|
+
### Run all tests
|
|
73
|
+
```bash
|
|
74
|
+
bundle exec rspec
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Run with coverage
|
|
78
|
+
```bash
|
|
79
|
+
COVERAGE=true bundle exec rspec
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Run specific test file
|
|
83
|
+
```bash
|
|
84
|
+
bundle exec rspec spec/client_spec.rb
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Run specific test
|
|
88
|
+
```bash
|
|
89
|
+
bundle exec rspec spec/client_spec.rb:15
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Run with documentation format
|
|
93
|
+
```bash
|
|
94
|
+
bundle exec rspec --format documentation
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Expected Coverage
|
|
98
|
+
|
|
99
|
+
With the comprehensive test suite, we should achieve:
|
|
100
|
+
|
|
101
|
+
- **Overall Coverage**: ~95-100%
|
|
102
|
+
- **Core Classes**: 100%
|
|
103
|
+
- **Providers**: ~95% (some edge cases may be hard to simulate)
|
|
104
|
+
- **Error Handling**: 100%
|
|
105
|
+
|
|
106
|
+
## Key Test Dependencies
|
|
107
|
+
|
|
108
|
+
- **RSpec** - Testing framework
|
|
109
|
+
- **WebMock** - HTTP request stubbing
|
|
110
|
+
- **SimpleCov** - Coverage reporting
|
|
111
|
+
|
|
112
|
+
## Coverage Report Location
|
|
113
|
+
|
|
114
|
+
After running tests with `COVERAGE=true`, view the report at:
|
|
115
|
+
```
|
|
116
|
+
coverage/index.html
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Open in browser:
|
|
120
|
+
```bash
|
|
121
|
+
open coverage/index.html
|
|
122
|
+
```
|
data/dni_peru.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require_relative "lib/dni_peru/version"
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "dni_peru"
|
|
5
|
+
spec.version = DniPeru::VERSION
|
|
6
|
+
spec.authors = ["Ruben Paz"]
|
|
7
|
+
spec.email = ["rubenpazch@gmail.com"]
|
|
8
|
+
|
|
9
|
+
spec.summary = "Ruby gem to query DNI information from Peruvian API providers"
|
|
10
|
+
spec.description = "A flexible Ruby gem that provides a unified interface to query DNI " \
|
|
11
|
+
"(Documento Nacional de Identidad) information from multiple Peruvian API providers."
|
|
12
|
+
spec.homepage = "https://github.com/rubenpazch/dni-peru"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
spec.required_ruby_version = ">= 2.7.0"
|
|
15
|
+
|
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/rubenpazch/dni-peru"
|
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/rubenpazch/dni-peru/blob/main/CHANGELOG.md"
|
|
19
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
spec.bindir = "exe"
|
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
29
|
+
spec.require_paths = ["lib"]
|
|
30
|
+
|
|
31
|
+
# Runtime dependencies
|
|
32
|
+
spec.add_dependency "faraday", "~> 2.0"
|
|
33
|
+
spec.add_dependency "faraday-retry", "~> 2.0"
|
|
34
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Example usage of dni_peru gem
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "dni_peru"
|
|
5
|
+
|
|
6
|
+
# Configure the gem with your Decolecta API key
|
|
7
|
+
# Get your API key from: https://decolecta.com/profile/
|
|
8
|
+
DniPeru.configure do |config|
|
|
9
|
+
config.default_provider = :decolecta
|
|
10
|
+
config.timeout = 30
|
|
11
|
+
|
|
12
|
+
# Option 1: Use environment variable (recommended)
|
|
13
|
+
config.set_api_key(:decolecta, ENV.fetch("DECOLECTA_API_KEY", nil))
|
|
14
|
+
|
|
15
|
+
# Option 2: Hard-code (not recommended for production)
|
|
16
|
+
# config.set_api_key(:decolecta, "your-api-key-here")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Example 1: Simple query
|
|
20
|
+
puts "Example 1: Simple DNI Query"
|
|
21
|
+
puts "-" * 50
|
|
22
|
+
|
|
23
|
+
begin
|
|
24
|
+
response = DniPeru.query("12345678")
|
|
25
|
+
|
|
26
|
+
if response.success?
|
|
27
|
+
puts "DNI encontrado!"
|
|
28
|
+
puts "DNI: #{response.dni}"
|
|
29
|
+
puts "Nombres: #{response.nombres}"
|
|
30
|
+
puts "Apellido Paterno: #{response.apellido_paterno}"
|
|
31
|
+
puts "Apellido Materno: #{response.apellido_materno}"
|
|
32
|
+
puts "Nombre Completo: #{response.nombre_completo}"
|
|
33
|
+
puts "Proveedor: #{response.provider}"
|
|
34
|
+
else
|
|
35
|
+
puts "No se encontró información"
|
|
36
|
+
end
|
|
37
|
+
rescue DniPeru::InvalidDniError => e
|
|
38
|
+
puts "Error: DNI inválido - #{e.message}"
|
|
39
|
+
rescue DniPeru::UnauthorizedError => e
|
|
40
|
+
puts "Error: API key inválida - #{e.message}"
|
|
41
|
+
rescue DniPeru::NotFoundError => e
|
|
42
|
+
puts "Error: DNI no encontrado - #{e.message}"
|
|
43
|
+
rescue DniPeru::Error => e
|
|
44
|
+
puts "Error: #{e.message}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
puts "\n"
|
|
48
|
+
|
|
49
|
+
# Example 2: Using specific provider
|
|
50
|
+
puts "Example 2: Using Specific Provider"
|
|
51
|
+
puts "-" * 50
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
client = DniPeru::Client.new(provider: :decolecta)
|
|
55
|
+
response = client.query("87654321")
|
|
56
|
+
|
|
57
|
+
puts response.to_h.inspect
|
|
58
|
+
rescue DniPeru::Error => e
|
|
59
|
+
puts "Error: #{e.message}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
puts "\n"
|
|
63
|
+
|
|
64
|
+
# Example 3: Validate DNI before querying
|
|
65
|
+
puts "Example 3: DNI Validation"
|
|
66
|
+
puts "-" * 50
|
|
67
|
+
|
|
68
|
+
invalid_dnis = ["", "123", "1234567a", "123456789"]
|
|
69
|
+
|
|
70
|
+
invalid_dnis.each do |dni|
|
|
71
|
+
DniPeru.query(dni)
|
|
72
|
+
rescue DniPeru::InvalidDniError => e
|
|
73
|
+
puts "DNI '#{dni}': #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
puts "\n"
|
|
77
|
+
|
|
78
|
+
# Example 4: Error handling
|
|
79
|
+
puts "Example 4: Comprehensive Error Handling"
|
|
80
|
+
puts "-" * 50
|
|
81
|
+
|
|
82
|
+
def query_dni_safely(dni)
|
|
83
|
+
response = DniPeru.query(dni)
|
|
84
|
+
{
|
|
85
|
+
success: true,
|
|
86
|
+
data: response.to_h
|
|
87
|
+
}
|
|
88
|
+
rescue DniPeru::InvalidDniError => e
|
|
89
|
+
{ success: false, error: "Formato inválido", message: e.message }
|
|
90
|
+
rescue DniPeru::NotFoundError => e
|
|
91
|
+
{ success: false, error: "No encontrado", message: e.message }
|
|
92
|
+
rescue DniPeru::UnauthorizedError => e
|
|
93
|
+
{ success: false, error: "No autorizado", message: e.message }
|
|
94
|
+
rescue DniPeru::RateLimitError => e
|
|
95
|
+
{ success: false, error: "Límite excedido", message: e.message }
|
|
96
|
+
rescue DniPeru::ConnectionError => e
|
|
97
|
+
{ success: false, error: "Error de conexión", message: e.message }
|
|
98
|
+
rescue DniPeru::ApiError => e
|
|
99
|
+
{ success: false, error: "Error de API", message: e.message }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
result = query_dni_safely("12345678")
|
|
103
|
+
puts result.inspect
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Rails Example: DNI Verification Service
|
|
2
|
+
# Place this in app/services/dni_verification_service.rb
|
|
3
|
+
|
|
4
|
+
class DniVerificationService
|
|
5
|
+
def initialize(dni)
|
|
6
|
+
@dni = dni
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
response = DniPeru.query(@dni)
|
|
11
|
+
|
|
12
|
+
if response.success?
|
|
13
|
+
{
|
|
14
|
+
success: true,
|
|
15
|
+
data: {
|
|
16
|
+
dni: response.dni,
|
|
17
|
+
nombres: response.nombres,
|
|
18
|
+
apellido_paterno: response.apellido_paterno,
|
|
19
|
+
apellido_materno: response.apellido_materno,
|
|
20
|
+
nombre_completo: response.nombre_completo
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else
|
|
24
|
+
{ success: false, error: "DNI no encontrado" }
|
|
25
|
+
end
|
|
26
|
+
rescue DniPeru::InvalidDniError => e
|
|
27
|
+
{ success: false, error: "Formato de DNI inválido: #{e.message}" }
|
|
28
|
+
rescue DniPeru::NotFoundError
|
|
29
|
+
{ success: false, error: "DNI no encontrado en RENIEC" }
|
|
30
|
+
rescue DniPeru::UnauthorizedError => e
|
|
31
|
+
Rails.logger.error "DniPeru API key error: #{e.message}"
|
|
32
|
+
{ success: false, error: "Error de configuración del servicio" }
|
|
33
|
+
rescue DniPeru::RateLimitError => e
|
|
34
|
+
Rails.logger.warn "DniPeru rate limit exceeded: #{e.message}"
|
|
35
|
+
{ success: false, error: "Servicio temporalmente no disponible. Intente más tarde." }
|
|
36
|
+
rescue DniPeru::ConnectionError => e
|
|
37
|
+
Rails.logger.error "DniPeru connection error: #{e.message}"
|
|
38
|
+
{ success: false, error: "Error de conexión con el servicio" }
|
|
39
|
+
rescue DniPeru::ApiError => e
|
|
40
|
+
Rails.logger.error "DniPeru API error: #{e.message}"
|
|
41
|
+
{ success: false, error: "Error en el servicio de verificación" }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Usage in controller:
|
|
46
|
+
# app/controllers/api/v1/dni_controller.rb
|
|
47
|
+
|
|
48
|
+
module Api
|
|
49
|
+
module V1
|
|
50
|
+
class DniController < ApplicationController
|
|
51
|
+
def verify
|
|
52
|
+
result = DniVerificationService.new(params[:dni]).call
|
|
53
|
+
|
|
54
|
+
if result[:success]
|
|
55
|
+
render json: result[:data], status: :ok
|
|
56
|
+
else
|
|
57
|
+
render json: { error: result[:error] }, status: :unprocessable_entity
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Usage in model:
|
|
65
|
+
# app/models/user.rb
|
|
66
|
+
|
|
67
|
+
class User < ApplicationRecord
|
|
68
|
+
validates :dni, presence: true, length: { is: 8 }
|
|
69
|
+
|
|
70
|
+
# Callback to auto-fill names from DNI
|
|
71
|
+
before_validation :fetch_dni_info, if: :will_save_change_to_dni?
|
|
72
|
+
|
|
73
|
+
def verify_dni
|
|
74
|
+
result = DniVerificationService.new(dni).call
|
|
75
|
+
|
|
76
|
+
if result[:success]
|
|
77
|
+
data = result[:data]
|
|
78
|
+
update(
|
|
79
|
+
nombres: data[:nombres],
|
|
80
|
+
apellido_paterno: data[:apellido_paterno],
|
|
81
|
+
apellido_materno: data[:apellido_materno],
|
|
82
|
+
nombre_completo: data[:nombre_completo],
|
|
83
|
+
dni_verified: true,
|
|
84
|
+
dni_verified_at: Time.current
|
|
85
|
+
)
|
|
86
|
+
else
|
|
87
|
+
errors.add(:dni, result[:error])
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def fetch_dni_info
|
|
95
|
+
return unless dni.present? && dni.length == 8
|
|
96
|
+
|
|
97
|
+
result = DniVerificationService.new(dni).call
|
|
98
|
+
|
|
99
|
+
if result[:success]
|
|
100
|
+
data = result[:data]
|
|
101
|
+
self.nombres = data[:nombres]
|
|
102
|
+
self.apellido_paterno = data[:apellido_paterno]
|
|
103
|
+
self.apellido_materno = data[:apellido_materno]
|
|
104
|
+
self.nombre_completo = data[:nombre_completo]
|
|
105
|
+
end
|
|
106
|
+
rescue StandardError => e
|
|
107
|
+
Rails.logger.error "Failed to fetch DNI info: #{e.message}"
|
|
108
|
+
# Don't block user creation if DNI service fails
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Usage in background job:
|
|
113
|
+
# app/jobs/verify_dni_job.rb
|
|
114
|
+
|
|
115
|
+
class VerifyDniJob < ApplicationJob
|
|
116
|
+
queue_as :default
|
|
117
|
+
|
|
118
|
+
def perform(user_id)
|
|
119
|
+
user = User.find(user_id)
|
|
120
|
+
result = DniVerificationService.new(user.dni).call
|
|
121
|
+
|
|
122
|
+
if result[:success]
|
|
123
|
+
user.update(
|
|
124
|
+
dni_verified: true,
|
|
125
|
+
dni_verified_at: Time.current,
|
|
126
|
+
**result[:data]
|
|
127
|
+
)
|
|
128
|
+
else
|
|
129
|
+
user.update(dni_verified: false)
|
|
130
|
+
Rails.logger.warn "DNI verification failed for user #{user.id}: #{result[:error]}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# Test your Decolecta API configuration
|
|
3
|
+
# Usage: ruby examples/test_config.rb
|
|
4
|
+
|
|
5
|
+
require "bundler/setup"
|
|
6
|
+
require "dni_peru"
|
|
7
|
+
|
|
8
|
+
puts "=" * 60
|
|
9
|
+
puts "DNI Peru Gem - Configuration Test"
|
|
10
|
+
puts "=" * 60
|
|
11
|
+
puts ""
|
|
12
|
+
|
|
13
|
+
# Check if API key is set
|
|
14
|
+
api_key = ENV.fetch("DECOLECTA_API_KEY", nil)
|
|
15
|
+
|
|
16
|
+
if api_key.nil? || api_key.empty?
|
|
17
|
+
puts "❌ ERROR: DECOLECTA_API_KEY environment variable not set"
|
|
18
|
+
puts ""
|
|
19
|
+
puts "To fix this:"
|
|
20
|
+
puts " 1. Get your API key from: https://decolecta.com/profile/"
|
|
21
|
+
puts " 2. Set it as environment variable:"
|
|
22
|
+
puts " export DECOLECTA_API_KEY='your-api-key-here'"
|
|
23
|
+
puts ""
|
|
24
|
+
exit 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
puts "✓ API key found in environment"
|
|
28
|
+
puts ""
|
|
29
|
+
|
|
30
|
+
# Configure the gem
|
|
31
|
+
DniPeru.configure do |config|
|
|
32
|
+
config.set_api_key(:decolecta, api_key)
|
|
33
|
+
config.timeout = 30
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
puts "✓ Gem configured successfully"
|
|
37
|
+
puts ""
|
|
38
|
+
|
|
39
|
+
# Test with a sample DNI (you can change this)
|
|
40
|
+
test_dni = ARGV[0] || "12345678"
|
|
41
|
+
|
|
42
|
+
puts "Testing with DNI: #{test_dni}"
|
|
43
|
+
puts "-" * 60
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
response = DniPeru.query(test_dni)
|
|
47
|
+
|
|
48
|
+
if response.success?
|
|
49
|
+
puts "✓ API connection successful!"
|
|
50
|
+
puts ""
|
|
51
|
+
puts "Response details:"
|
|
52
|
+
puts " DNI: #{response.dni}"
|
|
53
|
+
puts " Nombres: #{response.nombres}"
|
|
54
|
+
puts " Apellido Paterno: #{response.apellido_paterno}"
|
|
55
|
+
puts " Apellido Materno: #{response.apellido_materno}"
|
|
56
|
+
puts " Nombre Completo: #{response.nombre_completo}"
|
|
57
|
+
puts " Provider: #{response.provider}"
|
|
58
|
+
puts ""
|
|
59
|
+
puts "✓ Everything is working correctly!"
|
|
60
|
+
else
|
|
61
|
+
puts "⚠ API responded but no data found"
|
|
62
|
+
end
|
|
63
|
+
rescue DniPeru::UnauthorizedError => e
|
|
64
|
+
puts "❌ Authentication Error"
|
|
65
|
+
puts " #{e.message}"
|
|
66
|
+
puts ""
|
|
67
|
+
puts "Please check:"
|
|
68
|
+
puts " 1. Your API key is correct"
|
|
69
|
+
puts " 2. Your API key is active at https://decolecta.com/profile/"
|
|
70
|
+
rescue DniPeru::NotFoundError
|
|
71
|
+
puts "⚠ DNI not found in database"
|
|
72
|
+
puts " This is normal if the DNI doesn't exist"
|
|
73
|
+
puts " Try with a different DNI number"
|
|
74
|
+
rescue DniPeru::InvalidDniError => e
|
|
75
|
+
puts "❌ Invalid DNI format"
|
|
76
|
+
puts " #{e.message}"
|
|
77
|
+
puts " DNI must be exactly 8 digits"
|
|
78
|
+
rescue DniPeru::RateLimitError => e
|
|
79
|
+
puts "⚠ Rate limit exceeded"
|
|
80
|
+
puts " #{e.message}"
|
|
81
|
+
puts " Please wait a moment before trying again"
|
|
82
|
+
rescue DniPeru::ConnectionError => e
|
|
83
|
+
puts "❌ Connection Error"
|
|
84
|
+
puts " #{e.message}"
|
|
85
|
+
puts ""
|
|
86
|
+
puts "Please check:"
|
|
87
|
+
puts " 1. Your internet connection"
|
|
88
|
+
puts " 2. Decolecta service status"
|
|
89
|
+
rescue DniPeru::ApiError => e
|
|
90
|
+
puts "❌ API Error"
|
|
91
|
+
puts " #{e.message}"
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
puts "❌ Unexpected Error"
|
|
94
|
+
puts " #{e.class}: #{e.message}"
|
|
95
|
+
puts ""
|
|
96
|
+
puts " Please report this issue at:"
|
|
97
|
+
puts " https://github.com/rubenpazch/dni-peru/issues"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
puts ""
|
|
101
|
+
puts "=" * 60
|
|
102
|
+
puts "Test complete"
|
|
103
|
+
puts "=" * 60
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require "faraday"
|
|
2
|
+
require "faraday/retry"
|
|
3
|
+
|
|
4
|
+
module DniPeru
|
|
5
|
+
class Client
|
|
6
|
+
attr_reader :provider
|
|
7
|
+
|
|
8
|
+
PROVIDERS = {
|
|
9
|
+
decolecta: Providers::Decolecta,
|
|
10
|
+
api_peru_dev: Providers::ApiPeruDev
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def initialize(provider: nil)
|
|
14
|
+
@provider_name = provider || config.default_provider
|
|
15
|
+
@provider = load_provider(@provider_name)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def query(dni)
|
|
19
|
+
dni_string = validate_dni!(dni)
|
|
20
|
+
provider.query(dni_string)
|
|
21
|
+
rescue Faraday::Error => e
|
|
22
|
+
raise ConnectionError, "Failed to connect to provider: #{e.message}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def load_provider(provider_name)
|
|
28
|
+
provider_class = PROVIDERS[provider_name.to_sym]
|
|
29
|
+
raise InvalidProviderError, "Unknown provider: #{provider_name}" unless provider_class
|
|
30
|
+
|
|
31
|
+
provider_class.new(config)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validate_dni!(dni)
|
|
35
|
+
dni_string = dni.to_s
|
|
36
|
+
raise InvalidDniError, "DNI cannot be empty" if dni_string.empty?
|
|
37
|
+
raise InvalidDniError, "DNI must be 8 digits" unless dni_string.match?(/^\d{8}$/)
|
|
38
|
+
|
|
39
|
+
dni_string
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def config
|
|
43
|
+
DniPeru.configuration || Configuration.new
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module DniPeru
|
|
2
|
+
class Configuration
|
|
3
|
+
attr_accessor :default_provider, :timeout, :api_keys
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@default_provider = :decolecta
|
|
7
|
+
@timeout = 30
|
|
8
|
+
@api_keys = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def set_api_key(provider, key)
|
|
12
|
+
@api_keys[provider.to_sym] = key
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get_api_key(provider)
|
|
16
|
+
@api_keys[provider.to_sym]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module DniPeru
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
class InvalidDniError < Error; end
|
|
4
|
+
class InvalidProviderError < Error; end
|
|
5
|
+
class ConnectionError < Error; end
|
|
6
|
+
class ApiError < Error; end
|
|
7
|
+
class NotFoundError < ApiError; end
|
|
8
|
+
class UnauthorizedError < ApiError; end
|
|
9
|
+
class RateLimitError < ApiError; end
|
|
10
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module DniPeru
|
|
2
|
+
module Providers
|
|
3
|
+
class ApiPeruDev < Base
|
|
4
|
+
BASE_URL = "https://apiperu.dev/api/dni".freeze
|
|
5
|
+
|
|
6
|
+
def query(dni)
|
|
7
|
+
api_key = config.get_api_key(:api_peru_dev)
|
|
8
|
+
|
|
9
|
+
response = connection.get("#{BASE_URL}/#{dni}") do |req|
|
|
10
|
+
req.headers["Authorization"] = "Bearer #{api_key}" if api_key
|
|
11
|
+
req.headers["Content-Type"] = "application/json"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
data = parse_response(response)
|
|
15
|
+
build_response(normalize_data(data))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def normalize_data(data)
|
|
21
|
+
# Extract from nested structure if present
|
|
22
|
+
person_data = data[:data] || data
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
dni: person_data[:numero] || person_data[:dni],
|
|
26
|
+
nombres: person_data[:nombres],
|
|
27
|
+
apellido_paterno: person_data[:apellido_paterno],
|
|
28
|
+
apellido_materno: person_data[:apellido_materno]
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DniPeru
|
|
2
|
+
module Providers
|
|
3
|
+
class Decolecta < Base
|
|
4
|
+
BASE_URL = "https://api.decolecta.com/v1/reniec/dni".freeze
|
|
5
|
+
|
|
6
|
+
def query(dni)
|
|
7
|
+
api_key = config.get_api_key(:decolecta)
|
|
8
|
+
raise UnauthorizedError, "API key is required for Decolecta provider" unless api_key
|
|
9
|
+
|
|
10
|
+
response = connection.get(BASE_URL) do |req|
|
|
11
|
+
req.params["numero"] = dni
|
|
12
|
+
req.headers["Authorization"] = "Bearer #{api_key}"
|
|
13
|
+
req.headers["Accept"] = "application/json"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
data = parse_response(response)
|
|
17
|
+
build_response(normalize_data(data))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def normalize_data(data)
|
|
23
|
+
{
|
|
24
|
+
dni: data[:numeroDocumento] || data[:numero_documento],
|
|
25
|
+
nombres: data[:nombres],
|
|
26
|
+
apellido_paterno: data[:apellidoPaterno] || data[:apellido_paterno],
|
|
27
|
+
apellido_materno: data[:apellidoMaterno] || data[:apellido_materno]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require "faraday"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module DniPeru
|
|
5
|
+
module Providers
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :config
|
|
8
|
+
|
|
9
|
+
def initialize(config)
|
|
10
|
+
@config = config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def query(dni)
|
|
14
|
+
raise NotImplementedError, "Subclasses must implement the query method"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def connection
|
|
20
|
+
@connection ||= Faraday.new do |f|
|
|
21
|
+
f.request :retry, max: 2, interval: 0.5, backoff_factor: 2
|
|
22
|
+
f.adapter Faraday.default_adapter
|
|
23
|
+
f.options.timeout = config.timeout
|
|
24
|
+
f.options.open_timeout = 10
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse_response(response)
|
|
29
|
+
return JSON.parse(response.body, symbolize_names: true) if response.status == 200
|
|
30
|
+
|
|
31
|
+
handle_error_response(response)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def handle_error_response(response)
|
|
35
|
+
case response.status
|
|
36
|
+
when 404
|
|
37
|
+
raise NotFoundError, "DNI not found"
|
|
38
|
+
when 401
|
|
39
|
+
raise UnauthorizedError, "Invalid API key or unauthorized access"
|
|
40
|
+
when 429
|
|
41
|
+
raise RateLimitError, "Rate limit exceeded"
|
|
42
|
+
else
|
|
43
|
+
raise ApiError, "API returned status #{response.status}: #{response.body}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_response(data)
|
|
48
|
+
Response.new(data, provider: self.class.name.split("::").last)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DniPeru
|
|
2
|
+
module Providers
|
|
3
|
+
class Decolecta < Base
|
|
4
|
+
BASE_URL = "https://api.decolecta.com/v1/reniec/dni".freeze
|
|
5
|
+
|
|
6
|
+
def query(dni)
|
|
7
|
+
api_key = config.get_api_key(:decolecta)
|
|
8
|
+
raise UnauthorizedError, "API key is required for Decolecta provider" unless api_key
|
|
9
|
+
|
|
10
|
+
response = connection.get(BASE_URL) do |req|
|
|
11
|
+
req.params["numero"] = dni
|
|
12
|
+
req.headers["Authorization"] = "Bearer #{api_key}"
|
|
13
|
+
req.headers["Accept"] = "application/json"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
data = parse_response(response)
|
|
17
|
+
build_response(normalize_data(data))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def normalize_data(data)
|
|
23
|
+
{
|
|
24
|
+
dni: data[:numeroDocumento] || data[:numero_documento],
|
|
25
|
+
nombres: data[:nombres],
|
|
26
|
+
apellido_paterno: data[:apellidoPaterno] || data[:apellido_paterno],
|
|
27
|
+
apellido_materno: data[:apellidoMaterno] || data[:apellido_materno]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module DniPeru
|
|
2
|
+
class Response
|
|
3
|
+
attr_reader :dni, :nombres, :apellido_paterno, :apellido_materno,
|
|
4
|
+
:nombre_completo, :raw_data, :provider
|
|
5
|
+
|
|
6
|
+
def initialize(data, provider:)
|
|
7
|
+
@provider = provider
|
|
8
|
+
@raw_data = data
|
|
9
|
+
parse_data(data)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def success?
|
|
13
|
+
!@dni.nil?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_h
|
|
17
|
+
{
|
|
18
|
+
dni: @dni,
|
|
19
|
+
nombres: @nombres,
|
|
20
|
+
apellido_paterno: @apellido_paterno,
|
|
21
|
+
apellido_materno: @apellido_materno,
|
|
22
|
+
nombre_completo: @nombre_completo,
|
|
23
|
+
provider: @provider
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def parse_data(data)
|
|
30
|
+
@dni = extract_field(data, :dni, "dni")
|
|
31
|
+
@nombres = extract_field(data, :nombres, "nombres")
|
|
32
|
+
@apellido_paterno = extract_field(data, :apellido_paterno, "apellidoPaterno", "apellido_paterno")
|
|
33
|
+
@apellido_materno = extract_field(data, :apellido_materno, "apellidoMaterno", "apellido_materno")
|
|
34
|
+
@nombre_completo = extract_field(data, :nombre_completo, "nombreCompleto") || build_full_name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def extract_field(data, *keys)
|
|
38
|
+
keys.each do |key|
|
|
39
|
+
value = data[key]
|
|
40
|
+
return value if value
|
|
41
|
+
end
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def build_full_name
|
|
46
|
+
return nil unless @nombres && @apellido_paterno && @apellido_materno
|
|
47
|
+
|
|
48
|
+
"#{@apellido_paterno} #{@apellido_materno}, #{@nombres}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/dni_peru.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require_relative "dni_peru/version"
|
|
2
|
+
require_relative "dni_peru/configuration"
|
|
3
|
+
require_relative "dni_peru/errors"
|
|
4
|
+
require_relative "dni_peru/response"
|
|
5
|
+
require_relative "dni_peru/providers/base"
|
|
6
|
+
require_relative "dni_peru/providers/decolecta"
|
|
7
|
+
require_relative "dni_peru/providers/api_peru_dev"
|
|
8
|
+
require_relative "dni_peru/client"
|
|
9
|
+
|
|
10
|
+
module DniPeru
|
|
11
|
+
class << self
|
|
12
|
+
attr_accessor :configuration
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.configure
|
|
16
|
+
self.configuration ||= Configuration.new
|
|
17
|
+
yield(configuration)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.query(dni, provider: nil)
|
|
21
|
+
client = Client.new(provider: provider)
|
|
22
|
+
client.query(dni)
|
|
23
|
+
end
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dni_peru
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ruben Paz
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-28 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: faraday-retry
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.0'
|
|
41
|
+
description: A flexible Ruby gem that provides a unified interface to query DNI (Documento
|
|
42
|
+
Nacional de Identidad) information from multiple Peruvian API providers.
|
|
43
|
+
email:
|
|
44
|
+
- rubenpazch@gmail.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- ".env.example"
|
|
50
|
+
- ".rspec"
|
|
51
|
+
- ".rubocop.yml"
|
|
52
|
+
- CHANGELOG.md
|
|
53
|
+
- Gemfile
|
|
54
|
+
- LICENSE.txt
|
|
55
|
+
- README.md
|
|
56
|
+
- Rakefile
|
|
57
|
+
- TEST_COVERAGE.md
|
|
58
|
+
- dni_peru.gemspec
|
|
59
|
+
- examples/basic_usage.rb
|
|
60
|
+
- examples/rails_integration.rb
|
|
61
|
+
- examples/test_config.rb
|
|
62
|
+
- lib/dni_peru.rb
|
|
63
|
+
- lib/dni_peru/client.rb
|
|
64
|
+
- lib/dni_peru/configuration.rb
|
|
65
|
+
- lib/dni_peru/errors.rb
|
|
66
|
+
- lib/dni_peru/providers/api_peru_dev.rb
|
|
67
|
+
- lib/dni_peru/providers/apis_net_pe.rb
|
|
68
|
+
- lib/dni_peru/providers/base.rb
|
|
69
|
+
- lib/dni_peru/providers/decolecta.rb
|
|
70
|
+
- lib/dni_peru/response.rb
|
|
71
|
+
- lib/dni_peru/version.rb
|
|
72
|
+
homepage: https://github.com/rubenpazch/dni-peru
|
|
73
|
+
licenses:
|
|
74
|
+
- MIT
|
|
75
|
+
metadata:
|
|
76
|
+
homepage_uri: https://github.com/rubenpazch/dni-peru
|
|
77
|
+
source_code_uri: https://github.com/rubenpazch/dni-peru
|
|
78
|
+
changelog_uri: https://github.com/rubenpazch/dni-peru/blob/main/CHANGELOG.md
|
|
79
|
+
rubygems_mfa_required: 'true'
|
|
80
|
+
post_install_message:
|
|
81
|
+
rdoc_options: []
|
|
82
|
+
require_paths:
|
|
83
|
+
- lib
|
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: 2.7.0
|
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
requirements: []
|
|
95
|
+
rubygems_version: 3.5.22
|
|
96
|
+
signing_key:
|
|
97
|
+
specification_version: 4
|
|
98
|
+
summary: Ruby gem to query DNI information from Peruvian API providers
|
|
99
|
+
test_files: []
|