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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/README.md +76 -9
- data/lib/taro/errors.rb +1 -1
- data/lib/taro/export/open_api_v3.rb +76 -24
- data/lib/taro/rails/declaration.rb +44 -20
- data/lib/taro/rails/generators/install_generator.rb +1 -1
- data/lib/taro/rails/generators/templates/errors_type.erb +15 -10
- data/lib/taro/rails/normalized_route.rb +8 -0
- data/lib/taro/rails/tasks/export.rake +5 -1
- data/lib/taro/types/base_type.rb +2 -0
- data/lib/taro/types/coercion.rb +24 -14
- data/lib/taro/types/field.rb +8 -16
- data/lib/taro/types/field_validation.rb +1 -1
- data/lib/taro/types/list_type.rb +4 -6
- data/lib/taro/types/object_types/free_form_type.rb +1 -0
- data/lib/taro/types/object_types/no_content_type.rb +1 -0
- data/lib/taro/types/object_types/page_info_type.rb +2 -0
- data/lib/taro/types/object_types/page_type.rb +15 -25
- data/lib/taro/types/scalar/iso8601_date_type.rb +3 -3
- data/lib/taro/types/scalar/iso8601_datetime_type.rb +3 -3
- data/lib/taro/types/scalar/string_type.rb +17 -6
- data/lib/taro/types/scalar/timestamp_type.rb +1 -0
- data/lib/taro/types/scalar/uuid_v4_type.rb +3 -20
- data/lib/taro/types/scalar_type.rb +1 -0
- data/lib/taro/types/shared/custom_field_resolvers.rb +2 -2
- data/lib/taro/types/shared/deprecation.rb +3 -0
- data/lib/taro/types/shared/derived_types.rb +27 -0
- data/lib/taro/types/shared/errors.rb +3 -1
- data/lib/taro/types/shared/fields.rb +6 -5
- data/lib/taro/types/shared/item_type.rb +1 -0
- data/lib/taro/types/shared/object_coercion.rb +13 -0
- data/lib/taro/types/shared/openapi_name.rb +8 -6
- data/lib/taro/types/shared/pattern.rb +69 -0
- data/lib/taro/types/shared/rendering.rb +4 -4
- data/lib/taro/version.rb +1 -1
- data/tasks/benchmark.rake +1 -1
- metadata +7 -5
- data/lib/taro/types/shared/derivable_types.rb +0 -9
data/lib/taro/types/field.rb
CHANGED
@@ -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
|
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
|
-
|
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})
|
data/lib/taro/types/list_type.rb
CHANGED
@@ -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
|
-
|
26
|
-
|
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,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::
|
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
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
35
|
-
|
36
|
-
:page
|
30
|
+
def self.default_openapi_name
|
31
|
+
"#{item_type.openapi_name}_Page"
|
37
32
|
end
|
38
|
-
end
|
39
33
|
|
40
|
-
|
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?(
|
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?(
|
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)
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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,22 +1,5 @@
|
|
1
|
-
class Taro::Types::Scalar::UUIDv4Type < Taro::Types::
|
1
|
+
class Taro::Types::Scalar::UUIDv4Type < Taro::Types::Scalar::StringType
|
2
2
|
self.desc = "A UUID v4 string"
|
3
|
-
self.
|
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
|
@@ -15,8 +15,8 @@ module Taro::Types::Shared::CustomFieldResolvers
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def method_added(name)
|
18
|
-
if
|
19
|
-
raise(Taro::ArgumentError,
|
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,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
|
-
|
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
|
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
|
-
|
31
|
-
|
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, "#{
|
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
|
50
|
+
Taro::Types::Field.new(**field_def.except(*Taro::Types::Coercion.keys), type:)
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
@@ -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
|
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
|
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
|
-
|
5
|
-
|
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
data/tasks/benchmark.rake
CHANGED
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.
|
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-
|
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/
|
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.
|
130
|
+
rubygems_version: 3.5.22
|
129
131
|
signing_key:
|
130
132
|
specification_version: 4
|
131
133
|
summary: Typed Api using Ruby Objects.
|