rospatent 1.2.0 → 1.3.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 +40 -0
- data/README.md +1150 -229
- data/lib/generators/rospatent/install/templates/initializer.rb +39 -15
- data/lib/rospatent/client.rb +12 -8
- data/lib/rospatent/configuration.rb +10 -9
- data/lib/rospatent/input_validator.rb +235 -0
- data/lib/rospatent/search.rb +27 -21
- data/lib/rospatent/version.rb +1 -1
- metadata +8 -8
@@ -3,22 +3,46 @@
|
|
3
3
|
# Rospatent API client configuration
|
4
4
|
# Documentation: https://online.rospatent.gov.ru/open-data/open-api
|
5
5
|
Rospatent.configure do |config|
|
6
|
-
# API URL (default: https://searchplatform.rospatent.gov.ru)
|
7
|
-
# config.api_url = ENV.fetch("ROSPATENT_API_URL", "https://searchplatform.rospatent.gov.ru")
|
8
|
-
|
9
6
|
# JWT Bearer token for API authorization - REQUIRED
|
10
|
-
#
|
11
|
-
config.token = Rails.application.credentials.
|
12
|
-
|
13
|
-
|
7
|
+
# Priority: Rails credentials > Environment variable > Manual setting
|
8
|
+
config.token = Rails.application.credentials.rospatent_token ||
|
9
|
+
ENV["ROSPATENT_TOKEN"] ||
|
10
|
+
ENV.fetch("ROSPATENT_API_TOKEN", nil)
|
11
|
+
|
12
|
+
# Environment configuration - respect environment variables
|
13
|
+
config.environment = ENV.fetch("ROSPATENT_ENV", Rails.env)
|
14
|
+
|
15
|
+
# Cache configuration - respect environment variables or use Rails defaults
|
16
|
+
config.cache_enabled = if ENV.key?("ROSPATENT_CACHE_ENABLED")
|
17
|
+
ENV["ROSPATENT_CACHE_ENABLED"] == "true"
|
18
|
+
else
|
19
|
+
Rails.env.production?
|
20
|
+
end
|
14
21
|
|
15
|
-
#
|
16
|
-
config.
|
17
|
-
|
18
|
-
|
22
|
+
# Logging configuration - CRITICAL: Respect environment variables first!
|
23
|
+
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
|
24
|
+
ENV["ROSPATENT_LOG_LEVEL"].to_sym
|
25
|
+
else
|
26
|
+
Rails.env.production? ? :warn : :debug
|
27
|
+
end
|
19
28
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
29
|
+
config.log_requests = if ENV.key?("ROSPATENT_LOG_REQUESTS")
|
30
|
+
ENV["ROSPATENT_LOG_REQUESTS"] == "true"
|
31
|
+
else
|
32
|
+
!Rails.env.production?
|
33
|
+
end
|
34
|
+
|
35
|
+
config.log_responses = if ENV.key?("ROSPATENT_LOG_RESPONSES")
|
36
|
+
ENV["ROSPATENT_LOG_RESPONSES"] == "true"
|
37
|
+
else
|
38
|
+
Rails.env.development?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Optional: Override other defaults if needed
|
42
|
+
# config.api_url = ENV.fetch("ROSPATENT_API_URL", "https://searchplatform.rospatent.gov.ru")
|
43
|
+
# config.timeout = ENV.fetch("ROSPATENT_TIMEOUT", "30").to_i
|
44
|
+
# config.retry_count = ENV.fetch("ROSPATENT_RETRY_COUNT", "3").to_i
|
45
|
+
# config.cache_ttl = ENV.fetch("ROSPATENT_CACHE_TTL", "300").to_i
|
46
|
+
# config.cache_max_size = ENV.fetch("ROSPATENT_CACHE_MAX_SIZE", "1000").to_i
|
47
|
+
# config.connection_pool_size = ENV.fetch("ROSPATENT_POOL_SIZE", "5").to_i
|
24
48
|
end
|
data/lib/rospatent/client.rb
CHANGED
@@ -150,7 +150,7 @@ module Rospatent
|
|
150
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::ValidationError] If text has insufficient words or
|
153
|
+
# @raise [Rospatent::Errors::ValidationError] If text has insufficient words or errors
|
154
154
|
def similar_patents_by_text(text, count: 100)
|
155
155
|
# Validate inputs - text must have at least 50 words for the API
|
156
156
|
validated_text = validate_text_with_word_count(text, "search_text", min_words: 50,
|
@@ -507,12 +507,13 @@ module Rospatent
|
|
507
507
|
qn: { type: :string, max_length: 1000 },
|
508
508
|
limit: { type: :positive_integer, min_value: 1, max_value: 100 },
|
509
509
|
offset: { type: :positive_integer, min_value: 0, max_value: 10_000 },
|
510
|
-
pre_tag: { type: :
|
511
|
-
post_tag: { type: :
|
510
|
+
pre_tag: { type: :string_or_array, max_length: 50, max_size: 10 },
|
511
|
+
post_tag: { type: :string_or_array, max_length: 50, max_size: 10 },
|
512
512
|
sort: { type: :enum, allowed_values: %i[relevance pub_date filing_date] },
|
513
|
-
group_by: { type: :
|
513
|
+
group_by: { type: :string_enum, allowed_values: %w[family:docdb family:dwpi] },
|
514
514
|
include_facets: { type: :boolean },
|
515
|
-
highlight: { type: :
|
515
|
+
highlight: { type: :hash },
|
516
|
+
filter: { type: :filter },
|
516
517
|
datasets: { type: :array, max_size: 10 }
|
517
518
|
}
|
518
519
|
|
@@ -632,7 +633,8 @@ module Rospatent
|
|
632
633
|
|
633
634
|
error_msg = begin
|
634
635
|
data = JSON.parse(response.body)
|
635
|
-
|
636
|
+
# Try different possible error message fields used by Rospatent API
|
637
|
+
data["result"] || data["error"] || data["message"] || "Unknown error"
|
636
638
|
rescue JSON::ParserError
|
637
639
|
response.body
|
638
640
|
end
|
@@ -668,7 +670,8 @@ module Rospatent
|
|
668
670
|
# For binary endpoints, error responses might still be JSON
|
669
671
|
error_msg = begin
|
670
672
|
data = JSON.parse(response.body)
|
671
|
-
|
673
|
+
# Try different possible error message fields used by Rospatent API
|
674
|
+
data["result"] || data["error"] || data["message"] || "Unknown error"
|
672
675
|
rescue JSON::ParserError
|
673
676
|
"Binary request failed"
|
674
677
|
end
|
@@ -712,7 +715,8 @@ module Rospatent
|
|
712
715
|
# @return [Hash] Field-specific validation errors
|
713
716
|
def extract_validation_errors(response)
|
714
717
|
data = JSON.parse(response.body)
|
715
|
-
|
718
|
+
# Check various possible validation error fields
|
719
|
+
data["errors"] || data["validation_errors"] || data["details"] || {}
|
716
720
|
rescue JSON::ParserError
|
717
721
|
{}
|
718
722
|
end
|
@@ -31,7 +31,7 @@ module Rospatent
|
|
31
31
|
# Initialize a new configuration with default values
|
32
32
|
def initialize
|
33
33
|
@api_url = "https://searchplatform.rospatent.gov.ru"
|
34
|
-
@token = nil
|
34
|
+
@token = ENV["ROSPATENT_TOKEN"] || ENV.fetch("ROSPATENT_API_TOKEN", nil)
|
35
35
|
@timeout = 30
|
36
36
|
@retry_count = 3
|
37
37
|
@user_agent = "Rospatent Ruby Client/#{Rospatent::VERSION}"
|
@@ -106,6 +106,7 @@ module Rospatent
|
|
106
106
|
private
|
107
107
|
|
108
108
|
# Load environment-specific configuration
|
109
|
+
# Only override values that weren't explicitly set by environment variables
|
109
110
|
def load_environment_config
|
110
111
|
unless valid_environment?
|
111
112
|
raise ArgumentError, "Invalid environment: #{@environment}. " \
|
@@ -116,20 +117,20 @@ module Rospatent
|
|
116
117
|
when "production"
|
117
118
|
@timeout = 60
|
118
119
|
@retry_count = 5
|
119
|
-
@log_level = :warn
|
120
|
-
@cache_ttl = 600 # 10 minutes in production
|
120
|
+
@log_level = :warn unless ENV.key?("ROSPATENT_LOG_LEVEL")
|
121
|
+
@cache_ttl = 600 unless ENV.key?("ROSPATENT_CACHE_TTL") # 10 minutes in production
|
121
122
|
when "staging"
|
122
123
|
@timeout = 45
|
123
124
|
@retry_count = 3
|
124
|
-
@log_level = :info
|
125
|
-
@cache_ttl = 300 # 5 minutes in staging
|
125
|
+
@log_level = :info unless ENV.key?("ROSPATENT_LOG_LEVEL")
|
126
|
+
@cache_ttl = 300 unless ENV.key?("ROSPATENT_CACHE_TTL") # 5 minutes in staging
|
126
127
|
when "development"
|
127
128
|
@timeout = 10
|
128
129
|
@retry_count = 1
|
129
|
-
@log_level = :debug
|
130
|
-
@log_requests = true
|
131
|
-
@log_responses = true
|
132
|
-
@cache_ttl = 60 # 1 minute in development
|
130
|
+
@log_level = :debug unless ENV.key?("ROSPATENT_LOG_LEVEL")
|
131
|
+
@log_requests = true unless ENV.key?("ROSPATENT_LOG_REQUESTS")
|
132
|
+
@log_responses = true unless ENV.key?("ROSPATENT_LOG_RESPONSES")
|
133
|
+
@cache_ttl = 60 unless ENV.key?("ROSPATENT_CACHE_TTL") # 1 minute in development
|
133
134
|
end
|
134
135
|
end
|
135
136
|
end
|
@@ -170,6 +170,26 @@ module Rospatent
|
|
170
170
|
value
|
171
171
|
end
|
172
172
|
|
173
|
+
# Validate string enum value (preserves string type)
|
174
|
+
# @param value [String, nil] Value to validate
|
175
|
+
# @param allowed_values [Array<String>] Array of allowed string values
|
176
|
+
# @param field_name [String] Name of the field for error messages
|
177
|
+
# @return [String] Validated string
|
178
|
+
# @raise [ValidationError] If value is not in allowed list
|
179
|
+
def validate_string_enum(value, allowed_values, field_name)
|
180
|
+
return nil if value.nil?
|
181
|
+
|
182
|
+
# Ensure value is a string
|
183
|
+
value = value.to_s if value.respond_to?(:to_s)
|
184
|
+
|
185
|
+
unless allowed_values.include?(value)
|
186
|
+
raise Errors::ValidationError,
|
187
|
+
"Invalid #{field_name}. Allowed values: #{allowed_values.join(', ')}"
|
188
|
+
end
|
189
|
+
|
190
|
+
value
|
191
|
+
end
|
192
|
+
|
173
193
|
# Validate array parameter
|
174
194
|
# @param value [Array, nil] Array to validate
|
175
195
|
# @param field_name [String] Name of the field for error messages
|
@@ -207,6 +227,29 @@ module Rospatent
|
|
207
227
|
value
|
208
228
|
end
|
209
229
|
|
230
|
+
# Validate string or array parameter (for highlight tags)
|
231
|
+
# @param value [String, Array, nil] String or Array to validate
|
232
|
+
# @param field_name [String] Name of the field for error messages
|
233
|
+
# @param max_length [Integer, nil] Maximum string length (for string values)
|
234
|
+
# @param max_size [Integer, nil] Maximum array size (for array values)
|
235
|
+
# @return [String, Array] Validated string or array
|
236
|
+
# @raise [ValidationError] If value is invalid
|
237
|
+
def validate_string_or_array(value, field_name, max_length: nil, max_size: nil)
|
238
|
+
return nil if value.nil?
|
239
|
+
|
240
|
+
case value
|
241
|
+
when String
|
242
|
+
validate_string(value, field_name, max_length: max_length)
|
243
|
+
when Array
|
244
|
+
validate_array(value, field_name, max_size: max_size) do |element|
|
245
|
+
validate_string(element, "#{field_name} element", max_length: max_length)
|
246
|
+
end
|
247
|
+
else
|
248
|
+
raise Errors::ValidationError,
|
249
|
+
"Invalid #{field_name} type. Expected String or Array, got #{value.class}"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
210
253
|
# Validate hash parameter
|
211
254
|
# @param value [Hash, nil] Hash to validate
|
212
255
|
# @param field_name [String] Name of the field for error messages
|
@@ -296,8 +339,17 @@ module Rospatent
|
|
296
339
|
param_name.to_s,
|
297
340
|
max_length: rules[:max_length]
|
298
341
|
)
|
342
|
+
when :string_or_array
|
343
|
+
validate_string_or_array(
|
344
|
+
value,
|
345
|
+
param_name.to_s,
|
346
|
+
max_length: rules[:max_length],
|
347
|
+
max_size: rules[:max_size]
|
348
|
+
)
|
299
349
|
when :enum
|
300
350
|
validate_enum(value, rules[:allowed_values], param_name.to_s)
|
351
|
+
when :string_enum
|
352
|
+
validate_string_enum(value, rules[:allowed_values], param_name.to_s)
|
301
353
|
when :date
|
302
354
|
validate_date(value, param_name.to_s)
|
303
355
|
when :array
|
@@ -314,6 +366,11 @@ module Rospatent
|
|
314
366
|
required_keys: rules[:required_keys] || [],
|
315
367
|
allowed_keys: rules[:allowed_keys]
|
316
368
|
)
|
369
|
+
when :filter
|
370
|
+
validate_filter(value, param_name.to_s)
|
371
|
+
when :boolean
|
372
|
+
# Convert to boolean, nil values remain nil
|
373
|
+
value.nil? ? nil : !!value
|
317
374
|
else
|
318
375
|
value
|
319
376
|
end
|
@@ -334,5 +391,183 @@ module Rospatent
|
|
334
391
|
def count_words(text)
|
335
392
|
text.split.size
|
336
393
|
end
|
394
|
+
|
395
|
+
# Validate filter parameter according to Rospatent API specification
|
396
|
+
# @param filter [Hash, nil] Filter hash to validate
|
397
|
+
# @param field_name [String] Name of the field for error messages
|
398
|
+
# @return [Hash] Validated filter hash
|
399
|
+
# @raise [ValidationError] If filter structure is invalid
|
400
|
+
def validate_filter(filter, field_name = "filter")
|
401
|
+
return nil if filter.nil?
|
402
|
+
|
403
|
+
unless filter.is_a?(Hash)
|
404
|
+
raise Errors::ValidationError,
|
405
|
+
"Invalid #{field_name} type. Expected Hash, got #{filter.class}"
|
406
|
+
end
|
407
|
+
|
408
|
+
validated_filter = {}
|
409
|
+
|
410
|
+
filter.each do |filter_field, filter_value|
|
411
|
+
case filter_field.to_s
|
412
|
+
when "authors", "patent_holders", "country", "kind", "ids",
|
413
|
+
"classification.ipc", "classification.ipc_group", "classification.ipc_subclass",
|
414
|
+
"classification.cpc", "classification.cpc_group", "classification.cpc_subclass"
|
415
|
+
# These fields use {"values": [...]} format
|
416
|
+
validated_filter[filter_field] = validate_filter_values(filter_value, filter_field)
|
417
|
+
when "date_published", "application.filing_date"
|
418
|
+
# These fields use {"range": {"gt": "20000101"}} format
|
419
|
+
validated_filter[filter_field] = validate_filter_range(filter_value, filter_field)
|
420
|
+
else
|
421
|
+
raise Errors::ValidationError,
|
422
|
+
"Invalid filter field '#{filter_field}'. Allowed fields: authors, patent_holders, " \
|
423
|
+
"country, kind, ids, date_published, application.filing_date, classification.ipc, " \
|
424
|
+
"classification.ipc_group, classification.ipc_subclass, classification.cpc, " \
|
425
|
+
"classification.cpc_group, classification.cpc_subclass"
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
validated_filter
|
430
|
+
end
|
431
|
+
|
432
|
+
# Validate filter values structure (for list-based filters)
|
433
|
+
# @param filter_value [Hash] Filter value to validate
|
434
|
+
# @param filter_field [String] Filter field name for error messages
|
435
|
+
# @return [Hash] Validated filter value
|
436
|
+
# @raise [ValidationError] If structure is invalid
|
437
|
+
def validate_filter_values(filter_value, filter_field)
|
438
|
+
unless filter_value.is_a?(Hash)
|
439
|
+
raise Errors::ValidationError,
|
440
|
+
"Invalid #{filter_field} filter structure. Expected Hash with 'values' key, got #{filter_value.class}"
|
441
|
+
end
|
442
|
+
|
443
|
+
unless filter_value.key?("values") || filter_value.key?(:values)
|
444
|
+
raise Errors::ValidationError,
|
445
|
+
"Missing required 'values' key in #{filter_field} filter. Expected format: {\"values\": [...]}"
|
446
|
+
end
|
447
|
+
|
448
|
+
values = filter_value["values"] || filter_value[:values]
|
449
|
+
|
450
|
+
unless values.is_a?(Array)
|
451
|
+
raise Errors::ValidationError,
|
452
|
+
"Invalid 'values' type in #{filter_field} filter. Expected Array, got #{values.class}"
|
453
|
+
end
|
454
|
+
|
455
|
+
if values.empty?
|
456
|
+
raise Errors::ValidationError,
|
457
|
+
"Empty 'values' array in #{filter_field} filter. At least one value must be provided"
|
458
|
+
end
|
459
|
+
|
460
|
+
# Validate each value is a string
|
461
|
+
values.each_with_index do |value, index|
|
462
|
+
unless value.is_a?(String) || value.is_a?(Symbol)
|
463
|
+
raise Errors::ValidationError,
|
464
|
+
"Invalid value type at index #{index} in #{filter_field} filter. Expected String, got #{value.class}"
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
{ "values" => values.map(&:to_s) }
|
469
|
+
end
|
470
|
+
|
471
|
+
# Validate filter range structure (for date-based filters)
|
472
|
+
# @param filter_value [Hash] Filter value to validate
|
473
|
+
# @param filter_field [String] Filter field name for error messages
|
474
|
+
# @return [Hash] Validated filter value
|
475
|
+
# @raise [ValidationError] If structure is invalid
|
476
|
+
def validate_filter_range(filter_value, filter_field)
|
477
|
+
unless filter_value.is_a?(Hash)
|
478
|
+
raise Errors::ValidationError,
|
479
|
+
"Invalid #{filter_field} filter structure. Expected Hash with 'range' key, got #{filter_value.class}"
|
480
|
+
end
|
481
|
+
|
482
|
+
unless filter_value.key?("range") || filter_value.key?(:range)
|
483
|
+
raise Errors::ValidationError,
|
484
|
+
"Missing required 'range' key in #{filter_field} filter. Expected format: {\"range\": {\"gt\": \"20000101\"}}"
|
485
|
+
end
|
486
|
+
|
487
|
+
range = filter_value["range"] || filter_value[:range]
|
488
|
+
|
489
|
+
unless range.is_a?(Hash)
|
490
|
+
raise Errors::ValidationError,
|
491
|
+
"Invalid 'range' type in #{filter_field} filter. Expected Hash, got #{range.class}"
|
492
|
+
end
|
493
|
+
|
494
|
+
# Allowed range operators
|
495
|
+
allowed_operators = %w[gt gte lt lte]
|
496
|
+
validated_range = {}
|
497
|
+
|
498
|
+
if range.empty?
|
499
|
+
raise Errors::ValidationError,
|
500
|
+
"Empty 'range' object in #{filter_field} filter. At least one operator (gt, gte, lt, lte) must be provided"
|
501
|
+
end
|
502
|
+
|
503
|
+
range.each do |operator, value|
|
504
|
+
operator_str = operator.to_s
|
505
|
+
|
506
|
+
unless allowed_operators.include?(operator_str)
|
507
|
+
raise Errors::ValidationError,
|
508
|
+
"Invalid range operator '#{operator_str}' in #{filter_field} filter. " \
|
509
|
+
"Allowed operators: #{allowed_operators.join(', ')}"
|
510
|
+
end
|
511
|
+
|
512
|
+
# Validate date format (YYYYMMDD)
|
513
|
+
validated_date = validate_filter_date(value, filter_field, operator_str)
|
514
|
+
validated_range[operator_str] = validated_date
|
515
|
+
end
|
516
|
+
|
517
|
+
{ "range" => validated_range }
|
518
|
+
end
|
519
|
+
|
520
|
+
# Validate date format for filter ranges
|
521
|
+
# @param date_value [String, Date] Date value to validate
|
522
|
+
# @param filter_field [String] Filter field name for error messages
|
523
|
+
# @param operator [String] Range operator for error messages
|
524
|
+
# @return [String] Validated date in YYYYMMDD format
|
525
|
+
# @raise [ValidationError] If date format is invalid
|
526
|
+
def validate_filter_date(date_value, filter_field, operator)
|
527
|
+
# Convert Date objects to string
|
528
|
+
return date_value.strftime("%Y%m%d") if date_value.is_a?(Date)
|
529
|
+
|
530
|
+
unless date_value.is_a?(String)
|
531
|
+
raise Errors::ValidationError,
|
532
|
+
"Invalid date type for '#{operator}' in #{filter_field} filter. Expected String or Date, got #{date_value.class}"
|
533
|
+
end
|
534
|
+
|
535
|
+
# Check if it's already in YYYYMMDD format
|
536
|
+
if date_value.match?(/^\d{8}$/)
|
537
|
+
# Validate that it's a real date
|
538
|
+
begin
|
539
|
+
year = date_value[0..3].to_i
|
540
|
+
month = date_value[4..5].to_i
|
541
|
+
day = date_value[6..7].to_i
|
542
|
+
Date.new(year, month, day)
|
543
|
+
return date_value
|
544
|
+
rescue ArgumentError
|
545
|
+
raise Errors::ValidationError,
|
546
|
+
"Invalid date '#{date_value}' for '#{operator}' in #{filter_field} filter. Not a valid date"
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
# Try to parse various date formats and convert to YYYYMMDD
|
551
|
+
begin
|
552
|
+
parsed_date = case date_value
|
553
|
+
when /^\d{4}-\d{2}-\d{2}$/ # YYYY-MM-DD
|
554
|
+
Date.parse(date_value)
|
555
|
+
when %r{^\d{4}/\d{2}/\d{2}$} # YYYY/MM/DD
|
556
|
+
Date.parse(date_value)
|
557
|
+
when %r{^\d{2}/\d{2}/\d{4}$} # MM/DD/YYYY
|
558
|
+
Date.strptime(date_value, "%m/%d/%Y")
|
559
|
+
when /^\d{2}-\d{2}-\d{4}$/ # MM-DD-YYYY
|
560
|
+
Date.strptime(date_value, "%m-%d-%Y")
|
561
|
+
else
|
562
|
+
Date.parse(date_value) # Let Date.parse try to handle it
|
563
|
+
end
|
564
|
+
|
565
|
+
parsed_date.strftime("%Y%m%d")
|
566
|
+
rescue ArgumentError
|
567
|
+
raise Errors::ValidationError,
|
568
|
+
"Invalid date format '#{date_value}' for '#{operator}' in #{filter_field} filter. " \
|
569
|
+
"Expected YYYYMMDD format (e.g., '20200101') or standard date formats (YYYY-MM-DD, etc.)"
|
570
|
+
end
|
571
|
+
end
|
337
572
|
end
|
338
573
|
end
|
data/lib/rospatent/search.rb
CHANGED
@@ -41,14 +41,14 @@ module Rospatent
|
|
41
41
|
# @param qn [String] Natural language search query
|
42
42
|
# @param limit [Integer] Maximum number of results to return
|
43
43
|
# @param offset [Integer] Offset for pagination
|
44
|
-
# @param pre_tag [String] HTML tag to prepend to highlighted matches
|
45
|
-
# @param post_tag [String] HTML tag to append to highlighted matches
|
44
|
+
# @param pre_tag [String, Array<String>] HTML tag(s) to prepend to highlighted matches
|
45
|
+
# @param post_tag [String, Array<String>] HTML tag(s) to append to highlighted matches
|
46
46
|
# @param sort [Symbol, String] Sort option (:relevance, :pub_date, :filing_date)
|
47
|
-
# @param group_by [
|
47
|
+
# @param group_by [String] Grouping option ("family:docdb", "family:dwpi")
|
48
48
|
# @param include_facets [Boolean] Whether to include facet information
|
49
49
|
# @param filter [Hash] Filters to apply to the search
|
50
50
|
# @param datasets [Array<String>] Datasets to search within
|
51
|
-
# @param highlight [
|
51
|
+
# @param highlight [Hash] Advanced highlight configuration with profiles
|
52
52
|
#
|
53
53
|
# @return [Rospatent::SearchResult] Search result object
|
54
54
|
def execute(
|
@@ -111,31 +111,35 @@ module Rospatent
|
|
111
111
|
end
|
112
112
|
|
113
113
|
# Validate highlighting parameters (only if provided)
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
120
|
-
if params[:highlight] && params[:post_tag]
|
121
|
-
validated[:post_tag] =
|
122
|
-
validate_string(params[:post_tag], "post_tag", max_length: 50)
|
114
|
+
# pre_tag and post_tag must be provided together
|
115
|
+
if params[:pre_tag] || params[:post_tag]
|
116
|
+
unless params[:pre_tag] && params[:post_tag]
|
117
|
+
raise Errors::ValidationError,
|
118
|
+
"Both pre_tag and post_tag must be provided together for highlighting"
|
123
119
|
end
|
120
|
+
|
121
|
+
validated[:pre_tag] =
|
122
|
+
validate_string_or_array(params[:pre_tag], "pre_tag", max_length: 50, max_size: 10)
|
123
|
+
validated[:post_tag] =
|
124
|
+
validate_string_or_array(params[:post_tag], "post_tag", max_length: 50, max_size: 10)
|
124
125
|
end
|
125
126
|
|
127
|
+
# Validate highlight parameter (complex object for advanced highlighting)
|
128
|
+
validated[:highlight] = validate_hash(params[:highlight], "highlight") if params[:highlight]
|
129
|
+
|
126
130
|
# Validate sort parameter (only if provided)
|
127
131
|
validated[:sort] = validate_sort_parameter(params[:sort]) if params[:sort]
|
128
132
|
|
129
133
|
# Validate group_by parameter (only if provided)
|
130
134
|
if params[:group_by]
|
131
|
-
validated[:group_by] =
|
135
|
+
validated[:group_by] = validate_string_enum(params[:group_by], %w[family:docdb family:dwpi], "group_by")
|
132
136
|
end
|
133
137
|
|
134
138
|
# Validate boolean parameters (only if provided)
|
135
139
|
validated[:include_facets] = !params[:include_facets].nil? if params.key?(:include_facets)
|
136
140
|
|
137
141
|
# Validate filter parameter
|
138
|
-
validated[:filter] =
|
142
|
+
validated[:filter] = validate_filter(params[:filter], "filter") if params[:filter]
|
139
143
|
|
140
144
|
# Validate datasets parameter
|
141
145
|
if params[:datasets]
|
@@ -161,18 +165,20 @@ module Rospatent
|
|
161
165
|
payload[:limit] = params[:limit] if params[:limit]
|
162
166
|
payload[:offset] = params[:offset] if params[:offset]
|
163
167
|
|
164
|
-
# Add highlighting
|
165
|
-
if params
|
166
|
-
payload[:
|
167
|
-
payload[:
|
168
|
-
payload[:post_tag] = params[:post_tag] if params[:post_tag]
|
168
|
+
# Add highlighting tags (only if both are provided)
|
169
|
+
if params[:pre_tag] && params[:post_tag]
|
170
|
+
payload[:pre_tag] = params[:pre_tag]
|
171
|
+
payload[:post_tag] = params[:post_tag]
|
169
172
|
end
|
170
173
|
|
174
|
+
# Add advanced highlight parameter (independent of tags)
|
175
|
+
payload[:highlight] = params[:highlight] if params[:highlight]
|
176
|
+
|
171
177
|
# Add sort parameter (only if explicitly provided)
|
172
178
|
payload[:sort] = params[:sort] if params[:sort]
|
173
179
|
|
174
180
|
# Add grouping parameter (only if explicitly provided)
|
175
|
-
payload[:group_by] =
|
181
|
+
payload[:group_by] = params[:group_by] if params[:group_by]
|
176
182
|
|
177
183
|
# Add other parameters (only if explicitly provided)
|
178
184
|
payload[:include_facets] = params[:include_facets] if params.key?(:include_facets)
|
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.3.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-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -72,42 +72,42 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '1.
|
75
|
+
version: '1.76'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '1.
|
82
|
+
version: '1.76'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rubocop-minitest
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0.
|
89
|
+
version: '0.38'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0.
|
96
|
+
version: '0.38'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rubocop-rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0.
|
103
|
+
version: '0.7'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0.
|
110
|
+
version: '0.7'
|
111
111
|
description: A comprehensive Ruby client for interacting with the Rospatent patent
|
112
112
|
search API. Features include automatic caching, request validation, structured logging,
|
113
113
|
error handling, and batch operations for efficient patent data retrieval.
|