fun_with_json_api 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|