rospatent 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +77 -32
- data/lib/rospatent/client.rb +85 -20
- data/lib/rospatent/input_validator.rb +32 -0
- data/lib/rospatent/version.rb +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0fd09ca7a6d13e73a4ec4af7f9e06e4e4a5c640607b551c614289898d83a177
|
4
|
+
data.tar.gz: 76cca6feb25bf64b8c069059470f87e7aef159331f93a0fccb2e7147df69cbab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1022920b2f35d2db3558c6df5668db8421df46bbdba2ee13a40d5119a36ca5e3e4a4fb33d8432a10f088fa8dfcb9d1333a7a9b05f4c1ba287ddc8887cf3f32b8
|
7
|
+
data.tar.gz: 8ce447fae3280ee2d0a4ff83ab481c8a47321bddcb2cbd310c557d01700635ea271079dcda98f4993d312573d3ec29404a02377743396c2b38ac2f60498d5593
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [1.2.0] - 2025-06-04
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Binary data support for `patent_media` and `patent_media_by_id` methods to properly handle PDF, image, and other media files
|
12
|
+
- New `binary` parameter for `get` method to distinguish between JSON and binary responses
|
13
|
+
- New `handle_binary_response` method for processing binary API responses with proper error handling
|
14
|
+
- Russian patent number formatting with automatic zero-padding to 10 digits
|
15
|
+
- New `format_publication_number` private method for consistent number formatting across media methods
|
16
|
+
|
17
|
+
### Fixed
|
18
|
+
- API endpoint paths for `classification_search` and `classification_code` methods now include trailing slashes to prevent 404 errors
|
19
|
+
- Binary data corruption issue when downloading patent media files (PDFs, images) through media endpoints
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
- Enhanced test coverage for binary data handling and publication number formatting
|
23
|
+
- Updated README documentation for classification search examples and dataset display conventions
|
24
|
+
- Improved error handling consistency for binary vs JSON responses
|
25
|
+
|
26
|
+
## [1.1.0] - 2025-06-04
|
27
|
+
|
28
|
+
### Added
|
29
|
+
- Word count validation for `similar_patents_by_text` method (minimum 50 words required)
|
30
|
+
- New `validate_text_with_word_count` validation method with configurable minimum word requirements
|
31
|
+
- Staging environment configuration documentation and examples
|
32
|
+
- Enhanced Russian documentation sections
|
33
|
+
|
34
|
+
### Changed
|
35
|
+
- Improved error handling for insufficient word count with descriptive messages showing current vs required count
|
36
|
+
- Error type changed from `InvalidRequestError` to `ValidationError` for text validation consistency
|
37
|
+
|
38
|
+
### Fixed
|
39
|
+
- Documentation clarifications for similar patents API response format (`data` vs `hits` naming)
|
40
|
+
- Updated README examples to use correct API response structure
|
41
|
+
- Corrected minimum word requirements documentation for text-based similarity search
|
42
|
+
|
8
43
|
## [1.0.0] - 2025-06-03
|
9
44
|
|
10
45
|
### Added
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Rospatent
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/rospatent)
|
4
|
+
|
3
5
|
A comprehensive Ruby client for the Rospatent patent search API with advanced features including intelligent caching, input validation, structured logging, and robust error handling.
|
4
6
|
|
5
7
|
> 🇷🇺 **[Документация на русском языке](#-документация-на-русском-языке)** доступна ниже
|
@@ -211,13 +213,13 @@ similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
|
|
211
213
|
|
212
214
|
# Find similar patents by text description
|
213
215
|
similar = client.similar_patents_by_text(
|
214
|
-
"Ракетный двигатель с улучшенной тягой",
|
216
|
+
"Ракетный двигатель с улучшенной тягой ...", # 50 words in request minimum
|
215
217
|
count: 25
|
216
218
|
)
|
217
219
|
|
218
220
|
# Process similar patents
|
219
|
-
similar["
|
220
|
-
puts "Similar: #{patent['id']} (score: #{patent['
|
221
|
+
similar["data"]&.each do |patent|
|
222
|
+
puts "Similar: #{patent['id']} (score: #{patent['similarity']} (#{patent['similarity_norm']}))"
|
221
223
|
end
|
222
224
|
```
|
223
225
|
|
@@ -228,20 +230,20 @@ Search within patent classification systems (IPC and CPC) and get detailed infor
|
|
228
230
|
```ruby
|
229
231
|
# Search for classification codes related to rockets in IPC
|
230
232
|
ipc_results = client.classification_search("ipc", query: "ракета", lang: "ru")
|
231
|
-
puts "Found #{ipc_results
|
233
|
+
puts "Found #{ipc_results.size} IPC codes"
|
232
234
|
|
233
|
-
ipc_results
|
234
|
-
puts "#{
|
235
|
+
ipc_results&.each do |result|
|
236
|
+
puts "#{result['Code']}: #{result['Description']}"
|
235
237
|
end
|
236
238
|
|
237
239
|
# Search for rocket-related codes in CPC using English
|
238
240
|
cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
|
239
241
|
|
240
242
|
# Get detailed information about a specific classification code
|
241
|
-
|
242
|
-
puts "Code: #{
|
243
|
-
puts "Description: #{
|
244
|
-
puts "Hierarchy: #{
|
243
|
+
code, info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")&.first
|
244
|
+
puts "Code: #{code}"
|
245
|
+
puts "Description: #{info&.first['Description']}"
|
246
|
+
puts "Hierarchy: #{info&.map{|level| level['Code']}&.join(' → ')}"
|
245
247
|
|
246
248
|
# Get CPC code information in English
|
247
249
|
cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
|
@@ -277,9 +279,11 @@ pdf_data = client.patent_media_by_id(
|
|
277
279
|
)
|
278
280
|
|
279
281
|
# Get available datasets
|
280
|
-
datasets
|
281
|
-
|
282
|
-
|
282
|
+
datasets.each do |category|
|
283
|
+
puts "Category: #{category['name_en']}"
|
284
|
+
category.children.each do |dataset|
|
285
|
+
puts " #{dataset['id']}: #{dataset['name_en']}"
|
286
|
+
end
|
283
287
|
end
|
284
288
|
```
|
285
289
|
|
@@ -437,6 +441,20 @@ Rospatent.configure do |config|
|
|
437
441
|
end
|
438
442
|
```
|
439
443
|
|
444
|
+
### Staging Environment
|
445
|
+
|
446
|
+
```ruby
|
447
|
+
# Optimized for staging
|
448
|
+
Rospatent.configure do |config|
|
449
|
+
config.environment = "staging"
|
450
|
+
config.token = ENV['ROSPATENT_TOKEN']
|
451
|
+
config.log_level = :info
|
452
|
+
config.cache_ttl = 300 # Longer cache for performance
|
453
|
+
config.timeout = 45 # Longer timeouts for reliability
|
454
|
+
config.retry_count = 3 # More retries for resilience
|
455
|
+
end
|
456
|
+
```
|
457
|
+
|
440
458
|
### Production Environment
|
441
459
|
|
442
460
|
```ruby
|
@@ -597,8 +615,11 @@ The library uses **Faraday** as the HTTP client with redirect support for all en
|
|
597
615
|
- **All endpoints** (`/search`, `/docs/{id}`, `/similar_search`, `/datasets/tree`, etc.) - ✅ Working perfectly with Faraday
|
598
616
|
- **Redirect handling**: Configured with `faraday-follow_redirects` middleware to handle server redirects automatically
|
599
617
|
|
600
|
-
⚠️ **Minor server-side
|
601
|
-
- **Similar Patents by Text**: Occasionally returns `503 Service Unavailable` (server-side issue, not client implementation)
|
618
|
+
⚠️ **Minor server-side limitations**:
|
619
|
+
- **Similar Patents by Text**: Occasionally returns `503 Service Unavailable` (a server-side issue, not a client implementation issue)
|
620
|
+
⚠️ **Documentation inconsistencies**:
|
621
|
+
- **Similar Patents**: According to the documentation, the array of hits is named `hits`, but the real implementation uses the name `data`
|
622
|
+
- **Available Datasets**: The `name` key in the real implementation has the localization suffix — `name_ru`, `name_en`
|
602
623
|
|
603
624
|
All core functionality works perfectly and is production-ready with a unified HTTP approach.
|
604
625
|
|
@@ -885,13 +906,13 @@ similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
|
|
885
906
|
|
886
907
|
# Поиск похожих патентов по описанию текста
|
887
908
|
similar = client.similar_patents_by_text(
|
888
|
-
"Ракетный двигатель с улучшенной тягой",
|
909
|
+
"Ракетный двигатель с улучшенной тягой ...", # минимум 50 слов в запросе
|
889
910
|
count: 25
|
890
911
|
)
|
891
912
|
|
892
913
|
# Обработка похожих патентов
|
893
|
-
similar["
|
894
|
-
puts "Похожий: #{patent['id']} (оценка: #{patent['
|
914
|
+
similar["data"]&.each do |patent|
|
915
|
+
puts "Похожий: #{patent['id']} (оценка: #{patent['similarity']} (#{patent['similarity_norm']}))"
|
895
916
|
end
|
896
917
|
```
|
897
918
|
|
@@ -902,20 +923,20 @@ end
|
|
902
923
|
```ruby
|
903
924
|
# Поиск классификационных кодов, связанных с ракетами в IPC
|
904
925
|
ipc_results = client.classification_search("ipc", query: "ракета", lang: "ru")
|
905
|
-
puts "Найдено #{ipc_results
|
926
|
+
puts "Найдено #{ipc_results.size} кодов IPC"
|
906
927
|
|
907
|
-
ipc_results
|
908
|
-
puts "#{
|
928
|
+
ipc_results&.each do |result|
|
929
|
+
puts "#{result['Code']}: #{result['Description']}"
|
909
930
|
end
|
910
931
|
|
911
932
|
# Поиск кодов, связанных с ракетами в CPC на английском
|
912
933
|
cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
|
913
934
|
|
914
935
|
# Получение подробной информации о конкретном классификационном коде
|
915
|
-
|
916
|
-
puts "Код: #{
|
917
|
-
puts "Описание: #{
|
918
|
-
puts "Иерархия: #{
|
936
|
+
code, info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")&.first
|
937
|
+
puts "Код: #{code}"
|
938
|
+
puts "Описание: #{info&.first['Description']}"
|
939
|
+
puts "Иерархия: #{info&.map{|level| level['Code']}&.join(' → ')}"
|
919
940
|
|
920
941
|
# Получение информации о коде CPC на английском
|
921
942
|
cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
|
@@ -952,8 +973,11 @@ pdf_data = client.patent_media_by_id(
|
|
952
973
|
|
953
974
|
# Получение доступных датасетов
|
954
975
|
datasets = client.datasets_tree
|
955
|
-
datasets.each do |
|
956
|
-
puts "
|
976
|
+
datasets.each do |category|
|
977
|
+
puts "Категория: #{category['name_ru']}"
|
978
|
+
category.children.each do |dataset|
|
979
|
+
puts " #{dataset['id']}: #{dataset['name_ru']}"
|
980
|
+
end
|
957
981
|
end
|
958
982
|
```
|
959
983
|
|
@@ -1004,25 +1028,43 @@ end
|
|
1004
1028
|
### Разработка
|
1005
1029
|
|
1006
1030
|
```ruby
|
1031
|
+
# Оптимизировано для разработки
|
1007
1032
|
Rospatent.configure do |config|
|
1008
1033
|
config.environment = "development"
|
1009
1034
|
config.token = ENV['ROSPATENT_DEV_TOKEN']
|
1010
1035
|
config.log_level = :debug
|
1011
1036
|
config.log_requests = true
|
1012
|
-
config.
|
1037
|
+
config.log_responses = true
|
1038
|
+
config.cache_ttl = 60 # Короткий кеш для разработки
|
1039
|
+
config.timeout = 10 # Быстрые таймауты для быстрой обратной связи
|
1040
|
+
end
|
1041
|
+
```
|
1042
|
+
|
1043
|
+
### Staging
|
1044
|
+
|
1045
|
+
```ruby
|
1046
|
+
# Оптимизировано для staging
|
1047
|
+
Rospatent.configure do |config|
|
1048
|
+
config.environment = "staging"
|
1049
|
+
config.token = ENV['ROSPATENT_TOKEN']
|
1050
|
+
config.log_level = :info
|
1051
|
+
config.cache_ttl = 300 # Более длительный кеш для производительности
|
1052
|
+
config.timeout = 45 # Более длительные таймауты для надежности
|
1053
|
+
config.retry_count = 3 # Больше повторов для устойчивости
|
1013
1054
|
end
|
1014
1055
|
```
|
1015
1056
|
|
1016
1057
|
### Продакшн
|
1017
1058
|
|
1018
1059
|
```ruby
|
1060
|
+
# Оптимизировано для продакшна
|
1019
1061
|
Rospatent.configure do |config|
|
1020
1062
|
config.environment = "production"
|
1021
1063
|
config.token = ENV['ROSPATENT_TOKEN']
|
1022
1064
|
config.log_level = :warn
|
1023
|
-
config.cache_ttl = 600
|
1024
|
-
config.timeout = 60
|
1025
|
-
config.retry_count = 5
|
1065
|
+
config.cache_ttl = 600 # Более длительный кеш для производительности
|
1066
|
+
config.timeout = 60 # Более длительные таймауты для надежности
|
1067
|
+
config.retry_count = 5 # Больше повторов для устойчивости
|
1026
1068
|
end
|
1027
1069
|
```
|
1028
1070
|
|
@@ -1056,8 +1098,11 @@ end
|
|
1056
1098
|
- **Все endpoints** (`/search`, `/docs/{id}`, `/similar_search`, `/datasets/tree`, и т.д.) - ✅ Работают идеально с Faraday
|
1057
1099
|
- **Обработка редиректов**: Настроена с middleware `faraday-follow_redirects` для автоматической обработки серверных редиректов
|
1058
1100
|
|
1059
|
-
⚠️
|
1101
|
+
⚠️ **Незначительные серверные ограничения**:
|
1060
1102
|
- **Поиск похожих патентов по тексту**: Иногда возвращает `503 Service Unavailable` (проблема сервера, не клиентской реализации)
|
1103
|
+
⚠️ **Неточности документации**:
|
1104
|
+
- **Поиск похожих патентов**: Массив совпадений в документации назван `hits`, фактическая реализация использует `data`
|
1105
|
+
- **Перечень датасетов**: Ключ `name` в фактической реализации содержит признак локализации — `name_ru`, `name_en`
|
1061
1106
|
|
1062
1107
|
Вся основная функциональность реализована и готова для продакшена.
|
1063
1108
|
|
data/lib/rospatent/client.rb
CHANGED
@@ -147,13 +147,14 @@ module Rospatent
|
|
147
147
|
end
|
148
148
|
|
149
149
|
# Find patents similar to a given text
|
150
|
-
# @param text [String] The text to find similar patents to
|
150
|
+
# @param text [String] The text to find similar patents to (minimum 50 words required)
|
151
151
|
# @param count [Integer] Maximum number of results to return (default: 100)
|
152
152
|
# @return [Hash] The similar search results
|
153
|
-
# @raise [Rospatent::Errors::
|
153
|
+
# @raise [Rospatent::Errors::ValidationError] If text has insufficient words or other validation errors
|
154
154
|
def similar_patents_by_text(text, count: 100)
|
155
|
-
# Validate inputs
|
156
|
-
validated_text =
|
155
|
+
# Validate inputs - text must have at least 50 words for the API
|
156
|
+
validated_text = validate_text_with_word_count(text, "search_text", min_words: 50,
|
157
|
+
max_length: 10_000)
|
157
158
|
validated_count = validate_positive_integer(count, "count", max_value: 1000)
|
158
159
|
|
159
160
|
# Check cache first (using hash of text for key)
|
@@ -198,7 +199,7 @@ module Rospatent
|
|
198
199
|
@logger.log_cache("miss", cache_key)
|
199
200
|
|
200
201
|
# Make the API request
|
201
|
-
result = get("/patsearch/v0.2/datasets/tree")
|
202
|
+
result = get("/patsearch/v0.2/datasets/tree", {})
|
202
203
|
|
203
204
|
# Cache the result for longer since datasets don't change often
|
204
205
|
@cache.set(cache_key, result, ttl: 3600) # Cache for 1 hour
|
@@ -229,19 +230,22 @@ module Rospatent
|
|
229
230
|
# Format publication date
|
230
231
|
formatted_date = validated_date.strftime("%Y/%m/%d")
|
231
232
|
|
233
|
+
# Format publication number with appropriate padding
|
234
|
+
formatted_number = format_publication_number(validated_number, validated_country)
|
235
|
+
|
232
236
|
# Construct the path
|
233
237
|
path = "/media/#{validated_collection}/#{validated_country}/" \
|
234
|
-
"#{validated_doc_type}/#{formatted_date}/#{
|
238
|
+
"#{validated_doc_type}/#{formatted_date}/#{formatted_number}/" \
|
235
239
|
"#{validated_filename}"
|
236
240
|
|
237
|
-
#
|
238
|
-
get(path)
|
241
|
+
# Get binary data
|
242
|
+
get(path, {}, binary: true)
|
239
243
|
end
|
240
244
|
|
241
|
-
#
|
242
|
-
# @param document_id [String]
|
243
|
-
# @param collection_id [String]
|
244
|
-
# @param filename [String]
|
245
|
+
# Retrieve media using simplified patent ID format
|
246
|
+
# @param document_id [String] Patent document ID (e.g., "RU134694U1_20131120")
|
247
|
+
# @param collection_id [String] Collection identifier (e.g., "National")
|
248
|
+
# @param filename [String] Filename to retrieve (e.g., "document.pdf")
|
245
249
|
# @return [String] Binary content of the requested file
|
246
250
|
# @raise [Rospatent::Errors::InvalidRequestError] If document_id format is invalid
|
247
251
|
# or parameters are missing
|
@@ -257,9 +261,12 @@ module Rospatent
|
|
257
261
|
# Format the date from YYYYMMDD to YYYY/MM/DD
|
258
262
|
formatted_date = id_parts[:date].gsub(/^(\d{4})(\d{2})(\d{2})$/, '\1/\2/\3')
|
259
263
|
|
264
|
+
# Format publication number with appropriate padding
|
265
|
+
formatted_number = format_publication_number(id_parts[:number], id_parts[:country_code])
|
266
|
+
|
260
267
|
# Call the base method with extracted components
|
261
268
|
patent_media(validated_collection, id_parts[:country_code], id_parts[:doc_type],
|
262
|
-
formatted_date,
|
269
|
+
formatted_date, formatted_number, validated_filename)
|
263
270
|
end
|
264
271
|
|
265
272
|
# Extract and parse the abstract content from a patent document
|
@@ -333,7 +340,7 @@ module Rospatent
|
|
333
340
|
}
|
334
341
|
|
335
342
|
# Make a POST request to the classification search endpoint
|
336
|
-
result = post("/patsearch/v0.2/classification/#{validated_classifier}/search", payload)
|
343
|
+
result = post("/patsearch/v0.2/classification/#{validated_classifier}/search/", payload)
|
337
344
|
|
338
345
|
# Cache the result
|
339
346
|
@cache.set(cache_key, result, ttl: 1800) # Cache for 30 minutes
|
@@ -373,7 +380,7 @@ module Rospatent
|
|
373
380
|
}
|
374
381
|
|
375
382
|
# Make a POST request to the classification code endpoint
|
376
|
-
result = post("/patsearch/v0.2/classification/#{validated_classifier}/code", payload)
|
383
|
+
result = post("/patsearch/v0.2/classification/#{validated_classifier}/code/", payload)
|
377
384
|
|
378
385
|
# Cache the result for longer since classification codes don't change often
|
379
386
|
@cache.set(cache_key, result, ttl: 3600) # Cache for 1 hour
|
@@ -385,8 +392,9 @@ module Rospatent
|
|
385
392
|
# Execute a GET request to the API
|
386
393
|
# @param endpoint [String] API endpoint
|
387
394
|
# @param params [Hash] Query parameters (optional)
|
388
|
-
# @
|
389
|
-
|
395
|
+
# @param binary [Boolean] Whether to expect binary response (default: false)
|
396
|
+
# @return [Hash, String] Response data (Hash for JSON, String for binary)
|
397
|
+
def get(endpoint, params = {}, binary: false)
|
390
398
|
start_time = Time.now
|
391
399
|
request_id = generate_request_id
|
392
400
|
|
@@ -394,8 +402,12 @@ module Rospatent
|
|
394
402
|
@request_count += 1
|
395
403
|
|
396
404
|
response = connection.get(endpoint, params) do |req|
|
397
|
-
|
398
|
-
|
405
|
+
if binary
|
406
|
+
req.headers["Accept"] = "*/*"
|
407
|
+
else
|
408
|
+
req.headers["Accept"] = "application/json"
|
409
|
+
req.headers["Content-Type"] = "application/json"
|
410
|
+
end
|
399
411
|
req.headers["X-Request-ID"] = request_id
|
400
412
|
end
|
401
413
|
|
@@ -405,7 +417,11 @@ module Rospatent
|
|
405
417
|
@logger.log_response("GET", endpoint, response.status, duration,
|
406
418
|
response_size: response.body&.bytesize, request_id: request_id)
|
407
419
|
|
408
|
-
|
420
|
+
if binary
|
421
|
+
handle_binary_response(response, request_id)
|
422
|
+
else
|
423
|
+
handle_response(response, request_id)
|
424
|
+
end
|
409
425
|
rescue Faraday::Error => e
|
410
426
|
@logger.log_error(e, { endpoint: endpoint, params: params, request_id: request_id })
|
411
427
|
handle_error(e)
|
@@ -641,6 +657,42 @@ module Rospatent
|
|
641
657
|
end
|
642
658
|
end
|
643
659
|
|
660
|
+
# Process binary API response (for media files)
|
661
|
+
# @param response [Faraday::Response] Raw response from the API
|
662
|
+
# @param request_id [String] Request ID for tracking
|
663
|
+
# @return [String] Binary response data
|
664
|
+
# @raise [Rospatent::Errors::ApiError] If the response is not successful
|
665
|
+
def handle_binary_response(response, request_id = nil)
|
666
|
+
return response.body if response.success?
|
667
|
+
|
668
|
+
# For binary endpoints, error responses might still be JSON
|
669
|
+
error_msg = begin
|
670
|
+
data = JSON.parse(response.body)
|
671
|
+
data["error"] || data["message"] || "Unknown error"
|
672
|
+
rescue JSON::ParserError
|
673
|
+
"Binary request failed"
|
674
|
+
end
|
675
|
+
|
676
|
+
# Create specific error types based on status code
|
677
|
+
case response.status
|
678
|
+
when 401
|
679
|
+
raise Errors::AuthenticationError, "#{error_msg} [Request ID: #{request_id}]"
|
680
|
+
when 404
|
681
|
+
raise Errors::NotFoundError.new("#{error_msg} [Request ID: #{request_id}]", response.status)
|
682
|
+
when 422
|
683
|
+
errors = extract_validation_errors(response)
|
684
|
+
raise Errors::ValidationError.new(error_msg, errors)
|
685
|
+
when 429
|
686
|
+
retry_after = response.headers["Retry-After"]&.to_i
|
687
|
+
raise Errors::RateLimitError.new(error_msg, response.status, retry_after)
|
688
|
+
when 503
|
689
|
+
raise Errors::ServiceUnavailableError.new("#{error_msg} [Request ID: #{request_id}]",
|
690
|
+
response.status)
|
691
|
+
else
|
692
|
+
raise Errors::ApiError.new(error_msg, response.status, response.body, request_id)
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
644
696
|
# Handle connection errors
|
645
697
|
# @param error [Faraday::Error] Connection error
|
646
698
|
# @raise [Rospatent::Errors::ConnectionError] Wrapped connection error
|
@@ -694,5 +746,18 @@ module Rospatent
|
|
694
746
|
def generate_request_id
|
695
747
|
"req_#{Time.now.to_f}_#{rand(10_000)}"
|
696
748
|
end
|
749
|
+
|
750
|
+
# Pad publication number with leading zeros for specific countries
|
751
|
+
# @param number [String] Publication number to pad
|
752
|
+
# @param country_code [String] Country code (e.g., "RU")
|
753
|
+
# @return [String] Padded publication number
|
754
|
+
def format_publication_number(number, country_code)
|
755
|
+
# Russian patents require 10-digit publication numbers
|
756
|
+
if country_code == "RU" && number.length < 10
|
757
|
+
number.rjust(10, "0")
|
758
|
+
else
|
759
|
+
number
|
760
|
+
end
|
761
|
+
end
|
697
762
|
end
|
698
763
|
end
|
@@ -100,6 +100,29 @@ module Rospatent
|
|
100
100
|
value.strip
|
101
101
|
end
|
102
102
|
|
103
|
+
# Validate text with word count requirements
|
104
|
+
# @param value [String, nil] Text to validate
|
105
|
+
# @param field_name [String] Name of the field for error messages
|
106
|
+
# @param min_words [Integer] Minimum required word count
|
107
|
+
# @param max_length [Integer, nil] Maximum allowed character length
|
108
|
+
# @return [String] Validated text
|
109
|
+
# @raise [ValidationError] If text is invalid or has insufficient words
|
110
|
+
def validate_text_with_word_count(value, field_name, min_words:, max_length: nil)
|
111
|
+
# First, apply standard string validation
|
112
|
+
validated_text = validate_string(value, field_name, max_length: max_length)
|
113
|
+
return nil if validated_text.nil?
|
114
|
+
|
115
|
+
# Count words by splitting on whitespace
|
116
|
+
word_count = count_words(validated_text)
|
117
|
+
|
118
|
+
if word_count < min_words
|
119
|
+
raise Errors::ValidationError,
|
120
|
+
"#{field_name.capitalize} must contain at least #{min_words} words (currently has #{word_count})"
|
121
|
+
end
|
122
|
+
|
123
|
+
validated_text
|
124
|
+
end
|
125
|
+
|
103
126
|
# Validate required non-empty string (does not allow nil)
|
104
127
|
# @param value [String, nil] String to validate
|
105
128
|
# @param field_name [String] Name of the field for error messages
|
@@ -302,5 +325,14 @@ module Rospatent
|
|
302
325
|
|
303
326
|
validated.compact
|
304
327
|
end
|
328
|
+
|
329
|
+
private
|
330
|
+
|
331
|
+
# Count words in a text by splitting on whitespace
|
332
|
+
# @param text [String] Text to count words in
|
333
|
+
# @return [Integer] Number of words
|
334
|
+
def count_words(text)
|
335
|
+
text.split.size
|
336
|
+
end
|
305
337
|
end
|
306
338
|
end
|
data/lib/rospatent/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rospatent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aleksandr Dryzhuk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -134,16 +134,16 @@ files:
|
|
134
134
|
- lib/rospatent/railtie.rb
|
135
135
|
- lib/rospatent/search.rb
|
136
136
|
- lib/rospatent/version.rb
|
137
|
-
homepage: https://
|
137
|
+
homepage: https://github.com/amdest/rospatent
|
138
138
|
licenses:
|
139
139
|
- MIT
|
140
140
|
metadata:
|
141
|
-
homepage_uri: https://
|
142
|
-
source_code_uri: https://
|
143
|
-
changelog_uri: https://
|
144
|
-
documentation_uri: https://
|
145
|
-
bug_tracker_uri: https://
|
146
|
-
wiki_uri: https://
|
141
|
+
homepage_uri: https://github.com/amdest/rospatent
|
142
|
+
source_code_uri: https://github.com/amdest/rospatent
|
143
|
+
changelog_uri: https://github.com/amdest/rospatent/blob/master/CHANGELOG.md
|
144
|
+
documentation_uri: https://github.com/amdest/rospatent/blob/master/README.md
|
145
|
+
bug_tracker_uri: https://github.com/amdest/rospatent/issues
|
146
|
+
wiki_uri: https://github.com/amdest/rospatent/wiki
|
147
147
|
rubygems_mfa_required: 'true'
|
148
148
|
post_install_message:
|
149
149
|
rdoc_options: []
|