active_storage_validations 1.4.0 → 2.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.
- checksums.yaml +4 -4
- data/README.md +616 -213
- data/config/locales/da.yml +50 -30
- data/config/locales/de.yml +50 -30
- data/config/locales/en.yml +50 -30
- data/config/locales/es.yml +50 -30
- data/config/locales/fr.yml +50 -30
- data/config/locales/it.yml +50 -30
- data/config/locales/ja.yml +50 -30
- data/config/locales/nl.yml +50 -30
- data/config/locales/pl.yml +50 -30
- data/config/locales/pt-BR.yml +50 -30
- data/config/locales/ru.yml +50 -30
- data/config/locales/sv.yml +50 -30
- data/config/locales/tr.yml +50 -30
- data/config/locales/uk.yml +50 -30
- data/config/locales/vi.yml +50 -30
- data/config/locales/zh-CN.yml +50 -30
- data/lib/active_storage_validations/analyzer/audio_analyzer.rb +58 -0
- data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +60 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +4 -4
- data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +11 -12
- data/lib/active_storage_validations/analyzer/image_analyzer.rb +9 -53
- data/lib/active_storage_validations/analyzer/null_analyzer.rb +2 -2
- data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +61 -0
- data/lib/active_storage_validations/analyzer/video_analyzer.rb +130 -0
- data/lib/active_storage_validations/analyzer.rb +54 -1
- data/lib/active_storage_validations/aspect_ratio_validator.rb +15 -11
- data/lib/active_storage_validations/{base_size_validator.rb → base_comparison_validator.rb} +18 -16
- data/lib/active_storage_validations/content_type_validator.rb +49 -21
- data/lib/active_storage_validations/dimension_validator.rb +20 -19
- data/lib/active_storage_validations/duration_validator.rb +55 -0
- data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +24 -0
- data/lib/active_storage_validations/{marcel_extensor.rb → extensors/asv_marcelable.rb} +3 -0
- data/lib/active_storage_validations/limit_validator.rb +14 -2
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +1 -1
- data/lib/active_storage_validations/matchers/{base_size_validator_matcher.rb → base_comparison_validator_matcher.rb} +31 -25
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +7 -3
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +1 -1
- data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +39 -0
- data/lib/active_storage_validations/matchers/{processable_image_validator_matcher.rb → processable_file_validator_matcher.rb} +5 -5
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +18 -2
- data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +18 -2
- data/lib/active_storage_validations/matchers.rb +4 -3
- data/lib/active_storage_validations/{processable_image_validator.rb → processable_file_validator.rb} +4 -3
- data/lib/active_storage_validations/railtie.rb +5 -0
- data/lib/active_storage_validations/shared/asv_analyzable.rb +38 -3
- data/lib/active_storage_validations/shared/asv_attachable.rb +36 -15
- data/lib/active_storage_validations/size_validator.rb +11 -3
- data/lib/active_storage_validations/total_size_validator.rb +9 -3
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +7 -3
- metadata +14 -8
- data/lib/active_storage_validations/content_type_spoof_detector.rb +0 -96
@@ -18,18 +18,19 @@ module ActiveStorageValidations
|
|
18
18
|
|
19
19
|
AVAILABLE_CHECKS = %i[width height min max].freeze
|
20
20
|
ERROR_TYPES = %i[
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
21
|
+
dimension_min_not_included_in
|
22
|
+
dimension_max_not_included_in
|
23
|
+
dimension_width_not_included_in
|
24
|
+
dimension_height_not_included_in
|
25
|
+
dimension_width_not_greater_than_or_equal_to
|
26
|
+
dimension_height_not_greater_than_or_equal_to
|
27
|
+
dimension_width_not_less_than_or_equal_to
|
28
|
+
dimension_height_not_less_than_or_equal_to
|
29
|
+
dimension_width_not_equal_to
|
30
|
+
dimension_height_not_equal_to
|
31
|
+
media_metadata_missing
|
32
32
|
].freeze
|
33
|
+
METADATA_KEYS = %i[width height].freeze
|
33
34
|
|
34
35
|
def check_validity!
|
35
36
|
unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
@@ -40,7 +41,7 @@ module ActiveStorageValidations
|
|
40
41
|
def validate_each(record, attribute, _value)
|
41
42
|
return if no_attachments?(record, attribute)
|
42
43
|
|
43
|
-
validate_changed_files_from_metadata(record, attribute)
|
44
|
+
validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
|
44
45
|
end
|
45
46
|
|
46
47
|
private
|
@@ -51,7 +52,7 @@ module ActiveStorageValidations
|
|
51
52
|
|
52
53
|
# Validation fails unless file metadata contains valid width and height.
|
53
54
|
if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
|
54
|
-
add_error(record, attribute, :
|
55
|
+
add_error(record, attribute, :media_metadata_missing, **errors_options)
|
55
56
|
return false
|
56
57
|
end
|
57
58
|
|
@@ -64,7 +65,7 @@ module ActiveStorageValidations
|
|
64
65
|
errors_options[:width] = flat_options[:width][:min]
|
65
66
|
errors_options[:height] = flat_options[:height][:min]
|
66
67
|
|
67
|
-
add_error(record, attribute, :
|
68
|
+
add_error(record, attribute, :dimension_min_not_included_in, **errors_options)
|
68
69
|
return false
|
69
70
|
end
|
70
71
|
if flat_options[:max] && (
|
@@ -74,7 +75,7 @@ module ActiveStorageValidations
|
|
74
75
|
errors_options[:width] = flat_options[:width][:max]
|
75
76
|
errors_options[:height] = flat_options[:height][:max]
|
76
77
|
|
77
|
-
add_error(record, attribute, :
|
78
|
+
add_error(record, attribute, :dimension_max_not_included_in, **errors_options)
|
78
79
|
return false
|
79
80
|
end
|
80
81
|
|
@@ -86,7 +87,7 @@ module ActiveStorageValidations
|
|
86
87
|
next unless flat_options[length]
|
87
88
|
if flat_options[length].is_a?(Hash)
|
88
89
|
if flat_options[length][:in] && (metadata[length] < flat_options[length][:min] || metadata[length] > flat_options[length][:max])
|
89
|
-
error_type = :"dimension_#{length}
|
90
|
+
error_type = :"dimension_#{length}_not_included_in"
|
90
91
|
errors_options[:min] = flat_options[length][:min]
|
91
92
|
errors_options[:max] = flat_options[length][:max]
|
92
93
|
|
@@ -94,13 +95,13 @@ module ActiveStorageValidations
|
|
94
95
|
width_or_height_invalid = true
|
95
96
|
else
|
96
97
|
if flat_options[length][:min] && metadata[length] < flat_options[length][:min]
|
97
|
-
error_type = :"dimension_#{length}
|
98
|
+
error_type = :"dimension_#{length}_not_greater_than_or_equal_to"
|
98
99
|
errors_options[:length] = flat_options[length][:min]
|
99
100
|
|
100
101
|
add_error(record, attribute, error_type, **errors_options)
|
101
102
|
width_or_height_invalid = true
|
102
103
|
elsif flat_options[length][:max] && metadata[length] > flat_options[length][:max]
|
103
|
-
error_type = :"dimension_#{length}
|
104
|
+
error_type = :"dimension_#{length}_not_less_than_or_equal_to"
|
104
105
|
errors_options[:length] = flat_options[length][:max]
|
105
106
|
|
106
107
|
add_error(record, attribute, error_type, **errors_options)
|
@@ -109,7 +110,7 @@ module ActiveStorageValidations
|
|
109
110
|
end
|
110
111
|
else
|
111
112
|
if metadata[length] != flat_options[length]
|
112
|
-
error_type = :"dimension_#{length}
|
113
|
+
error_type = :"dimension_#{length}_not_equal_to"
|
113
114
|
errors_options[:length] = flat_options[length]
|
114
115
|
|
115
116
|
add_error(record, attribute, error_type, **errors_options)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_comparison_validator'
|
4
|
+
|
5
|
+
module ActiveStorageValidations
|
6
|
+
class DurationValidator < BaseComparisonValidator
|
7
|
+
include ASVAnalyzable
|
8
|
+
include ASVAttachable
|
9
|
+
|
10
|
+
ERROR_TYPES = %i[
|
11
|
+
duration_not_less_than
|
12
|
+
duration_not_less_than_or_equal_to
|
13
|
+
duration_not_greater_than
|
14
|
+
duration_not_greater_than_or_equal_to
|
15
|
+
duration_not_between
|
16
|
+
].freeze
|
17
|
+
METADATA_KEYS = %i[duration].freeze
|
18
|
+
|
19
|
+
def validate_each(record, attribute, _value)
|
20
|
+
return if no_attachments?(record, attribute)
|
21
|
+
|
22
|
+
flat_options = set_flat_options(record)
|
23
|
+
|
24
|
+
attachables_and_blobs(record, attribute).each do |attachable, blob|
|
25
|
+
duration = metadata_for(blob, attachable, METADATA_KEYS)&.fetch(:duration, nil)
|
26
|
+
|
27
|
+
if duration.to_i <= 0
|
28
|
+
errors_options = initialize_error_options(options, attachable)
|
29
|
+
add_error(record, attribute, :media_metadata_missing, **errors_options)
|
30
|
+
next
|
31
|
+
end
|
32
|
+
|
33
|
+
next if is_valid?(duration, flat_options)
|
34
|
+
|
35
|
+
errors_options = initialize_error_options(options, attachable)
|
36
|
+
populate_error_options(errors_options, flat_options)
|
37
|
+
errors_options[:duration] = format_bound_value(duration)
|
38
|
+
|
39
|
+
keys = AVAILABLE_CHECKS & flat_options.keys
|
40
|
+
error_type = "duration_not_#{keys.first}".to_sym
|
41
|
+
|
42
|
+
add_error(record, attribute, error_type, **errors_options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def format_bound_value(value)
|
49
|
+
return nil unless value
|
50
|
+
|
51
|
+
custom_value = value == value.to_i ? value.to_i : value
|
52
|
+
ActiveSupport::Duration.build(custom_value).inspect
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module ASVBlobMetadatable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
def active_storage_validations_metadata
|
9
|
+
metadata.dig('custom', 'active_storage_validations') || {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def active_storage_validations_metadata=(value)
|
13
|
+
metadata['custom'] ||= {}
|
14
|
+
metadata['custom']['active_storage_validations'] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def merge_into_active_storage_validations_metadata(new_data)
|
18
|
+
metadata['custom'] ||= {}
|
19
|
+
metadata['custom']['active_storage_validations'] ||= {}
|
20
|
+
metadata['custom']['active_storage_validations'].merge!(new_data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -7,3 +7,6 @@ Marcel::MimeType.extend "audio/x-hx-aac-adts", parents: %(audio/x-aac)
|
|
7
7
|
Marcel::MimeType.extend "audio/x-m4a", parents: %(audio/mp4)
|
8
8
|
Marcel::MimeType.extend "text/xml", parents: %(application/xml) # alias
|
9
9
|
Marcel::MimeType.extend "video/theora", parents: %(video/ogg)
|
10
|
+
|
11
|
+
# Add empty content type
|
12
|
+
Marcel::MimeType.extend "inode/x-empty", extensions: %w(empty)
|
@@ -15,6 +15,8 @@ module ActiveStorageValidations
|
|
15
15
|
AVAILABLE_CHECKS = %i[max min].freeze
|
16
16
|
ERROR_TYPES = %i[
|
17
17
|
limit_out_of_range
|
18
|
+
limit_min_not_reached
|
19
|
+
limit_max_exceeded
|
18
20
|
].freeze
|
19
21
|
|
20
22
|
def check_validity!
|
@@ -25,13 +27,23 @@ module ActiveStorageValidations
|
|
25
27
|
def validate_each(record, attribute, _value)
|
26
28
|
files = attached_files(record, attribute).reject(&:blank?)
|
27
29
|
flat_options = set_flat_options(record)
|
30
|
+
count = files.count
|
28
31
|
|
29
|
-
return if files_count_valid?(
|
32
|
+
return if files_count_valid?(count, flat_options)
|
30
33
|
|
31
34
|
errors_options = initialize_error_options(options)
|
32
35
|
errors_options[:min] = flat_options[:min]
|
33
36
|
errors_options[:max] = flat_options[:max]
|
34
|
-
|
37
|
+
errors_options[:count] = count
|
38
|
+
error_type = if flat_options[:min] && flat_options[:max]
|
39
|
+
:limit_out_of_range
|
40
|
+
elsif flat_options[:min] && count < flat_options[:min]
|
41
|
+
:limit_min_not_reached
|
42
|
+
else
|
43
|
+
:limit_max_exceeded
|
44
|
+
end
|
45
|
+
|
46
|
+
add_error(record, attribute, error_type, **errors_options)
|
35
47
|
end
|
36
48
|
|
37
49
|
private
|
@@ -13,8 +13,8 @@ require_relative 'shared/asv_validatable'
|
|
13
13
|
|
14
14
|
module ActiveStorageValidations
|
15
15
|
module Matchers
|
16
|
-
class
|
17
|
-
#
|
16
|
+
class BaseComparisonValidatorMatcher
|
17
|
+
# BaseComparisonValidatorMatcher is an abstract class and shouldn't be instantiated directly.
|
18
18
|
|
19
19
|
include ASVActiveStorageable
|
20
20
|
include ASVAllowBlankable
|
@@ -33,23 +33,23 @@ module ActiveStorageValidations
|
|
33
33
|
@min = @max = nil
|
34
34
|
end
|
35
35
|
|
36
|
-
def less_than(
|
37
|
-
@max =
|
36
|
+
def less_than(value)
|
37
|
+
@max = value - smallest_measurement
|
38
38
|
self
|
39
39
|
end
|
40
40
|
|
41
|
-
def less_than_or_equal_to(
|
42
|
-
@max =
|
41
|
+
def less_than_or_equal_to(value)
|
42
|
+
@max = value
|
43
43
|
self
|
44
44
|
end
|
45
45
|
|
46
|
-
def greater_than(
|
47
|
-
@min =
|
46
|
+
def greater_than(value)
|
47
|
+
@min = value + smallest_measurement
|
48
48
|
self
|
49
49
|
end
|
50
50
|
|
51
|
-
def greater_than_or_equal_to(
|
52
|
-
@min =
|
51
|
+
def greater_than_or_equal_to(value)
|
52
|
+
@min = value
|
53
53
|
self
|
54
54
|
end
|
55
55
|
|
@@ -78,45 +78,53 @@ module ActiveStorageValidations
|
|
78
78
|
|
79
79
|
message << " but there seem to have issues with the matcher methods you used, since:"
|
80
80
|
@failure_message_artefacts.each do |error_case|
|
81
|
-
message << " validation failed when provided with a #{error_case[:
|
81
|
+
message << " validation failed when provided with a #{error_case[:value]} #{failure_message_unit} test file"
|
82
82
|
end
|
83
83
|
message << " whereas it should have passed"
|
84
84
|
end
|
85
85
|
|
86
|
+
def failure_message_unit
|
87
|
+
raise NotImplementedError
|
88
|
+
end
|
89
|
+
|
86
90
|
def not_lower_than_min?
|
87
|
-
@min.nil? || !
|
91
|
+
@min.nil? || !passes_validation_with_value(@min - 1)
|
88
92
|
end
|
89
93
|
|
90
94
|
def higher_than_min?
|
91
|
-
@min.nil? ||
|
95
|
+
@min.nil? || passes_validation_with_value(@min + 1)
|
92
96
|
end
|
93
97
|
|
94
98
|
def lower_than_max?
|
95
|
-
@max.nil? || @max == Float::INFINITY ||
|
99
|
+
@max.nil? || @max == Float::INFINITY || passes_validation_with_value(@max - 1)
|
96
100
|
end
|
97
101
|
|
98
102
|
def not_higher_than_max?
|
99
|
-
@max.nil? || @max == Float::INFINITY || !
|
103
|
+
@max.nil? || @max == Float::INFINITY || !passes_validation_with_value(@max + 1)
|
100
104
|
end
|
101
105
|
|
102
|
-
def
|
103
|
-
|
106
|
+
def smallest_measurement
|
107
|
+
raise NotImplementedError
|
108
|
+
end
|
109
|
+
|
110
|
+
def passes_validation_with_value(value)
|
111
|
+
mock_value_for(io, value) do
|
104
112
|
attach_file
|
105
113
|
validate
|
106
114
|
detach_file
|
107
|
-
is_valid? || add_failure_message_artefact(
|
115
|
+
is_valid? || add_failure_message_artefact(value)
|
108
116
|
end
|
109
117
|
end
|
110
118
|
|
111
|
-
def add_failure_message_artefact(
|
112
|
-
@failure_message_artefacts << {
|
119
|
+
def add_failure_message_artefact(value)
|
120
|
+
@failure_message_artefacts << { value: value }
|
113
121
|
false
|
114
122
|
end
|
115
123
|
|
116
124
|
def is_custom_message_valid?
|
117
125
|
return true unless @custom_message
|
118
126
|
|
119
|
-
|
127
|
+
mock_value_for(io, -smallest_measurement) do
|
120
128
|
attach_file
|
121
129
|
validate
|
122
130
|
detach_file
|
@@ -124,10 +132,8 @@ module ActiveStorageValidations
|
|
124
132
|
end
|
125
133
|
end
|
126
134
|
|
127
|
-
def
|
128
|
-
|
129
|
-
yield
|
130
|
-
end
|
135
|
+
def mock_value_for(io, size)
|
136
|
+
raise NotImplementedError
|
131
137
|
end
|
132
138
|
end
|
133
139
|
end
|
@@ -46,12 +46,12 @@ module ActiveStorageValidations
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def allowing(*content_types)
|
49
|
-
@allowed_content_types = content_types.flatten
|
49
|
+
@allowed_content_types = content_types.map { |content_type| normalize_content_type(content_type) }.flatten
|
50
50
|
self
|
51
51
|
end
|
52
52
|
|
53
53
|
def rejecting(*content_types)
|
54
|
-
@rejected_content_types = content_types.flatten
|
54
|
+
@rejected_content_types = content_types.map { |content_type| normalize_content_type(content_type) }.flatten
|
55
55
|
self
|
56
56
|
end
|
57
57
|
|
@@ -88,6 +88,10 @@ module ActiveStorageValidations
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
def normalize_content_type(content_type)
|
92
|
+
Marcel::MimeType.for(declared_type: content_type.to_s, extension: content_type.to_s)
|
93
|
+
end
|
94
|
+
|
91
95
|
def all_allowed_content_types_allowed?
|
92
96
|
@allowed_content_types_not_allowed ||= @allowed_content_types.reject { |type| type_allowed?(type) }
|
93
97
|
@allowed_content_types_not_allowed.empty?
|
@@ -135,7 +139,7 @@ module ActiveStorageValidations
|
|
135
139
|
# (ie spoofed file basically), we need to ignore the error related to
|
136
140
|
# content type spoofing in our matcher to pass the tests
|
137
141
|
def validator_errors_for_attribute
|
138
|
-
super.reject { |hash| hash[:error] == :
|
142
|
+
super.reject { |hash| hash[:error] == :content_type_spoofed }
|
139
143
|
end
|
140
144
|
end
|
141
145
|
end
|
@@ -185,7 +185,7 @@ module ActiveStorageValidations
|
|
185
185
|
end
|
186
186
|
|
187
187
|
def mock_dimensions_for(attachment, width, height)
|
188
|
-
Matchers.mock_metadata(attachment, width, height) do
|
188
|
+
Matchers.mock_metadata(attachment, { width: width, height: height }) do
|
189
189
|
yield
|
190
190
|
end
|
191
191
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_comparison_validator_matcher'
|
4
|
+
|
5
|
+
module ActiveStorageValidations
|
6
|
+
module Matchers
|
7
|
+
def validate_duration_of(attribute_name)
|
8
|
+
DurationValidatorMatcher.new(attribute_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
class DurationValidatorMatcher < BaseComparisonValidatorMatcher
|
12
|
+
def description
|
13
|
+
"validate file duration of :#{@attribute_name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def failure_message
|
17
|
+
message = ["is expected to validate file duration of :#{@attribute_name}"]
|
18
|
+
build_failure_message(message)
|
19
|
+
message.join("\n")
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def failure_message_unit
|
25
|
+
"seconds"
|
26
|
+
end
|
27
|
+
|
28
|
+
def smallest_measurement
|
29
|
+
1.second
|
30
|
+
end
|
31
|
+
|
32
|
+
def mock_value_for(io, duration)
|
33
|
+
Matchers.mock_metadata(io, { duration: duration }) do
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -10,11 +10,11 @@ require_relative 'shared/asv_validatable'
|
|
10
10
|
|
11
11
|
module ActiveStorageValidations
|
12
12
|
module Matchers
|
13
|
-
def
|
14
|
-
|
13
|
+
def validate_processable_file_of(name)
|
14
|
+
ProcessableFileValidatorMatcher.new(name)
|
15
15
|
end
|
16
16
|
|
17
|
-
class
|
17
|
+
class ProcessableFileValidatorMatcher
|
18
18
|
include ASVActiveStorageable
|
19
19
|
include ASVAllowBlankable
|
20
20
|
include ASVAttachable
|
@@ -46,7 +46,7 @@ module ActiveStorageValidations
|
|
46
46
|
is_context_valid? &&
|
47
47
|
is_custom_message_valid? &&
|
48
48
|
is_valid_when_image_processable? &&
|
49
|
-
|
49
|
+
is_invalid_when_file_not_processable?
|
50
50
|
end
|
51
51
|
|
52
52
|
private
|
@@ -58,7 +58,7 @@ module ActiveStorageValidations
|
|
58
58
|
is_valid?
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
61
|
+
def is_invalid_when_file_not_processable?
|
62
62
|
attach_file(not_processable_image)
|
63
63
|
validate
|
64
64
|
detach_file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'base_comparison_validator_matcher'
|
4
4
|
|
5
5
|
module ActiveStorageValidations
|
6
6
|
module Matchers
|
@@ -8,7 +8,7 @@ module ActiveStorageValidations
|
|
8
8
|
SizeValidatorMatcher.new(attribute_name)
|
9
9
|
end
|
10
10
|
|
11
|
-
class SizeValidatorMatcher <
|
11
|
+
class SizeValidatorMatcher < BaseComparisonValidatorMatcher
|
12
12
|
def description
|
13
13
|
"validate file size of :#{@attribute_name}"
|
14
14
|
end
|
@@ -18,6 +18,22 @@ module ActiveStorageValidations
|
|
18
18
|
build_failure_message(message)
|
19
19
|
message.join("\n")
|
20
20
|
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def failure_message_unit
|
25
|
+
"bytes"
|
26
|
+
end
|
27
|
+
|
28
|
+
def smallest_measurement
|
29
|
+
1.byte
|
30
|
+
end
|
31
|
+
|
32
|
+
def mock_value_for(io, size)
|
33
|
+
Matchers.stub_method(io, :size, size) do
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
end
|
21
37
|
end
|
22
38
|
end
|
23
39
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'base_comparison_validator_matcher'
|
4
4
|
|
5
5
|
module ActiveStorageValidations
|
6
6
|
module Matchers
|
@@ -8,7 +8,7 @@ module ActiveStorageValidations
|
|
8
8
|
TotalSizeValidatorMatcher.new(attribute_name)
|
9
9
|
end
|
10
10
|
|
11
|
-
class TotalSizeValidatorMatcher <
|
11
|
+
class TotalSizeValidatorMatcher < BaseComparisonValidatorMatcher
|
12
12
|
def description
|
13
13
|
"validate total file size of :#{@attribute_name}"
|
14
14
|
end
|
@@ -26,6 +26,22 @@ module ActiveStorageValidations
|
|
26
26
|
@subject.public_send(@attribute_name).attach([dummy_blob])
|
27
27
|
@subject.public_send(@attribute_name)
|
28
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def failure_message_unit
|
33
|
+
"bytes"
|
34
|
+
end
|
35
|
+
|
36
|
+
def smallest_measurement
|
37
|
+
1.byte
|
38
|
+
end
|
39
|
+
|
40
|
+
def mock_value_for(io, size)
|
41
|
+
Matchers.stub_method(io, :size, size) do
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
end
|
29
45
|
end
|
30
46
|
end
|
31
47
|
end
|
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
require 'active_storage_validations/matchers/aspect_ratio_validator_matcher'
|
4
4
|
require 'active_storage_validations/matchers/attached_validator_matcher'
|
5
|
-
require 'active_storage_validations/matchers/
|
5
|
+
require 'active_storage_validations/matchers/processable_file_validator_matcher'
|
6
6
|
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
|
+
require 'active_storage_validations/matchers/duration_validator_matcher'
|
9
10
|
require 'active_storage_validations/matchers/size_validator_matcher'
|
10
11
|
require 'active_storage_validations/matchers/total_size_validator_matcher'
|
11
12
|
|
@@ -25,8 +26,8 @@ module ActiveStorageValidations
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
def self.mock_metadata(attachment,
|
29
|
-
mock = Struct.new(:metadata).new(
|
29
|
+
def self.mock_metadata(attachment, metadata = {})
|
30
|
+
mock = Struct.new(:metadata).new(metadata)
|
30
31
|
|
31
32
|
stub_method(ActiveStorageValidations::Analyzer, :new, mock) do
|
32
33
|
yield
|
data/lib/active_storage_validations/{processable_image_validator.rb → processable_file_validator.rb}
RENAMED
@@ -7,7 +7,7 @@ require_relative 'shared/asv_errorable'
|
|
7
7
|
require_relative 'shared/asv_symbolizable'
|
8
8
|
|
9
9
|
module ActiveStorageValidations
|
10
|
-
class
|
10
|
+
class ProcessableFileValidator < ActiveModel::EachValidator # :nodoc
|
11
11
|
include ASVActiveStorageable
|
12
12
|
include ASVAnalyzable
|
13
13
|
include ASVAttachable
|
@@ -15,13 +15,14 @@ module ActiveStorageValidations
|
|
15
15
|
include ASVSymbolizable
|
16
16
|
|
17
17
|
ERROR_TYPES = %i[
|
18
|
-
|
18
|
+
file_not_processable
|
19
19
|
].freeze
|
20
|
+
METADATA_KEYS = %i[].freeze
|
20
21
|
|
21
22
|
def validate_each(record, attribute, _value)
|
22
23
|
return if no_attachments?(record, attribute)
|
23
24
|
|
24
|
-
validate_changed_files_from_metadata(record, attribute)
|
25
|
+
validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
|
25
26
|
end
|
26
27
|
|
27
28
|
private
|
@@ -2,5 +2,10 @@
|
|
2
2
|
|
3
3
|
module ActiveStorageValidations
|
4
4
|
class Railtie < ::Rails::Railtie
|
5
|
+
initializer 'active_storage_validations.extend_active_storage_blob' do
|
6
|
+
Rails.application.config.to_prepare do
|
7
|
+
ActiveStorage::Blob.include(ActiveStorageValidations::ASVBlobMetadatable)
|
8
|
+
end
|
9
|
+
end
|
5
10
|
end
|
6
11
|
end
|
@@ -12,13 +12,36 @@ module ActiveStorageValidations
|
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
# Retrieve the ASV metadata from the blob.
|
16
|
+
# If the blob has not been analyzed by our gem yet, the gem will analyze the
|
17
|
+
# attachable with the corresponding analyzer and set the metadata in the
|
18
|
+
# blob.
|
19
|
+
def metadata_for(blob, attachable, metadata_keys)
|
20
|
+
return blob.active_storage_validations_metadata if blob_has_asv_metadata?(blob, metadata_keys)
|
21
|
+
|
22
|
+
new_metadata = generate_metadata_for(attachable, metadata_keys)
|
23
|
+
blob.merge_into_active_storage_validations_metadata(new_metadata)
|
24
|
+
end
|
25
|
+
|
26
|
+
def blob_has_asv_metadata?(blob, metadata_keys)
|
27
|
+
return false unless blob.active_storage_validations_metadata.present?
|
28
|
+
|
29
|
+
metadata_keys.all? { |key| blob.active_storage_validations_metadata.key?(key) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_metadata_for(attachable, metadata_keys)
|
33
|
+
if metadata_keys == ActiveStorageValidations::ContentTypeValidator::METADATA_KEYS
|
34
|
+
content_type_analyzer_for(attachable).content_type
|
35
|
+
else
|
36
|
+
metadata_analyzer_for(attachable).metadata
|
37
|
+
end
|
17
38
|
end
|
18
39
|
|
19
|
-
def
|
40
|
+
def metadata_analyzer_for(attachable)
|
20
41
|
case attachable_media_type(attachable)
|
21
42
|
when "image" then image_analyzer_for(attachable)
|
43
|
+
when "video" then video_analyzer_for(attachable)
|
44
|
+
when "audio" then audio_analyzer_for(attachable)
|
22
45
|
else fallback_analyzer_for(attachable)
|
23
46
|
end
|
24
47
|
end
|
@@ -38,8 +61,20 @@ module ActiveStorageValidations
|
|
38
61
|
ActiveStorage.variant_processor || DEFAULT_IMAGE_PROCESSOR
|
39
62
|
end
|
40
63
|
|
64
|
+
def video_analyzer_for(attachable)
|
65
|
+
ActiveStorageValidations::Analyzer::VideoAnalyzer.new(attachable)
|
66
|
+
end
|
67
|
+
|
68
|
+
def audio_analyzer_for(attachable)
|
69
|
+
ActiveStorageValidations::Analyzer::AudioAnalyzer.new(attachable)
|
70
|
+
end
|
71
|
+
|
41
72
|
def fallback_analyzer_for(attachable)
|
42
73
|
ActiveStorageValidations::Analyzer::NullAnalyzer.new(attachable)
|
43
74
|
end
|
75
|
+
|
76
|
+
def content_type_analyzer_for(attachable)
|
77
|
+
ActiveStorageValidations::Analyzer::ContentTypeAnalyzer.new(attachable)
|
78
|
+
end
|
44
79
|
end
|
45
80
|
end
|