fun_with_json_api 0.0.2 → 0.0.3
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/Rakefile +4 -1
- data/config/locales/fun_with_json_api.en.yml +29 -2
- data/lib/fun_with_json_api.rb +30 -2
- data/lib/fun_with_json_api/action_controller_extensions/serialization.rb +18 -0
- data/lib/fun_with_json_api/attribute.rb +3 -3
- data/lib/fun_with_json_api/attributes/relationship.rb +37 -23
- data/lib/fun_with_json_api/attributes/relationship_collection.rb +55 -38
- data/lib/fun_with_json_api/attributes/string_attribute.rb +12 -1
- data/lib/fun_with_json_api/attributes/uuid_v4_attribute.rb +27 -0
- data/lib/fun_with_json_api/controller_methods.rb +1 -1
- data/lib/fun_with_json_api/deserializer.rb +61 -8
- data/lib/fun_with_json_api/deserializer_class_methods.rb +37 -7
- data/lib/fun_with_json_api/exceptions/illegal_client_generated_identifier.rb +17 -0
- data/lib/fun_with_json_api/exceptions/invalid_client_generated_identifier.rb +17 -0
- data/lib/fun_with_json_api/exceptions/invalid_document_identifier.rb +17 -0
- data/lib/fun_with_json_api/exceptions/invalid_document_type.rb +20 -0
- data/lib/fun_with_json_api/exceptions/invalid_relationship.rb +5 -3
- data/lib/fun_with_json_api/exceptions/invalid_relationship_type.rb +17 -0
- data/lib/fun_with_json_api/exceptions/missing_resource.rb +15 -0
- data/lib/fun_with_json_api/exceptions/unknown_attribute.rb +15 -0
- data/lib/fun_with_json_api/exceptions/unknown_relationship.rb +15 -0
- data/lib/fun_with_json_api/find_collection_from_document.rb +124 -0
- data/lib/fun_with_json_api/find_resource_from_document.rb +112 -0
- data/lib/fun_with_json_api/pre_deserializer.rb +1 -0
- data/lib/fun_with_json_api/railtie.rb +30 -1
- data/lib/fun_with_json_api/schema_validator.rb +47 -0
- data/lib/fun_with_json_api/schema_validators/check_attributes.rb +52 -0
- data/lib/fun_with_json_api/schema_validators/check_document_id_matches_resource.rb +96 -0
- data/lib/fun_with_json_api/schema_validators/check_document_type_matches_resource.rb +40 -0
- data/lib/fun_with_json_api/schema_validators/check_relationships.rb +127 -0
- data/lib/fun_with_json_api/version.rb +1 -1
- data/spec/dummy/log/test.log +172695 -0
- data/spec/fixtures/active_record.rb +6 -0
- data/spec/fun_with_json_api/controller_methods_spec.rb +8 -3
- data/spec/fun_with_json_api/deserializer_class_methods_spec.rb +14 -6
- data/spec/fun_with_json_api/deserializer_spec.rb +155 -40
- data/spec/fun_with_json_api/exception_spec.rb +9 -9
- data/spec/fun_with_json_api/find_collection_from_document_spec.rb +203 -0
- data/spec/fun_with_json_api/find_resource_from_document_spec.rb +100 -0
- data/spec/fun_with_json_api/pre_deserializer_spec.rb +26 -26
- data/spec/fun_with_json_api/railtie_spec.rb +88 -0
- data/spec/fun_with_json_api/schema_validator_spec.rb +94 -0
- data/spec/fun_with_json_api/schema_validators/check_attributes_spec.rb +52 -0
- data/spec/fun_with_json_api/schema_validators/check_document_id_matches_resource_spec.rb +115 -0
- data/spec/fun_with_json_api/schema_validators/check_document_type_matches_resource_spec.rb +30 -0
- data/spec/fun_with_json_api/schema_validators/check_relationships_spec.rb +150 -0
- data/spec/fun_with_json_api_spec.rb +148 -4
- metadata +49 -4
- data/spec/example_spec.rb +0 -64
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbb6fa5d08ff453c7adef23b0c5a608e98897228
|
4
|
+
data.tar.gz: 1cb7de4f0f03fc553af3da253fea06bf33704cbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be8824b7d81fc131fe50591cb9ff83495407d17f053845279add97a6eeccd8a473dba036df315f45db3b7097d84c9105fc6d1228308d195edc488dbfde054a9f
|
7
|
+
data.tar.gz: 2d5c7af796c4224c4868a66bf876d447337304ac9616da5a254f0253e4563dd003700774187f2075ef76e6d28b12551974c00c20fb203d559fa15a23929506fd
|
data/Rakefile
CHANGED
@@ -17,7 +17,10 @@ end
|
|
17
17
|
Bundler::GemHelper.install_tasks
|
18
18
|
|
19
19
|
require 'rubocop/rake_task'
|
20
|
-
|
20
|
+
require 'rubocop-rspec'
|
21
|
+
RuboCop::RakeTask.new do |task|
|
22
|
+
task.requires << 'rubocop-rspec'
|
23
|
+
end
|
21
24
|
|
22
25
|
require 'rspec/core'
|
23
26
|
require 'rspec/core/rake_task'
|
@@ -2,12 +2,39 @@ en:
|
|
2
2
|
fun_with_json_api:
|
3
3
|
exceptions:
|
4
4
|
invalid_document: 'Request json_api document is invalid'
|
5
|
-
|
6
|
-
|
5
|
+
invalid_document_identifier: 'Request json_api data id is invalid'
|
6
|
+
invalid_client_generated_identifier: 'Request json_api data id has already been used for an existing resource'
|
7
|
+
illegal_client_generated_identifier: 'Request json_api attempted to set an unsupported client-generated id'
|
8
|
+
invalid_document_type: 'Request json_api data type does not match endpoint'
|
9
|
+
missing_resource: 'Unable to find the requested resource'
|
10
|
+
invalid_attribute: 'Request json_api attribute data is invalid'
|
11
|
+
unknown_attribute: 'Request json_api attribute is unsupported by the current endpoint'
|
12
|
+
invalid_relationship: 'Request json_api relationship data is invalid'
|
7
13
|
missing_relationship: 'Unable to find the requested relationship'
|
14
|
+
unknown_relationship: 'Request json_api relationship is unsupported by the current endpoint'
|
15
|
+
invalid_relationship_type: 'Request json_api relationship type does not match expected resource'
|
8
16
|
invalid_boolean_attribute: "Boolean value should only be true, false, or null"
|
9
17
|
invalid_date_attribute: "Date value should be in the format YYYY-MM-DD"
|
10
18
|
invalid_datetime_attribute: "Datetime value should be a ISO 8601 datetime"
|
11
19
|
invalid_decimal_attribute: "Decimal value must be a decimal number (i.e. 123.45)"
|
12
20
|
invalid_float_attribute: "Float value must be a floating point number (i.e. 123.45)"
|
13
21
|
invalid_integer_attribute: "Integer value must be a integer number (i.e. 123)"
|
22
|
+
invalid_string_attribute: 'String value must be a JSON String (i.e. "Example")'
|
23
|
+
invalid_uuid_v4_attribute: 'UUID value must be RFC 4122 Version 4 UUID (i.e. "f47ac10b-58cc-4372-a567-0e02b2c3d479")'
|
24
|
+
schema_validators:
|
25
|
+
document_id_does_not_match_resource: "Expected data id to match resource at endpoint: %{expected}"
|
26
|
+
document_type_does_not_match_endpoint: "Expected data type to be a '%{expected}' resource"
|
27
|
+
invalid_relationship_type_in_array: "Expected '%{relationship}' relationship to be an Array of '%{relationship_type}' resource identifiers"
|
28
|
+
invalid_relationship_type_in_hash: "Expected '%{relationship}' relationship to be null or a '%{relationship_type}' resource identifier Hash"
|
29
|
+
resource_id_can_not_be_client_generated: "The current endpoint does not allow you to set an id for a new '%{resource}' resource"
|
30
|
+
resource_id_has_already_been_assigned: "The provided id for a new '%{resource}' resource has already been used by another resource: %{id}"
|
31
|
+
unknown_attribute_for_resource: "The provided attribute '%{attribute}' can not be assigned to a '%{resource}' resource from the current endpoint"
|
32
|
+
unknown_relationship_for_resource: "The provided relationship '%{relationship}' can not be assigned to a '%{resource}' resource from the current endpoint"
|
33
|
+
find_resource_from_document:
|
34
|
+
invalid_document: "Expected data to be a Hash or null"
|
35
|
+
invalid_document_type: "Expected data type to be a '%{resource}' resource"
|
36
|
+
missing_resource: "Unable to find '%{resource}' with matching id: '%{resource_id}'"
|
37
|
+
find_collection_from_document:
|
38
|
+
invalid_document: "Expected data to be a Array of '%{resource}' resources"
|
39
|
+
invalid_document_type: "Expected '%{type}' to be a '%{resource}' resource"
|
40
|
+
missing_resource: "Unable to find '%{resource}' with matching id: '%{resource_id}'"
|
data/lib/fun_with_json_api.rb
CHANGED
@@ -3,22 +3,50 @@ require 'fun_with_json_api/attribute'
|
|
3
3
|
|
4
4
|
require 'fun_with_json_api/pre_deserializer'
|
5
5
|
require 'fun_with_json_api/deserializer'
|
6
|
-
require 'fun_with_json_api/
|
6
|
+
require 'fun_with_json_api/schema_validator'
|
7
|
+
require 'fun_with_json_api/find_collection_from_document'
|
8
|
+
require 'fun_with_json_api/find_resource_from_document'
|
7
9
|
|
8
10
|
# Makes working with JSON:API fun!
|
9
11
|
module FunWithJsonApi
|
12
|
+
MEDIA_TYPE = 'application/vnd.api+json'.freeze
|
13
|
+
|
10
14
|
module_function
|
11
15
|
|
12
|
-
def deserialize(api_document, deserializer_class, options = {})
|
16
|
+
def deserialize(api_document, deserializer_class, resource = nil, options = {})
|
13
17
|
# Prepare the deserializer and the expected config
|
14
18
|
deserializer = deserializer_class.create(options)
|
15
19
|
|
16
20
|
# Run through initial document structure validation and deserialization
|
17
21
|
unfiltered = FunWithJsonApi::PreDeserializer.parse(api_document, deserializer)
|
18
22
|
|
23
|
+
# Check the document matches up with expected resource parameters
|
24
|
+
FunWithJsonApi::SchemaValidator.check(api_document, deserializer, resource)
|
25
|
+
|
19
26
|
# Ensure document matches schema, and sanitize values
|
20
27
|
deserializer.sanitize_params(unfiltered)
|
21
28
|
end
|
29
|
+
|
30
|
+
def deserialize_resource(api_document, deserializer_class, resource, options = {})
|
31
|
+
raise ArgumentError, 'resource cannot be nil' if resource.nil?
|
32
|
+
deserialize(api_document, deserializer_class, resource, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_resource(api_document, deserializer_class, options = {})
|
36
|
+
# Prepare the deserializer for loading a resource
|
37
|
+
deserializer = deserializer_class.create(options.merge(attributes: [], relationships: []))
|
38
|
+
|
39
|
+
# Load the resource from the document id
|
40
|
+
FunWithJsonApi::FindResourceFromDocument.find(api_document, deserializer)
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_collection(api_document, deserializer_class, options = {})
|
44
|
+
# Prepare the deserializer for loading a resource
|
45
|
+
deserializer = deserializer_class.create(options.merge(attributes: [], relationships: []))
|
46
|
+
|
47
|
+
# Load the collection from the document
|
48
|
+
FunWithJsonApi::FindCollectionFromDocument.find(api_document, deserializer)
|
49
|
+
end
|
22
50
|
end
|
23
51
|
|
24
52
|
require 'fun_with_json_api/railtie' if defined?(Rails)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module FunWithJsonApi
|
2
|
+
module ActionControllerExtensions
|
3
|
+
module Serialization
|
4
|
+
# Overrides the dynamic render json_api methods to use ActiveModelSerializer
|
5
|
+
[:_render_option_json_api, :_render_with_renderer_json_api].each do |renderer_method|
|
6
|
+
define_method renderer_method do |resource, options|
|
7
|
+
options.fetch(:adapter) { options[:adapter] ||= :json_api }
|
8
|
+
options.fetch(:serialization_context) do
|
9
|
+
options[:serialization_context] ||=
|
10
|
+
ActiveModelSerializers::SerializationContext.new(request)
|
11
|
+
end
|
12
|
+
serializable_resource = get_serializer(resource, options)
|
13
|
+
super(serializable_resource, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -16,8 +16,8 @@ module FunWithJsonApi
|
|
16
16
|
def initialize(name, options = {})
|
17
17
|
raise ArgumentError, 'name cannot be blank!' unless name.present?
|
18
18
|
|
19
|
-
@name = name
|
20
|
-
@as = options.fetch(:as, name)
|
19
|
+
@name = name.to_sym
|
20
|
+
@as = options.fetch(:as, name).to_sym
|
21
21
|
end
|
22
22
|
|
23
23
|
def call(value)
|
@@ -29,7 +29,7 @@ module FunWithJsonApi
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def param_value
|
32
|
-
as
|
32
|
+
as
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -10,18 +10,17 @@ module FunWithJsonApi
|
|
10
10
|
new(name, deserializer_class_or_callable, options)
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
to: :deserializer
|
13
|
+
attr_reader :deserializer_class
|
14
|
+
attr_reader :options
|
15
|
+
delegate :type, to: :deserializer
|
17
16
|
|
18
17
|
def initialize(name, deserializer_class, options = {})
|
19
18
|
super(name, options)
|
20
19
|
@deserializer_class = deserializer_class
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
@options = options.reverse_merge(
|
21
|
+
attributes: [],
|
22
|
+
relationships: []
|
23
|
+
)
|
25
24
|
end
|
26
25
|
|
27
26
|
def call(id_value)
|
@@ -29,44 +28,59 @@ module FunWithJsonApi
|
|
29
28
|
raise build_invalid_relationship_error(id_value)
|
30
29
|
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
resource = deserializer.load_resource_from_id_value(id_value)
|
32
|
+
return resource.id if resource
|
33
|
+
|
34
|
+
raise build_missing_relationship_error(id_value)
|
35
|
+
end
|
36
|
+
|
37
|
+
# rubocop:disable Style/PredicateName
|
38
|
+
|
39
|
+
def has_many?
|
40
|
+
false
|
35
41
|
end
|
36
42
|
|
43
|
+
# rubocop:enable Style/PredicateName
|
44
|
+
|
37
45
|
def param_value
|
38
46
|
:"#{as}_id"
|
39
47
|
end
|
40
48
|
|
49
|
+
def deserializer
|
50
|
+
@deserializer ||= build_deserializer_from_options
|
51
|
+
end
|
52
|
+
|
41
53
|
private
|
42
54
|
|
43
|
-
|
44
|
-
def create_deserializer_from_deserializer_class
|
55
|
+
def build_deserializer_from_options
|
45
56
|
if @deserializer_class.respond_to?(:call)
|
46
57
|
@deserializer_class.call
|
47
58
|
else
|
48
59
|
@deserializer_class
|
49
|
-
end.create(
|
50
|
-
attributes: [],
|
51
|
-
relationships: []
|
52
|
-
)
|
60
|
+
end.create(options)
|
53
61
|
end
|
54
62
|
|
55
63
|
def build_invalid_relationship_error(id_value)
|
56
|
-
exception_message = "#{name} relationship should contain a single '#{type}'
|
64
|
+
exception_message = "#{name} relationship should contain a single '#{deserializer.type}'"\
|
65
|
+
' data hash'
|
57
66
|
payload = ExceptionPayload.new
|
58
67
|
payload.pointer = "/data/relationships/#{name}"
|
59
68
|
payload.detail = exception_message
|
60
69
|
Exceptions::InvalidRelationship.new(exception_message + ": #{id_value.inspect}", payload)
|
61
70
|
end
|
62
71
|
|
63
|
-
def
|
72
|
+
def build_missing_relationship_error(id_value, message = nil)
|
73
|
+
message ||= missing_resource_debug_message(id_value)
|
64
74
|
payload = ExceptionPayload.new
|
65
75
|
payload.pointer = "/data/relationships/#{name}/id"
|
66
|
-
payload.detail = "Unable to find '#{type}' with matching id
|
67
|
-
|
68
|
-
|
69
|
-
|
76
|
+
payload.detail = "Unable to find '#{deserializer.type}' with matching id"\
|
77
|
+
": #{id_value.inspect}"
|
78
|
+
Exceptions::MissingRelationship.new(message, payload)
|
79
|
+
end
|
80
|
+
|
81
|
+
def missing_resource_debug_message(id_value)
|
82
|
+
"Couldn't find #{deserializer.resource_class.name}"\
|
83
|
+
" where #{deserializer.id_param} = #{id_value.inspect}"
|
70
84
|
end
|
71
85
|
end
|
72
86
|
end
|
@@ -5,22 +5,19 @@ module FunWithJsonApi
|
|
5
5
|
new(name, deserializer_class_or_callable, options)
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
to: :deserializer
|
8
|
+
attr_reader :deserializer_class
|
9
|
+
attr_reader :options
|
10
|
+
delegate :type, to: :deserializer
|
12
11
|
|
13
12
|
def initialize(name, deserializer_class, options = {})
|
14
13
|
super(name, options.reverse_merge(as: name.to_s.singularize.to_sym))
|
15
14
|
@deserializer_class = deserializer_class
|
15
|
+
@options = options.reverse_merge(
|
16
|
+
attributes: [],
|
17
|
+
relationships: []
|
18
|
+
)
|
16
19
|
|
17
|
-
|
18
|
-
raise ArgumentError, "Use a singular relationship as value: {as: :#{as.to_s.singularize}}"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def deserializer
|
23
|
-
@deserializer ||= create_deserializer_from_deserializer_class
|
20
|
+
check_as_attribute_is_singular!
|
24
21
|
end
|
25
22
|
|
26
23
|
# Expects an array of id values for a nested collection
|
@@ -29,7 +26,7 @@ module FunWithJsonApi
|
|
29
26
|
raise build_invalid_relationship_collection_error(values)
|
30
27
|
end
|
31
28
|
|
32
|
-
collection =
|
29
|
+
collection = deserializer.load_collection_from_id_values(values)
|
33
30
|
|
34
31
|
# Ensure the collection size matches
|
35
32
|
expected_size = values.size
|
@@ -42,35 +39,50 @@ module FunWithJsonApi
|
|
42
39
|
convert_collection_to_ids(collection)
|
43
40
|
end
|
44
41
|
|
42
|
+
# rubocop:disable Style/PredicateName
|
43
|
+
|
44
|
+
def has_many?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
# rubocop:enable Style/PredicateName
|
49
|
+
|
45
50
|
# User the singular of `as` that is how AMS converts the value
|
46
51
|
def param_value
|
47
52
|
:"#{as}_ids"
|
48
53
|
end
|
49
54
|
|
55
|
+
def deserializer
|
56
|
+
@deserializer ||= build_deserializer_from_options
|
57
|
+
end
|
58
|
+
|
50
59
|
private
|
51
60
|
|
61
|
+
def build_deserializer_from_options
|
62
|
+
if @deserializer_class.respond_to?(:call)
|
63
|
+
@deserializer_class.call
|
64
|
+
else
|
65
|
+
@deserializer_class
|
66
|
+
end.create(options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_as_attribute_is_singular!
|
70
|
+
if as.to_s != as.to_s.singularize
|
71
|
+
raise ArgumentError, "Use a singular relationship as value: {as: :#{as.to_s.singularize}}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
52
75
|
def convert_collection_to_ids(collection)
|
53
76
|
if collection.respond_to? :pluck
|
54
77
|
# Well... pluck+arel doesn't work with SQLite, but select at least is safe
|
55
|
-
collection = collection.select(resource_class.arel_table[:id])
|
78
|
+
collection = collection.select(deserializer.resource_class.arel_table[:id])
|
56
79
|
end
|
57
80
|
collection.map(&:id)
|
58
81
|
end
|
59
82
|
|
60
|
-
# Creates a new Deserializer from the deserializer class
|
61
|
-
def create_deserializer_from_deserializer_class
|
62
|
-
if @deserializer_class.respond_to?(:call)
|
63
|
-
@deserializer_class.call
|
64
|
-
else
|
65
|
-
@deserializer_class
|
66
|
-
end.create(
|
67
|
-
attributes: [],
|
68
|
-
relationships: []
|
69
|
-
)
|
70
|
-
end
|
71
|
-
|
72
83
|
def build_invalid_relationship_collection_error(values)
|
73
|
-
exception_message = "#{name} relationship should contain a array of
|
84
|
+
exception_message = "#{name} relationship should contain a array of"\
|
85
|
+
" '#{deserializer.type}' data"
|
74
86
|
payload = ExceptionPayload.new
|
75
87
|
payload.pointer = "/data/relationships/#{name}"
|
76
88
|
payload.detail = exception_message
|
@@ -78,21 +90,26 @@ module FunWithJsonApi
|
|
78
90
|
end
|
79
91
|
|
80
92
|
def build_missing_relationship_error_from_collection(collection, values)
|
81
|
-
|
82
|
-
|
83
|
-
payload =
|
84
|
-
|
85
|
-
|
86
|
-
exception_message = "Couldn't find #{resource_class} items with "\
|
87
|
-
"#{id_param} in #{missing_values.inspect}"
|
93
|
+
collection_ids = deserializer.format_collection_ids(collection)
|
94
|
+
|
95
|
+
payload = build_missing_relationship_payload(collection_ids, values)
|
96
|
+
|
97
|
+
missing_values = values.reject { |value| collection_ids.include?(value.to_s) }
|
98
|
+
exception_message = "Couldn't find #{deserializer.resource_class} items with "\
|
99
|
+
"#{deserializer.id_param} in #{missing_values.inspect}"
|
88
100
|
Exceptions::MissingRelationship.new(exception_message, payload)
|
89
101
|
end
|
90
102
|
|
91
|
-
def build_missing_relationship_payload(
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
103
|
+
def build_missing_relationship_payload(collection_ids, values)
|
104
|
+
values.each_with_index.map do |resource_id, index|
|
105
|
+
next if collection_ids.include?(resource_id)
|
106
|
+
|
107
|
+
ExceptionPayload.new.tap do |payload|
|
108
|
+
payload.pointer = "/data/relationships/#{name}/#{index}/id"
|
109
|
+
payload.detail = "Unable to find '#{deserializer.type}' with matching id"\
|
110
|
+
": \"#{resource_id}\""
|
111
|
+
end
|
112
|
+
end.reject(&:nil?)
|
96
113
|
end
|
97
114
|
end
|
98
115
|
end
|
@@ -2,7 +2,18 @@ module FunWithJsonApi
|
|
2
2
|
module Attributes
|
3
3
|
class StringAttribute < Attribute
|
4
4
|
def call(value)
|
5
|
-
value
|
5
|
+
return value if value.nil? || value.is_a?(String)
|
6
|
+
|
7
|
+
raise build_invalid_attribute_error(value)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def build_invalid_attribute_error(value)
|
13
|
+
payload = ExceptionPayload.new
|
14
|
+
payload.detail = I18n.t('fun_with_json_api.exceptions.invalid_string_attribute')
|
15
|
+
payload.pointer = "/data/attributes/#{name}"
|
16
|
+
Exceptions::InvalidAttribute.new("Value is not a string: #{value.class.name}", payload)
|
6
17
|
end
|
7
18
|
end
|
8
19
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module FunWithJsonApi
|
2
|
+
module Attributes
|
3
|
+
# Attribute that only accepts a properly generated and formatted UUID version 4
|
4
|
+
# as described in RFC 4122
|
5
|
+
class UuidV4Attribute < Attribute
|
6
|
+
# http://blog.arkency.com/2014/10/how-to-start-using-uuid-in-activerecord-with-postgresql/
|
7
|
+
UUID_V4_REGEX = /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}\z/
|
8
|
+
|
9
|
+
def call(value)
|
10
|
+
return value if value.nil? || value =~ UUID_V4_REGEX
|
11
|
+
|
12
|
+
raise build_invalid_attribute_error(value)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def build_invalid_attribute_error(value)
|
18
|
+
payload = ExceptionPayload.new
|
19
|
+
payload.detail = I18n.t('fun_with_json_api.exceptions.invalid_uuid_v4_attribute')
|
20
|
+
payload.pointer = "/data/attributes/#{name}"
|
21
|
+
Exceptions::InvalidAttribute.new(
|
22
|
+
"Value is not a RFC 4122 Version 4 UUID: #{value.class.name}", payload
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|