ファイル
btrc-hub/backend/app/services/query_state.rb
T
2026-10-26 22:57:39 +09:00

89 行
3.1 KiB
Ruby

# Public query grammar shared with frontend/src/lib/queryState.ts.
class QueryState
SCHEMAS = {
'conditions' => ['field', 'operator', 'value', 'end', 'blank'],
'drill' => ['field', 'grain', 'value', 'value_type'],
'tag_refs' => ['name', 'tag_id', 'external_tag_id', 'negative', 'missing'],
'column_order' => ['value', 'value_type'],
'measures' => ['key', 'axis', 'value', 'value_type'] }.freeze
def self.list(value)
return [] if value.nil? || value == ''
raise ArgumentError unless value.is_a?(String) && value.bytesize <= 32_768
values = value.split(/ /, -1)
raise ArgumentError if values.length > 200 || values.any?(&:empty?) || values.uniq != values
values
end
def self.valid_key?(key)
root, index, field, extra = key.split('.', -1)
!extra && SCHEMAS.key?(root) && index&.match?(/\A(?:0|[1-9][0-9]?)\z/) &&
SCHEMAS[root].include?(field)
end
def self.validate!(params, allowed)
raise ArgumentError if params.length > 1000
params.each do |key, value|
raise ArgumentError unless allowed.include?(key) || valid_key?(key)
raise ArgumentError unless value.is_a?(String) && value.bytesize <= 131_072
end
SCHEMAS.each_key { records(params, _1) }
end
def self.records(params, key)
records = {}
params.each do |name, value|
next unless name.start_with?("#{ key }.")
raise ArgumentError unless params.key?(key) && valid_key?(name)
_root, index, field = name.split('.')
record = records[index.to_i] ||= {}
if ['blank', 'negative', 'missing'].include?(field)
raise ArgumentError unless ['0', '1'].include?(value)
value = value == '1'
elsif ['tag_id', 'external_tag_id', 'axis'].include?(field)
raise ArgumentError unless value.match?(/\A[0-9]{1,18}\z/)
value = value.to_i
end
record[field] = value
end
raise ArgumentError if params.key?(key) && params[key] != ''
raise ArgumentError unless records.keys.sort == (0...records.length).to_a
records.sort.map do |_index, record|
type = record.delete('value_type')
if ['drill', 'measures', 'column_order'].include?(key)
raise ArgumentError unless ['null', 'string', 'number', 'boolean'].include?(type)
raise ArgumentError unless type == 'null' || record.key?('value')
value = record['value']
record['value'] = case type
when 'null' then nil
when 'number'
raise ArgumentError unless value&.match?(/\A-?[0-9]{1,18}\z/)
Integer(value, 10)
when 'boolean'
raise ArgumentError unless ['true', 'false'].include?(value)
value == 'true'
else value
end
end
record
end
end
def self.axes(value)
list(value).map do |token|
field, grain, extra = token.split(':', -1)
raise ArgumentError if extra || !field.match?(/\A[a-z_]+\z/) || grain == ''
grain ? { 'field' => field, 'grain' => grain } : { 'field' => field }
end
end
end