Class: I18nJS::Schema

Inherits:
Object
  • Object
show all
Defined in:
lib/i18n-js/schema.rb

Constant Summary collapse

InvalidError =
Class.new(StandardError)
REQUIRED_LINT_TRANSLATIONS_KEYS =
%i[ignore].freeze
REQUIRED_LINT_SCRIPTS_KEYS =
%i[ignore patterns].freeze
REQUIRED_TRANSLATION_KEYS =
%i[file patterns].freeze
TRANSLATION_KEYS =
%i[file patterns].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target) ⇒ Schema

Returns a new instance of Schema.



33
34
35
# File 'lib/i18n-js/schema.rb', line 33

def initialize(target)
  @target = target
end

Instance Attribute Details

#targetObject (readonly)

Returns the value of attribute target.



31
32
33
# File 'lib/i18n-js/schema.rb', line 31

def target
  @target
end

Class Method Details

.required_root_keysObject



21
22
23
# File 'lib/i18n-js/schema.rb', line 21

def self.required_root_keys
  @required_root_keys ||= Set.new(%i[translations])
end

.root_keysObject



12
13
14
15
16
17
18
19
# File 'lib/i18n-js/schema.rb', line 12

def self.root_keys
  @root_keys ||= Set.new(%i[
    translations
    lint_translations
    lint_scripts
    check
  ])
end

.validate!(target) ⇒ Object



25
26
27
28
29
# File 'lib/i18n-js/schema.rb', line 25

def self.validate!(target)
  schema = new(target)
  schema.validate!
  schema
end

Instance Method Details

#expect_array_with_items(path:) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/i18n-js/schema.rb', line 158

def expect_array_with_items(path:)
  expect_type(path: path, types: Array)

  path = prepare_path(path: path)
  value = value_for(path: path)

  return unless value.empty?

  reject "Expected #{path.join('.').inspect} to have at least one item",
         target
end

#expect_required_keys(keys:, path:) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/i18n-js/schema.rb', line 170

def expect_required_keys(keys:, path:)
  path = prepare_path(path: path)
  value = value_for(path: path)
  actual_keys = value.keys.map(&:to_sym)

  keys.each do |key|
    next if actual_keys.include?(key)

    path_desc = if path.empty?
                  key.to_s.inspect
                else
                  (path + [key]).join(".").inspect
                end

    reject "Expected #{path_desc} to be defined", target
  end
end

#expect_type(path:, types:) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/i18n-js/schema.rb', line 135

def expect_type(path:, types:)
  path = prepare_path(path: path)
  value = value_for(path: path)
  types = Array(types)

  return if types.any? {|type| value.is_a?(type) }

  actual_type = value.class

  type_desc = if types.size == 1
                types[0].to_s.inspect
              else
                "one of #{types.inspect}"
              end

  message = [
    "Expected #{path.join('.').inspect} to be #{type_desc};",
    "got #{actual_type} instead"
  ].join(" ")

  reject message, target
end

#prepare_path(path:) ⇒ Object



207
208
209
210
# File 'lib/i18n-js/schema.rb', line 207

def prepare_path(path:)
  path = path.to_s.split(".").map(&:to_sym) unless path.is_a?(Array)
  path
end

#reject(error_message, node = nil) ⇒ Object

Raises:



130
131
132
133
# File 'lib/i18n-js/schema.rb', line 130

def reject(error_message, node = nil)
  node_json = "\n#{JSON.pretty_generate(node)}" if node
  raise InvalidError, "#{error_message}#{node_json}"
end

#reject_extraneous_keys(keys:, path:) ⇒ Object



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/i18n-js/schema.rb', line 188

def reject_extraneous_keys(keys:, path:)
  path = prepare_path(path: path)
  value = value_for(path: path)

  actual_keys = value.keys.map(&:to_sym)
  extraneous = actual_keys.to_a - keys.to_a

  return if extraneous.empty?

  path_desc = if path.empty?
                "config"
              else
                path.join(".").inspect
              end

  reject "#{path_desc} has unexpected keys: #{extraneous.inspect}",
         target
end

#validate!Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/i18n-js/schema.rb', line 37

def validate!
  validate_root

  expect_required_keys(
    keys: self.class.required_root_keys,
    path: nil
  )

  reject_extraneous_keys(
    keys: self.class.root_keys,
    path: nil
  )

  validate_translations
  validate_lint_translations
  validate_lint_scripts
  validate_plugins
end

#validate_lint_scriptsObject



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/i18n-js/schema.rb', line 93

def validate_lint_scripts
  key = :lint_scripts

  return unless target.key?(key)

  expect_type(path: [key], types: Hash)
  expect_required_keys(
    keys: REQUIRED_LINT_SCRIPTS_KEYS,
    path: [key]
  )
  expect_type(path: [key, :ignore], types: Array)
  expect_type(path: [key, :patterns], types: Array)
end

#validate_lint_translationsObject



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/i18n-js/schema.rb', line 78

def validate_lint_translations
  key = :lint_translations

  return unless target.key?(key)

  expect_type(path: [key], types: Hash)

  expect_required_keys(
    keys: REQUIRED_LINT_TRANSLATIONS_KEYS,
    path: [key]
  )

  expect_type(path: [key, :ignore], types: Array)
end

#validate_pluginsObject



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/i18n-js/schema.rb', line 56

def validate_plugins
  I18nJS.plugins.each do |plugin|
    next unless target.key?(plugin.config_key)

    expect_type(
      path: [plugin.config_key, :enabled],
      types: [TrueClass, FalseClass]
    )

    plugin.validate_schema
  end
end

#validate_rootObject



69
70
71
72
73
74
75
76
# File 'lib/i18n-js/schema.rb', line 69

def validate_root
  return if target.is_a?(Hash)

  message =  "Expected config to be \"Hash\"; " \
             "got #{target.class} instead"

  reject message, target
end

#validate_translation(_translation, index) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/i18n-js/schema.rb', line 115

def validate_translation(_translation, index)
  expect_required_keys(
    path: [:translations, index],
    keys: REQUIRED_TRANSLATION_KEYS
  )

  reject_extraneous_keys(
    keys: TRANSLATION_KEYS,
    path: [:translations, index]
  )

  expect_type(path: [:translations, index, :file], types: String)
  expect_array_with_items(path: [:translations, index, :patterns])
end

#validate_translationsObject



107
108
109
110
111
112
113
# File 'lib/i18n-js/schema.rb', line 107

def validate_translations
  expect_array_with_items(path: [:translations])

  target[:translations].each_with_index do |translation, index|
    validate_translation(translation, index)
  end
end

#value_for(path:) ⇒ Object



212
213
214
# File 'lib/i18n-js/schema.rb', line 212

def value_for(path:)
  path.empty? ? target : target.dig(*path)
end