active_storage_validations 1.4.0 → 2.0.1
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 +619 -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 +56 -23
- 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 +49 -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 +5 -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,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module ASVBlobMetadatable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
# This method returns the metadata that has been set by our gem.
|
9
|
+
# The metadata is stored in the blob's custom metadata. All keys are prefixed with 'asv_'
|
10
|
+
# to avoid conflicts with other metadata.
|
11
|
+
# It is not to set a active_storage_validation key equal to a a hash of our gem's metadata,
|
12
|
+
# because this would result in errors down the road with services such as S3.
|
13
|
+
#
|
14
|
+
# Because of how the metadata is stored, we need to convert the values from String
|
15
|
+
# to Integer or Boolean.
|
16
|
+
def active_storage_validations_metadata
|
17
|
+
metadata.dig('custom')
|
18
|
+
&.select { |key, _| key.to_s.start_with?('asv_') }
|
19
|
+
&.transform_keys { |key| key.to_s.delete_prefix('asv_') }
|
20
|
+
&.transform_values do |value|
|
21
|
+
case value
|
22
|
+
when /\A\d+\z/ then value.to_i
|
23
|
+
when /\A\d+\.\d+\z/ then value.to_f
|
24
|
+
when 'true' then true
|
25
|
+
when 'false' then false
|
26
|
+
else value
|
27
|
+
end
|
28
|
+
end || {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method sets the metadata that has been detected by our gem.
|
32
|
+
# The metadata is stored in the blob's custom metadata. All keys are prefixed with 'asv_'.
|
33
|
+
# We need to store values as String, because services such as S3 will not accept other types.
|
34
|
+
def merge_into_active_storage_validations_metadata(hash)
|
35
|
+
aws_compatible_metadata = normalize_active_storage_validations_metadata_for_aws(hash)
|
36
|
+
|
37
|
+
metadata['custom'] ||= {}
|
38
|
+
metadata['custom'].merge!(aws_compatible_metadata)
|
39
|
+
|
40
|
+
active_storage_validations_metadata
|
41
|
+
end
|
42
|
+
|
43
|
+
def normalize_active_storage_validations_metadata_for_aws(hash)
|
44
|
+
hash.transform_keys { |key, _| key.to_s.start_with?('asv_') ? key : "asv_#{key}" }
|
45
|
+
.transform_values(&:to_s)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
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,9 @@ module ActiveStorageValidations
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
def self.mock_metadata(attachment,
|
29
|
-
|
29
|
+
def self.mock_metadata(attachment, metadata = {})
|
30
|
+
asv_metadata_available_keys = { width: nil, height: nil, duration: nil, content_type: nil }
|
31
|
+
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
|
30
32
|
|
31
33
|
stub_method(ActiveStorageValidations::Analyzer, :new, mock) do
|
32
34
|
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
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
7
|
+
include(ActiveStorageValidations::ASVBlobMetadatable)
|
8
|
+
end
|
9
|
+
end
|
5
10
|
end
|
6
11
|
end
|