taro 1.1.0 → 1.3.0

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +76 -9
  4. data/lib/taro/errors.rb +1 -1
  5. data/lib/taro/export/open_api_v3.rb +76 -24
  6. data/lib/taro/rails/declaration.rb +44 -20
  7. data/lib/taro/rails/generators/install_generator.rb +1 -1
  8. data/lib/taro/rails/generators/templates/errors_type.erb +15 -10
  9. data/lib/taro/rails/normalized_route.rb +8 -0
  10. data/lib/taro/rails/tasks/export.rake +5 -1
  11. data/lib/taro/types/base_type.rb +2 -0
  12. data/lib/taro/types/coercion.rb +24 -14
  13. data/lib/taro/types/field.rb +8 -16
  14. data/lib/taro/types/field_validation.rb +1 -1
  15. data/lib/taro/types/list_type.rb +4 -6
  16. data/lib/taro/types/object_types/free_form_type.rb +1 -0
  17. data/lib/taro/types/object_types/no_content_type.rb +1 -0
  18. data/lib/taro/types/object_types/page_info_type.rb +2 -0
  19. data/lib/taro/types/object_types/page_type.rb +15 -25
  20. data/lib/taro/types/scalar/iso8601_date_type.rb +3 -3
  21. data/lib/taro/types/scalar/iso8601_datetime_type.rb +3 -3
  22. data/lib/taro/types/scalar/string_type.rb +17 -6
  23. data/lib/taro/types/scalar/timestamp_type.rb +1 -0
  24. data/lib/taro/types/scalar/uuid_v4_type.rb +3 -20
  25. data/lib/taro/types/scalar_type.rb +1 -0
  26. data/lib/taro/types/shared/custom_field_resolvers.rb +2 -2
  27. data/lib/taro/types/shared/deprecation.rb +3 -0
  28. data/lib/taro/types/shared/derived_types.rb +27 -0
  29. data/lib/taro/types/shared/errors.rb +3 -1
  30. data/lib/taro/types/shared/fields.rb +6 -5
  31. data/lib/taro/types/shared/item_type.rb +1 -0
  32. data/lib/taro/types/shared/object_coercion.rb +13 -0
  33. data/lib/taro/types/shared/openapi_name.rb +8 -6
  34. data/lib/taro/types/shared/pattern.rb +69 -0
  35. data/lib/taro/types/shared/rendering.rb +4 -4
  36. data/lib/taro/version.rb +1 -1
  37. data/tasks/benchmark.rake +1 -1
  38. metadata +7 -5
  39. data/lib/taro/types/shared/derivable_types.rb +0 -9
@@ -1,11 +1,11 @@
1
1
  require_relative 'field_validation'
2
2
 
3
- Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum, :defined_at, :desc) do
3
+ Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum, :defined_at, :desc, :deprecated) do
4
4
  include Taro::Types::FieldValidation
5
5
 
6
- def initialize(name:, type:, null:, method: name, default: :none, enum: nil, defined_at: nil, desc: nil)
6
+ def initialize(name:, type:, null:, method: name, default: :none, enum: nil, defined_at: nil, desc: nil, deprecated: nil)
7
7
  enum = coerce_to_enum(enum)
8
- super(name:, type:, null:, method:, default:, enum:, defined_at:, desc:)
8
+ super(name:, type:, null:, method:, default:, enum:, defined_at:, desc:, deprecated:)
9
9
  end
10
10
 
11
11
  def value_for_input(object)
@@ -40,14 +40,15 @@ Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum,
40
40
  end
41
41
 
42
42
  def retrieve_response_value(object, context, object_is_hash)
43
- if object_is_hash
44
- retrieve_hash_value(object)
45
- elsif context&.resolve?(method)
43
+ if context&.resolve?(method)
46
44
  context.public_send(method)
45
+ elsif object_is_hash
46
+ retrieve_hash_value(object)
47
47
  elsif object.respond_to?(method, true)
48
48
  object.public_send(method)
49
49
  else
50
- raise_response_coercion_error(object)
50
+ # Note that the ObjectCoercion module rescues this and adds context.
51
+ raise Taro::ResponseError, "No such method or resolver `:#{method}`."
51
52
  end
52
53
  end
53
54
 
@@ -65,14 +66,5 @@ Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum,
65
66
 
66
67
  type_obj = type.new(value)
67
68
  from_input ? type_obj.coerce_input : type_obj.coerce_response
68
- rescue Taro::Error => e
69
- raise e.class, "#{e.message}, after using method/key `:#{method}` to resolve field `#{name}`"
70
- end
71
-
72
- def raise_response_coercion_error(object)
73
- raise Taro::ResponseError, <<~MSG
74
- Failed to coerce value #{object.inspect} for field `#{name}` using method/key `:#{method}`.
75
- It is not a valid #{type} value.
76
- MSG
77
69
  end
78
70
  end
@@ -18,7 +18,7 @@ module Taro::Types::FieldValidation
18
18
  end
19
19
 
20
20
  def validate_enum_inclusion(value, for_input)
21
- return if enum.nil? || enum.include?(value)
21
+ return if enum.nil? || null && value.nil? || enum.include?(value)
22
22
 
23
23
  raise for_input ? Taro::InputError : Taro::ResponseError, <<~MSG
24
24
  Field #{name} has an invalid value #{value.inspect} (expected one of #{enum.inspect})
@@ -2,7 +2,6 @@
2
2
  # Unlike other types, this one should not be manually inherited from,
3
3
  # but is used indirectly via `array_of: SomeType`.
4
4
  class Taro::Types::ListType < Taro::Types::BaseType
5
- extend Taro::Types::Shared::DerivableType
6
5
  extend Taro::Types::Shared::ItemType
7
6
 
8
7
  self.openapi_type = :array
@@ -20,11 +19,10 @@ class Taro::Types::ListType < Taro::Types::BaseType
20
19
  item_type = self.class.item_type
21
20
  object.map { |el| item_type.new(el).coerce_response }
22
21
  end
23
- end
24
22
 
25
- # add shortcut to other types
26
- class Taro::Types::BaseType
27
- def self.array
28
- Taro::Types::ListType.for(self)
23
+ def self.default_openapi_name
24
+ "#{item_type.openapi_name}_List"
29
25
  end
26
+
27
+ define_derived_type :array, 'Taro::Types::ListType'
30
28
  end
@@ -1,6 +1,7 @@
1
1
  class Taro::Types::ObjectTypes::FreeFormType < Taro::Types::ObjectType
2
2
  self.desc = 'An arbitrary, unvalidated Hash or JSON object. Use with care.'
3
3
  self.additional_properties = true
4
+ self.openapi_name = 'FreeForm'
4
5
 
5
6
  def coerce_input
6
7
  object.is_a?(Hash) && object || input_error('must be a Hash')
@@ -1,5 +1,6 @@
1
1
  class Taro::Types::ObjectTypes::NoContentType < Taro::Types::ObjectType
2
2
  self.desc = 'An empty response'
3
+ self.openapi_name = 'NoContent'
3
4
 
4
5
  # render takes no arguments in this case
5
6
  def self.render
@@ -1,4 +1,6 @@
1
1
  class Taro::Types::ObjectTypes::PageInfoType < Taro::Types::ObjectType
2
+ self.openapi_name = 'PageInfo'
3
+
2
4
  field :has_previous_page, type: 'Boolean', null: false, desc: 'Whether there is a previous page of results'
3
5
  field :has_next_page, type: 'Boolean', null: false, desc: 'Whether there is another page of results'
4
6
  field :start_cursor, type: 'String', null: true, desc: 'The first cursor in the current page of results (null if zero results)'
@@ -4,42 +4,32 @@
4
4
  #
5
5
  # The gem rails_cursor_pagination must be installed to use this.
6
6
  #
7
- class Taro::Types::ObjectTypes::PageType < Taro::Types::BaseType
8
- extend Taro::Types::Shared::DerivableType
7
+ class Taro::Types::ObjectTypes::PageType < Taro::Types::ObjectType
9
8
  extend Taro::Types::Shared::ItemType
10
9
 
10
+ def self.derive_from(from_type)
11
+ super
12
+ field(:page, array_of: from_type.name, null: false)
13
+ field(:page_info, type: 'Taro::Types::ObjectTypes::PageInfoType', null: false)
14
+ end
15
+
11
16
  def coerce_input
12
17
  input_error 'PageTypes cannot be used as input types'
13
18
  end
14
19
 
15
- def coerce_response(after:, limit: 20, order_by: nil, order: nil)
16
- list = RailsCursorPagination::Paginator.new(
17
- object, limit:, order_by:, order:, after:
20
+ def self.render(relation, after:, limit: 20, order_by: nil, order: nil)
21
+ result = RailsCursorPagination::Paginator.new(
22
+ relation, limit:, order_by:, order:, after:
18
23
  ).fetch
19
- coerce_paginated_list(list)
20
- end
21
24
 
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
25
+ result[:page].map! { |el| el.fetch(:data) }
27
26
 
28
- {
29
- self.class.items_key => items,
30
- page_info: Taro::Types::ObjectTypes::PageInfoType.new(list[:page_info]).coerce_response,
31
- }
27
+ super(result)
32
28
  end
33
29
 
34
- # support overrides, e.g. based on item_type
35
- def self.items_key
36
- :page
30
+ def self.default_openapi_name
31
+ "#{item_type.openapi_name}_Page"
37
32
  end
38
- end
39
33
 
40
- # add shortcut to other types
41
- class Taro::Types::BaseType
42
- def self.page
43
- Taro::Types::ObjectTypes::PageType.for(self)
44
- end
34
+ define_derived_type :page, 'Taro::Types::ObjectTypes::PageType'
45
35
  end
@@ -1,11 +1,11 @@
1
1
  class Taro::Types::Scalar::ISO8601DateType < Taro::Types::ScalarType
2
2
  self.desc = 'Represents a time as Date in ISO8601 format.'
3
+ self.openapi_name = 'ISO8601Date'
3
4
  self.openapi_type = :string
4
-
5
- PATTERN = /\A\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])\z/
5
+ self.pattern = /\A\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])\z/
6
6
 
7
7
  def coerce_input
8
- if object.instance_of?(String) && object.match?(PATTERN)
8
+ if object.instance_of?(String) && object.match?(pattern)
9
9
  Date.parse(object)
10
10
  else
11
11
  input_error("must be a ISO8601 formatted string")
@@ -1,11 +1,11 @@
1
1
  class Taro::Types::Scalar::ISO8601DateTimeType < Taro::Types::ScalarType
2
2
  self.desc = 'Represents a time as DateTime in ISO8601 format.'
3
+ self.openapi_name = 'ISO8601DateTime'
3
4
  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/
5
+ self.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
6
 
7
7
  def coerce_input
8
- if object.instance_of?(String) && object.match?(PATTERN)
8
+ if object.instance_of?(String) && object.match?(pattern)
9
9
  DateTime.iso8601(object)
10
10
  else
11
11
  input_error("must be a ISO8601 formatted string")
@@ -2,14 +2,25 @@ class Taro::Types::Scalar::StringType < Taro::Types::ScalarType
2
2
  self.openapi_type = :string
3
3
 
4
4
  def coerce_input
5
- object.instance_of?(String) ? object : input_error('must be a String')
5
+ object.instance_of?(String) || input_error('must be a String')
6
+
7
+ pattern.nil? || pattern.match?(object) ||
8
+ input_error("must match pattern #{pattern.inspect}")
9
+
10
+ object
6
11
  end
7
12
 
8
13
  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
+ str =
15
+ case object
16
+ when String then object
17
+ when Symbol then object.to_s
18
+ else response_error('must be a String or Symbol')
19
+ end
20
+
21
+ pattern.nil? || pattern.match?(str) ||
22
+ response_error("must match pattern #{pattern.inspect}")
23
+
24
+ str
14
25
  end
15
26
  end
@@ -1,5 +1,6 @@
1
1
  class Taro::Types::Scalar::TimestampType < Taro::Types::ScalarType
2
2
  self.desc = 'Represents a time as Time on the server side and UNIX timestamp (integer) on the client side.'
3
+ self.openapi_name = 'Timestamp'
3
4
  self.openapi_type = :integer
4
5
 
5
6
  def coerce_input
@@ -1,22 +1,5 @@
1
- class Taro::Types::Scalar::UUIDv4Type < Taro::Types::ScalarType
1
+ class Taro::Types::Scalar::UUIDv4Type < Taro::Types::Scalar::StringType
2
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
3
+ self.openapi_name = 'UUIDv4'
4
+ self.pattern = /\A\h{8}-?(?:\h{4}-?){3}\h{12}\z/
22
5
  end
@@ -1,5 +1,6 @@
1
1
  # Abstract base class for scalar types, i.e. types without fields.
2
2
  class Taro::Types::ScalarType < Taro::Types::BaseType
3
+ include Taro::Types::Shared::Pattern
3
4
  end
4
5
 
5
6
  module Taro::Types::Scalar
@@ -15,8 +15,8 @@ module Taro::Types::Shared::CustomFieldResolvers
15
15
  end
16
16
 
17
17
  def method_added(name)
18
- if name == :object
19
- raise(Taro::ArgumentError, '#object is a reserved, internally used method name')
18
+ if [:object, :pattern].include?(name)
19
+ raise(Taro::ArgumentError, "##{name} is a reserved, internally used method name")
20
20
  elsif ![:coerce_input, :coerce_response].include?(name) &&
21
21
  !self.name.to_s.start_with?('Taro::Types::')
22
22
  custom_resolvers[name] = true
@@ -0,0 +1,3 @@
1
+ module Taro::Types::Shared::Deprecation
2
+ attr_accessor :deprecated
3
+ end
@@ -0,0 +1,27 @@
1
+ module Taro::Types::Shared::DerivedTypes
2
+ # Adds `name` as a method to all type classes and adds
3
+ # `name`_of as a supported key to the Coercion module.
4
+ # When `name` is called on a type class T, it returns a new subclass
5
+ # S inheriting from `type` and passes T to S::derive_from.
6
+ def define_derived_type(name, type)
7
+ root = Taro::Types::BaseType
8
+ raise ArgumentError, "#{name} is already in use" if root.respond_to?(name)
9
+
10
+ ckey = :"#{name}#{Taro::Types::Coercion.derived_suffix}"
11
+ ckeys = Taro::Types::Coercion.keys
12
+ raise ArgumentError, "#{ckey} is already in use" if ckeys.include?(ckey)
13
+
14
+ root.define_singleton_method(name) do
15
+ derived_types[type] ||= begin
16
+ type_class = Taro::Types::Coercion.call(type:)
17
+ Class.new(type_class).tap { |t| t.derive_from(self) }
18
+ end
19
+ end
20
+
21
+ ckeys << ckey
22
+ end
23
+
24
+ def derived_types
25
+ @derived_types ||= {}
26
+ end
27
+ end
@@ -8,6 +8,8 @@ module Taro::Types::Shared::Errors
8
8
  end
9
9
 
10
10
  def coerce_error_message(msg)
11
- "#{object.inspect} (#{object.class}) is not valid as #{self.class}: #{msg}"
11
+ type_desc = (self.class.name || self.class.superclass.name)
12
+ .sub(/^Taro::Types::.*?([^:]+)Type$/, '\1')
13
+ "#{object.class} is not valid as #{type_desc}: #{msg}"
12
14
  end
13
15
  end
@@ -2,7 +2,7 @@
2
2
  module Taro::Types::Shared::Fields
3
3
  # Field types are set using class name Strings. The respective type classes
4
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.
5
+ # and to avoid unnecessary autoloading of all types in dev/test envs.
6
6
  def field(name, **kwargs)
7
7
  defined_at = kwargs[:defined_at] || caller_locations(1..1)[0]
8
8
  validate_name(name, defined_at:)
@@ -27,11 +27,12 @@ module Taro::Types::Shared::Fields
27
27
  [true, false].include?(kwargs[:null]) ||
28
28
  raise(Taro::ArgumentError, "null has to be specified as true or false for field #{name} at #{defined_at}")
29
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}")
30
+ c_keys = Taro::Types::Coercion.keys
31
+ (type_keys = (kwargs.keys & c_keys)).size == 1 ||
32
+ raise(Taro::ArgumentError, "exactly one of #{c_keys.join(', ')} must be given for field #{name} at #{defined_at}")
32
33
 
33
34
  kwargs[type_keys.first].class == String ||
34
- raise(Taro::ArgumentError, "#{type_key} must be a String for field #{name} at #{defined_at}")
35
+ raise(Taro::ArgumentError, "#{type_keys.first} must be a String for field #{name} at #{defined_at}")
35
36
  end
36
37
 
37
38
  def validate_no_override(name, defined_at:)
@@ -46,7 +47,7 @@ module Taro::Types::Shared::Fields
46
47
  def evaluate_field_defs
47
48
  field_defs.transform_values do |field_def|
48
49
  type = Taro::Types::Coercion.call(field_def)
49
- Taro::Types::Field.new(**field_def.except(*Taro::Types::Coercion::KEYS), type:)
50
+ Taro::Types::Field.new(**field_def.except(*Taro::Types::Coercion.keys), type:)
50
51
  end
51
52
  end
52
53
 
@@ -6,6 +6,7 @@ module Taro::Types::Shared::ItemType
6
6
  item_type.nil? || new_type == item_type || raise_mixed_types(new_type)
7
7
  @item_type = new_type
8
8
  end
9
+ alias_method :derive_from, :item_type=
9
10
 
10
11
  def raise_mixed_types(new_type)
11
12
  raise Taro::ArgumentError, <<~MSG
@@ -3,6 +3,8 @@ module Taro::Types::Shared::ObjectCoercion
3
3
  def coerce_input
4
4
  self.class.fields.transform_values do |field|
5
5
  field.value_for_input(object)
6
+ rescue Taro::Error => e
7
+ raise_enriched_coercion_error(e, field)
6
8
  end
7
9
  end
8
10
 
@@ -11,6 +13,17 @@ module Taro::Types::Shared::ObjectCoercion
11
13
  object_is_hash = object.is_a?(Hash)
12
14
  self.class.fields.transform_values do |field|
13
15
  field.value_for_response(object, context: self, object_is_hash:)
16
+ rescue Taro::Error => e
17
+ raise_enriched_coercion_error(e, field)
14
18
  end
15
19
  end
20
+
21
+ def raise_enriched_coercion_error(error, field)
22
+ # The indentation is on purpose. These errors can be recursively rescued
23
+ # and re-raised by a tree of object types, which should be made apparent.
24
+ raise error.class, <<~MSG
25
+ Failed to read #{self.class.name} field `#{field.name}` from #{object.class}:
26
+ #{error.message.lines.map { |line| " #{line}" }.join}
27
+ MSG
28
+ end
16
29
  end
@@ -5,13 +5,19 @@ module Taro::Types::Shared::OpenAPIName
5
5
  @openapi_name ||= default_openapi_name
6
6
  end
7
7
 
8
+ def openapi_name?
9
+ !!openapi_name
10
+ rescue Taro::Error
11
+ false
12
+ end
13
+
8
14
  def openapi_name=(arg)
9
15
  arg.nil? || arg.is_a?(String) ||
10
16
  raise(Taro::ArgumentError, 'openapi_name must be a String')
11
17
  @openapi_name = arg
12
18
  end
13
19
 
14
- def default_openapi_name # rubocop:disable Metrics
20
+ def default_openapi_name
15
21
  if self < Taro::Types::EnumType ||
16
22
  self < Taro::Types::InputType ||
17
23
  self < Taro::Types::ObjectType
@@ -19,12 +25,8 @@ module Taro::Types::Shared::OpenAPIName
19
25
  raise(Taro::Error, 'openapi_name must be set for anonymous type classes')
20
26
  elsif self < Taro::Types::ScalarType
21
27
  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
28
  else
27
- raise NotImplementedError, 'no default_openapi_name for this type'
29
+ raise Taro::Error, 'no default_openapi_name implemented for this type'
28
30
  end
29
31
  end
30
32
  end
@@ -0,0 +1,69 @@
1
+ module Taro::Types::Shared::Pattern
2
+ def pattern
3
+ self.class.pattern
4
+ end
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ attr_reader :pattern, :openapi_pattern
12
+
13
+ def pattern=(regexp)
14
+ openapi_type == :string ||
15
+ raise(Taro::RuntimeError, 'pattern requires openapi_type :string')
16
+
17
+ @pattern = regexp
18
+ @openapi_pattern = Taro::Types::Shared::Pattern.to_es262(regexp)
19
+ end
20
+ end
21
+
22
+ def self.to_es262(regexp)
23
+ validate(regexp).source.gsub(
24
+ /#{NOT_ESCAPED}\\[Ahz]/,
25
+ { '\\A' => '^', '\\h' => '[0-9a-fA-F]', '\\z' => '$' }
26
+ )
27
+ end
28
+
29
+ def self.validate(regexp)
30
+ validate_no_flags(regexp)
31
+ validate_not_empty(regexp)
32
+ validate_no_advanced_syntax(regexp)
33
+ regexp
34
+ end
35
+
36
+ def self.validate_no_flags(regexp)
37
+ (flags = regexp.inspect[%r{/\w+\z}]) &&
38
+ raise(Taro::ArgumentError, "pattern flags (#{flags}) are not supported")
39
+ end
40
+
41
+ def self.validate_not_empty(regexp)
42
+ regexp.source.empty? &&
43
+ raise(Taro::ArgumentError, 'pattern cannot be empty')
44
+ end
45
+
46
+ def self.validate_no_advanced_syntax(regexp)
47
+ return unless (match = regexp.source.match(ADVANCED_RUBY_REGEXP_SYNTAX_REGEXP))
48
+
49
+ feature = match.named_captures.find { |k, v| break k if v }
50
+ raise Taro::ArgumentError, <<~MSG
51
+ pattern uses non-JS syntax #{match} (#{feature}) at index #{match.begin(0)}
52
+ MSG
53
+ end
54
+
55
+ NOT_ESCAPED = /(?<!\\)(?:\\\\)*\K/
56
+
57
+ # This is not 100% accurate, e.g. /[?+]/ is a false positive, but it should be
58
+ # good enough so we don't need regexp_parser or js_regex as a dependency.
59
+ ADVANCED_RUBY_REGEXP_SYNTAX_REGEXP = /
60
+ #{NOT_ESCAPED}
61
+ (?:
62
+ (?<a special group or lookaround> \(\?[^:] )
63
+ | (?<a Ruby-specific escape> \\[a-zA-Z&&[^bBdDsSwWAzfhnrv]] )
64
+ | (?<an advanced quantifier> [?*+}][?+] )
65
+ | (?<a nested set> \[[^\]]*(?<!\\)\[ )
66
+ | (?<a set intersection> && )
67
+ )
68
+ /x
69
+ end
@@ -1,8 +1,8 @@
1
- # The `::render` method is intended for use in controllers.
2
- # Special types (e.g. PageType) may accept kwargs for `#coerce_response`.
3
1
  module Taro::Types::Shared::Rendering
4
- def render(object, opts = {})
5
- result = new(object).coerce_response(**opts)
2
+ # The `::render` method is intended for use in controllers.
3
+ # Overrides of this method must call super.
4
+ def render(object)
5
+ result = new(object).coerce_response
6
6
  self.last_render = [self, result.__id__]
7
7
  result
8
8
  end
data/lib/taro/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # :nocov:
2
2
  module Taro
3
- VERSION = "1.1.0"
3
+ VERSION = "1.3.0"
4
4
  end
data/tasks/benchmark.rake CHANGED
@@ -13,7 +13,7 @@ task :benchmark do
13
13
  field :version, type: 'Float', null: false
14
14
  end
15
15
 
16
- type = Taro::Types::ListType.for(item_type)
16
+ type = item_type.array
17
17
 
18
18
  # 143.889k (± 2.7%) i/s - 723.816k in 5.034247s
19
19
  Benchmark.ips do |x|
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taro
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janosch Müller
8
+ - Johannes Opper
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2024-11-16 00:00:00.000000000 Z
12
+ date: 2024-11-25 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rack
@@ -27,7 +28,6 @@ dependencies:
27
28
  description: This library provides an object-based type system for RESTful Ruby APIs,
28
29
  with built-in parameter parsing, response rendering, and OpenAPI schema export.
29
30
  email:
30
- - janosch84@gmail.com
31
31
  executables: []
32
32
  extensions: []
33
33
  extra_rdoc_files: []
@@ -90,7 +90,8 @@ files:
90
90
  - lib/taro/types/shared.rb
91
91
  - lib/taro/types/shared/additional_properties.rb
92
92
  - lib/taro/types/shared/custom_field_resolvers.rb
93
- - lib/taro/types/shared/derivable_types.rb
93
+ - lib/taro/types/shared/deprecation.rb
94
+ - lib/taro/types/shared/derived_types.rb
94
95
  - lib/taro/types/shared/description.rb
95
96
  - lib/taro/types/shared/errors.rb
96
97
  - lib/taro/types/shared/fields.rb
@@ -98,6 +99,7 @@ files:
98
99
  - lib/taro/types/shared/object_coercion.rb
99
100
  - lib/taro/types/shared/openapi_name.rb
100
101
  - lib/taro/types/shared/openapi_type.rb
102
+ - lib/taro/types/shared/pattern.rb
101
103
  - lib/taro/types/shared/rendering.rb
102
104
  - lib/taro/version.rb
103
105
  - tasks/benchmark.rake
@@ -125,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
127
  - !ruby/object:Gem::Version
126
128
  version: '0'
127
129
  requirements: []
128
- rubygems_version: 3.5.16
130
+ rubygems_version: 3.5.22
129
131
  signing_key:
130
132
  specification_version: 4
131
133
  summary: Typed Api using Ruby Objects.
@@ -1,9 +0,0 @@
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