Module: Rospatent::InputValidator

Included in:
Client, Search
Defined in:
lib/rospatent/input_validator.rb

Overview

Module for validating input parameters and converting types

Instance Method Summary collapse

Instance Method Details

#validate_array(value, field_name, max_size: nil, element_validator: nil) ⇒ Array

Validate array parameter

Parameters:

  • value (Array, nil)

    Array to validate

  • field_name (String)

    Name of the field for error messages

  • max_size (Integer, nil) (defaults to: nil)

    Maximum array size

  • element_validator (Proc, nil) (defaults to: nil)

    Proc to validate each element

Returns:

  • (Array)

    Validated array

Raises:

  • (ValidationError)

    If array is invalid



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/rospatent/input_validator.rb', line 180

def validate_array(value, field_name, max_size: nil, element_validator: nil)
  return nil if value.nil?

  unless value.is_a?(Array)
    raise Errors::ValidationError,
          "Invalid #{field_name} type. Expected Array, got #{value.class}"
  end

  raise Errors::ValidationError, "#{field_name.capitalize} cannot be empty" if value.empty?

  if max_size && value.size > max_size
    raise Errors::ValidationError,
          "#{field_name.capitalize} cannot contain more than #{max_size} items"
  end

  if element_validator
    value.each_with_index do |element, index|
      element_validator.call(element)
    rescue Errors::ValidationError => e
      raise Errors::ValidationError,
            "Invalid #{field_name}[#{index}]: #{e.message}"
    rescue StandardError => e
      raise Errors::ValidationError,
            "Invalid #{field_name}[#{index}]: #{e.message}"
    end
  end

  value
end

#validate_date(date, field_name = "date") ⇒ Date

Validate and normalize date input

Parameters:

  • date (String, Date, nil)

    Date input to validate

  • field_name (String) (defaults to: "date")

    Name of the field for error messages

Returns:

  • (Date)

    Normalized Date object

Raises:

  • (ValidationError)

    If date format is invalid



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/rospatent/input_validator.rb', line 13

def validate_date(date, field_name = "date")
  return nil if date.nil?
  return date if date.is_a?(Date)

  if date.is_a?(String)
    begin
      return Date.parse(date)
    rescue Date::Error
      raise Errors::ValidationError,
            "Invalid #{field_name} format. Expected YYYY-MM-DD or Date object"
    end
  end

  raise Errors::ValidationError,
        "Invalid #{field_name} type. Expected String or Date, got #{date.class}"
end

#validate_enum(value, allowed_values, field_name) ⇒ Symbol

Validate enum value

Parameters:

  • value (Symbol, String, nil)

    Value to validate

  • allowed_values (Array)

    Array of allowed values

  • field_name (String)

    Name of the field for error messages

Returns:

  • (Symbol)

    Validated symbol

Raises:

  • (ValidationError)

    If value is not in allowed list



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/rospatent/input_validator.rb', line 156

def validate_enum(value, allowed_values, field_name)
  return nil if value.nil?

  # Convert to symbol for consistency
  value = value.to_sym if value.respond_to?(:to_sym)

  # Convert allowed values to symbols for comparison
  allowed_symbols = allowed_values.map(&:to_sym)

  unless allowed_symbols.include?(value)
    raise Errors::ValidationError,
          "Invalid #{field_name}. Allowed values: #{allowed_values.join(', ')}"
  end

  value
end

#validate_hash(value, field_name, required_keys: [], allowed_keys: nil) ⇒ Hash

Validate hash parameter

Parameters:

  • value (Hash, nil)

    Hash to validate

  • field_name (String)

    Name of the field for error messages

  • required_keys (Array) (defaults to: [])

    Required keys in the hash

  • allowed_keys (Array, nil) (defaults to: nil)

    Allowed keys (if nil, any keys allowed)

Returns:

  • (Hash)

    Validated hash

Raises:

  • (ValidationError)

    If hash is invalid



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/rospatent/input_validator.rb', line 217

def validate_hash(value, field_name, required_keys: [], allowed_keys: nil)
  return nil if value.nil?

  unless value.is_a?(Hash)
    raise Errors::ValidationError,
          "Invalid #{field_name} type. Expected Hash, got #{value.class}"
  end

  # Check required keys
  missing_keys = required_keys.map(&:to_s) - value.keys.map(&:to_s)
  unless missing_keys.empty?
    raise Errors::ValidationError,
          "Missing required #{field_name} keys: #{missing_keys.join(', ')}"
  end

  # Check allowed keys if specified
  if allowed_keys
    invalid_keys = value.keys.map(&:to_s) - allowed_keys.map(&:to_s)
    unless invalid_keys.empty?
      raise Errors::ValidationError,
            "Invalid #{field_name} keys: #{invalid_keys.join(', ')}"
    end
  end

  value
end

#validate_params(params, validations) ⇒ Hash

Validate multiple parameters at once

Examples:

validate_params(
  { limit: "10", offset: "0" },
  {
    limit: { type: :positive_integer, max_value: 100 },
    offset: { type: :positive_integer, min_value: 0 }
  }
)

Parameters:

  • params (Hash)

    Parameters to validate

  • validations (Hash)

    Validation rules for each parameter

Returns:

  • (Hash)

    Hash of validated parameters

Raises:

  • (ValidationError)

    If any validation fails



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/rospatent/input_validator.rb', line 279

def validate_params(params, validations)
  validated = {}
  errors = {}

  validations.each do |param_name, rules|
    value = params[param_name]
    validated[param_name] = case rules[:type]
                            when :positive_integer
                              validate_positive_integer(
                                value,
                                param_name.to_s,
                                min_value: rules[:min_value] || 1,
                                max_value: rules[:max_value]
                              )
                            when :string
                              validate_string(
                                value,
                                param_name.to_s,
                                max_length: rules[:max_length]
                              )
                            when :enum
                              validate_enum(value, rules[:allowed_values], param_name.to_s)
                            when :date
                              validate_date(value, param_name.to_s)
                            when :array
                              validate_array(
                                value,
                                param_name.to_s,
                                max_size: rules[:max_size],
                                element_validator: rules[:element_validator]
                              )
                            when :hash
                              validate_hash(
                                value,
                                param_name.to_s,
                                required_keys: rules[:required_keys] || [],
                                allowed_keys: rules[:allowed_keys]
                              )
                            else
                              value
                            end
  rescue Errors::ValidationError => e
    errors[param_name] = e.message
  end

  raise Errors::ValidationError.new("Validation failed", errors) unless errors.empty?

  validated.compact
end

#validate_patent_id(document_id) ⇒ String

Validate patent ID format

Parameters:

  • document_id (String)

    Patent document ID

Returns:

  • (String)

    Validated document ID

Raises:

  • (ValidationError)

    If format is invalid



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/rospatent/input_validator.rb', line 248

def validate_patent_id(document_id)
  raise Errors::ValidationError, "Document_id is required" if document_id.nil?

  value = validate_string(document_id, "document_id")
  return nil if value.nil?

  # Regex pattern for patent IDs
  # Format: {country code (2 letters)}{publication number (alphanumeric)}{document type (letter+digits)}_{date (YYYYMMDD)}
  pattern = /^[A-Z]{2}[A-Z0-9]+[A-Z]\d*_\d{8}$/

  unless value.match?(pattern)
    raise Errors::ValidationError,
          "Invalid patent ID format. Expected format: 'XX12345Y1_YYYYMMDD' (country code + alphanumeric publication number + document type + date)"
  end

  value
end

#validate_positive_integer(value, field_name, min_value: 1, max_value: nil) ⇒ Integer

Validate positive integer

Parameters:

  • value (Integer, String, nil)

    Value to validate

  • field_name (String)

    Name of the field for error messages

  • min_value (Integer) (defaults to: 1)

    Minimum allowed value

  • max_value (Integer, nil) (defaults to: nil)

    Maximum allowed value (optional)

Returns:

  • (Integer)

    Validated integer

Raises:

  • (ValidationError)

    If value is invalid



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rospatent/input_validator.rb', line 48

def validate_positive_integer(value, field_name, min_value: 1, max_value: nil)
  return nil if value.nil?

  # Convert string to integer if possible
  if value.is_a?(String)
    begin
      value = Integer(value)
    rescue ArgumentError
      raise Errors::ValidationError,
            "Invalid #{field_name}. Expected integer, got non-numeric string"
    end
  end

  unless value.is_a?(Integer)
    raise Errors::ValidationError,
          "Invalid #{field_name} type. Expected Integer, got #{value.class}"
  end

  if value < min_value
    raise Errors::ValidationError,
          "#{field_name.capitalize} must be at least #{min_value}"
  end

  if max_value && value > max_value
    raise Errors::ValidationError,
          "#{field_name.capitalize} must be at most #{max_value}"
  end

  value
end

#validate_required_date(date, field_name = "date") ⇒ Date

Validate and normalize required date input (does not allow nil)

Parameters:

  • date (String, Date)

    Date input to validate

  • field_name (String) (defaults to: "date")

    Name of the field for error messages

Returns:

  • (Date)

    Normalized Date object

Raises:

  • (ValidationError)

    If date format is invalid or nil



35
36
37
38
39
# File 'lib/rospatent/input_validator.rb', line 35

def validate_required_date(date, field_name = "date")
  raise Errors::ValidationError, "#{field_name.capitalize} is required" if date.nil?

  validate_date(date, field_name)
end

#validate_required_string(value, field_name, max_length: nil) ⇒ String

Validate required non-empty string (does not allow nil)

Parameters:

  • value (String, nil)

    String to validate

  • field_name (String)

    Name of the field for error messages

  • max_length (Integer, nil) (defaults to: nil)

    Maximum allowed length

Returns:

  • (String)

    Validated string

Raises:

  • (ValidationError)

    If string is invalid or nil



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/rospatent/input_validator.rb', line 132

def validate_required_string(value, field_name, max_length: nil)
  raise Errors::ValidationError, "#{field_name.capitalize} is required" if value.nil?

  unless value.is_a?(String)
    raise Errors::ValidationError,
          "Invalid #{field_name} type. Expected String, got #{value.class}"
  end

  raise Errors::ValidationError, "#{field_name.capitalize} cannot be empty" if value.empty?

  if max_length && value.length > max_length
    raise Errors::ValidationError,
          "#{field_name.capitalize} cannot exceed #{max_length} characters"
  end

  value.strip
end

#validate_string(value, field_name, max_length: nil) ⇒ String

Validate non-empty string

Parameters:

  • value (String, nil)

    String to validate

  • field_name (String)

    Name of the field for error messages

  • max_length (Integer, nil) (defaults to: nil)

    Maximum allowed length

Returns:

  • (String)

    Validated string

Raises:

  • (ValidationError)

    If string is invalid



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/rospatent/input_validator.rb', line 85

def validate_string(value, field_name, max_length: nil)
  return nil if value.nil?

  unless value.is_a?(String)
    raise Errors::ValidationError,
          "Invalid #{field_name} type. Expected String, got #{value.class}"
  end

  raise Errors::ValidationError, "#{field_name.capitalize} cannot be empty" if value.empty?

  if max_length && value.length > max_length
    raise Errors::ValidationError,
          "#{field_name.capitalize} cannot exceed #{max_length} characters"
  end

  value.strip
end

#validate_text_with_word_count(value, field_name, min_words:, max_length: nil) ⇒ String

Validate text with word count requirements

Parameters:

  • value (String, nil)

    Text to validate

  • field_name (String)

    Name of the field for error messages

  • min_words (Integer)

    Minimum required word count

  • max_length (Integer, nil) (defaults to: nil)

    Maximum allowed character length

Returns:

  • (String)

    Validated text

Raises:

  • (ValidationError)

    If text is invalid or has insufficient words



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rospatent/input_validator.rb', line 110

def validate_text_with_word_count(value, field_name, min_words:, max_length: nil)
  # First, apply standard string validation
  validated_text = validate_string(value, field_name, max_length: max_length)
  return nil if validated_text.nil?

  # Count words by splitting on whitespace
  word_count = count_words(validated_text)

  if word_count < min_words
    raise Errors::ValidationError,
          "#{field_name.capitalize} must contain at least #{min_words} words (currently has #{word_count})"
  end

  validated_text
end