Skip to content

Commit cc7da11

Browse files
authored
Validate data types of fields upon receive (#94)
1 parent 7025019 commit cc7da11

File tree

16 files changed

+249
-12
lines changed

16 files changed

+249
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
33

4+
## [6.9.0] - 2025-10-02
5+
### Changed
6+
- Add check of data scalar types in order to provide readable error text.
7+
48
## [6.8.0] - 2025-08-25
59
### Changed
610
- Updated `rabbit_messaging` gem version to `1.7.0`

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
table_sync (6.8.0)
4+
table_sync (6.9.0)
55
memery
66
rabbit_messaging (>= 1.7.0)
77
rails

lib/table_sync/receiving/handler.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,20 @@ def validate_data(data, target_keys:)
131131
end
132132
end
133133

134+
def validate_data_types(model, data)
135+
errors = model.validate_types(data)
136+
return if errors.nil?
137+
138+
raise TableSync::DataError.new(data, errors.keys, errors.to_json)
139+
end
140+
134141
def perform(config, params)
135142
model = config.model
136143

137144
model.transaction do
138145
results = if event == :update
139146
config.before_update(**params)
147+
validate_data_types(model, params[:data])
140148
model.upsert(**params)
141149
else
142150
config.before_destroy(**params)

lib/table_sync/receiving/model/active_record.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def initialize(table_name)
2525
self.table_name = table_name
2626
self.inheritance_column = nil
2727
end
28+
@types_validator = TableSync::Utils::Schema::Builder::ActiveRecord.build(@raw_model)
2829

2930
model_naming = ::TableSync::NamingResolver::ActiveRecord.new(table_name:)
3031

@@ -105,6 +106,10 @@ def destroy(data:, target_keys:, version_key:)
105106
result
106107
end
107108

109+
def validate_types(data)
110+
types_validator.validate(data)
111+
end
112+
108113
def transaction(&)
109114
::ActiveRecord::Base.transaction(&)
110115
end
@@ -115,7 +120,7 @@ def after_commit(&)
115120

116121
private
117122

118-
attr_reader :raw_model
123+
attr_reader :raw_model, :types_validator
119124

120125
def db
121126
raw_model.connection

lib/table_sync/receiving/model/sequel.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class Sequel
66

77
def initialize(table_name)
88
@raw_model = Class.new(::Sequel::Model(table_name)).tap(&:unrestrict_primary_key)
9+
@types_validator = TableSync::Utils::Schema::Builder::Sequel.build(@raw_model)
910

1011
model_naming = ::TableSync::NamingResolver::Sequel.new(
1112
table_name:,
@@ -52,6 +53,10 @@ def destroy(data:, target_keys:, version_key:)
5253
result
5354
end
5455

56+
def validate_types(data)
57+
types_validator.validate(data)
58+
end
59+
5560
def transaction(&)
5661
db.transaction(&)
5762
end
@@ -62,7 +67,7 @@ def after_commit(&)
6267

6368
private
6469

65-
attr_reader :raw_model
70+
attr_reader :raw_model, :types_validator
6671

6772
def dataset
6873
raw_model.dataset

lib/table_sync/utils.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ module Utils
66
require_relative "utils/proc_keywords_resolver"
77
require_relative "utils/interface_checker"
88
require_relative "utils/required_validator"
9+
require_relative "utils/schema"
910
end
1011
end

lib/table_sync/utils/schema.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# frozen_string_literal: true
2+
3+
# Next code checks that hash values have required data types
4+
5+
require_relative "schema/builder"
6+
7+
class TableSync::Utils::Schema
8+
attr_reader :schema
9+
10+
def initialize(schema)
11+
@schema = schema
12+
end
13+
14+
def validate(data)
15+
errors = nil
16+
data.each do |row|
17+
schema.each do |key, value|
18+
if (error = value.validate(row[key]))
19+
errors ||= {}
20+
errors[key] = error
21+
end
22+
end
23+
24+
return errors.freeze unless errors.nil?
25+
end
26+
errors
27+
end
28+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "validator"
4+
require_relative "builder/sequel"
5+
require_relative "builder/active_record"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
class TableSync::Utils::Schema
4+
module Builder
5+
class ActiveRecord
6+
class << self
7+
Type = TableSync::Utils::Schema::Validator::Type
8+
9+
def build(model)
10+
TableSync::Utils::Schema.new(schema(model))
11+
end
12+
13+
private
14+
15+
def schema(model)
16+
model.attribute_types.to_h do |key, value|
17+
next [nil, nil] unless (type = type(value))
18+
[key.to_sym, TableSync::Utils::Schema::Validator.new(type)]
19+
end.tap(&:compact!)
20+
end
21+
22+
def type(value)
23+
case value
24+
when ActiveModel::Type::String,
25+
ActiveModel::Type::ImmutableString
26+
Type::STRING
27+
when ActiveModel::Type::DateTime,
28+
ActiveModel::Type::Date,
29+
ActiveModel::Type::Time
30+
Type::DATETIME
31+
when ActiveModel::Type::Integer
32+
Type::INTEGER
33+
when ActiveModel::Type::Decimal,
34+
ActiveModel::Type::Float,
35+
ActiveModel::Type::BigInteger
36+
Type::DECIMAL
37+
when ActiveModel::Type::Boolean
38+
Type::BOOLEAN
39+
end
40+
end
41+
end
42+
end
43+
end
44+
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# frozen_string_literal: true
2+
3+
class TableSync::Utils::Schema
4+
module Builder
5+
class Sequel
6+
class << self
7+
Type = TableSync::Utils::Schema::Validator::Type
8+
9+
def build(model)
10+
TableSync::Utils::Schema.new(schema(model))
11+
end
12+
13+
private
14+
15+
def schema(model)
16+
model.db_schema.transform_values do |value|
17+
next unless (type = type(value))
18+
TableSync::Utils::Schema::Validator.new(type)
19+
end.tap(&:compact!)
20+
end
21+
22+
def type(value)
23+
case value[:type]
24+
when :string
25+
Type::STRING
26+
when :datetime, :date, :time
27+
Type::DATETIME
28+
when :integer
29+
Type::INTEGER
30+
when :decimal, :float
31+
Type::DECIMAL
32+
when :boolean
33+
Type::BOOLEAN
34+
end
35+
end
36+
end
37+
end
38+
end
39+
end

0 commit comments

Comments
 (0)