active_storage_validations 3.0.6 → 4.1.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +227 -116
  4. data/Rakefile +13 -15
  5. data/config/locales/da.yml +2 -0
  6. data/config/locales/de.yml +2 -0
  7. data/config/locales/en-GB.yml +2 -0
  8. data/config/locales/en.yml +2 -0
  9. data/config/locales/es.yml +2 -0
  10. data/config/locales/fr.yml +2 -0
  11. data/config/locales/it.yml +2 -0
  12. data/config/locales/ja.yml +2 -0
  13. data/config/locales/nl.yml +2 -0
  14. data/config/locales/pl.yml +2 -0
  15. data/config/locales/pt-BR.yml +2 -0
  16. data/config/locales/ru.yml +2 -0
  17. data/config/locales/sv.yml +2 -0
  18. data/config/locales/tr.yml +2 -0
  19. data/config/locales/uk.yml +2 -0
  20. data/config/locales/vi.yml +2 -0
  21. data/config/locales/zh-CN.yml +2 -0
  22. data/lib/active_storage_validations/analyzer/audio_analyzer.rb +0 -1
  23. data/lib/active_storage_validations/analyzer/content_type_analyzer/file.rb +31 -0
  24. data/lib/active_storage_validations/analyzer/content_type_analyzer/magika.rb +44 -0
  25. data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +15 -24
  26. data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +41 -7
  27. data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +36 -10
  28. data/lib/active_storage_validations/analyzer/pdf_analyzer.rb +3 -9
  29. data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +5 -4
  30. data/lib/active_storage_validations/analyzer/video_analyzer.rb +0 -1
  31. data/lib/active_storage_validations/analyzer.rb +35 -3
  32. data/lib/active_storage_validations/content_type_validator.rb +24 -6
  33. data/lib/active_storage_validations/dimension_validator.rb +4 -2
  34. data/lib/active_storage_validations/form_builder.rb +43 -0
  35. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +8 -0
  36. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +4 -0
  37. data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +11 -1
  38. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +12 -0
  39. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +8 -0
  40. data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +8 -0
  41. data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +4 -0
  42. data/lib/active_storage_validations/matchers/pages_validator_matcher.rb +8 -0
  43. data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +8 -0
  44. data/lib/active_storage_validations/matchers/shared/asv_except_onable.rb +57 -0
  45. data/lib/active_storage_validations/matchers/shared/asv_spoofing_protectable.rb +44 -0
  46. data/lib/active_storage_validations/matchers/shared/asv_timeoutable.rb +32 -0
  47. data/lib/active_storage_validations/matchers/with_audio_validator_matcher.rb +114 -0
  48. data/lib/active_storage_validations/matchers.rb +10 -1
  49. data/lib/active_storage_validations/railtie.rb +6 -0
  50. data/lib/active_storage_validations/shared/asv_analyzable.rb +66 -8
  51. data/lib/active_storage_validations/shared/asv_attachable.rb +2 -9
  52. data/lib/active_storage_validations/shared/asv_commandable.rb +95 -0
  53. data/lib/active_storage_validations/shared/asv_optionable.rb +5 -1
  54. data/lib/active_storage_validations/version.rb +1 -1
  55. data/lib/active_storage_validations/with_audio_validator.rb +37 -0
  56. data/lib/active_storage_validations.rb +23 -0
  57. metadata +27 -72
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "shared/asv_active_storageable"
4
+ require_relative "shared/asv_allow_blankable"
5
+ require_relative "shared/asv_attachable"
6
+ require_relative "shared/asv_contextable"
7
+ require_relative "shared/asv_except_onable"
8
+ require_relative "shared/asv_messageable"
9
+ require_relative "shared/asv_rspecable"
10
+ require_relative "shared/asv_timeoutable"
11
+ require_relative "shared/asv_validatable"
12
+
13
+ module ActiveStorageValidations
14
+ module Matchers
15
+ def validate_with_audio_of(attribute_name)
16
+ WithAudioValidatorMatcher.new(attribute_name)
17
+ end
18
+
19
+ class WithAudioValidatorMatcher
20
+ include ASVActiveStorageable
21
+ include ASVAllowBlankable
22
+ include ASVAttachable
23
+ include ASVContextable
24
+ include ASVExceptOnable
25
+ include ASVMessageable
26
+ include ASVRspecable
27
+ include ASVTimeoutable
28
+ include ASVValidatable
29
+
30
+ def initialize(attribute_name)
31
+ initialize_allow_blankable
32
+ initialize_contextable
33
+ initialize_except_onable
34
+ initialize_messageable
35
+ initialize_rspecable
36
+ initialize_timeoutable
37
+ @attribute_name = attribute_name
38
+ @expected_audio = true
39
+ end
40
+
41
+ def description
42
+ "validate that :#{@attribute_name} #{audio_expectation}"
43
+ end
44
+
45
+ def failure_message
46
+ "is expected to validate that :#{@attribute_name} #{audio_expectation}"
47
+ end
48
+
49
+ def without_audio
50
+ @expected_audio = false
51
+ self
52
+ end
53
+
54
+ def matches?(subject)
55
+ @subject = subject.is_a?(Class) ? subject.new : subject
56
+
57
+ is_a_valid_active_storage_attribute? &&
58
+ is_context_valid? &&
59
+ is_except_on_valid? &&
60
+ is_allowing_blank? &&
61
+ is_timeout_valid? &&
62
+ is_custom_message_valid? &&
63
+ is_valid_with_expected_audio? &&
64
+ is_invalid_with_unexpected_audio?
65
+ end
66
+
67
+ private
68
+
69
+ def audio_expectation
70
+ @expected_audio ? "has an audio track" : "has no audio track"
71
+ end
72
+
73
+ def is_valid_with_expected_audio?
74
+ validation_passes_with_audio?(@expected_audio)
75
+ end
76
+
77
+ def is_invalid_with_unexpected_audio?
78
+ !validation_passes_with_audio?(!@expected_audio)
79
+ end
80
+
81
+ def is_custom_message_valid?
82
+ return true unless @custom_message
83
+
84
+ with_audio_metadata(false) do
85
+ attach_file(video_file)
86
+ validate
87
+ detach_file
88
+ has_an_error_message_which_is_custom_message?
89
+ end
90
+ end
91
+
92
+ def validation_passes_with_audio?(audio)
93
+ with_audio_metadata(audio) do
94
+ attach_file(video_file)
95
+ validate
96
+ detach_file
97
+ is_valid?
98
+ end
99
+ end
100
+
101
+ def with_audio_metadata(audio, &block)
102
+ Matchers.mock_metadata(io, { audio: audio }, &block)
103
+ end
104
+
105
+ def video_file
106
+ {
107
+ io: io,
108
+ filename: "test.mp4",
109
+ content_type: "video/mp4"
110
+ }
111
+ end
112
+ end
113
+ end
114
+ end
@@ -7,6 +7,7 @@ require "active_storage_validations/matchers/limit_validator_matcher"
7
7
  require "active_storage_validations/matchers/content_type_validator_matcher"
8
8
  require "active_storage_validations/matchers/dimension_validator_matcher"
9
9
  require "active_storage_validations/matchers/duration_validator_matcher"
10
+ require "active_storage_validations/matchers/with_audio_validator_matcher"
10
11
  require "active_storage_validations/matchers/size_validator_matcher"
11
12
  require "active_storage_validations/matchers/total_size_validator_matcher"
12
13
  require "active_storage_validations/matchers/pages_validator_matcher"
@@ -29,7 +30,15 @@ module ActiveStorageValidations
29
30
 
30
31
  def self.mock_metadata(attachment, metadata = {})
31
32
  asv_metadata_available_keys = { width: nil, height: nil, duration: nil, content_type: nil }
32
- mock = Struct.new(:metadata).new(asv_metadata_available_keys.merge(metadata)) # ensure all keys are present, and it does not raise while trying to access them
33
+ merged = asv_metadata_available_keys.merge(metadata)
34
+ # Stubbing Analyzer.new also covers content-type sniffers (File / Magika),
35
+ # which call #content_type — not #metadata. Default to the dummy file type
36
+ # so stacked validators with spoofing_protection still pass under the mock.
37
+ detected_content_type = merged[:content_type].presence || "image/png"
38
+ mock = Struct.new(:metadata, :content_type).new(
39
+ merged,
40
+ { content_type: detected_content_type, content_type_backend: "file" }
41
+ )
33
42
 
34
43
  stub_method(ActiveStorageValidations::Analyzer, :new, mock) do
35
44
  yield
@@ -11,6 +11,12 @@ module ActiveStorageValidations
11
11
  end
12
12
  end
13
13
 
14
+ initializer "active_storage_validations.form_builder" do
15
+ ActiveSupport.on_load(:action_view) do
16
+ ActionView::Helpers::FormBuilder.prepend(ActiveStorageValidations::FormBuilder)
17
+ end
18
+ end
19
+
14
20
  initializer "active_storage_validations.extend_active_storage_blob" do
15
21
  ActiveSupport.on_load(:active_storage_blob) do
16
22
  include ActiveStorageValidations::ASVBlobMetadatable
@@ -17,15 +17,62 @@ module ActiveStorageValidations
17
17
  # attachable with the corresponding analyzer and set the metadata in the
18
18
  # blob.
19
19
  def metadata_for(blob, attachable, metadata_keys)
20
+ if content_type_metadata_keys?(metadata_keys)
21
+ return content_type_metadata_for(blob, attachable)
22
+ end
23
+
20
24
  return blob.active_storage_validations_metadata if blob_has_asv_metadata?(blob, metadata_keys)
21
25
 
26
+ new_metadata = generate_metadata_for(attachable, metadata_keys) || {}
27
+ blob.merge_into_active_storage_validations_metadata(new_metadata)
28
+ blob.save!
29
+
30
+ blob.active_storage_validations_metadata
31
+ end
32
+
33
+ def content_type_metadata_keys?(metadata_keys)
34
+ metadata_keys == ActiveStorageValidations::ContentTypeValidator::METADATA_KEYS
35
+ end
36
+
37
+ # Same cache semantics as +metadata_for+, plus backend matching.
38
+ # Legacy blobs that only have +asv_content_type+ (no backend key) are treated
39
+ # as +:file+ — so existing file-backend analyses keep hitting the cache and
40
+ # are not re-analyzed / rewritten.
41
+ def content_type_metadata_for(blob, attachable)
42
+ backend = spoofing_protection_backend
43
+ cached = blob.active_storage_validations_metadata
44
+ return cached if content_type_cache_hit?(cached, backend)
45
+
46
+ metadata_keys = ActiveStorageValidations::ContentTypeValidator::METADATA_KEYS
22
47
  new_metadata = generate_metadata_for(attachable, metadata_keys)
48
+ return failed_content_type_metadata(backend) if content_type_analysis_failed?(new_metadata)
49
+
23
50
  blob.merge_into_active_storage_validations_metadata(new_metadata)
24
51
  blob.save!
25
52
 
26
53
  blob.active_storage_validations_metadata
27
54
  end
28
55
 
56
+ def content_type_analysis_failed?(new_metadata)
57
+ new_metadata.blank? || new_metadata[:content_type].blank?
58
+ end
59
+
60
+ # Do not return / persist another backend's cached type after a failed sniff
61
+ # (timeout, unsupported file, invalid Magika JSON, …). Fail closed for this
62
+ # request; leave blob metadata unchanged so a later attempt can retry.
63
+ def failed_content_type_metadata(backend)
64
+ { content_type: nil, content_type_backend: backend.to_s }
65
+ end
66
+
67
+ def content_type_cache_hit?(cached, backend)
68
+ return false unless cached.present? && cached[:content_type].present?
69
+
70
+ # Missing / blank backend => legacy file analysis (pre-Magika support).
71
+ # Those blobs keep hitting the cache for +:file+ and are not rewritten.
72
+ stored_backend = (cached[:content_type_backend].presence || "file").to_s
73
+ stored_backend == backend.to_s
74
+ end
75
+
29
76
  def blob_has_asv_metadata?(blob, metadata_keys)
30
77
  return false unless blob.active_storage_validations_metadata.present?
31
78
 
@@ -33,7 +80,7 @@ module ActiveStorageValidations
33
80
  end
34
81
 
35
82
  def generate_metadata_for(attachable, metadata_keys)
36
- if metadata_keys == ActiveStorageValidations::ContentTypeValidator::METADATA_KEYS
83
+ if content_type_metadata_keys?(metadata_keys)
37
84
  content_type_analyzer_for(attachable).content_type
38
85
  else
39
86
  metadata_analyzer_for(attachable).metadata
@@ -52,15 +99,15 @@ module ActiveStorageValidations
52
99
  end
53
100
 
54
101
  def pdf_analyzer_for(attachable)
55
- ActiveStorageValidations::Analyzer::PdfAnalyzer.new(attachable)
102
+ ActiveStorageValidations::Analyzer::PdfAnalyzer.new(attachable, **analyzer_timeout_options)
56
103
  end
57
104
 
58
105
  def image_analyzer_for(attachable)
59
106
  case image_processor
60
107
  when :mini_magick
61
- ActiveStorageValidations::Analyzer::ImageAnalyzer::ImageMagick.new(attachable)
108
+ ActiveStorageValidations::Analyzer::ImageAnalyzer::ImageMagick.new(attachable, **analyzer_timeout_options)
62
109
  when :vips
63
- ActiveStorageValidations::Analyzer::ImageAnalyzer::Vips.new(attachable)
110
+ ActiveStorageValidations::Analyzer::ImageAnalyzer::Vips.new(attachable, **analyzer_timeout_options)
64
111
  end
65
112
  end
66
113
 
@@ -71,19 +118,30 @@ module ActiveStorageValidations
71
118
  end
72
119
 
73
120
  def video_analyzer_for(attachable)
74
- ActiveStorageValidations::Analyzer::VideoAnalyzer.new(attachable)
121
+ ActiveStorageValidations::Analyzer::VideoAnalyzer.new(attachable, **analyzer_timeout_options)
75
122
  end
76
123
 
77
124
  def audio_analyzer_for(attachable)
78
- ActiveStorageValidations::Analyzer::AudioAnalyzer.new(attachable)
125
+ ActiveStorageValidations::Analyzer::AudioAnalyzer.new(attachable, **analyzer_timeout_options)
79
126
  end
80
127
 
81
128
  def fallback_analyzer_for(attachable)
82
- ActiveStorageValidations::Analyzer::NullAnalyzer.new(attachable)
129
+ ActiveStorageValidations::Analyzer::NullAnalyzer.new(attachable, **analyzer_timeout_options)
83
130
  end
84
131
 
85
132
  def content_type_analyzer_for(attachable)
86
- ActiveStorageValidations::Analyzer::ContentTypeAnalyzer.new(attachable)
133
+ case spoofing_protection_backend
134
+ when :magika
135
+ ActiveStorageValidations::Analyzer::ContentTypeAnalyzer::Magika.new(attachable, **analyzer_timeout_options)
136
+ else
137
+ ActiveStorageValidations::Analyzer::ContentTypeAnalyzer::File.new(attachable, **analyzer_timeout_options)
138
+ end
139
+ end
140
+
141
+ # Passes raw validator +timeout:+ through to analyzers. Kept out of
142
+ # AVAILABLE_CHECKS so ASVOptionable does not flatten it as a comparison bound.
143
+ def analyzer_timeout_options
144
+ options.key?(:timeout) ? { timeout: options[:timeout] } : {}
87
145
  end
88
146
  end
89
147
  end
@@ -126,14 +126,14 @@ module ActiveStorageValidations
126
126
  def attachable_io(attachable, max_byte_size: nil)
127
127
  io = case attachable
128
128
  when ActiveStorage::Blob
129
- (max_byte_size && supports_blob_download_chunk?) ? attachable.download_chunk(0...max_byte_size) : attachable.download
129
+ max_byte_size ? attachable.download_chunk(0...max_byte_size) : attachable.download
130
130
  when ActionDispatch::Http::UploadedFile
131
131
  max_byte_size ? attachable.read(max_byte_size) : attachable.read
132
132
  when Rack::Test::UploadedFile
133
133
  max_byte_size ? attachable.read(max_byte_size) : attachable.read
134
134
  when String
135
135
  blob = ActiveStorage::Blob.find_signed!(attachable)
136
- (max_byte_size && supports_blob_download_chunk?) ? blob.download_chunk(0...max_byte_size) : blob.download
136
+ max_byte_size ? blob.download_chunk(0...max_byte_size) : blob.download
137
137
  when Hash
138
138
  max_byte_size ? attachable[:io].read(max_byte_size) : attachable[:io].read
139
139
  when File
@@ -214,13 +214,6 @@ module ActiveStorageValidations
214
214
  end
215
215
  alias :supports_pathname_attachment? :supports_file_attachment?
216
216
 
217
- # Check if the current Rails version supports ActiveStorage::Blob#download_chunk
218
- #
219
- # https://github.com/rails/rails/blob/7-0-stable/activestorage/CHANGELOG.md#rails-700alpha1-september-15-2021
220
- def supports_blob_download_chunk?
221
- Rails.gem_version >= Gem::Version.new("7.0.0.alpha1")
222
- end
223
-
224
217
  # Retrieve the content_type from the file name only
225
218
  def marcel_content_type_from_filename(attachable)
226
219
  Marcel::MimeType.for(name: attachable_filename(attachable).to_s)
@@ -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.6"
4
+ VERSION = "4.1.0"
5
5
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "shared/asv_active_storageable"
4
+ require_relative "shared/asv_analyzable"
5
+ require_relative "shared/asv_attachable"
6
+ require_relative "shared/asv_errorable"
7
+ require_relative "shared/asv_symbolizable"
8
+
9
+ module ActiveStorageValidations
10
+ class WithAudioValidator < ActiveModel::EachValidator # :nodoc
11
+ include ASVActiveStorageable
12
+ include ASVAnalyzable
13
+ include ASVAttachable
14
+ include ASVErrorable
15
+ include ASVSymbolizable
16
+
17
+ ERROR_TYPES = %i[audio_missing audio_present].freeze
18
+ METADATA_KEYS = %i[audio].freeze
19
+
20
+ def validate_each(record, attribute, _value)
21
+ return if no_attachments?(record, attribute)
22
+
23
+ validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
24
+ end
25
+
26
+ private
27
+
28
+ def is_valid?(record, attribute, attachable, metadata)
29
+ expected_audio = options.fetch(:with, true)
30
+ return if metadata&.fetch(:audio, false) == expected_audio
31
+
32
+ errors_options = initialize_error_options(options, attachable)
33
+ error_type = expected_audio ? :audio_missing : :audio_present
34
+ add_error(record, attribute, error_type, **errors_options)
35
+ end
36
+ end
37
+ 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"
@@ -20,11 +41,13 @@ require "active_storage_validations/content_type_validator"
20
41
  require "active_storage_validations/limit_validator"
21
42
  require "active_storage_validations/dimension_validator"
22
43
  require "active_storage_validations/duration_validator"
44
+ require "active_storage_validations/with_audio_validator"
23
45
  require "active_storage_validations/aspect_ratio_validator"
24
46
  require "active_storage_validations/processable_file_validator"
25
47
  require "active_storage_validations/size_validator"
26
48
  require "active_storage_validations/total_size_validator"
27
49
  require "active_storage_validations/pages_validator"
28
50
 
51
+ require "active_storage_validations/form_builder"
29
52
  require "active_storage_validations/engine"
30
53
  require "active_storage_validations/railtie"