exwiw 0.9.7 → 0.9.9

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.
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exwiw
4
+ # Raised when a schema config JSON carries a key that no declared attribute
5
+ # accepts. A subclass of ArgumentError so existing "invalid config" handling
6
+ # (and specs) that rescue ArgumentError keep working.
7
+ UnknownConfigKeyError = Class.new(ArgumentError)
8
+
9
+ # Strict key validation for the schema config JSON.
10
+ #
11
+ # Serdes deserialization is lenient: a key that matches no declared attribute
12
+ # is silently dropped. For hand-maintained schema configs that turns a typo
13
+ # (`reverse_scop`) — or a key another adapter supports but this one does not
14
+ # (`raw_sql` on a MongoDB field) — into a silent no-op: the config loads, the
15
+ # dump runs, and the requested masking/scoping simply never happens.
16
+ # {TableConfig.from} / {MongodbCollectionConfig.from} therefore validate the
17
+ # raw hash against the declared attributes before deserializing, recursing
18
+ # into nested config objects (belongs_tos, columns/fields, reverse_scope,
19
+ # embedded_in, replace_with_fake_data).
20
+ #
21
+ # Free-form annotations do not need an escape hatch: `comment` is a declared
22
+ # (documentation-only) attribute on the table/collection configs and their
23
+ # belongs_to/column/field entries, so it always passes.
24
+ module StrictKeys
25
+ module_function
26
+
27
+ # Validate `hash` (a raw config hash, string- or symbol-keyed) against the
28
+ # attributes declared on the Serdes class `klass`, recursing into nested
29
+ # Serdes-typed attributes. `owner` names the config for the error message
30
+ # (e.g. "table 'users'"); `path` locates a nested entry within it (e.g.
31
+ # "belongs_tos[0]"). No-op for non-Hash input — Serdes' own type errors
32
+ # cover malformed values.
33
+ def validate!(klass, hash, owner:, path: nil)
34
+ return unless hash.is_a?(Hash)
35
+
36
+ attributes = klass.__send__(:_serde_attrs)
37
+ rename_strategy = klass.__send__(:_serde_rename_strategy)
38
+ allowed = attributes.each_value.to_h { |attr| [attr.serialized_name(rename_strategy), attr] }
39
+
40
+ unknown = hash.keys.map(&:to_s) - allowed.keys
41
+ unless unknown.empty?
42
+ location = path ? " (in #{path})" : ""
43
+ raise UnknownConfigKeyError,
44
+ "Unknown key#{'s' if unknown.size > 1} #{unknown.map { |key| "'#{key}'" }.join(', ')} " \
45
+ "in #{owner}#{location}. Allowed keys: #{allowed.keys.sort.join(', ')}. " \
46
+ "Unknown keys are rejected because they would otherwise be silently ignored " \
47
+ "(hiding typos and keys this adapter does not support); remove or rename the key, " \
48
+ "or use `comment` for free-form notes."
49
+ end
50
+
51
+ allowed.each do |key, attr|
52
+ nested_klass = serdes_class(attr.attr_type)
53
+ next if nested_klass.nil?
54
+
55
+ value = hash.key?(key) ? hash[key] : hash[key.to_sym]
56
+ nested_path = path ? "#{path}.#{key}" : key
57
+ case value
58
+ when Hash
59
+ validate!(nested_klass, value, owner: owner, path: nested_path)
60
+ when Array
61
+ value.each_with_index do |element, index|
62
+ validate!(nested_klass, element, owner: owner, path: "#{nested_path}[#{index}]") if element.is_a?(Hash)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ # The Serdes-including class a (possibly optional/array-wrapped) attribute
69
+ # type deserializes into, or nil for scalar types.
70
+ def serdes_class(type)
71
+ case type
72
+ when Serdes::OptionalType then serdes_class(type.base_type)
73
+ when Serdes::ArrayType then serdes_class(type.element_type)
74
+ when Serdes::ConcreteType then serdes_class(type.exact_type)
75
+ when Class then type if type.include?(Serdes)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -48,6 +48,15 @@ module Exwiw
48
48
  attribute :reverse_scope, Serdes::OptionalType.new(ReverseScope), skip_serializing_if_nil: true
49
49
 
50
50
  def self.from(hash)
51
+ # Reject unknown keys before deserializing: Serdes silently drops them,
52
+ # which would turn a typo'd or unsupported key into a silent no-op (see
53
+ # Exwiw::StrictKeys). `comment` is a declared attribute here and on the
54
+ # nested belongs_to/column entries, so free-form notes stay accepted.
55
+ if hash.is_a?(Hash)
56
+ table_name = hash["name"] || hash[:name]
57
+ StrictKeys.validate!(self, hash, owner: "table '#{table_name}'")
58
+ end
59
+
51
60
  config = super
52
61
  config.send(:validate_after_load!)
53
62
  config
@@ -244,10 +253,11 @@ module Exwiw
244
253
  fake_data = column.replace_with_fake_data
245
254
  return unless fake_data
246
255
 
247
- unless RowTransformer::FAKE_TYPES.key?(fake_data.type)
256
+ supported_types = RowTransformer::PERSON_TYPES.keys + RowTransformer::FAKE_TYPES.keys
257
+ unless supported_types.include?(fake_data.type)
248
258
  raise ArgumentError,
249
259
  "Table '#{name}' column '#{column.name}': unknown replace_with_fake_data type '#{fake_data.type}' " \
250
- "(supported: #{RowTransformer::FAKE_TYPES.keys.join(', ')})."
260
+ "(supported: #{supported_types.join(', ')})."
251
261
  end
252
262
 
253
263
  seed_column = fake_data.seed.delete_prefix("#{name}.")
data/lib/exwiw/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Exwiw
4
- VERSION = "0.9.7"
4
+ VERSION = "0.9.9"
5
5
  end
data/lib/exwiw.rb CHANGED
@@ -7,6 +7,7 @@ require "serdes"
7
7
 
8
8
  require_relative "exwiw/ext_json"
9
9
  require_relative "exwiw/config_file"
10
+ require_relative "exwiw/strict_keys"
10
11
  require_relative "exwiw/belongs_to"
11
12
  require_relative "exwiw/fake_data"
12
13
  require_relative "exwiw/table_column"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exwiw
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 0.9.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shia
@@ -71,6 +71,7 @@ files:
71
71
  - lib/exwiw/explain_runner.rb
72
72
  - lib/exwiw/ext_json.rb
73
73
  - lib/exwiw/fake_data.rb
74
+ - lib/exwiw/japanese_names.rb
74
75
  - lib/exwiw/mongo_query.rb
75
76
  - lib/exwiw/mongodb_collection_config.rb
76
77
  - lib/exwiw/mongodb_field.rb
@@ -84,6 +85,7 @@ files:
84
85
  - lib/exwiw/row_transformer.rb
85
86
  - lib/exwiw/runner.rb
86
87
  - lib/exwiw/schema_generator.rb
88
+ - lib/exwiw/strict_keys.rb
87
89
  - lib/exwiw/table_column.rb
88
90
  - lib/exwiw/table_config.rb
89
91
  - lib/exwiw/version.rb