active_storage_validations 3.0.5 → 4.0.0

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +178 -115
  4. data/Rakefile +13 -15
  5. data/lib/active_storage_validations/analyzer/audio_analyzer.rb +0 -1
  6. data/lib/active_storage_validations/analyzer/content_type_analyzer/file.rb +31 -0
  7. data/lib/active_storage_validations/analyzer/content_type_analyzer/magika.rb +44 -0
  8. data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +15 -24
  9. data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +41 -7
  10. data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +36 -10
  11. data/lib/active_storage_validations/analyzer/pdf_analyzer.rb +3 -9
  12. data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +5 -4
  13. data/lib/active_storage_validations/analyzer/video_analyzer.rb +0 -1
  14. data/lib/active_storage_validations/analyzer.rb +35 -3
  15. data/lib/active_storage_validations/content_type_validator.rb +24 -6
  16. data/lib/active_storage_validations/dimension_validator.rb +4 -2
  17. data/lib/active_storage_validations/form_builder.rb +43 -0
  18. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +8 -0
  19. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +4 -0
  20. data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +11 -1
  21. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +12 -0
  22. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +8 -0
  23. data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +8 -0
  24. data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +4 -0
  25. data/lib/active_storage_validations/matchers/pages_validator_matcher.rb +8 -0
  26. data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +8 -0
  27. data/lib/active_storage_validations/matchers/shared/asv_except_onable.rb +57 -0
  28. data/lib/active_storage_validations/matchers/shared/asv_spoofing_protectable.rb +44 -0
  29. data/lib/active_storage_validations/matchers/shared/asv_timeoutable.rb +32 -0
  30. data/lib/active_storage_validations/matchers.rb +9 -1
  31. data/lib/active_storage_validations/processable_file_validator.rb +60 -1
  32. data/lib/active_storage_validations/railtie.rb +6 -0
  33. data/lib/active_storage_validations/shared/asv_analyzable.rb +66 -8
  34. data/lib/active_storage_validations/shared/asv_attachable.rb +9 -10
  35. data/lib/active_storage_validations/shared/asv_commandable.rb +95 -0
  36. data/lib/active_storage_validations/shared/asv_optionable.rb +5 -1
  37. data/lib/active_storage_validations/version.rb +1 -1
  38. data/lib/active_storage_validations.rb +22 -0
  39. metadata +25 -58
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageValidations
4
+ # ActiveStorageValidations::ASVCommandable
5
+ #
6
+ # Runs external analyzer commands with an optional deadline and process-group
7
+ # kill. Used by analyzers that shell out (ffprobe, pdfinfo, file, identify).
8
+ module ASVCommandable
9
+ extend ActiveSupport::Concern
10
+
11
+ CommandResult = Struct.new(:stdout, :stderr, :status, :timed_out, keyword_init: true) do
12
+ def success?
13
+ !timed_out && status.respond_to?(:success?) && status.success?
14
+ end
15
+ end
16
+
17
+ TERM_GRACE_SECONDS = 1
18
+
19
+ private
20
+
21
+ def run_command(*argv, payload: nil)
22
+ timeout = timeout_in_seconds
23
+ stdout_reader, stdout_writer = IO.pipe
24
+ stderr_reader, stderr_writer = IO.pipe
25
+
26
+ pid = Process.spawn(*argv.map(&:to_s), out: stdout_writer, err: stderr_writer, pgroup: true)
27
+ stdout_writer.close
28
+ stderr_writer.close
29
+
30
+ # Drain pipes concurrently so a chatty child cannot fill the buffer and
31
+ # deadlock while we wait / kill it.
32
+ stdout_thread = Thread.new { stdout_reader.read }
33
+ stderr_thread = Thread.new { stderr_reader.read }
34
+
35
+ status, timed_out = wait_for_command(pid, timeout)
36
+ mark_timed_out!(payload, argv.first) if timed_out
37
+
38
+ CommandResult.new(
39
+ stdout: stdout_thread.value,
40
+ stderr: stderr_thread.value,
41
+ status: status,
42
+ timed_out: timed_out
43
+ )
44
+ ensure
45
+ close_pipe(stdout_reader)
46
+ close_pipe(stderr_reader)
47
+ close_pipe(stdout_writer)
48
+ close_pipe(stderr_writer)
49
+ end
50
+
51
+ # Waits via a reaper thread + Thread#join(timeout) so fast commands return
52
+ # as soon as they exit (no polling sleep). On timeout, TERM then always KILL
53
+ # the process group — KILL is unconditional because the direct child can exit
54
+ # on TERM while a grandchild forked after the group signal is still alive.
55
+ # The same waiter reaps so we never leave zombies.
56
+ def wait_for_command(pid, timeout)
57
+ return [ Process.wait2(pid)[1], false ] if timeout.nil?
58
+
59
+ waiter = Thread.new do
60
+ Process.wait2(pid)
61
+ rescue Errno::ECHILD
62
+ nil
63
+ end
64
+
65
+ if waiter.join(timeout)
66
+ return [ wait_status_from(waiter), false ]
67
+ end
68
+
69
+ signal_process_group(pid, :TERM)
70
+ waiter.join(TERM_GRACE_SECONDS)
71
+ signal_process_group(pid, :KILL)
72
+ waiter.join
73
+
74
+ [ wait_status_from(waiter), true ]
75
+ end
76
+
77
+ def wait_status_from(waiter)
78
+ waiter.value&.last
79
+ end
80
+
81
+ def signal_process_group(pid, signal)
82
+ Process.kill(signal, -pid)
83
+ rescue Errno::ESRCH
84
+ # Already exited; waiter will reap.
85
+ end
86
+
87
+ def close_pipe(io)
88
+ io&.close unless io.nil? || io.closed?
89
+ end
90
+
91
+ def monotonic_time
92
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
93
+ end
94
+ end
95
+ end
@@ -22,7 +22,11 @@ module ActiveStorageValidations
22
22
  when Array
23
23
  options.map { |option| flatten_options(record, option, available_checks) }
24
24
  else
25
- options.is_a?(Proc) ? options.call(record) : options
25
+ if options.is_a?(Proc)
26
+ options.arity == 0 ? options.call : options.call(record)
27
+ else
28
+ options
29
+ end
26
30
  end
27
31
  end
28
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageValidations
4
- VERSION = "3.0.5"
4
+ VERSION = "4.0.0"
5
5
  end
@@ -2,8 +2,29 @@
2
2
 
3
3
  require "active_model"
4
4
  require "active_support/concern"
5
+ require "active_support/core_ext/module/attribute_accessors"
6
+ require "active_support/core_ext/numeric/time"
7
+
8
+ module ActiveStorageValidations
9
+ # When true (default), FormBuilder#file_field automatically sets the HTML
10
+ # +accept+ attribute from +content_type+ validators. Can also be overridden
11
+ # per field with +infer_accept:+.
12
+ mattr_accessor :infer_file_field_accept, instance_accessor: false, default: true
13
+
14
+ # Maximum time allowed for external analyzer commands (ffprobe, pdfinfo, file,
15
+ # magika, MiniMagick, libvips). Set to +nil+ to disable. Per-validator +timeout:+
16
+ # overrides this value for the analysis that validator triggers.
17
+ mattr_accessor :command_timeout, instance_accessor: false, default: 10.seconds
18
+
19
+ def self.configure
20
+ yield self
21
+ end
22
+ end
5
23
 
6
24
  require "active_storage_validations/analyzer"
25
+ require "active_storage_validations/analyzer/content_type_analyzer"
26
+ require "active_storage_validations/analyzer/content_type_analyzer/file"
27
+ require "active_storage_validations/analyzer/content_type_analyzer/magika"
7
28
  require "active_storage_validations/analyzer/image_analyzer"
8
29
  require "active_storage_validations/analyzer/image_analyzer/image_magick"
9
30
  require "active_storage_validations/analyzer/image_analyzer/vips"
@@ -26,5 +47,6 @@ require "active_storage_validations/size_validator"
26
47
  require "active_storage_validations/total_size_validator"
27
48
  require "active_storage_validations/pages_validator"
28
49
 
50
+ require "active_storage_validations/form_builder"
29
51
  require "active_storage_validations/engine"
30
52
  require "active_storage_validations/railtie"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_storage_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.5
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-17 00:00:00.000000000 Z
11
+ date: 2026-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -16,56 +16,56 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 6.1.4
19
+ version: 7.0.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 6.1.4
26
+ version: 7.0.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activemodel
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 6.1.4
33
+ version: 7.0.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 6.1.4
40
+ version: 7.0.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: activestorage
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 6.1.4
47
+ version: 7.0.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 6.1.4
54
+ version: 7.0.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: activesupport
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 6.1.4
61
+ version: 7.0.1
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 6.1.4
68
+ version: 7.0.1
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: marcel
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -109,73 +109,33 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: 4.9.5
111
111
  - !ruby/object:Gem::Dependency
112
- name: minitest-focus
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '1.4'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '1.4'
125
- - !ruby/object:Gem::Dependency
126
- name: minitest-mock_expectations
112
+ name: pry
127
113
  requirement: !ruby/object:Gem::Requirement
128
114
  requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '1.2'
132
115
  - - ">="
133
116
  - !ruby/object:Gem::Version
134
- version: 1.2.0
117
+ version: '0'
135
118
  type: :development
136
119
  prerelease: false
137
120
  version_requirements: !ruby/object:Gem::Requirement
138
121
  requirements:
139
- - - "~>"
140
- - !ruby/object:Gem::Version
141
- version: '1.2'
142
122
  - - ">="
143
123
  - !ruby/object:Gem::Version
144
- version: 1.2.0
124
+ version: '0'
145
125
  - !ruby/object:Gem::Dependency
146
- name: minitest-stub_any_instance
126
+ name: rspec-rails
147
127
  requirement: !ruby/object:Gem::Requirement
148
128
  requirements:
149
129
  - - "~>"
150
130
  - !ruby/object:Gem::Version
151
- version: '1.0'
152
- - - ">="
153
- - !ruby/object:Gem::Version
154
- version: 1.0.3
131
+ version: '7.0'
155
132
  type: :development
156
133
  prerelease: false
157
134
  version_requirements: !ruby/object:Gem::Requirement
158
135
  requirements:
159
136
  - - "~>"
160
137
  - !ruby/object:Gem::Version
161
- version: '1.0'
162
- - - ">="
163
- - !ruby/object:Gem::Version
164
- version: 1.0.3
165
- - !ruby/object:Gem::Dependency
166
- name: pry
167
- requirement: !ruby/object:Gem::Requirement
168
- requirements:
169
- - - ">="
170
- - !ruby/object:Gem::Version
171
- version: '0'
172
- type: :development
173
- prerelease: false
174
- version_requirements: !ruby/object:Gem::Requirement
175
- requirements:
176
- - - ">="
177
- - !ruby/object:Gem::Version
178
- version: '0'
138
+ version: '7.0'
179
139
  - !ruby/object:Gem::Dependency
180
140
  name: ruby-vips
181
141
  requirement: !ruby/object:Gem::Requirement
@@ -232,7 +192,7 @@ dependencies:
232
192
  - - ">="
233
193
  - !ruby/object:Gem::Version
234
194
  version: '3'
235
- description: Validations for Active Storage (presence)
195
+ description: Validations for Active Storage (presence, size, content type, ...)
236
196
  email:
237
197
  - igorkasyanchuk@gmail.com
238
198
  executables: []
@@ -263,6 +223,8 @@ files:
263
223
  - lib/active_storage_validations/analyzer.rb
264
224
  - lib/active_storage_validations/analyzer/audio_analyzer.rb
265
225
  - lib/active_storage_validations/analyzer/content_type_analyzer.rb
226
+ - lib/active_storage_validations/analyzer/content_type_analyzer/file.rb
227
+ - lib/active_storage_validations/analyzer/content_type_analyzer/magika.rb
266
228
  - lib/active_storage_validations/analyzer/image_analyzer.rb
267
229
  - lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb
268
230
  - lib/active_storage_validations/analyzer/image_analyzer/vips.rb
@@ -279,6 +241,7 @@ files:
279
241
  - lib/active_storage_validations/engine.rb
280
242
  - lib/active_storage_validations/extensors/asv_blob_metadatable.rb
281
243
  - lib/active_storage_validations/extensors/asv_marcelable.rb
244
+ - lib/active_storage_validations/form_builder.rb
282
245
  - lib/active_storage_validations/limit_validator.rb
283
246
  - lib/active_storage_validations/matchers.rb
284
247
  - lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb
@@ -294,8 +257,11 @@ files:
294
257
  - lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb
295
258
  - lib/active_storage_validations/matchers/shared/asv_attachable.rb
296
259
  - lib/active_storage_validations/matchers/shared/asv_contextable.rb
260
+ - lib/active_storage_validations/matchers/shared/asv_except_onable.rb
297
261
  - lib/active_storage_validations/matchers/shared/asv_messageable.rb
298
262
  - lib/active_storage_validations/matchers/shared/asv_rspecable.rb
263
+ - lib/active_storage_validations/matchers/shared/asv_spoofing_protectable.rb
264
+ - lib/active_storage_validations/matchers/shared/asv_timeoutable.rb
299
265
  - lib/active_storage_validations/matchers/shared/asv_validatable.rb
300
266
  - lib/active_storage_validations/matchers/size_validator_matcher.rb
301
267
  - lib/active_storage_validations/matchers/total_size_validator_matcher.rb
@@ -305,6 +271,7 @@ files:
305
271
  - lib/active_storage_validations/shared/asv_active_storageable.rb
306
272
  - lib/active_storage_validations/shared/asv_analyzable.rb
307
273
  - lib/active_storage_validations/shared/asv_attachable.rb
274
+ - lib/active_storage_validations/shared/asv_commandable.rb
308
275
  - lib/active_storage_validations/shared/asv_errorable.rb
309
276
  - lib/active_storage_validations/shared/asv_loggable.rb
310
277
  - lib/active_storage_validations/shared/asv_optionable.rb
@@ -328,7 +295,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
328
295
  requirements:
329
296
  - - ">="
330
297
  - !ruby/object:Gem::Version
331
- version: 2.5.0
298
+ version: 3.3.0
332
299
  required_rubygems_version: !ruby/object:Gem::Requirement
333
300
  requirements:
334
301
  - - ">="