forest_admin_datasource_toolkit 1.0.0.pre.beta.27 → 1.0.0.pre.beta.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da476de3594dde88a2fba472ab9c95f835ff430aafb566000101721183462777
4
- data.tar.gz: ccc40eae51cf545e43c357181f2dde06ed6b3bf3473d97c8029c0f9a588d5738
3
+ metadata.gz: eb59a941fffbdba03d6ce1966b69d7ce5415507d120591e7b76f3d642b6d83ec
4
+ data.tar.gz: d5519a29087ec84a16dc0d4ccc433168f31e7d68a7e1906ff4a9e4fc882cc35c
5
5
  SHA512:
6
- metadata.gz: 2c1918f8da7f4ebcbcd377591e63224fedd85e8e039dae2b3874599b8869cdec57052e117013762bfa5ddc8d7749c3d7b6826bd7eb46476d4616683b7e3abe02
7
- data.tar.gz: c6fb011db51fcdb3f10bf8724d0fd403860b0c46862d73129514d746524c5e85a1e3183f3b95d055ea729d022ea8d7f46f10fac45f500e504bde9d7ad68e273d
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.27"
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.27
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-02 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