low_type 0.8.7 → 0.8.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94ec8cf1ae80a7540248e9a0a97e2e9bbc99fb81724f9a89e3d35de0ccd3dfaa
4
- data.tar.gz: f1bf339671b5d17ec247814a08ea665aca64df51f404f625dff5d6bf77cf7c2e
3
+ metadata.gz: 65c0e662bfe788a1132c9ecf802392285089139cc2d721427a7285d62b87e606
4
+ data.tar.gz: da9f514df1428147990f55e7bc68a343dff87ce3df86d2e7913414ac97c80e15
5
5
  SHA512:
6
- metadata.gz: 7a6ff0fa4038f53cd1650f27e7523ec2e0c0ac75c66190d62d976dbaded7887c2dc1900cb514709255af87f1c01545eb7c0748c100cd8133ea947c3d03f0c785
7
- data.tar.gz: 141e666816a66e0446b0d9559d288dc24b7e838c200537fbfd5b18297533f8812517e37e29d3bcf7388efbef82fffb1aa90a6407037c4803db8bc6a1b26645f8
6
+ metadata.gz: 323ed1e9adfe8a07e0e8b6e2ed6fba157caac080611827f32453816db5b0c40cb7631e99c9a004391e1565f05d4ad26f8e1a9ed7b710a13846bf37da1b4290e6
7
+ data.tar.gz: 7b85c07ee7cfe34c2c004a4c62381627d8f258236d891295a073dd9e4c2b304c35ccba6ef6521faf9e1da52558a8610862fb73ca76e184cbf4c19ea362fcc730
@@ -1,8 +1,8 @@
1
1
  require 'prism'
2
2
 
3
- require_relative 'sinatra_return_proxy'
4
3
  require_relative '../interfaces/adapter_interface'
5
4
  require_relative '../proxies/file_proxy'
5
+ require_relative '../proxies/return_proxy'
6
6
  require_relative '../error_types'
7
7
 
8
8
  module LowType
@@ -17,7 +17,7 @@ module LowType
17
17
  @file_path = file_path
18
18
  end
19
19
 
20
- def redefine_methods
20
+ def process
21
21
  method_calls = @parser.method_calls(method_names: [:get, :post, :patch, :put, :delete, :options, :query])
22
22
 
23
23
  # Type check return values.
@@ -35,54 +35,35 @@ module LowType
35
35
 
36
36
  route = "#{method_call.name.upcase} #{pattern}"
37
37
  @klass.low_methods[route] = MethodProxy.new(name: method_call.name, params:, return_proxy:)
38
+ end
39
+ end
38
40
 
39
- # We're in Sinatra now. Objects request/response are from Sinatra.
40
- @klass.after pattern do
41
- if (method_proxy = self.class.low_methods["#{request.request_method} #{pattern}"])
42
- proxy = method_proxy.return_proxy
41
+ def redefine
42
+ Module.new do
43
+ def invoke(&block)
44
+ res = catch(:halt, &block)
43
45
 
44
- # Inclusive rather than exclusive validation. If one type/value is valid then there's no need to error.
45
- valid_type = proxy.type_expression.types.any? do |type|
46
- proxy.type_expression.validate(value: SinatraAdapter.reconstruct_return_value(type:, response:), proxy:)
47
- end
46
+ raise AllowedTypeError, 'Did you mean "Response.finish"?' if res.to_s == 'Response'
48
47
 
49
- # No valid types so let's return a server error to the client.
50
- unless valid_type
51
- proxy.type_expression.types.each do |type|
52
- value = SinatraAdapter.reconstruct_return_value(type:, response:)
53
- unless proxy.type_expression.validate(value:, proxy:)
54
- halt 500, {}, proxy.error_message(value: value.inspect)
55
- end
56
- end
48
+ route = "#{request.request_method} #{request.path}"
49
+ if (res && (method_proxy = self.class.low_methods[route]) && (proxy = method_proxy.return_proxy))
50
+ proxy.type_expression.types.each do |type|
51
+ proxy.type_expression.validate!(value: res, proxy:)
57
52
  end
58
53
  end
59
- end
60
- end
61
- end
62
54
 
63
- # The route's String/Array/Enumerable return value populates a Rack::Response object.
64
- # This response also contains values added via Sinatra DSL's header()/body() methods.
65
- # So reconstruct the return value from the response object, based on the return type.
66
- def self.reconstruct_return_value(type:, response:)
67
- valid_types = {
68
- Integer => -> (response) { response.status },
69
- String => -> (response) { response.body.first },
70
- HTML => -> (response) { response.body.first },
71
- JSON => -> (response) { response.body.first },
72
-
73
- [String] => -> (response) { response.body }, # Body is stored internally as an array.
74
- [Integer, String] => -> (response) { [response.status, response.body.first] },
75
- [Integer, Hash, String] => -> (response) { [response.status, response.headers, response.body.first] },
76
-
77
- # TODO: Represent Enumerable[T]. How would we match a Module of a class in a hash key?
78
- }
79
-
80
- raise AllowedTypeError, 'Did you mean "Response.finish"?' if type.to_s == 'Response'
81
-
82
- if (reconstructed_value = valid_types[type])
83
- return reconstructed_value.call(response)
84
- else
85
- raise AllowedTypeError, "Valid Sinatra return types: #{valid_types.keys.map(&:to_s).join(' | ')}"
55
+ res = [res] if (Integer === res) || (String === res)
56
+ if (Array === res) && (Integer === res.first)
57
+ res = res.dup
58
+ status(res.shift)
59
+ body(res.pop)
60
+ headers(*res)
61
+ elsif res.respond_to? :each
62
+ body res
63
+ end
64
+
65
+ nil # avoid double setting the same response tuple twice
66
+ end
86
67
  end
87
68
  end
88
69
 
@@ -95,7 +76,7 @@ module LowType
95
76
  expression = eval(return_type.slice).call
96
77
  expression = TypeExpression.new(type: expression) unless TypeExpression === expression
97
78
 
98
- SinatraReturnProxy.new(type_expression: expression, name: "#{method_node.name.upcase} #{pattern}", file:)
79
+ ReturnProxy.new(type_expression: expression, name: "#{method_node.name.upcase} #{pattern}", file:)
99
80
  end
100
81
  end
101
82
  end
data/lib/basic_types.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module LowType
2
2
  class Boolean; end # TrueClass or FalseClass
3
+
3
4
  class HTML < String; end
4
5
  class JSON < String; end
6
+ class XML < String; end
5
7
  end
@@ -1,6 +1,6 @@
1
1
  module LowType
2
2
  class AdapterInterface
3
- def redefine_methods
3
+ def process
4
4
  raise NotImplementedError
5
5
  end
6
6
  end
data/lib/low_type.rb CHANGED
@@ -43,8 +43,10 @@ module LowType
43
43
  klass.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.instance_methods, klass:, private_start_line:, file_path:)
44
44
  klass.singleton_class.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.class_methods, klass:, private_start_line:, file_path:)
45
45
 
46
- adapter = AdapterLoader.load(klass:, parser:, file_path:)
47
- adapter.redefine_methods if adapter
46
+ if (adapter = AdapterLoader.load(klass:, parser:, file_path:))
47
+ adapter.process
48
+ klass.prepend adapter.redefine
49
+ end
48
50
  ensure
49
51
  Array.define_singleton_method('[]', array_class_method)
50
52
  Hash.define_singleton_method('[]', hash_class_method)
data/lib/redefiner.rb CHANGED
@@ -7,8 +7,6 @@ require_relative 'type_expression'
7
7
 
8
8
  module LowType
9
9
  class Redefiner
10
- FILE_PATH = File.expand_path(__FILE__)
11
-
12
10
  class << self
13
11
  def redefine_methods(method_nodes:, klass:, private_start_line:, file_path:)
14
12
  Module.new do
@@ -1,13 +1,17 @@
1
1
  require_relative 'proxies/param_proxy'
2
2
 
3
3
  module LowType
4
- class TypeExpression
5
- FILE_PATH = File.expand_path(__FILE__)
4
+ root_path = File.expand_path(__dir__)
5
+ file_path = File.expand_path(__FILE__)
6
+ adapter_paths = Dir.chdir(root_path) { Dir.glob('adapters/*') }.map { |path| File.join(root_path, path) }
7
+
8
+ HIDDEN_PATHS = [file_path, *adapter_paths, File.join(root_path, 'redefiner.rb')]
6
9
 
10
+ class TypeExpression
7
11
  attr_reader :types, :default_value
8
12
 
13
+ # @param type - A literal type or an instance representation of a typed structure.
9
14
  def initialize(type: nil, default_value: :LOW_TYPE_UNDEFINED)
10
- # Types can be instance representations of a structure.
11
15
  @types = []
12
16
  @types << type unless type.nil?
13
17
  @default_value = default_value
@@ -30,26 +34,6 @@ module LowType
30
34
  @default_value == :LOW_TYPE_UNDEFINED
31
35
  end
32
36
 
33
- # Called in situations where we want to be inclusive rather than exclusive and pass validation if one of the types is valid.
34
- def validate(value:, proxy:)
35
- if value.nil?
36
- return true if @default_value.nil?
37
- return false if required?
38
- end
39
-
40
- @types.each do |type|
41
- return true if LowType.type?(type) && type <= value.class # Example: HTML is a subclass of String and should pass as a String.
42
- return true if ::Array === type && ::Array === value && array_types_match_values?(types: type, values: value)
43
-
44
- # TODO: Shallow validation of enumerables could be made deeper with user config.
45
- if type.class == ::Hash && value.class == ::Hash && type.keys[0] == value.keys[0].class && type.values[0] == value.values[0].class
46
- return true
47
- end
48
- end
49
-
50
- false
51
- end
52
-
53
37
  def validate!(value:, proxy:)
54
38
  if value.nil?
55
39
  return true if @default_value.nil?
@@ -68,8 +52,7 @@ module LowType
68
52
 
69
53
  raise proxy.error_type, proxy.error_message(value:)
70
54
  rescue proxy.error_type => e
71
- file_paths = [FILE_PATH, LowType::Redefiner::FILE_PATH]
72
- raise proxy.error_type, e.message, backtrace_with_proxy(file_paths:, backtrace: e.backtrace, proxy:)
55
+ raise proxy.error_type, e.message, backtrace_with_proxy(backtrace: e.backtrace, proxy:)
73
56
  end
74
57
 
75
58
  def valid_types
@@ -84,7 +67,7 @@ module LowType
84
67
  # [T, T, T]
85
68
  if types.length > 1
86
69
  types.each_with_index do |type, index|
87
- return false unless type === values[index]
70
+ return false unless type <= values[index].class # Example: HTML is a subclass of String and should pass as a String.
88
71
  end
89
72
  # [T]
90
73
  elsif types.length == 1
@@ -95,9 +78,9 @@ module LowType
95
78
  true
96
79
  end
97
80
 
98
- def backtrace_with_proxy(file_paths:, proxy:, backtrace:)
99
- # Remove LowType file paths from the backtrace.
100
- filtered_backtrace = backtrace.reject { |line| file_paths.find { |file_path| line.include?(file_path) } }
81
+ def backtrace_with_proxy(proxy:, backtrace:)
82
+ # Remove LowType defined method file paths from the backtrace.
83
+ filtered_backtrace = backtrace.reject { |line| HIDDEN_PATHS.find { |file_path| line.include?(file_path) } }
101
84
 
102
85
  # Add the proxied file to the backtrace.
103
86
  proxy_file_backtrace = "#{proxy.file.path}:#{proxy.file.line}:in '#{proxy.file.scope}'"
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LowType
4
- VERSION = '0.8.7'
4
+ VERSION = '0.8.9'
5
5
  end
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: 0.8.7
4
+ version: 0.8.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -19,7 +19,6 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - lib/adapters/adapter_loader.rb
21
21
  - lib/adapters/sinatra_adapter.rb
22
- - lib/adapters/sinatra_return_proxy.rb
23
22
  - lib/basic_types.rb
24
23
  - lib/error_types.rb
25
24
  - lib/interfaces/adapter_interface.rb
@@ -1,18 +0,0 @@
1
- require_relative '../proxies/return_proxy'
2
-
3
- module LowType
4
- class SinatraReturnProxy < ReturnProxy
5
- attr_reader :type_expression, :name
6
-
7
- def initialize(type_expression:, name:, file:)
8
- @type_expression = type_expression
9
- @name = name
10
- @file = file
11
- end
12
-
13
- def error_message(value:)
14
- value = value[0...20] if value
15
- "Invalid return value '#{value}' for method '#{@name}'. Valid types: '#{@type_expression.valid_types}'"
16
- end
17
- end
18
- end