forest_admin_datasource_toolkit 1.0.0.pre.beta.28 → 1.0.0.pre.beta.29

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac62d583445015740cf9755d82d742e7e1afbdf7f06a522e993cb9db6bc9afbc
4
- data.tar.gz: '094c6d02689766596720110469317254aa1e3f45fa0a61555d8b50de5f91eb50'
3
+ metadata.gz: eb59a941fffbdba03d6ce1966b69d7ce5415507d120591e7b76f3d642b6d83ec
4
+ data.tar.gz: d5519a29087ec84a16dc0d4ccc433168f31e7d68a7e1906ff4a9e4fc882cc35c
5
5
  SHA512:
6
- metadata.gz: 88c76ae3ab88b6a888a9aa41f785ba6c53545d28fc7029b66e4870ef90436bb4d6d92fff5296fc53637c537bf7e44559bbb660c49439cd0e2ca83d612e0c79b0
7
- data.tar.gz: 3a741eb0906ac2d8f25faaabfe0006d403a0dee569bb8f677e72204c8d392d07ef10119fdcacc1d0fc0e29467c9b0e6ec85fdd0a234b80ccd8380e03ea797239
6
+ metadata.gz: e30897a61d2675a5f63bc0cce508f3779b3c7e7c56c1b5b67051c4217216d47e3aa1c820c7e863c864864fc7357d0be14a0b6b1f422e82815ec7ac1cfbdb0531
7
+ data.tar.gz: efdbf2b8d9f8603a803c6c5cbe5c8092a2cdd5da4dd4c4f9258311653c9278b889d1e2304e6c9a9e87781cb607a583a86e41a3713dc5f68c79bb96a06711ac08
@@ -36,6 +36,28 @@ module ForestAdminDatasourceToolkit
36
36
  def nest(prefix: nil)
37
37
  prefix ? Projection.new(map { |path| "#{prefix}:#{path}" }) : self
38
38
  end
39
+
40
+ def unnest
41
+ prefix = first.split(':')[0]
42
+ raise 'Cannot unnest projection.' unless all? { |path| path.start_with?(prefix) }
43
+
44
+ Projection.new(map { |path| path[prefix.length + 1, path.length - prefix.length - 1] })
45
+ end
46
+
47
+ def replace(...)
48
+ Projection.new(
49
+ map(...)
50
+ .reduce(Projection.new) do |memo, path|
51
+ return memo.union([path]) if path.is_a?(String)
52
+
53
+ memo.union(path)
54
+ end
55
+ )
56
+ end
57
+
58
+ def equals(other)
59
+ length == other.length && all? { |field| other.include?(field) }
60
+ end
39
61
  end
40
62
  end
41
63
  end
@@ -2,24 +2,14 @@ module ForestAdminDatasourceToolkit
2
2
  module Decorators
3
3
  class CollectionDecorator < Collection
4
4
  attr_reader :datasource, :child_collection, :last_schema
5
+ attr_writer :parent
5
6
 
6
7
  def initialize(child_collection, datasource)
7
8
  super
8
9
  @child_collection = child_collection
9
10
  @datasource = datasource
10
11
 
11
- # When the child collection invalidates its schema, we also invalidate ours.
12
- # This is done like this, and not in the markSchemaAsDirty method, because we don't have
13
- # a reference to parent collections from children.
14
- return unless child_collection.is_a?(CollectionDecorator)
15
-
16
- child_collection.define_singleton_method(:mark_schema_as_dirty) do
17
- # Call the original method (the child)
18
- original_child_mark_schema_as_dirty.call(child_collection)
19
-
20
- # Invalidate our schema (the parent)
21
- mark_schema_as_dirty
22
- end
12
+ child_collection.parent = self if child_collection.is_a?(CollectionDecorator)
23
13
  end
24
14
 
25
15
  def native_driver
@@ -87,6 +77,7 @@ module ForestAdminDatasourceToolkit
87
77
 
88
78
  def mark_schema_as_dirty
89
79
  @last_schema = nil
80
+ @parent&.mark_schema_as_dirty
90
81
  end
91
82
 
92
83
  def refine_filter(_caller, filter = nil)
@@ -96,14 +87,6 @@ module ForestAdminDatasourceToolkit
96
87
  def refine_schema(sub_schema)
97
88
  sub_schema
98
89
  end
99
-
100
- private
101
-
102
- def push_customization(customization)
103
- @stack.queue_customization(customization)
104
-
105
- self
106
- end
107
90
  end
108
91
  end
109
92
  end
@@ -0,0 +1,9 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Exceptions
3
+ class ValidationError < ForestException
4
+ def initialize(msg = '')
5
+ super
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Schema
3
+ module Concerns
4
+ class PrimitiveTypes
5
+ BINARY = 'Binary'.freeze
6
+ BOOLEAN = 'Boolean'.freeze
7
+ DATE = 'Date'.freeze
8
+ DATE_ONLY = 'Dateonly'.freeze
9
+ ENUM = 'Enum'.freeze
10
+ JSON = 'Json'.freeze
11
+ NUMBER = 'Number'.freeze
12
+ POINT = 'Point'.freeze
13
+ STRING = 'String'.freeze
14
+ TIME_ONLY = 'Timeonly'.freeze
15
+ UUID = 'Uuid'.freeze
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,80 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Validations
3
+ class FieldValidator
4
+ include ForestAdminDatasourceToolkit::Schema
5
+
6
+ def self.validate(collection, field, values = nil)
7
+ dot_index = field.index(':')
8
+
9
+ if dot_index.nil?
10
+ schema = collection.schema[:fields][field]
11
+
12
+ raise Exceptions::ValidationError, "Column not found: '#{collection.name}.#{field}'" if schema.nil?
13
+
14
+ if schema.type != 'Column'
15
+ raise Exceptions::ValidationError,
16
+ "Unexpected field type: '#{collection.name}.#{field}' (found '#{schema.type}' expected 'Column')"
17
+ end
18
+
19
+ values&.each do |value|
20
+ validate_value(field, schema, value)
21
+ end
22
+ else
23
+ prefix = field[0, dot_index]
24
+ schema = collection.schema[:fields][prefix]
25
+
26
+ raise Exceptions::ValidationError, "Relation not found: '#{collection.name}.#{prefix}'" if schema.nil?
27
+
28
+ if schema.type != 'ManyToOne' && schema.type != 'OneToOne'
29
+ raise Exceptions::ValidationError,
30
+ "Unexpected field type: '#{collection.name}.#{prefix}' (found '#{schema.type}' expected 'ManyToOne' or 'OneToOne')"
31
+ end
32
+
33
+ suffix = field[dot_index + 1, field.length - dot_index - 1]
34
+ association = collection.datasource.get_collection(schema.foreign_collection)
35
+ validate(association, suffix, values)
36
+ end
37
+ end
38
+
39
+ def self.validate_value_for_id(field, schema, value)
40
+ validate_value(field, schema, value, [schema.column_type])
41
+ end
42
+
43
+ def self.validate_value(field, schema, value, allowed_types = nil)
44
+ allowed_types ||= Rules.get_allowed_types_for_column_type(schema.column_type)
45
+
46
+ # TODO: FIXME: handle complex type from ColumnType
47
+ # if schema.column_type != PrimitiveType::STRING
48
+ # end
49
+
50
+ type = TypeGetter.get(value, schema.column_type)
51
+
52
+ unless allowed_types.include?(type)
53
+ raise Exceptions::ValidationError,
54
+ "The given value has a wrong type for '#{field}': #{value}.\n Expects #{allowed_types}"
55
+ end
56
+
57
+ return unless value && schema.column_type == PrimitiveType::ENUM
58
+
59
+ check_enum_value(schema, value)
60
+ end
61
+
62
+ def self.validate_name(collection_name, name)
63
+ return unless name.include?(' ')
64
+
65
+ sanitized_name = name.gsub(/ (.)/, &:upcase)
66
+ raise Exceptions::ValidationError,
67
+ "The name of field '#{name}' you configured on '#{collection_name}' must not contain space. Something like '#{sanitized_name}' should work has expected."
68
+ end
69
+
70
+ def self.check_enum_value(column_schema, enum_value)
71
+ is_enum_allowed = column_schema.enum_values.include?(enum_value)
72
+
73
+ return if is_enum_allowed
74
+
75
+ raise Exceptions::ValidationError,
76
+ "The given enum value(s) #{enum_value} is not listed in #{column_schema.enum_values}"
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,144 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Validations
3
+ class Rules
4
+ include ForestAdminDatasourceToolkit::Schema
5
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
6
+
7
+ BASE_OPERATORS = [Operators::BLANK, Operators::EQUAL, Operators::MISSING, Operators::NOT_EQUAL,
8
+ Operators::PRESENT].freeze
9
+
10
+ ARRAY_OPERATORS = [Operators::IN, Operators::NOT_IN, Operators::INCLUDES_ALL].freeze
11
+
12
+ BASE_DATEONLY_OPERATORS = [
13
+ Operators::TODAY,
14
+ Operators::YESTERDAY,
15
+ Operators::PREVIOUS_X_DAYS,
16
+ Operators::PREVIOUS_WEEK,
17
+ Operators::PREVIOUS_MONTH,
18
+ Operators::PREVIOUS_QUARTER,
19
+ Operators::PREVIOUS_YEAR,
20
+ Operators::PREVIOUS_X_DAYS_TO_DATE,
21
+ Operators::PREVIOUS_WEEK_TO_DATE,
22
+ Operators::PREVIOUS_MONTH_TO_DATE,
23
+ Operators::PREVIOUS_QUARTER_TO_DATE,
24
+ Operators::PREVIOUS_YEAR_TO_DATE,
25
+ Operators::PAST,
26
+ Operators::FUTURE,
27
+ Operators::BEFORE,
28
+ Operators::AFTER
29
+ ].freeze
30
+
31
+ def self.get_allowed_operators_for_column_type(primitive_type = nil)
32
+ allowed_operators = {
33
+ PrimitiveType::STRING => [
34
+ *Rules::BASE_OPERATORS,
35
+ *Rules::ARRAY_OPERATORS,
36
+ Operators::CONTAINS,
37
+ Operators::NOT_CONTAINS,
38
+ Operators::ENDS_WITH,
39
+ Operators::STARTS_WITH,
40
+ Operators::LONGER_THAN,
41
+ Operators::SHORTER_THAN,
42
+ Operators::LIKE,
43
+ Operators::I_LIKE,
44
+ Operators::I_CONTAINS,
45
+ Operators::I_ENDS_WITH,
46
+ Operators::I_STARTS_WITH
47
+ ],
48
+ PrimitiveType::NUMBER => [
49
+ *Rules::BASE_OPERATORS,
50
+ *Rules::ARRAY_OPERATORS,
51
+ Operators::GREATER_THAN,
52
+ Operators::LESS_THAN
53
+ ],
54
+ PrimitiveType::DATE => [
55
+ *Rules::BASE_OPERATORS,
56
+ *Rules::BASE_DATEONLY_OPERATORS,
57
+ Operators::BEFORE_X_HOURS_AGO,
58
+ Operators::AFTER_X_HOURS_AGO
59
+ ],
60
+ PrimitiveType::TIMEONLY => [
61
+ *Rules::BASE_OPERATORS,
62
+ Operators::LESS_THAN,
63
+ Operators::GREATER_THAN
64
+ ],
65
+ PrimitiveType::JSON => [
66
+ Operators::BLANK,
67
+ Operators::MISSING,
68
+ Operators::PRESENT
69
+ ],
70
+ PrimitiveType::DATEONLY => [*Rules::BASE_OPERATORS, *Rules::BASE_DATEONLY_OPERATORS],
71
+ PrimitiveType::ENUM => [*Rules::BASE_OPERATORS, *Rules::ARRAY_OPERATORS],
72
+ PrimitiveType::UUID => [*Rules::BASE_OPERATORS, *Rules::ARRAY_OPERATORS],
73
+ PrimitiveType::BOOLEAN => Rules::BASE_OPERATORS,
74
+ PrimitiveType::POINT => Rules::BASE_OPERATORS
75
+ }
76
+
77
+ primitive_type ? allowed_operators[primitive_type] : allowed_operators
78
+ end
79
+
80
+ def self.get_allowed_types_for_column_type(primitive_type = nil)
81
+ allowed_types = {
82
+ PrimitiveType::STRING => [PrimitiveType::STRING, nil],
83
+ PrimitiveType::NUMBER => [PrimitiveType::NUMBER, nil],
84
+ PrimitiveType::DATEONLY => [PrimitiveType::DATEONLY, nil],
85
+ PrimitiveType::DATE => [PrimitiveType::DATE, nil],
86
+ PrimitiveType::TIMEONLY => [PrimitiveType::TIMEONLY, nil],
87
+ PrimitiveType::ENUM => [PrimitiveType::ENUM, nil],
88
+ PrimitiveType::UUID => [PrimitiveType::UUID, nil],
89
+ PrimitiveType::JSON => [PrimitiveType::JSON, nil],
90
+ PrimitiveType::BOOLEAN => [PrimitiveType::BOOLEAN, nil],
91
+ PrimitiveType::POINT => [PrimitiveType::POINT, nil]
92
+ }
93
+
94
+ primitive_type ? allowed_types[primitive_type] : allowed_types
95
+ end
96
+
97
+ def self.compute_allowed_types_for_operators
98
+ get_allowed_operators_for_column_type.keys.reduce do |result, type|
99
+ allowed_operators = get_allowed_operators_for_column_type(type)
100
+ allowed_operators.each do |operator|
101
+ if result[operator]
102
+ result[operator] << type
103
+ else
104
+ result[operator] = [type]
105
+ end
106
+ end
107
+
108
+ result
109
+ end
110
+ end
111
+
112
+ def self.get_allowed_types_for_operator(operator = nil)
113
+ no_type_allowed = [nil]
114
+ allowed_types = compute_allowed_types_for_operators
115
+ merged = allowed_types.merge(
116
+ Operators::IN => allowed_types[Operators::IN] + [nil],
117
+ Operators::NOT_IN => allowed_types[Operators::NOT_IN] + [nil],
118
+ Operators::INCLUDES_ALL => allowed_types[Operators::INCLUDES_ALL] + [nil],
119
+ Operators::BLANK => no_type_allowed,
120
+ Operators::MISSING => no_type_allowed,
121
+ Operators::PRESENT => no_type_allowed,
122
+ Operators::YESTERDAY => no_type_allowed,
123
+ Operators::TODAY => no_type_allowed,
124
+ Operators::PREVIOUS_QUARTER => no_type_allowed,
125
+ Operators::PREVIOUS_YEAR => no_type_allowed,
126
+ Operators::PREVIOUS_MONTH => no_type_allowed,
127
+ Operators::PREVIOUS_WEEK => no_type_allowed,
128
+ Operators::PAST => no_type_allowed,
129
+ Operators::FUTURE => no_type_allowed,
130
+ Operators::PREVIOUS_WEEK_TO_DATE => no_type_allowed,
131
+ Operators::PREVIOUS_MONTH_TO_DATE => no_type_allowed,
132
+ Operators::PREVIOUS_QUARTER_TO_DATE => no_type_allowed,
133
+ Operators::PREVIOUS_YEAR_TO_DATE => no_type_allowed,
134
+ Operators::PREVIOUS_X_DAYS_TO_DATE => ['Number'],
135
+ Operators::PREVIOUS_X_DAYS => ['Number'],
136
+ Operators::BEFORE_X_HOURS_AGO => ['Number'],
137
+ Operators::AFTER_X_HOURS_AGO => ['Number']
138
+ )
139
+
140
+ operator ? merged[operator] : merged
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,97 @@
1
+ require 'date'
2
+ require 'time'
3
+ require 'openssl'
4
+
5
+ module ForestAdminDatasourceToolkit
6
+ module Validations
7
+ class TypeGetter
8
+ include ForestAdminDatasourceToolkit::Schema::Concerns
9
+ def self.get(value, type_context)
10
+ return PrimitiveTypes::JSON if type_context == PrimitiveTypes::JSON
11
+
12
+ return get_type_from_string(value, type_context) if value.is_a?(String)
13
+
14
+ return PrimitiveTypes::NUMBER if value.is_a?(Numeric)
15
+
16
+ return PrimitiveTypes::DATE if value.is_a?(Date)
17
+
18
+ return PrimitiveTypes::BOOLEAN if value.is_a?(TrueClass) || value.is_a?(FalseClass)
19
+
20
+ return PrimitiveTypes::BINARY if value.is_a?(buffer)
21
+
22
+ nil
23
+ end
24
+
25
+ class << self
26
+ include ForestAdminDatasourceToolkit::Schema::Concerns
27
+ def get_date_type(value)
28
+ return PrimitiveTypes::DATE_ONLY if date?(value) && Date.parse(value).iso8601 == value
29
+
30
+ if time?(value) && (Time.parse(value).strftime('%H:%M:%S.%L') == value ||
31
+ Time.parse(value).strftime('%H:%M:%S') == value)
32
+ return PrimitiveTypes::TIME_ONLY
33
+ end
34
+
35
+ PrimitiveTypes::DATE
36
+ end
37
+
38
+ def get_type_from_string(value, type_context)
39
+ return type_context if [PrimitiveTypes::ENUM, PrimitiveTypes::STRING].include?(type_context)
40
+
41
+ return PrimitiveTypes::UUID if uuid?(value)
42
+
43
+ return get_date_type(value) if valid_date?(value) && [PrimitiveTypes::DATE, PrimitiveTypes::DATE_ONLY,
44
+ PrimitiveTypes::TIME_ONLY].include?(type_context)
45
+
46
+ return PrimitiveTypes::POINT if point?(value, type_context)
47
+
48
+ PrimitiveTypes::STRING
49
+ end
50
+
51
+ def valid_date?(value)
52
+ date?(value) || time?(value)
53
+ end
54
+
55
+ def date?(value)
56
+ true if Date.parse(value)
57
+ rescue ArgumentError
58
+ false
59
+ end
60
+
61
+ def time?(value)
62
+ true if Time.parse(value)
63
+ rescue ArgumentError
64
+ false
65
+ end
66
+
67
+ def number?(value)
68
+ true if Float(value)
69
+ rescue ArgumentError
70
+ false
71
+ end
72
+
73
+ def point?(value, type_context)
74
+ potential_point = value.split(',')
75
+
76
+ potential_point.length == 2 && type_context == PrimitiveTypes::POINT && potential_point.all? do |point|
77
+ number?(point) && get(point.to_i, PrimitiveTypes::NUMBER) == PrimitiveTypes::NUMBER
78
+ end
79
+ end
80
+
81
+ def uuid?(uuid)
82
+ format = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
83
+
84
+ true if format.match?(uuid.to_s.downcase)
85
+ end
86
+
87
+ def buffer
88
+ if defined?(IO::Buffer)
89
+ IO::Buffer
90
+ else
91
+ OpenSSL::Buffering::Buffer
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,3 +1,3 @@
1
1
  module ForestAdminDatasourceToolkit
2
- VERSION = "1.0.0-beta.28"
2
+ VERSION = "1.0.0-beta.29"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_datasource_toolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.beta.28
4
+ version: 1.0.0.pre.beta.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2024-01-19 00:00:00.000000000 Z
12
+ date: 2024-01-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -85,7 +85,9 @@ files:
85
85
  - lib/forest_admin_datasource_toolkit/decorators/collection_decorator.rb
86
86
  - lib/forest_admin_datasource_toolkit/decorators/datasource_decorator.rb
87
87
  - lib/forest_admin_datasource_toolkit/exceptions/forest_exception.rb
88
+ - lib/forest_admin_datasource_toolkit/exceptions/validation_error.rb
88
89
  - lib/forest_admin_datasource_toolkit/schema/column_schema.rb
90
+ - lib/forest_admin_datasource_toolkit/schema/concerns/primitive_types.rb
89
91
  - lib/forest_admin_datasource_toolkit/schema/primitive_type.rb
90
92
  - lib/forest_admin_datasource_toolkit/schema/relation_schema.rb
91
93
  - lib/forest_admin_datasource_toolkit/schema/relations/many_to_many_schema.rb
@@ -96,6 +98,9 @@ files:
96
98
  - lib/forest_admin_datasource_toolkit/utils/record.rb
97
99
  - lib/forest_admin_datasource_toolkit/utils/schema.rb
98
100
  - lib/forest_admin_datasource_toolkit/validations/chart_validator.rb
101
+ - lib/forest_admin_datasource_toolkit/validations/field_validator.rb
102
+ - lib/forest_admin_datasource_toolkit/validations/rules.rb
103
+ - lib/forest_admin_datasource_toolkit/validations/type_getter.rb
99
104
  - lib/forest_admin_datasource_toolkit/version.rb
100
105
  - sig/forest_admin_datasource_toolkit.rbs
101
106
  - sig/forest_admin_datasource_toolkit/collection.rbs