low_type 1.0.8 → 1.1.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.
- checksums.yaml +4 -4
- data/lib/factories/type_factory.rb +17 -0
- data/lib/interfaces/error_interface.rb +18 -5
- data/lib/queries/type_query.rb +18 -10
- data/lib/redefiner.rb +1 -1
- data/lib/syntax/syntax.rb +2 -2
- data/lib/type_expression.rb +62 -30
- data/lib/types/complex_types.rb +11 -8
- data/lib/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8dae7f03ed51a1acea72d53deec50af0722e9c57ac8baab72297420bae36f4b
|
|
4
|
+
data.tar.gz: d7de0927fb139ac7d2b934c01f464224bb30fe2fbba6ca8b62a789955561e602
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4a91869b8c041091cd2dcadfb5ff931883041f7d57319b6cfb60b8ef7c1607924d81b93fc1a936724a3d577e904ed1d76c5bb03c89d6bf872e6446f0490f52e
|
|
7
|
+
data.tar.gz: d42d49968af57b0122e76449d01a5ed118bb4394d28fe42a6a9ec9095f6900ec02940e237e75fe3c1c4417b307c0b85497193a492b721e3c139902a15d92be7b
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LowType
|
|
4
|
+
class TypeFactory
|
|
5
|
+
class << self
|
|
6
|
+
def complex_type(parent_type)
|
|
7
|
+
Class.new(parent_type) do
|
|
8
|
+
def self.match?(value:)
|
|
9
|
+
return true if value.instance_of?(self.class) || value.instance_of?(superclass)
|
|
10
|
+
|
|
11
|
+
false
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -10,9 +10,18 @@ module LowType
|
|
|
10
10
|
@output_size = LowType.config.output_size
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def error_type
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def error_message(value:)
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
13
21
|
def output(value:)
|
|
14
22
|
case @output_mode
|
|
15
23
|
when :type
|
|
24
|
+
# TODO: Show full type structure in error output instead of just the type of the supertype.
|
|
16
25
|
value.class
|
|
17
26
|
when :value
|
|
18
27
|
value.inspect[0...@output_size]
|
|
@@ -21,12 +30,16 @@ module LowType
|
|
|
21
30
|
end
|
|
22
31
|
end
|
|
23
32
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
def backtrace(backtrace:, hidden_paths:)
|
|
34
|
+
# Remove LowType defined method file paths from the backtrace.
|
|
35
|
+
filtered_backtrace = backtrace.reject { |line| hidden_paths.find { |file_path| line.include?(file_path) } }
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
# Add the proxied file to the backtrace.
|
|
38
|
+
proxy_file_backtrace = "#{file.path}:#{file.line}:in '#{file.scope}'"
|
|
39
|
+
from_prefix = filtered_backtrace.first.match(/\s+from /)
|
|
40
|
+
proxy_file_backtrace = "#{from_prefix}#{proxy_file_backtrace}" if from_prefix
|
|
41
|
+
|
|
42
|
+
[proxy_file_backtrace, *filtered_backtrace]
|
|
30
43
|
end
|
|
31
44
|
end
|
|
32
45
|
end
|
data/lib/queries/type_query.rb
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../type_expression'
|
|
4
|
+
|
|
3
5
|
module LowType
|
|
4
6
|
# TODO: Unit test.
|
|
5
7
|
class TypeQuery
|
|
6
8
|
class << self
|
|
7
|
-
def type?(
|
|
8
|
-
basic_type?(
|
|
9
|
+
def type?(expression)
|
|
10
|
+
basic_type?(expression:) || complex_type?(expression:)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def typed_array?(expression:)
|
|
14
|
+
expression.is_a?(Array) && (basic_type?(expression: expression.first) || expression.first.is_a?(TypeExpression))
|
|
9
15
|
end
|
|
10
16
|
|
|
11
|
-
def
|
|
12
|
-
|
|
17
|
+
def value?(value)
|
|
18
|
+
!basic_type?(expression: value) && !complex_type?(expression: value)
|
|
13
19
|
end
|
|
14
20
|
|
|
15
|
-
def complex_type?(
|
|
16
|
-
|
|
21
|
+
def complex_type?(expression:)
|
|
22
|
+
LowType::COMPLEX_TYPES.include?(expression) || typed_array?(expression:) || typed_hash?(expression:)
|
|
17
23
|
end
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def basic_type?(expression:)
|
|
28
|
+
expression.instance_of?(Class)
|
|
21
29
|
end
|
|
22
30
|
|
|
23
|
-
def
|
|
24
|
-
|
|
31
|
+
def typed_hash?(expression:)
|
|
32
|
+
expression.is_a?(Hash) && basic_type?(expression: expression.keys.first) && basic_type?(expression: expression.values.first)
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
end
|
data/lib/redefiner.rb
CHANGED
|
@@ -106,7 +106,7 @@ module LowType
|
|
|
106
106
|
}
|
|
107
107
|
RUBY
|
|
108
108
|
|
|
109
|
-
# Called with only required args (as nil) and optional args omitted, to evaluate type expressions (
|
|
109
|
+
# Called with only required args (as nil) and optional args omitted, to evaluate type expressions (from default values).
|
|
110
110
|
eval(typed_method, binding, __FILE__, __LINE__).call(*required_args, **required_kwargs) # rubocop:disable Security/Eval
|
|
111
111
|
|
|
112
112
|
# TODO: Write spec for this.
|
data/lib/syntax/syntax.rb
CHANGED
|
@@ -5,8 +5,8 @@ require_relative '../queries/type_query'
|
|
|
5
5
|
module LowType
|
|
6
6
|
module Syntax
|
|
7
7
|
refine Array.singleton_class do
|
|
8
|
-
def [](*
|
|
9
|
-
return TypeExpression.new(type: [*
|
|
8
|
+
def [](*expression)
|
|
9
|
+
return TypeExpression.new(type: [*expression]) if TypeQuery.type?(expression.first) || TypeQuery.typed_array?(expression:)
|
|
10
10
|
|
|
11
11
|
super
|
|
12
12
|
end
|
data/lib/type_expression.rb
CHANGED
|
@@ -5,10 +5,10 @@ require_relative 'queries/type_query'
|
|
|
5
5
|
|
|
6
6
|
module LowType
|
|
7
7
|
root_path = File.expand_path(__dir__)
|
|
8
|
-
file_path = File.expand_path(__FILE__)
|
|
9
8
|
adapter_paths = Dir.chdir(root_path) { Dir.glob('adapters/*') }.map { |path| File.join(root_path, path) }
|
|
9
|
+
module_paths = %w[instance_types local_types redefiner].map { |path| File.join(root_path, "#{path}.rb") }
|
|
10
10
|
|
|
11
|
-
HIDDEN_PATHS = [
|
|
11
|
+
HIDDEN_PATHS = [File.expand_path(__FILE__), *adapter_paths, *module_paths].freeze
|
|
12
12
|
|
|
13
13
|
# Represent types and default values as a series of chainable expressions.
|
|
14
14
|
class TypeExpression
|
|
@@ -19,6 +19,8 @@ module LowType
|
|
|
19
19
|
@types = []
|
|
20
20
|
@types << type unless type.nil?
|
|
21
21
|
@default_value = default_value
|
|
22
|
+
# TODO: Override per type expression with a config expression.
|
|
23
|
+
@deep_type_check = LowType.config.deep_type_check
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def |(expression)
|
|
@@ -45,64 +47,94 @@ module LowType
|
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
@types.each do |type|
|
|
48
|
-
|
|
49
|
-
return true if
|
|
50
|
-
return true if type.is_a?(
|
|
51
|
-
return true if type.is_a?(::Hash) && value.is_a?(::Hash) && hash_types_match_values?(type:, value:)
|
|
50
|
+
return true if type_matches_value?(type:, value:, proxy:)
|
|
51
|
+
return true if type.is_a?(Array) && value.is_a?(Array) && array_types_match_values?(types: type, values: value, proxy:)
|
|
52
|
+
return true if type.is_a?(Hash) && value.is_a?(Hash) && hash_types_match_values?(types: type, values: value)
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
raise proxy.error_type, proxy.error_message(value:)
|
|
55
56
|
rescue proxy.error_type => e
|
|
56
|
-
raise proxy.error_type, e.message,
|
|
57
|
+
raise proxy.error_type, e.message, proxy.backtrace(backtrace: e.backtrace, hidden_paths: HIDDEN_PATHS)
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
def valid_types
|
|
60
61
|
types = @types.map do |type|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"[#{type.map { |subtype| subtype.to_s.delete_prefix('LowType::') }.join(', ')}]"
|
|
62
|
+
if type.is_a?(Array)
|
|
63
|
+
"[#{type.map { |subtype| valid_subtype(subtype:) }.join(', ')}]"
|
|
64
64
|
else
|
|
65
65
|
type.inspect.to_s.delete_prefix('LowType::')
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
types
|
|
69
|
+
types << 'nil' if @default_value.nil?
|
|
70
70
|
types.join(' | ')
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
private
|
|
74
74
|
|
|
75
|
-
def
|
|
76
|
-
|
|
75
|
+
def valid_subtype(subtype:)
|
|
76
|
+
if subtype.is_a?(TypeExpression)
|
|
77
|
+
types = subtype.types
|
|
78
|
+
types << 'nil' if subtype.default_value.nil?
|
|
79
|
+
types.join(' | ')
|
|
80
|
+
else
|
|
81
|
+
subtype.to_s.delete_prefix('LowType::')
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def array_types_match_values?(types:, values:, proxy:)
|
|
86
|
+
# [X, Y, Z] An arbitrary amount of elements are arbitrary types in an arbitrary order.
|
|
77
87
|
if types.length > 1
|
|
78
|
-
types
|
|
79
|
-
|
|
80
|
-
return false unless type <= values[index].class
|
|
81
|
-
end
|
|
82
|
-
# [T]
|
|
88
|
+
return multiple_types_match_values?(types:, values:, proxy:)
|
|
89
|
+
# [T] All elements are the same type.
|
|
83
90
|
elsif types.length == 1
|
|
84
|
-
return
|
|
91
|
+
return single_type_matches_values?(type: types.first, values:, proxy:)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# [] Misconfigured empty Array[] type.
|
|
95
|
+
true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def multiple_types_match_values?(types:, values:, proxy:)
|
|
99
|
+
types.each_with_index do |type, index|
|
|
100
|
+
return false unless type_matches_value?(type:, value: values[index], proxy:)
|
|
85
101
|
end
|
|
86
|
-
# TODO: Deep type check (all elements for [T]).
|
|
87
102
|
|
|
88
103
|
true
|
|
89
104
|
end
|
|
90
105
|
|
|
91
|
-
def
|
|
106
|
+
def single_type_matches_values?(type:, values:, proxy:)
|
|
107
|
+
# [V, ...] Type check all elements.
|
|
108
|
+
if deep_type_check?
|
|
109
|
+
return false if values.any? { |value| !type_matches_value?(type:, value:, proxy:) }
|
|
110
|
+
# [V] Type check the first element.
|
|
111
|
+
else
|
|
112
|
+
return false unless type_matches_value?(type:, value: values.first, proxy:)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def hash_types_match_values?(types:, values:)
|
|
92
119
|
# TODO: Shallow validation of hash could be made deeper with user config.
|
|
93
|
-
|
|
120
|
+
types.keys[0] == values.keys[0].class && types.values[0] == values.values[0].class
|
|
94
121
|
end
|
|
95
122
|
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
def type_matches_value?(type:, value:, proxy:)
|
|
124
|
+
if type.instance_of?(Class)
|
|
125
|
+
return type.match?(value:) if LowType::TypeQuery.complex_type?(expression: type)
|
|
126
|
+
|
|
127
|
+
return type == value.class
|
|
128
|
+
elsif type.instance_of?(::LowType::TypeExpression)
|
|
129
|
+
type.validate!(value:, proxy:)
|
|
130
|
+
return true
|
|
131
|
+
end
|
|
99
132
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
from_prefix = filtered_backtrace.first.match(/\s+from /)
|
|
103
|
-
proxy_file_backtrace = "#{from_prefix}#{proxy_file_backtrace}" if from_prefix
|
|
133
|
+
false
|
|
134
|
+
end
|
|
104
135
|
|
|
105
|
-
|
|
136
|
+
def deep_type_check?
|
|
137
|
+
@deep_type_check || LowType.config.deep_type_check || false
|
|
106
138
|
end
|
|
107
139
|
end
|
|
108
140
|
end
|
data/lib/types/complex_types.rb
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../factories/type_factory'
|
|
4
|
+
|
|
3
5
|
module LowType
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
COMPLEX_TYPES = [
|
|
7
|
+
Boolean = TypeFactory.complex_type(Object),
|
|
8
|
+
Headers = TypeFactory.complex_type(Hash),
|
|
9
|
+
HTML = TypeFactory.complex_type(String),
|
|
10
|
+
JSON = TypeFactory.complex_type(String),
|
|
11
|
+
Status = TypeFactory.complex_type(Integer),
|
|
12
|
+
Tuple = TypeFactory.complex_type(Array),
|
|
13
|
+
XML = TypeFactory.complex_type(String)
|
|
14
|
+
].freeze
|
|
12
15
|
end
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: low_type
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -21,6 +21,7 @@ files:
|
|
|
21
21
|
- lib/adapters/sinatra_adapter.rb
|
|
22
22
|
- lib/factories/expression_factory.rb
|
|
23
23
|
- lib/factories/proxy_factory.rb
|
|
24
|
+
- lib/factories/type_factory.rb
|
|
24
25
|
- lib/instance_types.rb
|
|
25
26
|
- lib/interfaces/adapter_interface.rb
|
|
26
27
|
- lib/interfaces/error_interface.rb
|