belpost 0.1.0 → 0.7.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 +4 -4
- data/.env.test.example +11 -0
- data/CHANGELOG.md +39 -0
- data/CONTRIBUTING.md +63 -0
- data/README.md +136 -92
- data/RELEASING.md +41 -0
- data/Rakefile +66 -3
- data/lib/belpost/api_service.rb +3 -1
- data/lib/belpost/client.rb +71 -0
- data/lib/belpost/configuration.rb +25 -2
- data/lib/belpost/validations/address_schema.rb +30 -0
- data/lib/belpost/version.rb +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5555f3f61fcb03030a1e5fd226abac810104277326db2ee42b505656ff0f3ce8
|
4
|
+
data.tar.gz: 585c4832344b4cfa9a654e4605926bacd605bf854e77e21391910de03d3d22db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eaf547611c62aa03df55771a57d4405ea1352b04cf9fe3fddd04f1120b3df24a100a38e4f449e7dcadffa1a7b1bd58101212425d263d7e168b448712af597d5c
|
7
|
+
data.tar.gz: 4eb26318bd0e28467e50a31caa184880fab8276e331efcd7240723124ebf3795615d47eca35516f7f0d5048dbf3bb44663bb25182815d807fd4aef9d59313899
|
data/.env.test.example
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Example environment variables for testing
|
2
|
+
# Copy this file to .env.test and adjust values as needed
|
3
|
+
|
4
|
+
# API URL for testing (required)
|
5
|
+
BELPOST_API_URL=https://test-api.belpost.by
|
6
|
+
|
7
|
+
# Test JWT token - not a real token
|
8
|
+
BELPOST_JWT_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRlc3QgVXNlciIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
9
|
+
|
10
|
+
# Timeout for API requests in seconds
|
11
|
+
BELPOST_TIMEOUT=5
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [0.7.0] - 2025-04-01
|
4
|
+
### Added
|
5
|
+
- Added postal code search functionality via `search_postcode` method
|
6
|
+
- Support for searching postal codes by city, street, and building number
|
7
|
+
- Added comprehensive tests for the new postal code search endpoint
|
8
|
+
|
9
|
+
## [0.6.0] - 2025-04-01
|
10
|
+
### Added
|
11
|
+
- Added search addresses search functionality via `find_address_by_string` method
|
12
|
+
|
13
|
+
## [0.5.1] - 2025-04-01
|
14
|
+
### Fixed
|
15
|
+
- Improved error handling for invalid timeout environment variable
|
16
|
+
- Added fallback for BELPOST_API_URL environment variable to make tests more robust
|
17
|
+
- Fixed Configuration class by implementing `validate!` and `to_h` methods
|
18
|
+
|
19
|
+
## [0.5.0] - 2025-04-01
|
20
|
+
### Added
|
21
|
+
- Added address search functionality via `find_address_by_string` method
|
22
|
+
- Support for query parameters in GET requests
|
23
|
+
|
24
|
+
## [0.4.0] - 2025-04-01
|
25
|
+
### Added
|
26
|
+
- Added configuration via environment variables
|
27
|
+
- Added test coverage and CI setup
|
28
|
+
- Added basic documentation in README
|
29
|
+
- Added .env.test.example for testing setup
|
30
|
+
- Added spec helper with test configuration
|
31
|
+
|
32
|
+
|
33
|
+
## [0.1.0] - 2025-03-31
|
34
|
+
### Added
|
35
|
+
- Initial gem version
|
36
|
+
- Core classes for interacting with the Belpost API
|
37
|
+
- Support for creating parcels
|
38
|
+
- Data validation before sending to API
|
39
|
+
- Error handling and request retries
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Contributing Guide
|
2
|
+
|
3
|
+
## Development Environment Setup
|
4
|
+
|
5
|
+
1. Fork the repository and clone it:
|
6
|
+
```
|
7
|
+
git clone https://github.com/YOUR_USERNAME/belpost.git
|
8
|
+
cd belpost
|
9
|
+
```
|
10
|
+
|
11
|
+
2. Install dependencies:
|
12
|
+
```
|
13
|
+
bundle install
|
14
|
+
```
|
15
|
+
|
16
|
+
3. Run tests to make sure everything works:
|
17
|
+
```
|
18
|
+
bundle exec rspec
|
19
|
+
```
|
20
|
+
|
21
|
+
## Development Process
|
22
|
+
|
23
|
+
1. Create a new branch for your feature or fix:
|
24
|
+
```
|
25
|
+
git checkout -b feature/your-feature-name
|
26
|
+
```
|
27
|
+
|
28
|
+
2. Make changes and add tests for new functionality.
|
29
|
+
|
30
|
+
3. Make sure all tests pass:
|
31
|
+
```
|
32
|
+
bundle exec rspec
|
33
|
+
```
|
34
|
+
|
35
|
+
4. Update documentation if you've added new features or changed existing ones.
|
36
|
+
|
37
|
+
5. Commit your changes:
|
38
|
+
```
|
39
|
+
git commit -am "Added new feature: XYZ"
|
40
|
+
```
|
41
|
+
|
42
|
+
6. Push your branch to GitHub:
|
43
|
+
```
|
44
|
+
git push origin feature/your-feature-name
|
45
|
+
```
|
46
|
+
|
47
|
+
7. Create a Pull Request from your branch to the main repository.
|
48
|
+
|
49
|
+
## Code Conventions
|
50
|
+
|
51
|
+
- Follow the code style used in the project
|
52
|
+
- Write YARD format documentation for new classes and methods
|
53
|
+
- Always add tests for new functionality
|
54
|
+
- Update CHANGELOG.md with descriptions of your changes in the [Unreleased] section
|
55
|
+
|
56
|
+
## Review Process
|
57
|
+
|
58
|
+
After creating a Pull Request:
|
59
|
+
1. Wait for all CI checks to pass
|
60
|
+
2. Respond to comments and make necessary corrections
|
61
|
+
3. After approval, your Pull Request will be merged into the main branch
|
62
|
+
|
63
|
+
Thank you for your contribution!
|
data/README.md
CHANGED
@@ -1,54 +1,57 @@
|
|
1
1
|
# Belpost
|
2
2
|
|
3
|
-
|
3
|
+
Client for working with Belpochta API (Belpost).
|
4
4
|
|
5
|
-
|
5
|
+

|
6
|
+

|
6
7
|
|
7
|
-
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
8
11
|
|
9
12
|
```ruby
|
10
13
|
gem 'belpost'
|
11
14
|
```
|
12
15
|
|
13
|
-
|
16
|
+
And then execute:
|
14
17
|
|
15
18
|
```bash
|
16
19
|
$ bundle install
|
17
20
|
```
|
18
21
|
|
19
|
-
|
22
|
+
Or install it yourself:
|
20
23
|
|
21
24
|
```bash
|
22
25
|
$ gem install belpost
|
23
26
|
```
|
24
27
|
|
25
|
-
##
|
28
|
+
## Configuration
|
26
29
|
|
27
|
-
|
30
|
+
Configure the client to work with the Belpost API:
|
28
31
|
|
29
32
|
```ruby
|
30
33
|
require 'belpost'
|
31
34
|
|
32
35
|
Belpost.configure do |config|
|
33
|
-
config.jwt_token = '
|
36
|
+
config.jwt_token = 'your_jwt_token_from_belpost'
|
34
37
|
config.base_url = 'https://api.belpost.by'
|
35
|
-
config.timeout = 30 #
|
38
|
+
config.timeout = 30 # Timeout in seconds (default 10)
|
36
39
|
end
|
37
40
|
```
|
38
41
|
|
39
|
-
|
42
|
+
You can also use environment variables:
|
40
43
|
|
41
44
|
```
|
42
|
-
BELPOST_JWT_TOKEN
|
45
|
+
BELPOST_JWT_TOKEN=your_jwt_token_from_belpost
|
43
46
|
BELPOST_BASE_URL=https://api.belpost.by
|
44
47
|
BELPOST_TIMEOUT=30
|
45
48
|
```
|
46
49
|
|
47
|
-
##
|
50
|
+
## Usage
|
48
51
|
|
49
|
-
###
|
52
|
+
### Creating a parcel
|
50
53
|
|
51
|
-
####
|
54
|
+
#### Basic example
|
52
55
|
|
53
56
|
```ruby
|
54
57
|
client = Belpost::Client.new
|
@@ -82,23 +85,23 @@ parcel_data = {
|
|
82
85
|
sender: {
|
83
86
|
type: "legal_person",
|
84
87
|
info: {
|
85
|
-
organization_name: "
|
88
|
+
organization_name: "LLC \"Company\"",
|
86
89
|
taxpayer_number: "123456789",
|
87
90
|
IBAN: "BY26BAPB30123418400100000000",
|
88
91
|
BIC: "BAPBBY2X",
|
89
|
-
bank: "
|
92
|
+
bank: "JSC 'BELAGROPROMBANK'"
|
90
93
|
},
|
91
94
|
location: {
|
92
95
|
code: "225212",
|
93
|
-
region: "
|
94
|
-
district: "
|
96
|
+
region: "Brest",
|
97
|
+
district: "Bereza",
|
95
98
|
locality: {
|
96
|
-
type: "
|
97
|
-
name: "
|
99
|
+
type: "city",
|
100
|
+
name: "Bereza"
|
98
101
|
},
|
99
102
|
road: {
|
100
|
-
type: "
|
101
|
-
name: "
|
103
|
+
type: "street",
|
104
|
+
name: "Lenin"
|
102
105
|
},
|
103
106
|
building: "1",
|
104
107
|
housing: "",
|
@@ -110,21 +113,21 @@ parcel_data = {
|
|
110
113
|
recipient: {
|
111
114
|
type: "natural_person",
|
112
115
|
info: {
|
113
|
-
first_name: "
|
114
|
-
second_name: "
|
115
|
-
last_name: "
|
116
|
+
first_name: "Ivan",
|
117
|
+
second_name: "Ivanovich",
|
118
|
+
last_name: "Ivanov"
|
116
119
|
},
|
117
120
|
location: {
|
118
121
|
code: "231365",
|
119
|
-
region: "
|
120
|
-
district: "
|
122
|
+
region: "Grodno",
|
123
|
+
district: "Ivye",
|
121
124
|
locality: {
|
122
|
-
type: "
|
123
|
-
name: "
|
125
|
+
type: "village",
|
126
|
+
name: "Dudy"
|
124
127
|
},
|
125
128
|
road: {
|
126
|
-
type: "
|
127
|
-
name: "
|
129
|
+
type: "street",
|
130
|
+
name: "Central"
|
128
131
|
},
|
129
132
|
building: "1",
|
130
133
|
housing: "",
|
@@ -136,40 +139,40 @@ parcel_data = {
|
|
136
139
|
}
|
137
140
|
|
138
141
|
response = client.create_parcel(parcel_data)
|
139
|
-
puts "
|
142
|
+
puts "Tracking code: #{response["data"]["parcel"]["s10code"]}"
|
140
143
|
```
|
141
144
|
|
142
|
-
####
|
145
|
+
#### Using ParcelBuilder
|
143
146
|
|
144
147
|
```ruby
|
145
148
|
client = Belpost::Client.new
|
146
149
|
|
147
|
-
#
|
150
|
+
# Creating a domestic parcel
|
148
151
|
parcel_data = Belpost::Models::ParcelBuilder.new
|
149
152
|
.with_type("package")
|
150
153
|
.with_attachment_type("products")
|
151
|
-
.with_weight(1500) #
|
152
|
-
.with_dimensions(300, 200, 100) #
|
154
|
+
.with_weight(1500) # weight in grams
|
155
|
+
.with_dimensions(300, 200, 100) # length, width, height in mm
|
153
156
|
.to_country("BY")
|
154
157
|
.with_declared_value(100)
|
155
158
|
.with_cash_on_delivery(50)
|
156
159
|
.add_service(:simple_notification)
|
157
160
|
.add_service(:email_notification)
|
158
|
-
.from_legal_person("
|
161
|
+
.from_legal_person("LLC \"Company\"")
|
159
162
|
.with_sender_details(
|
160
163
|
taxpayer_number: "123456789",
|
161
|
-
bank: "
|
164
|
+
bank: "JSC 'BELAGROPROMBANK'",
|
162
165
|
iban: "BY26BAPB30123418400100000000",
|
163
166
|
bic: "BAPBBY2X"
|
164
167
|
)
|
165
168
|
.with_sender_location(
|
166
169
|
postal_code: "225212",
|
167
|
-
region: "
|
168
|
-
district: "
|
169
|
-
locality_type: "
|
170
|
-
locality_name: "
|
171
|
-
road_type: "
|
172
|
-
road_name: "
|
170
|
+
region: "Brest",
|
171
|
+
district: "Bereza",
|
172
|
+
locality_type: "city",
|
173
|
+
locality_name: "Bereza",
|
174
|
+
road_type: "street",
|
175
|
+
road_name: "Lenin",
|
173
176
|
building: "1"
|
174
177
|
)
|
175
178
|
.with_sender_contact(
|
@@ -177,18 +180,18 @@ parcel_data = Belpost::Models::ParcelBuilder.new
|
|
177
180
|
phone: "375291234567"
|
178
181
|
)
|
179
182
|
.to_natural_person(
|
180
|
-
first_name: "
|
181
|
-
last_name: "
|
182
|
-
second_name: "
|
183
|
+
first_name: "Ivan",
|
184
|
+
last_name: "Ivanov",
|
185
|
+
second_name: "Ivanovich"
|
183
186
|
)
|
184
187
|
.with_recipient_location(
|
185
188
|
postal_code: "231365",
|
186
|
-
region: "
|
187
|
-
district: "
|
188
|
-
locality_type: "
|
189
|
-
locality_name: "
|
190
|
-
road_type: "
|
191
|
-
road_name: "
|
189
|
+
region: "Grodno",
|
190
|
+
district: "Ivye",
|
191
|
+
locality_type: "village",
|
192
|
+
locality_name: "Dudy",
|
193
|
+
road_type: "street",
|
194
|
+
road_name: "Central",
|
192
195
|
building: "1"
|
193
196
|
)
|
194
197
|
.with_recipient_contact(
|
@@ -197,24 +200,24 @@ parcel_data = Belpost::Models::ParcelBuilder.new
|
|
197
200
|
.build
|
198
201
|
|
199
202
|
response = client.create_parcel(parcel_data)
|
200
|
-
puts "
|
203
|
+
puts "Tracking code: #{response["data"]["parcel"]["s10code"]}"
|
201
204
|
```
|
202
205
|
|
203
|
-
####
|
206
|
+
#### Creating an international parcel with a customs declaration
|
204
207
|
|
205
208
|
```ruby
|
206
209
|
client = Belpost::Client.new
|
207
210
|
|
208
|
-
#
|
211
|
+
# Creating a customs declaration
|
209
212
|
customs_declaration = Belpost::Models::CustomsDeclaration.new
|
210
213
|
customs_declaration.set_category("gift")
|
211
214
|
customs_declaration.set_price("USD", 50)
|
212
215
|
customs_declaration.add_item(
|
213
216
|
{
|
214
|
-
name: "
|
215
|
-
local: "
|
217
|
+
name: "Book",
|
218
|
+
local: "Book",
|
216
219
|
unit: {
|
217
|
-
local: "
|
220
|
+
local: "PCS",
|
218
221
|
en: "PCS"
|
219
222
|
},
|
220
223
|
count: 1,
|
@@ -227,22 +230,22 @@ customs_declaration.add_item(
|
|
227
230
|
}
|
228
231
|
)
|
229
232
|
|
230
|
-
#
|
233
|
+
# Creating an international parcel
|
231
234
|
parcel_data = Belpost::Models::ParcelBuilder.new
|
232
235
|
.with_type("package")
|
233
236
|
.with_attachment_type("products")
|
234
237
|
.with_weight(500)
|
235
|
-
.to_country("DE") #
|
238
|
+
.to_country("DE") # Germany
|
236
239
|
.with_declared_value(50, "USD")
|
237
|
-
.from_legal_person("
|
240
|
+
.from_legal_person("LLC \"Company\"")
|
238
241
|
.with_sender_location(
|
239
242
|
postal_code: "225212",
|
240
|
-
region: "
|
241
|
-
district: "
|
242
|
-
locality_type: "
|
243
|
-
locality_name: "
|
244
|
-
road_type: "
|
245
|
-
road_name: "
|
243
|
+
region: "Brest",
|
244
|
+
district: "Bereza",
|
245
|
+
locality_type: "city",
|
246
|
+
locality_name: "Bereza",
|
247
|
+
road_type: "street",
|
248
|
+
road_name: "Lenin",
|
246
249
|
building: "1"
|
247
250
|
)
|
248
251
|
.with_sender_contact(
|
@@ -265,10 +268,10 @@ parcel_data = Belpost::Models::ParcelBuilder.new
|
|
265
268
|
.build
|
266
269
|
|
267
270
|
response = client.create_parcel(parcel_data)
|
268
|
-
puts "
|
271
|
+
puts "Tracking code: #{response["data"]["parcel"]["s10code"]}"
|
269
272
|
```
|
270
273
|
|
271
|
-
###
|
274
|
+
### Getting a list of available countries
|
272
275
|
|
273
276
|
```ruby
|
274
277
|
client = Belpost::Client.new
|
@@ -276,7 +279,7 @@ countries = client.fetch_available_countries
|
|
276
279
|
puts countries
|
277
280
|
```
|
278
281
|
|
279
|
-
###
|
282
|
+
### Obtaining data for postal item validation
|
280
283
|
|
281
284
|
```ruby
|
282
285
|
client = Belpost::Client.new
|
@@ -284,7 +287,7 @@ validation_data = client.validate_postal_delivery("BY")
|
|
284
287
|
puts validation_data
|
285
288
|
```
|
286
289
|
|
287
|
-
###
|
290
|
+
### Obtaining HS codes for customs declaration
|
288
291
|
|
289
292
|
```ruby
|
290
293
|
client = Belpost::Client.new
|
@@ -292,43 +295,84 @@ hs_codes = client.fetch_hs_codes
|
|
292
295
|
puts hs_codes
|
293
296
|
```
|
294
297
|
|
295
|
-
|
298
|
+
### Searching for postal codes
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
client = Belpost::Client.new
|
302
|
+
postcodes = client.search_postcode(
|
303
|
+
city: "Витебск",
|
304
|
+
street: "Ильинского",
|
305
|
+
building: "51/1", # optional
|
306
|
+
limit: 50 # optional, default: 50, range: 1-200
|
307
|
+
)
|
308
|
+
puts postcodes
|
309
|
+
```
|
310
|
+
|
311
|
+
## Error handling
|
296
312
|
|
297
|
-
|
313
|
+
The client may throw the following exceptions:
|
298
314
|
|
299
|
-
- `Belpost::ConfigurationError` -
|
300
|
-
- `Belpost::ValidationError` -
|
301
|
-
- `Belpost::ApiError` -
|
302
|
-
- `Belpost::AuthenticationError` -
|
303
|
-
- `Belpost::InvalidRequestError` -
|
304
|
-
- `Belpost::RateLimitError` -
|
305
|
-
- `Belpost::ServerError` -
|
306
|
-
- `Belpost::NetworkError` -
|
307
|
-
- `Belpost::
|
315
|
+
- `Belpost::ConfigurationError` - configuration error
|
316
|
+
- `Belpost::ValidationError` - data validation error
|
317
|
+
- `Belpost::ApiError` - basic API error
|
318
|
+
- `Belpost::AuthenticationError` - authentication error
|
319
|
+
- `Belpost::InvalidRequestError` - request error
|
320
|
+
- `Belpost::RateLimitError` - request limit exceeded
|
321
|
+
- `Belpost::ServerError` - server error
|
322
|
+
- `Belpost::NetworkError` - network error
|
323
|
+
- `Belpost::RequestError` - request timeout
|
308
324
|
|
309
|
-
|
325
|
+
Example of error handling:
|
310
326
|
|
311
327
|
```ruby
|
312
328
|
begin
|
313
329
|
client = Belpost::Client.new
|
314
330
|
response = client.create_parcel(parcel_data)
|
315
331
|
rescue Belpost::ValidationError => e
|
316
|
-
puts "
|
332
|
+
puts "Validation error: #{e.message}"
|
317
333
|
rescue Belpost::AuthenticationError => e
|
318
|
-
puts "
|
334
|
+
puts "Authentication error: #{e.message}"
|
319
335
|
rescue Belpost::ApiError => e
|
320
|
-
puts "
|
336
|
+
puts "API error: #{e.message}"
|
321
337
|
end
|
322
338
|
```
|
323
339
|
|
324
|
-
##
|
340
|
+
## Documentation
|
341
|
+
|
342
|
+
Full documentation on the Belpost API is available in the official documentation.
|
343
|
+
|
344
|
+
## Development
|
345
|
+
|
346
|
+
After cloning the repository, run `bin/setup` to install dependencies. Then run `rake spec` to run tests. You can also run `bin/console` for an interactive REPL that allows you to experiment.
|
325
347
|
|
326
|
-
|
348
|
+
### Setting up test environment
|
327
349
|
|
328
|
-
|
350
|
+
For running tests, the gem uses environment variables that can be configured in different ways:
|
329
351
|
|
330
|
-
|
352
|
+
1. Copy the `.env.test.example` file to `.env.test` and adjust the values:
|
353
|
+
```
|
354
|
+
cp .env.test.example .env.test
|
355
|
+
```
|
356
|
+
|
357
|
+
2. The test suite will automatically use these values, or fall back to default test values if not provided.
|
358
|
+
|
359
|
+
3. For CI environments, the necessary environment variables are already configured in the GitHub workflow files.
|
360
|
+
|
361
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
362
|
+
|
363
|
+
## Continuous Integration (CI/CD)
|
364
|
+
|
365
|
+
The project is set up to use GitHub Actions for continuous integration (CI) and continuous delivery (CD):
|
366
|
+
|
367
|
+
1. **Testing**: Every push and pull request to the `master` branch automatically runs tests on various Ruby versions.
|
368
|
+
2. **Release**: When a tag starting with `v` is created (e.g., `v0.1.0`), the gem will be automatically published to RubyGems.
|
369
|
+
|
370
|
+
For more detailed information about the release process, see the [RELEASING.md](RELEASING.md) file.
|
331
371
|
|
332
372
|
## Contributing
|
333
373
|
|
334
|
-
|
374
|
+
For information on how to contribute to the project, please see [CONTRIBUTING.md](CONTRIBUTING.md).
|
375
|
+
|
376
|
+
## License
|
377
|
+
|
378
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/RELEASING.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Releasing a New Gem Version
|
2
|
+
|
3
|
+
This document describes the process for releasing a new version of the Belpost gem.
|
4
|
+
|
5
|
+
## Release Steps
|
6
|
+
|
7
|
+
1. Make sure all tests pass successfully:
|
8
|
+
```
|
9
|
+
bundle exec rspec
|
10
|
+
```
|
11
|
+
|
12
|
+
2. Update the version number in `lib/belpost/version.rb`
|
13
|
+
|
14
|
+
3. Update CHANGELOG.md with descriptions of changes in the new version
|
15
|
+
|
16
|
+
4. Commit your changes:
|
17
|
+
```
|
18
|
+
git add lib/belpost/version.rb CHANGELOG.md
|
19
|
+
git commit -m "Release version X.Y.Z"
|
20
|
+
```
|
21
|
+
|
22
|
+
5. Create a tag for the new version:
|
23
|
+
```
|
24
|
+
git tag -a vX.Y.Z -m "Version X.Y.Z"
|
25
|
+
```
|
26
|
+
|
27
|
+
6. Push the commit and tag to GitHub:
|
28
|
+
```
|
29
|
+
git push origin master
|
30
|
+
git push origin vX.Y.Z
|
31
|
+
```
|
32
|
+
|
33
|
+
7. GitHub Actions will automatically publish the gem to RubyGems.org when a new tag starting with 'v' is created.
|
34
|
+
|
35
|
+
## Setup
|
36
|
+
|
37
|
+
For automatic gem publication, you need to add the `RUBYGEMS_API_KEY` secret in your GitHub repository settings:
|
38
|
+
|
39
|
+
1. Create an API key on RubyGems.org (if not already created)
|
40
|
+
2. Go to your repository settings on GitHub: Settings > Secrets and variables > Actions
|
41
|
+
3. Add a new secret named `RUBYGEMS_API_KEY` with the value of your API key from RubyGems.org
|
data/Rakefile
CHANGED
@@ -2,11 +2,74 @@
|
|
2
2
|
|
3
3
|
require "bundler/gem_tasks"
|
4
4
|
require "rspec/core/rake_task"
|
5
|
-
|
6
|
-
RSpec::Core::RakeTask.new(:spec)
|
7
|
-
|
8
5
|
require "rubocop/rake_task"
|
9
6
|
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
8
|
RuboCop::RakeTask.new
|
11
9
|
|
12
10
|
task default: %i[spec rubocop]
|
11
|
+
|
12
|
+
namespace :gem do
|
13
|
+
desc "Build the belpost gem"
|
14
|
+
task :build do
|
15
|
+
system "gem build belpost.gemspec"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Install the gem locally"
|
19
|
+
task install: :build do
|
20
|
+
system "gem install belpost-*.gem"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Clean up gem-related files"
|
24
|
+
task :clean do
|
25
|
+
system "rm -f *.gem"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
namespace :version do
|
30
|
+
desc "Display current version"
|
31
|
+
task :show do
|
32
|
+
require_relative "lib/belpost/version"
|
33
|
+
puts "Current version: #{Belpost::VERSION}"
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Increment patch version (x.y.Z)"
|
37
|
+
task :patch do
|
38
|
+
update_version(:patch)
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Increment minor version (x.Y.z)"
|
42
|
+
task :minor do
|
43
|
+
update_version(:minor)
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Increment major version (X.y.z)"
|
47
|
+
task :major do
|
48
|
+
update_version(:major)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_version(level)
|
53
|
+
version_file = "lib/belpost/version.rb"
|
54
|
+
content = File.read(version_file)
|
55
|
+
major, minor, patch = content.match(/VERSION\s*=\s*["'](\d+)\.(\d+)\.(\d+)["']/)[1, 3].map(&:to_i)
|
56
|
+
|
57
|
+
case level
|
58
|
+
when :major
|
59
|
+
major += 1
|
60
|
+
minor = 0
|
61
|
+
patch = 0
|
62
|
+
when :minor
|
63
|
+
minor += 1
|
64
|
+
patch = 0
|
65
|
+
when :patch
|
66
|
+
patch += 1
|
67
|
+
end
|
68
|
+
|
69
|
+
new_version = "#{major}.#{minor}.#{patch}"
|
70
|
+
new_content = content.gsub(/VERSION\s*=\s*["']\d+\.\d+\.\d+["']/, "VERSION = \"#{new_version}\"")
|
71
|
+
|
72
|
+
File.write(version_file, new_content)
|
73
|
+
puts "Version updated to #{new_version}"
|
74
|
+
puts "Don't forget to update CHANGELOG.md!"
|
75
|
+
end
|
data/lib/belpost/api_service.rb
CHANGED
@@ -23,10 +23,12 @@ module Belpost
|
|
23
23
|
# Performs a GET request to the specified path.
|
24
24
|
#
|
25
25
|
# @param path [String] The API endpoint path.
|
26
|
+
# @param params [Hash] The query parameters (default: {}).
|
26
27
|
# @return [Models::ApiResponse] The parsed JSON response from the API.
|
27
|
-
def get(path)
|
28
|
+
def get(path, params = {})
|
28
29
|
Retry.with_retry do
|
29
30
|
uri = URI("#{@base_url}#{path}")
|
31
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
30
32
|
request = Net::HTTP::Get.new(uri)
|
31
33
|
add_headers(request)
|
32
34
|
|
data/lib/belpost/client.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require_relative "api_service"
|
4
4
|
require_relative "models/parcel"
|
5
5
|
require_relative "models/api_response"
|
6
|
+
require_relative "validations/address_schema"
|
6
7
|
|
7
8
|
module Belpost
|
8
9
|
# Main client class for interacting with the BelPost API.
|
@@ -67,5 +68,75 @@ module Belpost
|
|
67
68
|
response = @api_service.get("/api/v1/business/postal-deliveries/countries")
|
68
69
|
response.to_h
|
69
70
|
end
|
71
|
+
|
72
|
+
# Allows you to find an address by a string.
|
73
|
+
#
|
74
|
+
# Accepts a string with an address in any form and returns found addresses (up to 50 records).
|
75
|
+
# Building numbers should be specified without spaces: "building number""letter""building".
|
76
|
+
# The letter should be uppercase, and "building" (or "корп", "кор", "к") should be replaced with "/".
|
77
|
+
# Example: "город Минск улица Автодоровская 3Е корпус 4" should be transformed to "город Минск улица Автодоровская 3Е/4".
|
78
|
+
#
|
79
|
+
# @param address [String] The address string to search for.
|
80
|
+
# @return [Array<Hash>] An array of found addresses with postcode, region, city, street and other information.
|
81
|
+
# @raise [Belpost::ApiError] If the API returns an error response.
|
82
|
+
# @raise [Belpost::InvalidRequestError] If the address parameter is missing or has an incorrect format.
|
83
|
+
def find_address_by_string(address)
|
84
|
+
raise ValidationError, "Address must be filled" if address.nil?
|
85
|
+
raise ValidationError, "Address must be a string" unless address.is_a?(String)
|
86
|
+
raise ValidationError, "Address must be filled" if address.empty?
|
87
|
+
|
88
|
+
formatted_address = format_address(address)
|
89
|
+
response = @api_service.get("/api/v1/business/geo-directory/search-address", { search: formatted_address })
|
90
|
+
response.to_h
|
91
|
+
end
|
92
|
+
|
93
|
+
# Searches for postal codes by city, street, and building number.
|
94
|
+
#
|
95
|
+
# @param city [String] The city name (required)
|
96
|
+
# @param street [String] The street name (required)
|
97
|
+
# @param building [String] The building number (optional)
|
98
|
+
# @param limit [Integer] Maximum number of results (optional, default: 50, range: 1-200)
|
99
|
+
# @return [Array<Hash>] An array of found addresses with postcode, region, city, street and other information
|
100
|
+
# @raise [Belpost::ValidationError] If required parameters are missing or invalid
|
101
|
+
# @raise [Belpost::ApiError] If the API returns an error response
|
102
|
+
def search_postcode(city:, street:, building: nil, limit: 50)
|
103
|
+
raise ValidationError, "City must be filled" if city.nil?
|
104
|
+
raise ValidationError, "City must be a string" unless city.is_a?(String)
|
105
|
+
raise ValidationError, "City must be filled" if city.empty?
|
106
|
+
raise ValidationError, "Street must be filled" if street.nil?
|
107
|
+
raise ValidationError, "Street must be a string" unless street.is_a?(String)
|
108
|
+
raise ValidationError, "Street must be filled" if street.empty?
|
109
|
+
raise ValidationError, "Building must be a string" if building && !building.is_a?(String)
|
110
|
+
raise ValidationError, "Limit must be between 1 and 200" if limit < 1 || limit > 200
|
111
|
+
|
112
|
+
params = { city: city, street: street }
|
113
|
+
params[:building] = format_building_number(building) if building
|
114
|
+
params[:limit] = limit
|
115
|
+
|
116
|
+
response = @api_service.get("/api/v1/business/geo-directory/postcode", params)
|
117
|
+
response.to_h
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def format_address(address)
|
123
|
+
address.gsub(/\s+/, " ")
|
124
|
+
.gsub(/\s*корпус\s*(\d+)\s*/i, '/\1')
|
125
|
+
.gsub(/\s*корп\s*(\d+)\s*/i, '/\1')
|
126
|
+
.gsub(/\s*кор\s*(\d+)\s*/i, '/\1')
|
127
|
+
.gsub(/\s*к\s*(\d+)\s*/i, '/\1')
|
128
|
+
.strip
|
129
|
+
end
|
130
|
+
|
131
|
+
def format_building_number(building)
|
132
|
+
return building unless building
|
133
|
+
|
134
|
+
building.gsub(/\s+/, " ")
|
135
|
+
.gsub(/\s*корпус\s*(\d+)\s*/i, '/\1')
|
136
|
+
.gsub(/\s*корп\s*(\d+)\s*/i, '/\1')
|
137
|
+
.gsub(/\s*кор\s*(\d+)\s*/i, '/\1')
|
138
|
+
.gsub(/\s*к\s*(\d+)\s*/i, '/\1')
|
139
|
+
.strip
|
140
|
+
end
|
70
141
|
end
|
71
142
|
end
|
@@ -7,9 +7,32 @@ module Belpost
|
|
7
7
|
attr_accessor :base_url, :jwt_token, :timeout
|
8
8
|
|
9
9
|
def initialize
|
10
|
-
@base_url = ENV.fetch("BELPOST_API_URL")
|
10
|
+
@base_url = ENV.fetch("BELPOST_API_URL", "https://api.belpost.by")
|
11
11
|
@jwt_token = ENV.fetch("BELPOST_JWT_TOKEN", nil)
|
12
|
-
|
12
|
+
|
13
|
+
# Convert timeout to integer with a fallback to default
|
14
|
+
begin
|
15
|
+
@timeout = Integer(ENV.fetch("BELPOST_TIMEOUT", 10))
|
16
|
+
rescue ArgumentError
|
17
|
+
@timeout = 10
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Validates that all required configuration is present
|
22
|
+
# @raise [Belpost::ConfigurationError] If required configuration is missing
|
23
|
+
def validate!
|
24
|
+
raise ConfigurationError, "Base URL is required" if base_url.nil?
|
25
|
+
raise ConfigurationError, "JWT token is required" if jwt_token.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a hash representation of the configuration
|
29
|
+
# @return [Hash] The configuration as a hash
|
30
|
+
def to_h
|
31
|
+
{
|
32
|
+
base_url: base_url,
|
33
|
+
jwt_token: jwt_token,
|
34
|
+
timeout: timeout
|
35
|
+
}
|
13
36
|
end
|
14
37
|
end
|
15
38
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-validation"
|
4
|
+
|
5
|
+
module Belpost
|
6
|
+
module Validation
|
7
|
+
# Schema for validating and formatting address strings
|
8
|
+
class AddressSchema < Dry::Validation::Contract
|
9
|
+
params do
|
10
|
+
required(:address).filled(:string)
|
11
|
+
end
|
12
|
+
|
13
|
+
rule(:address) do
|
14
|
+
# Remove extra spaces
|
15
|
+
address = value.gsub(/\s+/, " ").strip
|
16
|
+
|
17
|
+
# Replace building indicators with "/"
|
18
|
+
address = address.gsub(/\s+(корпус|корп|кор|к)\s+/, "/")
|
19
|
+
|
20
|
+
# Ensure building number format (no spaces between number, letter, and building)
|
21
|
+
address = address.gsub(/(\d+)\s*([А-Я])\s*(\d+)/, '\1\2/\3')
|
22
|
+
|
23
|
+
# Ensure letter is uppercase
|
24
|
+
address = address.gsub(/(\d+)([а-я])(\/\d+)/) { |m| "#{$1}#{$2.upcase}#{$3}" }
|
25
|
+
|
26
|
+
key.failure(address)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/belpost/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: belpost
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- KuberLite
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date: 2025-
|
10
|
+
date: 2025-04-01 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: dotenv
|
@@ -46,9 +45,13 @@ extensions: []
|
|
46
45
|
extra_rdoc_files: []
|
47
46
|
files:
|
48
47
|
- ".env.example "
|
48
|
+
- ".env.test.example"
|
49
49
|
- ".rspec"
|
50
50
|
- ".rubocop.yml"
|
51
|
+
- CHANGELOG.md
|
52
|
+
- CONTRIBUTING.md
|
51
53
|
- README.md
|
54
|
+
- RELEASING.md
|
52
55
|
- Rakefile
|
53
56
|
- belpost.gemspec
|
54
57
|
- lib/belpost.rb
|
@@ -61,6 +64,7 @@ files:
|
|
61
64
|
- lib/belpost/models/parcel.rb
|
62
65
|
- lib/belpost/models/parcel_builder.rb
|
63
66
|
- lib/belpost/retry.rb
|
67
|
+
- lib/belpost/validations/address_schema.rb
|
64
68
|
- lib/belpost/validations/parcel_schema.rb
|
65
69
|
- lib/belpost/version.rb
|
66
70
|
homepage: https://github.com/KuberLite/belpost
|
@@ -72,7 +76,6 @@ metadata:
|
|
72
76
|
source_code_uri: https://github.com/belpost/evropochta
|
73
77
|
homepage_uri: https://github.com/KuberLite/belpost
|
74
78
|
rubygems_mfa_required: 'true'
|
75
|
-
post_install_message:
|
76
79
|
rdoc_options: []
|
77
80
|
require_paths:
|
78
81
|
- lib
|
@@ -87,8 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
90
|
- !ruby/object:Gem::Version
|
88
91
|
version: '0'
|
89
92
|
requirements: []
|
90
|
-
rubygems_version: 3.
|
91
|
-
signing_key:
|
93
|
+
rubygems_version: 3.6.2
|
92
94
|
specification_version: 4
|
93
95
|
summary: Belpost API wrapper
|
94
96
|
test_files: []
|