taro 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +22 -0
  4. data/CHANGELOG.md +5 -0
  5. data/README.md +262 -1
  6. data/Rakefile +11 -0
  7. data/lib/taro/config.rb +22 -0
  8. data/lib/taro/errors.rb +6 -0
  9. data/lib/taro/export/base.rb +29 -0
  10. data/lib/taro/export/open_api_v3.rb +189 -0
  11. data/lib/taro/export.rb +3 -0
  12. data/lib/taro/rails/active_declarations.rb +19 -0
  13. data/lib/taro/rails/declaration.rb +101 -0
  14. data/lib/taro/rails/declaration_buffer.rb +24 -0
  15. data/lib/taro/rails/dsl.rb +18 -0
  16. data/lib/taro/rails/generators/install_generator.rb +19 -0
  17. data/lib/taro/rails/generators/templates/enum_type.erb +4 -0
  18. data/lib/taro/rails/generators/templates/error_type.erb +10 -0
  19. data/lib/taro/rails/generators/templates/errors_type.erb +25 -0
  20. data/lib/taro/rails/generators/templates/input_type.erb +4 -0
  21. data/lib/taro/rails/generators/templates/no_content_type.erb +4 -0
  22. data/lib/taro/rails/generators/templates/object_type.erb +4 -0
  23. data/lib/taro/rails/generators/templates/scalar_type.erb +4 -0
  24. data/lib/taro/rails/generators.rb +3 -0
  25. data/lib/taro/rails/normalized_route.rb +29 -0
  26. data/lib/taro/rails/param_parsing.rb +19 -0
  27. data/lib/taro/rails/railtie.rb +15 -0
  28. data/lib/taro/rails/response_validation.rb +63 -0
  29. data/lib/taro/rails/route_finder.rb +35 -0
  30. data/lib/taro/rails/tasks/export.rake +15 -0
  31. data/lib/taro/rails.rb +18 -0
  32. data/lib/taro/types/base_type.rb +17 -0
  33. data/lib/taro/types/coercion.rb +72 -0
  34. data/lib/taro/types/enum_type.rb +43 -0
  35. data/lib/taro/types/field.rb +78 -0
  36. data/lib/taro/types/field_validation.rb +27 -0
  37. data/lib/taro/types/input_type.rb +13 -0
  38. data/lib/taro/types/list_type.rb +30 -0
  39. data/lib/taro/types/object_type.rb +19 -0
  40. data/lib/taro/types/object_types/free_form_type.rb +13 -0
  41. data/lib/taro/types/object_types/no_content_type.rb +16 -0
  42. data/lib/taro/types/object_types/page_info_type.rb +6 -0
  43. data/lib/taro/types/object_types/page_type.rb +45 -0
  44. data/lib/taro/types/scalar/boolean_type.rb +19 -0
  45. data/lib/taro/types/scalar/float_type.rb +15 -0
  46. data/lib/taro/types/scalar/integer_type.rb +11 -0
  47. data/lib/taro/types/scalar/iso8601_date_type.rb +23 -0
  48. data/lib/taro/types/scalar/iso8601_datetime_type.rb +25 -0
  49. data/lib/taro/types/scalar/string_type.rb +15 -0
  50. data/lib/taro/types/scalar/timestamp_type.rb +23 -0
  51. data/lib/taro/types/scalar/uuid_v4_type.rb +22 -0
  52. data/lib/taro/types/scalar_type.rb +7 -0
  53. data/lib/taro/types/shared/additional_properties.rb +12 -0
  54. data/lib/taro/types/shared/custom_field_resolvers.rb +33 -0
  55. data/lib/taro/types/shared/derivable_types.rb +9 -0
  56. data/lib/taro/types/shared/description.rb +9 -0
  57. data/lib/taro/types/shared/errors.rb +13 -0
  58. data/lib/taro/types/shared/fields.rb +57 -0
  59. data/lib/taro/types/shared/item_type.rb +16 -0
  60. data/lib/taro/types/shared/object_coercion.rb +16 -0
  61. data/lib/taro/types/shared/openapi_name.rb +30 -0
  62. data/lib/taro/types/shared/openapi_type.rb +27 -0
  63. data/lib/taro/types/shared/rendering.rb +36 -0
  64. data/lib/taro/types/shared.rb +3 -0
  65. data/lib/taro/types.rb +3 -0
  66. data/lib/taro/version.rb +2 -3
  67. data/lib/taro.rb +1 -6
  68. data/tasks/benchmark.rake +40 -0
  69. data/tasks/benchmark_1kb.json +23 -0
  70. metadata +90 -7
@@ -0,0 +1,16 @@
1
+ class Taro::Types::ObjectTypes::NoContentType < Taro::Types::ObjectType
2
+ self.desc = 'An empty response'
3
+
4
+ # render takes no arguments in this case
5
+ def self.render
6
+ super(nil)
7
+ end
8
+
9
+ def coerce_input
10
+ input_error 'NoContentType cannot be used as input type'
11
+ end
12
+
13
+ def coerce_response
14
+ {}
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ class Taro::Types::ObjectTypes::PageInfoType < Taro::Types::ObjectType
2
+ field :has_previous_page, type: 'Boolean', null: false, desc: 'Whether there is a previous page of results'
3
+ field :has_next_page, type: 'Boolean', null: false, desc: 'Whether there is another page of results'
4
+ field :start_cursor, type: 'String', null: true, desc: 'The first cursor in the current page of results (null if zero results)'
5
+ field :end_cursor, type: 'String', null: true, desc: 'The last cursor in the current page of results (null if zero results)'
6
+ end
@@ -0,0 +1,45 @@
1
+ # Abstract base class for Page types (paginated ActiveRecord data).
2
+ # Unlike other types, this one should not be manually inherited from,
3
+ # but is used indirectly via `page_of: SomeType`.
4
+ #
5
+ # The gem rails_cursor_pagination must be installed to use this.
6
+ #
7
+ class Taro::Types::ObjectTypes::PageType < Taro::Types::BaseType
8
+ extend Taro::Types::Shared::DerivableType
9
+ extend Taro::Types::Shared::ItemType
10
+
11
+ def coerce_input
12
+ input_error 'PageTypes cannot be used as input types'
13
+ end
14
+
15
+ def coerce_response(after:, limit: 20, order_by: nil, order: nil)
16
+ list = RailsCursorPagination::Paginator.new(
17
+ object, limit:, order_by:, order:, after:
18
+ ).fetch
19
+ coerce_paginated_list(list)
20
+ end
21
+
22
+ def coerce_paginated_list(list)
23
+ item_type = self.class.item_type
24
+ items = list[:page].map do |item|
25
+ item_type.new(item[:data]).coerce_response
26
+ end
27
+
28
+ {
29
+ self.class.items_key => items,
30
+ page_info: Taro::Types::ObjectTypes::PageInfoType.new(list[:page_info]).coerce_response,
31
+ }
32
+ end
33
+
34
+ # support overrides, e.g. based on item_type
35
+ def self.items_key
36
+ :page
37
+ end
38
+ end
39
+
40
+ # add shortcut to other types
41
+ class Taro::Types::BaseType
42
+ def self.page
43
+ Taro::Types::ObjectTypes::PageType.for(self)
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ class Taro::Types::Scalar::BooleanType < Taro::Types::ScalarType
2
+ self.openapi_type = :boolean
3
+
4
+ def coerce_input
5
+ if object == true || object == false
6
+ object
7
+ else
8
+ input_error('must be true or false')
9
+ end
10
+ end
11
+
12
+ def coerce_response
13
+ if object == true || object == false
14
+ object
15
+ else
16
+ response_error('must be true or false')
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ class Taro::Types::Scalar::FloatType < Taro::Types::ScalarType
2
+ self.openapi_type = :number
3
+
4
+ def coerce_input
5
+ object.instance_of?(Float) ? object : input_error('must be a Float')
6
+ end
7
+
8
+ def coerce_response
9
+ case object
10
+ when Float then object
11
+ when Integer then object.to_f
12
+ else response_error('must be a Float or Integer')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ class Taro::Types::Scalar::IntegerType < Taro::Types::ScalarType
2
+ self.openapi_type = :integer
3
+
4
+ def coerce_input
5
+ object.instance_of?(Integer) ? object : input_error('must be an Integer')
6
+ end
7
+
8
+ def coerce_response
9
+ object.instance_of?(Integer) ? object : response_error('must be an Integer')
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ class Taro::Types::Scalar::ISO8601DateType < Taro::Types::ScalarType
2
+ self.desc = 'Represents a time as Date in ISO8601 format.'
3
+ self.openapi_type = :string
4
+
5
+ PATTERN = /\A\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])\z/
6
+
7
+ def coerce_input
8
+ if object.instance_of?(String) && object.match?(PATTERN)
9
+ Date.parse(object)
10
+ else
11
+ input_error("must be a ISO8601 formatted string")
12
+ end
13
+ end
14
+
15
+ def coerce_response
16
+ case object
17
+ when Date, DateTime, Time
18
+ object.strftime("%Y-%m-%d")
19
+ else
20
+ response_error("must be a Time, Date, or DateTime")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ class Taro::Types::Scalar::ISO8601DateTimeType < Taro::Types::ScalarType
2
+ self.desc = 'Represents a time as DateTime in ISO8601 format.'
3
+ self.openapi_type = :string
4
+
5
+ PATTERN = /\A\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(Z|[+-](0[0-9]|1[0-4]):[0-5]\d)\z/
6
+
7
+ def coerce_input
8
+ if object.instance_of?(String) && object.match?(PATTERN)
9
+ DateTime.iso8601(object)
10
+ else
11
+ input_error("must be a ISO8601 formatted string")
12
+ end
13
+ end
14
+
15
+ def coerce_response
16
+ case object
17
+ when Date
18
+ object.to_datetime.utc.iso8601
19
+ when DateTime, Time
20
+ object.utc.iso8601
21
+ else
22
+ response_error("must be a Time, Date, or DateTime")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ class Taro::Types::Scalar::StringType < Taro::Types::ScalarType
2
+ self.openapi_type = :string
3
+
4
+ def coerce_input
5
+ object.instance_of?(String) ? object : input_error('must be a String')
6
+ end
7
+
8
+ def coerce_response
9
+ case object
10
+ when String then object
11
+ when Symbol then object.to_s
12
+ else response_error('must be a String or Symbol')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ class Taro::Types::Scalar::TimestampType < Taro::Types::ScalarType
2
+ self.desc = 'Represents a time as Time on the server side and UNIX timestamp (integer) on the client side.'
3
+ self.openapi_type = :integer
4
+
5
+ def coerce_input
6
+ if object.instance_of?(Integer)
7
+ Time.at(object)
8
+ else
9
+ input_error("must be an Integer")
10
+ end
11
+ end
12
+
13
+ def coerce_response
14
+ case object
15
+ when Date, DateTime, Time
16
+ object.strftime('%s').to_i
17
+ when Integer
18
+ object
19
+ else
20
+ response_error("must be a Time, Date, DateTime, or Integer")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ class Taro::Types::Scalar::UUIDv4Type < Taro::Types::ScalarType
2
+ self.desc = "A UUID v4 string"
3
+ self.openapi_type = :string
4
+
5
+ PATTERN = /\A\h{8}-?(?:\h{4}-?){3}\h{12}\z/
6
+
7
+ def coerce_input
8
+ if object.is_a?(String) && object.match?(PATTERN)
9
+ object
10
+ else
11
+ input_error("must be a UUID v4 string")
12
+ end
13
+ end
14
+
15
+ def coerce_response
16
+ if object.is_a?(String) && object.match?(PATTERN)
17
+ object
18
+ else
19
+ response_error("must be a UUID v4 string")
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ # Abstract base class for scalar types, i.e. types without fields.
2
+ class Taro::Types::ScalarType < Taro::Types::BaseType
3
+ end
4
+
5
+ module Taro::Types::Scalar
6
+ Dir[File.join(__dir__, 'scalar', '**', '*.rb')].each { |f| require f }
7
+ end
@@ -0,0 +1,12 @@
1
+ module Taro::Types::Shared::AdditionalProperties
2
+ attr_writer :additional_properties
3
+
4
+ def additional_properties?
5
+ !!@additional_properties
6
+ end
7
+
8
+ def inherited(subclass)
9
+ super
10
+ subclass.additional_properties = @additional_properties
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ # Allows implementing methods on types to override or implement the method
2
+ # used to retrieve the value of a field.
3
+ module Taro::Types::Shared::CustomFieldResolvers
4
+ def resolve?(method)
5
+ self.class.custom_resolvers.key?(method)
6
+ end
7
+
8
+ def self.included(mod)
9
+ mod.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def custom_resolvers
14
+ @custom_resolvers ||= {}
15
+ end
16
+
17
+ def method_added(name)
18
+ if name == :object
19
+ raise(Taro::ArgumentError, '#object is a reserved, internally used method name')
20
+ elsif ![:coerce_input, :coerce_response].include?(name) &&
21
+ !self.name.to_s.start_with?('Taro::Types::')
22
+ custom_resolvers[name] = true
23
+ end
24
+
25
+ super
26
+ end
27
+
28
+ def inherited(subclass)
29
+ subclass.instance_variable_set(:@custom_resolvers, custom_resolvers.dup)
30
+ super
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module Taro::Types::Shared::DerivableType
2
+ def for(type)
3
+ derived_types[type] ||= Class.new(self).tap { |t| t.item_type = type }
4
+ end
5
+
6
+ def derived_types
7
+ @derived_types ||= {}
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Taro::Types::Shared::Description
2
+ def desc
3
+ @desc
4
+ end
5
+
6
+ def desc=(arg)
7
+ @desc = arg
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Taro::Types::Shared::Errors
2
+ def input_error(msg)
3
+ raise Taro::InputError, coerce_error_message(msg)
4
+ end
5
+
6
+ def response_error(msg)
7
+ raise Taro::ResponseError, coerce_error_message(msg)
8
+ end
9
+
10
+ def coerce_error_message(msg)
11
+ "#{object.inspect} (#{object.class}) is not valid as #{self.class}: #{msg}"
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ # Adds the `::field` method to object and input types.
2
+ module Taro::Types::Shared::Fields
3
+ # Field types are set using class name Strings. The respective type classes
4
+ # are evaluated lazily to allow for circular or recursive type references,
5
+ # and to avoid unnecessary eager loading of all types in dev/test envs.
6
+ def field(name, **kwargs)
7
+ defined_at = kwargs[:defined_at] || caller_locations(1..1)[0]
8
+ validate_name(name, defined_at:)
9
+ validate_no_override(name, defined_at:)
10
+ validate_options(name, defined_at:, **kwargs)
11
+
12
+ field_defs[name] = { name:, defined_at:, **kwargs }
13
+ end
14
+
15
+ def fields
16
+ @fields ||= evaluate_field_defs
17
+ end
18
+
19
+ private
20
+
21
+ def validate_name(name, defined_at:)
22
+ name.is_a?(Symbol) ||
23
+ raise(Taro::ArgumentError, "field name must be a Symbol, got #{name.class} at #{defined_at}")
24
+ end
25
+
26
+ def validate_options(name, defined_at:, **kwargs)
27
+ [true, false].include?(kwargs[:null]) ||
28
+ raise(Taro::ArgumentError, "null has to be specified as true or false for field #{name} at #{defined_at}")
29
+
30
+ (type_keys = (kwargs.keys & Taro::Types::Coercion::KEYS)).size == 1 ||
31
+ raise(Taro::ArgumentError, "exactly one of type, array_of, or page_of must be given for field #{name} at #{defined_at}")
32
+
33
+ kwargs[type_keys.first].class == String ||
34
+ raise(Taro::ArgumentError, "#{type_key} must be a String for field #{name} at #{defined_at}")
35
+ end
36
+
37
+ def validate_no_override(name, defined_at:)
38
+ prev = field_defs[name]
39
+ prev && raise(Taro::ArgumentError, "field #{name} at #{defined_at} previously defined at #{prev[:defined_at]}")
40
+ end
41
+
42
+ def field_defs
43
+ @field_defs ||= {}
44
+ end
45
+
46
+ def evaluate_field_defs
47
+ field_defs.transform_values do |field_def|
48
+ type = Taro::Types::Coercion.call(field_def)
49
+ Taro::Types::Field.new(**field_def.except(*Taro::Types::Coercion::KEYS), type:)
50
+ end
51
+ end
52
+
53
+ def inherited(subclass)
54
+ subclass.instance_variable_set(:@field_defs, field_defs.dup)
55
+ super
56
+ end
57
+ end
@@ -0,0 +1,16 @@
1
+ # Provides coercion and validation for the "inner" type of enums and arrays.
2
+ module Taro::Types::Shared::ItemType
3
+ attr_reader :item_type
4
+
5
+ def item_type=(new_type)
6
+ item_type.nil? || new_type == item_type || raise_mixed_types(new_type)
7
+ @item_type = new_type
8
+ end
9
+
10
+ def raise_mixed_types(new_type)
11
+ raise Taro::ArgumentError, <<~MSG
12
+ All items must be of the same type. Mixed types are not supported for now.
13
+ Expected another #{item_type} item but got a #{new_type} for #{self}.
14
+ MSG
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Provides input and response handling for types with fields.
2
+ module Taro::Types::Shared::ObjectCoercion
3
+ def coerce_input
4
+ self.class.fields.transform_values do |field|
5
+ field.value_for_input(object)
6
+ end
7
+ end
8
+
9
+ # Render the object into a hash.
10
+ def coerce_response
11
+ object_is_hash = object.is_a?(Hash)
12
+ self.class.fields.transform_values do |field|
13
+ field.value_for_response(object, context: self, object_is_hash:)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ # Provides a setter, getter and defaults for type classes' `openapi_name`,
2
+ # for use in the OpenAPI export ($refs and corresponding component names).
3
+ module Taro::Types::Shared::OpenAPIName
4
+ def openapi_name
5
+ @openapi_name ||= default_openapi_name
6
+ end
7
+
8
+ def openapi_name=(arg)
9
+ arg.nil? || arg.is_a?(String) ||
10
+ raise(Taro::ArgumentError, 'openapi_name must be a String')
11
+ @openapi_name = arg
12
+ end
13
+
14
+ def default_openapi_name # rubocop:disable Metrics
15
+ if self < Taro::Types::EnumType ||
16
+ self < Taro::Types::InputType ||
17
+ self < Taro::Types::ObjectType
18
+ name && name.chomp('Type').gsub('::', '_') ||
19
+ raise(Taro::Error, 'openapi_name must be set for anonymous type classes')
20
+ elsif self < Taro::Types::ScalarType
21
+ openapi_type
22
+ elsif self < Taro::Types::ListType
23
+ "#{item_type.openapi_name}_List"
24
+ elsif self < Taro::Types::ObjectTypes::PageType
25
+ "#{item_type.openapi_name}_Page"
26
+ else
27
+ raise NotImplementedError, 'no default_openapi_name for this type'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # Provides a setter and getter for type classes' `openapi_type`,
2
+ # for use in the OpenAPI export.
3
+ module Taro::Types::Shared::OpenAPIType
4
+ OPENAPI_TYPES = %i[
5
+ array
6
+ boolean
7
+ integer
8
+ number
9
+ object
10
+ string
11
+ ].freeze
12
+
13
+ def openapi_type
14
+ @openapi_type || raise(Taro::RuntimeError, "Type lacks openapi_type: #{self}")
15
+ end
16
+
17
+ def openapi_type=(arg)
18
+ OPENAPI_TYPES.include?(arg) ||
19
+ raise(Taro::ArgumentError, "openapi_type must be a Symbol, one of #{OPENAPI_TYPES}")
20
+ @openapi_type = arg
21
+ end
22
+
23
+ def inherited(subclass)
24
+ subclass.instance_variable_set(:@openapi_type, @openapi_type)
25
+ super
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ # The `::render` method is intended for use in controllers.
2
+ # Special types (e.g. PageType) may accept kwargs for `#coerce_response`.
3
+ module Taro::Types::Shared::Rendering
4
+ def render(object, opts = {})
5
+ if (prev = rendering)
6
+ raise Taro::RuntimeError, <<~MSG
7
+ Type.render should only be called once per request.
8
+ (First called on #{prev}, then on #{self}.)
9
+ MSG
10
+ end
11
+
12
+ result = new(object).coerce_response(**opts)
13
+
14
+ # Only mark this as the used type if coercion worked so that
15
+ # rescue_from can be used to render another type.
16
+ self.rendering = self
17
+
18
+ result
19
+ end
20
+
21
+ def rendering=(value)
22
+ ActiveSupport::IsolatedExecutionState[:taro_type_rendering] = value
23
+ end
24
+
25
+ def rendering
26
+ ActiveSupport::IsolatedExecutionState[:taro_type_rendering]
27
+ end
28
+
29
+ def used_in_response=(value)
30
+ ActiveSupport::IsolatedExecutionState[:taro_type_used_in_response] = value
31
+ end
32
+
33
+ def used_in_response
34
+ ActiveSupport::IsolatedExecutionState[:taro_type_used_in_response]
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module Taro::Types::Shared
2
+ Dir[File.join(__dir__, "shared", "*.rb")].each { |f| require f }
3
+ end
data/lib/taro/types.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Taro::Types
2
+ Dir[File.join(__dir__, "types", "*.rb")].each { |f| require f }
3
+ end
data/lib/taro/version.rb CHANGED
@@ -1,5 +1,4 @@
1
- # frozen_string_literal: true
2
-
1
+ # :nocov:
3
2
  module Taro
4
- VERSION = "0.0.0"
3
+ VERSION = "1.0.0"
5
4
  end
data/lib/taro.rb CHANGED
@@ -1,8 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "taro/version"
4
-
5
1
  module Taro
6
- class Error < StandardError; end
7
- # Your code goes here...
2
+ Dir[File.join(__dir__, "taro", "*.rb")].each { |f| require f }
8
3
  end
@@ -0,0 +1,40 @@
1
+ task :benchmark do
2
+ require 'benchmark/ips'
3
+ require 'json'
4
+ require_relative '../lib/taro'
5
+
6
+ data = JSON.load_file("#{__dir__}/benchmark_1kb.json", symbolize_names: true)
7
+
8
+ item_type = Class.new(Taro::Types::ObjectType) do
9
+ field :name, type: 'String', null: false
10
+ field :language, type: 'String', null: false
11
+ field :id, type: 'String', null: false
12
+ field :bio, type: 'String', null: false
13
+ field :version, type: 'Float', null: false
14
+ end
15
+
16
+ type = Taro::Types::ListType.for(item_type)
17
+
18
+ # 143.889k (± 2.7%) i/s - 723.816k in 5.034247s
19
+ Benchmark.ips do |x|
20
+ x.report('parse 1 KB of params') { type.new(data).coerce_input }
21
+ end
22
+
23
+ # 103.382k (± 6.5%) i/s - 522.550k in 5.087725s
24
+ Benchmark.ips do |x|
25
+ x.report('validate a 1 KB response') { type.new(data).coerce_response }
26
+ end
27
+
28
+ big_data = data * 1000
29
+ big_data.each { |el| el.merge('version' => rand) }
30
+
31
+ # 101.359 (± 5.9%) i/s - 513.000 in 5.078335s
32
+ Benchmark.ips do |x|
33
+ x.report('parse 1 MB of params') { type.new(big_data).coerce_input }
34
+ end
35
+
36
+ # 84.412 (± 2.4%) i/s - 427.000 in 5.061117s
37
+ Benchmark.ips do |x|
38
+ x.report('validate a 1 MB response') { type.new(big_data).coerce_response }
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ [
2
+ {
3
+ "name": "Adeel Solangi",
4
+ "language": "Sindhi",
5
+ "id": "V59OF92YF627HFY0",
6
+ "bio": "Donec lobortis eleifend condimentum. Cras dictum dolor lacinia lectus vehicula rutrum. Maecenas quis nisi nunc. Nam tristique feugiat est vitae mollis. Maecenas quis nisi nunc.",
7
+ "version": 6.1
8
+ },
9
+ {
10
+ "name": "Afzal Ghaffar",
11
+ "language": "Sindhi",
12
+ "id": "ENTOCR13RSCLZ6KU",
13
+ "bio": "Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna. Etiam congue dignissim volutpat. Vestibulum pharetra libero et velit gravida euismod.",
14
+ "version": 1.88
15
+ },
16
+ {
17
+ "name": "Aamir Solangi",
18
+ "language": "Sindhi",
19
+ "id": "IAKPO3R4761JDRVG",
20
+ "bio": "Vestibulum pharetra libero et velit gravida euismod. Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. Fusce eu ultrices elit, vel posuere neque.",
21
+ "version": 7.27
22
+ }
23
+ ]