Class: CodeToQuery::Validator

Inherits:
Object
  • Object
show all
Defined in:
lib/code_to_query/validator.rb

Constant Summary collapse

IntentSchema =
Dry::Schema.Params do
  required(:type).filled(:string)
  required(:table).filled(:string)
  required(:columns).array(:string)
  optional(:filters).array(:hash) do
    optional(:column).maybe(:string)
    required(:op).filled(:string)
    optional(:param).filled(:string)
    optional(:param_start).filled(:string)
    optional(:param_end).filled(:string)
    # Optional fields to support correlated subqueries (NOT EXISTS)
    optional(:related_table).filled(:string)
    optional(:fk_column).filled(:string)
    optional(:base_column).filled(:string)
    optional(:related_filters).array(:hash) do
      required(:column).filled(:string)
      required(:op).filled(:string)
      optional(:param).filled(:string)
      optional(:param_start).filled(:string)
      optional(:param_end).filled(:string)
    end
  end
  optional(:order).array(:hash) do
    required(:column).filled(:string)
    required(:dir).filled(:string)
  end
  optional(:limit).filled(:integer)
  optional(:params).hash
  optional(:distinct).filled(:bool)
  optional(:distinct_on).array(:string)
  optional(:aggregations).array(:hash) do
    required(:type).filled(:string)
    optional(:column).filled(:string)
  end
  optional(:group_by).array(:string)
end

Instance Method Summary collapse

Instance Method Details

#validate(intent_hash, current_user: nil, allow_tables: nil) ⇒ Object

Raises:

  • (ArgumentError)


44
45
46
47
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
# File 'lib/code_to_query/validator.rb', line 44

def validate(intent_hash, current_user: nil, allow_tables: nil)
  preprocessed = preprocess_exists_filters(intent_hash)

  if !preprocessed.key?('limit') && CodeToQuery.config.default_limit
    preprocessed = preprocessed.merge('limit' => CodeToQuery.config.default_limit)
  end

  result = IntentSchema.call(preprocessed)
  raise ArgumentError, "Invalid intent: #{result.errors.to_h}" unless result.success?

  validated = result.to_h

  original_metrics = intent_hash['_metrics'] || intent_hash[:_metrics]
  validated['_metrics'] = original_metrics if original_metrics.is_a?(Hash)

  Array(validated['filters']).each_with_index do |f, idx|
    op = f['op'].to_s
    if %w[exists not_exists].include?(op)
      unless f['related_table'].to_s.strip != '' && f['fk_column'].to_s.strip != ''
        raise ArgumentError, "Invalid intent: filters[#{idx}] requires related_table and fk_column for #{op}"
      end

      f['base_column'] ||= 'id'
      f['column'] ||= 'id'
    else
      unless f['column'].to_s.strip != ''
        raise ArgumentError, "Invalid intent: filters[#{idx}].column must be filled"
      end
    end
  end
  enforce_allowlists!(validated, current_user: current_user, allow_tables: allow_tables)
  validated
end