jsonapionify 0.9.0 → 0.9.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.
Files changed (84) hide show
  1. checksums.yaml +13 -5
  2. data/.rubocop.yml +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +8 -0
  5. data/README.md +85 -3
  6. data/Rakefile +14 -0
  7. data/jsonapionify.gemspec +3 -0
  8. data/lib/jsonapionify/api/action.rb +84 -121
  9. data/lib/jsonapionify/api/attribute.rb +97 -20
  10. data/lib/jsonapionify/api/base/class_methods.rb +5 -4
  11. data/lib/jsonapionify/api/base/delegation.rb +20 -4
  12. data/lib/jsonapionify/api/base/doc_helper.rb +3 -3
  13. data/lib/jsonapionify/api/base/reloader.rb +1 -1
  14. data/lib/jsonapionify/api/base/resource_definitions.rb +28 -15
  15. data/lib/jsonapionify/api/base.rb +6 -0
  16. data/lib/jsonapionify/api/context.rb +18 -5
  17. data/lib/jsonapionify/api/context_delegate.rb +24 -7
  18. data/lib/jsonapionify/api/errors.rb +2 -0
  19. data/lib/jsonapionify/api/errors_object.rb +6 -5
  20. data/lib/jsonapionify/api/relationship/blocks.rb +1 -1
  21. data/lib/jsonapionify/api/relationship/many.rb +35 -11
  22. data/lib/jsonapionify/api/relationship/one.rb +17 -7
  23. data/lib/jsonapionify/api/relationship.rb +20 -6
  24. data/lib/jsonapionify/api/resource/builders.rb +81 -30
  25. data/lib/jsonapionify/api/resource/caching.rb +28 -0
  26. data/lib/jsonapionify/api/resource/caller.rb +61 -0
  27. data/lib/jsonapionify/api/resource/class_methods.rb +6 -2
  28. data/lib/jsonapionify/api/resource/defaults/actions.rb +47 -0
  29. data/lib/jsonapionify/api/resource/defaults/errors.rb +61 -15
  30. data/lib/jsonapionify/api/resource/defaults/hooks.rb +68 -0
  31. data/lib/jsonapionify/api/resource/defaults/options.rb +16 -28
  32. data/lib/jsonapionify/api/resource/defaults/params.rb +3 -0
  33. data/lib/jsonapionify/api/resource/defaults/request_contexts.rb +80 -32
  34. data/lib/jsonapionify/api/resource/defaults/response_contexts.rb +13 -6
  35. data/lib/jsonapionify/api/resource/defaults.rb +1 -1
  36. data/lib/jsonapionify/api/resource/definitions/actions.rb +81 -55
  37. data/lib/jsonapionify/api/resource/definitions/attributes.rb +46 -10
  38. data/lib/jsonapionify/api/resource/definitions/contexts.rb +6 -2
  39. data/lib/jsonapionify/api/resource/definitions/helpers.rb +1 -1
  40. data/lib/jsonapionify/api/resource/definitions/pagination.rb +47 -56
  41. data/lib/jsonapionify/api/resource/definitions/params.rb +11 -15
  42. data/lib/jsonapionify/api/resource/definitions/relationships.rb +43 -7
  43. data/lib/jsonapionify/api/resource/definitions/request_headers.rb +6 -3
  44. data/lib/jsonapionify/api/resource/definitions/response_headers.rb +1 -1
  45. data/lib/jsonapionify/api/resource/definitions/scopes.rb +5 -5
  46. data/lib/jsonapionify/api/resource/definitions/sorting.rb +12 -11
  47. data/lib/jsonapionify/api/resource/definitions.rb +1 -1
  48. data/lib/jsonapionify/api/resource/error_handling.rb +92 -20
  49. data/lib/jsonapionify/api/resource/exec.rb +11 -0
  50. data/lib/jsonapionify/api/resource/includer.rb +89 -1
  51. data/lib/jsonapionify/api/resource.rb +55 -8
  52. data/lib/jsonapionify/api/response.rb +43 -14
  53. data/lib/jsonapionify/api/server/media_type.rb +36 -0
  54. data/lib/jsonapionify/api/server/request.rb +25 -11
  55. data/lib/jsonapionify/api/server.rb +8 -4
  56. data/lib/jsonapionify/api/sort_field.rb +18 -0
  57. data/lib/jsonapionify/api/sort_field_set.rb +1 -1
  58. data/lib/jsonapionify/api/test_helper.rb +46 -0
  59. data/lib/jsonapionify/documentation/template.erb +2 -2
  60. data/lib/jsonapionify/documentation.rb +10 -0
  61. data/lib/jsonapionify/structure/collections/base.rb +10 -3
  62. data/lib/jsonapionify/structure/helpers/object_defaults.rb +5 -10
  63. data/lib/jsonapionify/structure/maps/relationships.rb +4 -0
  64. data/lib/jsonapionify/structure/objects/attributes.rb +4 -0
  65. data/lib/jsonapionify/structure/objects/base.rb +22 -9
  66. data/lib/jsonapionify/structure/objects/error.rb +2 -0
  67. data/lib/jsonapionify/structure/objects/jsonapi.rb +1 -0
  68. data/lib/jsonapionify/structure/objects/link.rb +1 -0
  69. data/lib/jsonapionify/structure/objects/relationship.rb +2 -0
  70. data/lib/jsonapionify/structure/objects/resource.rb +2 -0
  71. data/lib/jsonapionify/structure/objects/resource_identifier.rb +12 -4
  72. data/lib/jsonapionify/structure/objects/top_level.rb +4 -2
  73. data/lib/jsonapionify/types/array_type.rb +16 -11
  74. data/lib/jsonapionify/types/boolean_type.rb +9 -4
  75. data/lib/jsonapionify/types/date_string_type.rb +7 -10
  76. data/lib/jsonapionify/types/float_type.rb +13 -0
  77. data/lib/jsonapionify/types/integer_type.rb +12 -0
  78. data/lib/jsonapionify/types/object_type.rb +7 -2
  79. data/lib/jsonapionify/types/string_type.rb +12 -0
  80. data/lib/jsonapionify/types/time_string_type.rb +8 -10
  81. data/lib/jsonapionify/types.rb +43 -5
  82. data/lib/jsonapionify/version.rb +1 -1
  83. data/lib/jsonapionify.rb +36 -1
  84. metadata +121 -74
@@ -38,6 +38,15 @@ module JSONAPIonify::Api
38
38
  end
39
39
  end
40
40
 
41
+ def contains_arel
42
+ case @order
43
+ when :asc
44
+ :gteq
45
+ when :desc
46
+ :lteq
47
+ end
48
+ end
49
+
41
50
  def outside_operator
42
51
  case @order
43
52
  when :asc
@@ -47,6 +56,15 @@ module JSONAPIonify::Api
47
56
  end
48
57
  end
49
58
 
59
+ def outside_arel
60
+ case @order
61
+ when :asc
62
+ :gt
63
+ when :desc
64
+ :lt
65
+ end
66
+ end
67
+
50
68
  def to_s
51
69
  @str
52
70
  end
@@ -12,7 +12,7 @@ module JSONAPIonify::Api
12
12
  map(&:to_hash).reduce(:merge)
13
13
  end
14
14
 
15
- def reverse
15
+ def invert
16
16
  self.class.new.tap do |set|
17
17
  each do |field|
18
18
  name =
@@ -6,6 +6,23 @@ module JSONAPIonify
6
6
  extend ActiveSupport::Concern
7
7
  include Rack::Test::Methods
8
8
 
9
+ included do
10
+ around(:each) do |example|
11
+ begin
12
+ if example.run && last_response && last_response_json
13
+ aggregate_failures do
14
+ "response failures"
15
+ last_response_error_messages&.each do |message|
16
+ expect(message).to be_empty, message
17
+ end
18
+ end
19
+ end
20
+ rescue Oj::ParseError
21
+ expect(last_response.body).to be_empty, last_response.body
22
+ end
23
+ end
24
+ end
25
+
9
26
  module ClassMethods
10
27
  def set_api(api)
11
28
  define_method(:app) do
@@ -14,6 +31,35 @@ module JSONAPIonify
14
31
  end
15
32
  end
16
33
 
34
+ def last_response_error_messages
35
+ last_response_errors&.map do |error|
36
+ {
37
+ id: error['id'],
38
+ code: error['code'],
39
+ status: error['status'],
40
+ title: error['title'],
41
+ detail: error['detail'],
42
+ links: error['links'],
43
+ source: error['source']&.each_with_object([]) { |(k, v), a|
44
+ a << "\n #{k}: #{v}" if v
45
+ }&.join,
46
+ meta: error['meta']&.except('backtrace')&.each_with_object([]) { |(k, v), a|
47
+ a << "\n #{k}: #{v}" if v
48
+ }&.join,
49
+ ': ': ':',
50
+ backtrace: ["\n", *(error.dig('meta', 'backtrace') || [])].join("\n")
51
+ }.each_with_object([]) { |(k, v), a|
52
+ a << "#{k}: #{v}" if v&.strip
53
+ }.join("\n")
54
+ end
55
+ end
56
+
57
+ def last_response_errors
58
+ last_response_json['errors']
59
+ rescue
60
+ []
61
+ end
62
+
17
63
  def set_headers
18
64
  @set_headers ||= Rack::Utils::HeaderHash.new
19
65
  end
@@ -390,8 +390,8 @@
390
390
  <div class="col-xs-4 attribute-name">
391
391
  <strong><%= attribute.name %></strong>
392
392
  <i class="attribute-type"><%= attribute.type %></i>
393
- <% if attribute.required %>
394
- <p class="optional">required</p>
393
+ <% if attribute.required.present? %>
394
+ <p class="optional">required: <%= attribute.required? %></p>
395
395
  <% else %>
396
396
  <p class="optional">optional</p>
397
397
  <% end %>
@@ -2,10 +2,12 @@ require 'erb'
2
2
  require 'redcarpet'
3
3
  require 'active_support/core_ext/string/inflections'
4
4
  require 'active_support/core_ext/array'
5
+ require 'redcarpet/render_strip'
5
6
 
6
7
  module JSONAPIonify
7
8
  class Documentation
8
9
  using JSONAPIonify::IndentedString
10
+ STRIPPER = Redcarpet::Markdown.new(Redcarpet::Render::StripDown)
9
11
  RENDERER = Redcarpet::Markdown.new(
10
12
  Redcarpet::Render::HTML,
11
13
  autolink: true,
@@ -24,6 +26,14 @@ module JSONAPIonify
24
26
  RENDERER.render(string.deindent)
25
27
  end
26
28
 
29
+ def self.onelinify_markdown(string)
30
+ strip_markdown(string).gsub(/[\r\n\t]/, ' ').strip
31
+ end
32
+
33
+ def self.strip_markdown(string)
34
+ STRIPPER.render(string.deindent)
35
+ end
36
+
27
37
  attr_reader :api
28
38
 
29
39
  def initialize(api, template: nil)
@@ -1,9 +1,10 @@
1
1
  require 'enumerable_observer'
2
2
  require 'active_support/core_ext/module/delegation'
3
+ require 'concurrent'
3
4
 
4
5
  module JSONAPIonify::Structure
5
6
  module Collections
6
- class Base < Array
7
+ class Base < Concurrent::Array
7
8
  include EnumerableObserver
8
9
  include Helpers::InheritsOrigin
9
10
  attr_reader :parent
@@ -72,8 +73,14 @@ module JSONAPIonify::Structure
72
73
  when type_class
73
74
  instance
74
75
  else
75
- raise ValidationError,
76
- " Can 't initialize collection `#{self.class.name}` with a type of `#{instance.class.name}`"
76
+ if type_class < instance.class
77
+ type_class.from_hash instance.to_hash
78
+ else
79
+ raise(
80
+ ValidationError,
81
+ "Can't initialize collection `#{self.class.name}` with a type of `#{instance.class.name}`"
82
+ )
83
+ end
77
84
  end
78
85
  super new_instance
79
86
  end
@@ -59,7 +59,11 @@ module JSONAPIonify::Structure
59
59
  inherited_hash_attribute :implementations, :collections
60
60
 
61
61
  before_initialize do
62
- @unset = {}
62
+ @unset = {}
63
+ newly_set = unset.select { |_, v| v.present? }
64
+ newly_set.each do |k, v|
65
+ self[k] = v
66
+ end
63
67
  end
64
68
  end
65
69
 
@@ -80,15 +84,6 @@ module JSONAPIonify::Structure
80
84
  super k, coerce_value(k, v)
81
85
  end
82
86
 
83
- def object
84
- super.tap do
85
- newly_set = unset.select { |_, v| v.present? }
86
- newly_set.each do |k, v|
87
- self[k] = v
88
- end
89
- end
90
- end
91
-
92
87
  private
93
88
 
94
89
  def coerce_value(k, v)
@@ -18,6 +18,10 @@ module JSONAPIonify::Structure
18
18
 
19
19
  value_is Objects::Relationship, strict: true
20
20
 
21
+ def to_hash
22
+ super.sort.to_h
23
+ end
24
+
21
25
  end
22
26
  end
23
27
  end
@@ -24,6 +24,10 @@ module JSONAPIonify::Structure
24
24
  # **SHOULD NOT** appear as attributes.
25
25
  should_not_contain! { |key| key.to_s.end_with? '_id' }
26
26
 
27
+ def to_hash
28
+ super.sort.to_h
29
+ end
30
+
27
31
  end
28
32
  end
29
33
  end
@@ -5,6 +5,7 @@ require 'active_support/core_ext/hash'
5
5
  require 'active_support/core_ext/array/conversions'
6
6
  require 'active_support/core_ext/hash/keys'
7
7
  require 'enumerable_observer'
8
+ require 'concurrent'
8
9
 
9
10
  module JSONAPIonify::Structure
10
11
  module Objects
@@ -24,12 +25,11 @@ module JSONAPIonify::Structure
24
25
  # Attributes
25
26
  attr_reader :object, :parent
26
27
 
27
- delegate :fetch, :select, :has_key?, :keys, :values, :each, :present?, :blank?, :empty?, to: :object
28
28
  delegate :cache_store, to: JSONAPIonify
29
+ delegate *(Hash.instance_methods - instance_methods), to: :object
29
30
 
30
31
  before_initialize do
31
- @object = {}
32
- observe(@object).added do |items|
32
+ observe(self.object).added do |items|
33
33
  items.each do |_, value|
34
34
  value.instance_variable_set(:@parent, self) unless value.frozen?
35
35
  end
@@ -40,12 +40,21 @@ module JSONAPIonify::Structure
40
40
  new hash.deep_symbolize_keys
41
41
  end
42
42
 
43
+ def self.define_order(*keys)
44
+ define_singleton_method :ordered_keys do
45
+ keys
46
+ end
47
+ end
48
+
49
+ define_order
50
+
43
51
  def self.from_json(json)
44
52
  from_hash Oj.load json
45
53
  end
46
54
 
47
55
  # Initialize the object
48
56
  def initialize(**attributes)
57
+ @object = Concurrent::Hash.new
49
58
  run_callbacks :initialize do
50
59
  attributes.each do |k, v|
51
60
  self[k] = v
@@ -72,16 +81,16 @@ module JSONAPIonify::Structure
72
81
  attr_reader :errors, :warnings
73
82
 
74
83
  def compile(validate: true)
75
- self.validate if validate
84
+ self.validate if validate && !JSONAPIonify.validation_disabled?
76
85
  to_hash
77
86
  end
78
87
 
79
- def as_json
80
- compile.deep_stringify_keys
88
+ def as_json(**opts)
89
+ compile(**opts).deep_stringify_keys
81
90
  end
82
91
 
83
- def to_json
84
- Oj.dump(as_json)
92
+ def to_json(**opts)
93
+ Oj.dump(as_json **opts)
85
94
  end
86
95
 
87
96
  def signature
@@ -89,7 +98,11 @@ module JSONAPIonify::Structure
89
98
  end
90
99
 
91
100
  def to_hash
92
- object.reduce({}) do |hash, (k, v)|
101
+ ordered_object = [
102
+ *object.slice(*self.class.ordered_keys),
103
+ *object.except(*self.class.ordered_keys)
104
+ ]
105
+ ordered_object.reduce({}) do |hash, (k, v)|
93
106
  hash[k] =
94
107
  case v
95
108
  when Objects::Base
@@ -1,6 +1,8 @@
1
1
  module JSONAPIonify::Structure
2
2
  module Objects
3
3
  class Error < Base
4
+ define_order *%i{id code status source title detail meta links}
5
+
4
6
  may_contain! :id, :links, :status, :code, :title, :detail, :source, :meta
5
7
 
6
8
  implements :links, as: Maps::ErrorLinks
@@ -1,6 +1,7 @@
1
1
  module JSONAPIonify::Structure
2
2
  module Objects
3
3
  class Jsonapi < Base
4
+ define_order *%i{version meta}
4
5
  may_contain! :version, :meta
5
6
  end
6
7
  end
@@ -1,6 +1,7 @@
1
1
  module JSONAPIonify::Structure
2
2
  module Objects
3
3
  class Link < Base
4
+ define_order *%i{href meta}
4
5
 
5
6
  may_contain! :href, :meta
6
7
 
@@ -1,6 +1,8 @@
1
1
  module JSONAPIonify::Structure
2
2
  module Objects
3
3
  class Relationship < Base
4
+ define_order *%i{data meta links}
5
+
4
6
  # A "relationship object" MUST contain at least one of the following:
5
7
  must_contain_one_of! :links, # A links object.
6
8
  :data, # Resource linkage.
@@ -2,6 +2,8 @@ module JSONAPIonify::Structure
2
2
  module Objects
3
3
  # ResourceObjects appear in a JSON API document to represent resources.
4
4
  class Resource < ResourceIdentifier
5
+ define_order *%i{type id attributes relationships meta links}
6
+
5
7
  # The `id` member is not required when the resource object originates at the
6
8
  # client and represents a new resource to be created on the server.
7
9
  must_contain! :id, if: ->(obj) { obj.server? } # an id representing the resource
@@ -1,6 +1,8 @@
1
1
  module JSONAPIonify::Structure
2
2
  module Objects
3
3
  class ResourceIdentifier < Base
4
+ define_order *%i{type id}
5
+
4
6
  # A resource object **MUST** contain at least the following top-level members:
5
7
  must_contain! :id, :type # Describes ResourceObjects that share common attributes and relationships.
6
8
 
@@ -21,7 +23,11 @@ module JSONAPIonify::Structure
21
23
  def duplicate_exists?
22
24
  return false unless parent.is_a?(Array)
23
25
  peers = parent - [self]
24
- peers.any? { |peer| same_as? peer }
26
+ !!peers.index(self)
27
+ end
28
+
29
+ def ==(other)
30
+ same_as? other
25
31
  end
26
32
 
27
33
  def duplicate_does_not_exist?
@@ -30,9 +36,11 @@ module JSONAPIonify::Structure
30
36
 
31
37
  def same_as?(other)
32
38
  return false unless other.is_a? ResourceIdentifier
33
- matches_id = other.has_key?(:id) && has_key?(:id) && other[:id] == self[:id]
34
- matches_type = other[:type] == self[:type]
35
- matches_type && matches_id
39
+ other_type, other_id = other.values_at :type, :id
40
+ local_type, local_id = values_at :type, :id
41
+ other_type == local_type &&
42
+ !(other_id || local_id).nil? &&
43
+ other_id == local_id
36
44
  end
37
45
 
38
46
  end
@@ -8,6 +8,8 @@ module JSONAPIonify::Structure
8
8
  class TopLevel < Base
9
9
  attr_reader :origin
10
10
 
11
+ define_order *%i{jsonapi data included errors meta links}
12
+
11
13
  default(:jsonapi) { Jsonapi.new version: '1.0' }
12
14
 
13
15
  # A document **MUST** contain at least one of:
@@ -75,8 +77,8 @@ module JSONAPIonify::Structure
75
77
  Collections::ResourceIdentifiers
76
78
  ]
77
79
 
78
- def compile(*)
79
- compiled = super(validate: ENV['RACK_ENV'] != 'production')
80
+ def compile(**opts)
81
+ compiled = super(**opts)
80
82
  compiled_errors = compiled['errors'] || []
81
83
  all_errors = compiled_errors | errors.as_collection.compile
82
84
  if all_errors.present?
@@ -7,24 +7,29 @@ module JSONAPIonify::Types
7
7
  end
8
8
  end
9
9
 
10
- def load(value)
11
- super unless options[:of]
12
- value.map do |item|
13
- options[:of].load(item)
10
+ def sample(field_name)
11
+ field_name = field_name.to_s.singularize.to_sym
12
+ 3.times.map do
13
+ (options[:of] || StringType.new).sample(field_name)
14
14
  end
15
15
  end
16
16
 
17
- def dump(value)
18
- super unless options[:of]
17
+ loader do |value|
18
+ raise LoadError, 'invalid type' unless value.is_a?(Array)
19
+ return super(value) unless options[:of]
19
20
  value.map do |item|
20
- options[:of].dump(item)
21
+ options[:of].load(item)
21
22
  end
22
23
  end
23
24
 
24
- def sample(field_name)
25
- field_name = field_name.to_s.singularize.to_sym
26
- 3.times.map do
27
- (options[:of] || StringType.new).sample(field_name)
25
+ dumper do |value|
26
+ raise DumpError, 'cannot convert value to array' unless value.respond_to?(:to_a)
27
+ value.to_a.tap do |array|
28
+ raise DumpError, 'output value was not an array' unless array.is_a? Array
29
+ end
30
+ return super(value) unless options[:of]
31
+ value.map do |item|
32
+ options[:of].dump(item)
28
33
  end
29
34
  end
30
35
 
@@ -1,16 +1,21 @@
1
1
  module JSONAPIonify::Types
2
2
  class BooleanType < BaseType
3
3
 
4
- def load(value)
5
- value
4
+ loader do |value|
5
+ case value
6
+ when true, false
7
+ value
8
+ else
9
+ raise LoadError, "#{value} is not a valid JSON #{name}."
10
+ end
6
11
  end
7
12
 
8
- def dump(value)
13
+ dumper do |value|
9
14
  case value
10
15
  when true, false
11
16
  value
12
17
  else
13
- raise TypeError, "#{value} is not a valid JSON #{name}."
18
+ raise DumpError, "#{value} is not a valid JSON #{name}."
14
19
  end
15
20
  end
16
21
 
@@ -1,18 +1,14 @@
1
1
  require 'faker'
2
2
 
3
3
  module JSONAPIonify::Types
4
- class DateStringType < BaseType
5
- def load(value)
6
- Date.parse value
4
+ class DateStringType < StringType
5
+ loader do |value|
6
+ Date.parse super(value)
7
7
  end
8
8
 
9
- def dump(value)
10
- case value
11
- when Date
12
- JSON.load JSON.dump(value.to_date)
13
- else
14
- raise TypeError, "#{value} is not a valid JSON #{name}."
15
- end
9
+ dumper do |value|
10
+ raise DumpError, 'cannot convert value to date' unless value.respond_to?(:to_date)
11
+ JSON.load JSON.dump(value.to_date)
16
12
  end
17
13
 
18
14
  def sample(field_name)
@@ -26,3 +22,4 @@ module JSONAPIonify::Types
26
22
 
27
23
  end
28
24
  end
25
+
@@ -4,5 +4,18 @@ module JSONAPIonify::Types
4
4
  def sample(*)
5
5
  rand(0.0..201.42).round(2)
6
6
  end
7
+
8
+ loader do |value|
9
+ raise LoadError, 'input value was not a float' unless value.is_a?(Float)
10
+ value
11
+ end
12
+
13
+ dumper do |value|
14
+ raise DumpError, 'cannot convert value to float' unless value.respond_to?(:to_f)
15
+ value.to_f.tap do |float|
16
+ raise DumpError, 'output value was not a float' unless float.is_a? Float
17
+ end
18
+ end
19
+
7
20
  end
8
21
  end
@@ -5,5 +5,17 @@ module JSONAPIonify::Types
5
5
  rand(1..123)
6
6
  end
7
7
 
8
+ loader do |value|
9
+ raise LoadError, 'input value was not an integer' unless value.is_a?(Fixnum)
10
+ value
11
+ end
12
+
13
+ dumper do |value|
14
+ raise DumpError, 'cannot convert value to integer' unless value.respond_to?(:to_i)
15
+ value.to_i.tap do |int|
16
+ raise DumpError, 'output value was not a integer' unless int.is_a? Fixnum
17
+ end
18
+ end
19
+
8
20
  end
9
21
  end
@@ -3,11 +3,16 @@ require 'active_support/core_ext/hash/keys'
3
3
  module JSONAPIonify::Types
4
4
  class ObjectType < BaseType
5
5
 
6
- def load(value)
6
+ loader do |value|
7
+ raise LoadError, 'invalid type' unless value.is_a?(Hash)
7
8
  super(value).deep_symbolize_keys
8
9
  end
9
10
 
10
- def dump(value)
11
+ dumper do |value|
12
+ raise DumpError, 'cannot convert value to hash' unless value.respond_to?(:to_h)
13
+ value = value.to_h.tap do |hash|
14
+ raise DumpError, 'output value was not a hash' unless hash.is_a? Hash
15
+ end
11
16
  super(value.deep_stringify_keys)
12
17
  end
13
18
 
@@ -59,6 +59,18 @@ module JSONAPIonify::Types
59
59
 
60
60
  end
61
61
 
62
+ loader do |value|
63
+ raise LoadError, 'input value was not a string' unless value.is_a?(String)
64
+ value
65
+ end
66
+
67
+ dumper do |value|
68
+ raise DumpError, 'cannot convert value to string' unless value.respond_to?(:to_s)
69
+ value.to_s.tap do |string|
70
+ raise DumpError, 'output value was not a string' unless string.is_a? String
71
+ end
72
+ end
73
+
62
74
  def sample(field_name)
63
75
  StringSampler.new(field_name).value
64
76
  end
@@ -1,18 +1,14 @@
1
1
  require 'faker'
2
2
 
3
3
  module JSONAPIonify::Types
4
- class TimeStringType < BaseType
5
- def load(value)
6
- Time.parse value
4
+ class TimeStringType < StringType
5
+ loader do |value|
6
+ Time.parse super(value)
7
7
  end
8
8
 
9
- def dump(value)
10
- case value
11
- when Time
12
- JSON.load JSON.dump(value.to_time)
13
- else
14
- raise TypeError, "#{value} is not a valid JSON #{name}."
15
- end
9
+ dumper do |value|
10
+ raise DumpError, 'cannot convert value to time' unless value.respond_to?(:to_time)
11
+ JSON.load JSON.dump(value.to_time.utc)
16
12
  end
17
13
 
18
14
  def sample(field_name)
@@ -21,6 +17,8 @@ module JSONAPIonify::Types
21
17
  Faker::Time.backward
22
18
  elsif field_name.include?('end')
23
19
  Faker::Time.forward
20
+ else
21
+ Faker::Time.backward
24
22
  end
25
23
  end
26
24