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 ||= %i[translations].freeze
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 ||= %i[
    translations
    lint_scripts
    lint_translations
    pipeline
  ].freeze
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



162
163
164
165
166
167
168
169
170
171
172
# File 'lib/i18n-js/schema.rb', line 162

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

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

  return unless value.empty?

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

#expect_required_keys(keys:, path: []) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/i18n-js/schema.rb', line 174

def expect_required_keys(keys:, path: [])
  path = prepare_path(path:)
  value = value_for(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



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/i18n-js/schema.rb', line 139

def expect_type(path:, types:)
  path = prepare_path(path:)
  value = value_for(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



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

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:



134
135
136
137
# File 'lib/i18n-js/schema.rb', line 134

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



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/i18n-js/schema.rb', line 192

def reject_extraneous_keys(keys:, path: [])
  path = prepare_path(path:)
  value = value_for(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_pipeline if target.key?(:pipeline)
  validate_lint_translations
  validate_lint_scripts
end

#validate_lint_scriptsObject



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

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



65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/i18n-js/schema.rb', line 65

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_pipelineObject



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

def validate_pipeline
  expect_type(path: [:pipeline], types: Array)

  target[:pipeline].each_with_index do |_, index|
    expect_type(path: [:pipeline, index], types: Hash)
    expect_required_keys(
      path: [:pipeline, index],
      keys: %i[plugin enabled]
    )
    expect_type(path: [:pipeline, index, :plugin], types: String)
    expect_type(
      path: [:pipeline, index, :enabled],
      types: [TrueClass, FalseClass]
    )
  end
end

#validate_rootObject



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

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



119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/i18n-js/schema.rb', line 119

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



111
112
113
114
115
116
117
# File 'lib/i18n-js/schema.rb', line 111

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



216
217
218
# File 'lib/i18n-js/schema.rb', line 216

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