krpc 0.2.0 → 0.2.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.
@@ -2,10 +2,11 @@
2
2
 
3
3
  lib = File.expand_path('../lib', __FILE__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'krpc/version'
5
6
 
6
7
  Gem::Specification.new do |s|
7
8
  s.name = "krpc"
8
- s.version = "0.2.0"
9
+ s.version = KRPC::VERSION
9
10
  s.authors = ["Tomasz Więch"]
10
11
  s.email = ["tewu.dev@gmail.com"]
11
12
 
@@ -27,7 +28,8 @@ Gem::Specification.new do |s|
27
28
  s.add_runtime_dependency "ruby_protobuf", "~> 0.4"
28
29
  s.add_runtime_dependency "colorize", "~> 0.7"
29
30
  s.add_runtime_dependency "nokogiri", "~> 1.6"
30
- s.add_development_dependency "bundler", "~> 1.10"
31
+ s.add_development_dependency 'bundler', '~> 1.7', '>= 1.7.0'
31
32
  s.add_development_dependency "pry", "~> 0.10"
33
+ s.add_development_dependency "rspec", "~> 3.0"
32
34
  s.add_development_dependency "hanna-nouveau", "~> 0.4"
33
35
  end
@@ -1,3 +1,4 @@
1
+ require 'krpc/version'
1
2
  require 'krpc/client'
2
3
 
3
4
  module KRPC
@@ -14,7 +14,7 @@ module KRPC
14
14
  # A kRPC client, through which all Remote Procedure Calls are made. To make RPC calls client
15
15
  # must first connect to server. This can be achieved by calling Client#connect or Client#connect!
16
16
  # methods. Client object can connect and disconnect from the server many times during it's
17
- # lifetime. RPCs can be made by calling Client#rpc method. After generating services API (with
17
+ # lifetime. RPCs can be made by calling Client#execute_rpc method. After generating services API (with
18
18
  # Client#generate_services_api! call), RPCs can be also made using
19
19
  # `client.service_name.procedure_name(parameter, ...)`
20
20
  #
@@ -126,72 +126,86 @@ module KRPC
126
126
 
127
127
  # Returns `true` if services API has been already generated, `false` otherwise.
128
128
  def services_api_generated?
129
- respond_to? :space_center
129
+ respond_to? :space_center or respond_to? :test_service
130
130
  end
131
131
 
132
132
  # Execute an RPC.
133
- def rpc(service, procedure, args=[], kwargs={}, param_names=[], param_types=[], param_default=[], return_type: nil)
134
- # Send request
135
- req = build_request(service, procedure, args, kwargs, param_names, param_types, param_default)
136
- rpc_connection.send Encoder.encode_request(req)
137
- # Receive response
138
- resp_length = rpc_connection.recv_varint
139
- resp_data = rpc_connection.recv resp_length
140
- resp = PB::Response.new
141
- resp.parse_from_string resp_data
142
- # Check for an error response
133
+ def execute_rpc(service, procedure, args=[], kwargs={}, param_names=[], param_types=[], param_default=[], return_type: nil)
134
+ send_request(service, procedure, args, kwargs, param_names, param_types, param_default)
135
+ resp = receive_response
143
136
  raise(RPCError, resp.error) if resp.has_field? "error"
144
- # Optionally decode and return the response' return value
145
- if return_type == nil
146
- nil
147
- else
137
+ unless return_type.nil?
148
138
  Decoder.decode(resp.return_value, return_type, self)
139
+ else
140
+ nil
149
141
  end
150
142
  rescue IOError => e
151
143
  raise(Exception, "RPC call attempt while not connected to server -- call Client#connect first") if not connected?
152
144
  raise e
153
145
  end
154
-
146
+
155
147
  # Build a PB::Request object.
156
148
  def build_request(service, procedure, args=[], kwargs={}, param_names=[], param_types=[], param_default=[])
157
149
  begin
158
150
  raise(ArgumentError, "param_names and param_types should be equal length\n\tparam_names = #{param_names}\n\tparam_types = #{param_types}") unless param_names.length == param_types.length
159
151
  raise(ArgumentError, "param_names and param_default should be equal length\n\tparam_names = #{param_names}\n\tparam_default = #{param_default}") unless param_names.length == param_default.length
160
- required_params_count = param_default.take_while(&:nil?).count
152
+ required_params_count = param_default.take_while{|pd| pd == :no_default_value}.count
161
153
  raise ArgumentsNumberErrorSig.new(args.count, required_params_count..param_names.count) unless args.count <= param_names.count
162
- kwargs_remaining = kwargs.count
163
-
164
- param_names_symbols = param_names.map(&:to_sym)
165
- req_args = param_names_symbols.map.with_index do |name,i|
166
- is_kwarg = kwargs.has_key? name
167
- raise ArgumentErrorSig.new("there are both positional and keyword arguments for parameter \"#{name}\"") if is_kwarg && i < args.count
168
- kwargs_remaining -= 1 if is_kwarg
169
- unless i >= required_params_count &&
170
- (!is_kwarg && i >= args.count ||
171
- !is_kwarg && args[i] == param_default[i] ||
172
- is_kwarg && kwargs[name] == param_default[i])
173
- arg = if is_kwarg then kwargs[name]
174
- elsif i < args.count then args[i]
175
- else raise ArgumentErrorSig.new("missing argument for parameter \"#{name}\"")
176
- end
177
- begin
178
- arg = TypeStore.coerce_to(arg, param_types[i])
179
- rescue ValueError
180
- raise ArgumentErrorSig.new("argument for parameter \"#{name}\" must be a #{param_types[i].ruby_type} -- got #{args[i]} of type #{args[i].class}")
181
- end
182
- v = Encoder.encode(arg, param_types[i])
183
- PB::Argument.new(position: i, value: v)
184
- end
185
- end.compact
186
- raise ArgumentErrorSig.new("keyword arguments for non existing parameters: #{(kwargs.keys - param_names_symbols).join(", ")}") unless kwargs_remaining == 0
154
+ req_args = construct_arguments(args, kwargs, param_names, param_types, param_default, required_params_count)
187
155
  rescue ArgumentErrorSig => err
188
156
  raise err.with_signature(Doc.docstring_for_procedure(service, procedure, false))
189
157
  end
190
158
  PB::Request.new(service: service, procedure: procedure, arguments: req_args)
191
159
  end
192
-
160
+
193
161
  protected #----------------------------------
194
162
 
163
+ def construct_arguments(args, kwargs, param_names, param_types, param_default, required_params_count)
164
+ param_names_symbols = param_names.map(&:to_sym)
165
+ kwargs_remaining = kwargs.count
166
+
167
+ req_args = param_names_symbols.map.with_index do |name, i|
168
+ is_kwarg = kwargs.has_key? name
169
+ kwargs_remaining -= 1 if is_kwarg
170
+ raise ArgumentErrorSig.new("there are both positional and keyword arguments for parameter \"#{name}\"") if is_kwarg && i < args.count
171
+ is_parameter_optional = i >= required_params_count
172
+ is_parameter_has_default_value = !is_kwarg && i >= args.count ||
173
+ !is_kwarg && args[i] == param_default[i] ||
174
+ is_kwarg && kwargs[name] == param_default[i]
175
+ unless is_parameter_optional and is_parameter_has_default_value
176
+ arg = if is_kwarg then
177
+ kwargs[name]
178
+ elsif i < args.count then
179
+ args[i]
180
+ else
181
+ raise ArgumentErrorSig.new("missing argument for parameter \"#{name}\"")
182
+ end
183
+ begin
184
+ arg = TypeStore.coerce_to(arg, param_types[i])
185
+ rescue ValueError
186
+ raise ArgumentErrorSig.new("argument for parameter \"#{name}\" must be a #{param_types[i].ruby_type} -- got #{args[i].inspect} of type #{args[i].class}")
187
+ end
188
+ v = Encoder.encode(arg, param_types[i])
189
+ PB::Argument.new(position: i, value: v)
190
+ end
191
+ end.compact
192
+
193
+ raise ArgumentErrorSig.new("keyword arguments for non existing parameters: #{(kwargs.keys - param_names_symbols).join(", ")}") unless kwargs_remaining == 0
194
+ req_args
195
+ end
196
+
197
+ def send_request(service, procedure, args, kwargs, param_names, param_types, param_default)
198
+ req = build_request(service, procedure, args, kwargs, param_names, param_types, param_default)
199
+ rpc_connection.send Encoder.encode_request(req)
200
+ end
201
+
202
+ def receive_response
203
+ resp_length = rpc_connection.recv_varint
204
+ resp_data = rpc_connection.recv resp_length
205
+ resp = PB::Response.new
206
+ resp.parse_from_string resp_data
207
+ end
208
+
195
209
  def call_block_and_close(block)
196
210
  begin block.call(self) ensure close end
197
211
  end
@@ -24,8 +24,17 @@ class String
24
24
  tr("-", "_").
25
25
  downcase
26
26
  end
27
+
28
+ def integer?
29
+ Integer(self) != nil rescue false
30
+ end
31
+
32
+ def numeric?
33
+ Float(self) != nil rescue false
34
+ end
27
35
  end
28
36
 
37
+
29
38
  class Array
30
39
  def extract_kwargs!
31
40
  last.is_a?(::Hash) ? pop : {}
@@ -13,7 +13,7 @@ module KRPC
13
13
  service_module_name, class_name = ruby_class_to_pb_module_class_pair(class_cls)
14
14
  key = [service_module_name, is_static, class_name, method_name.to_s].hash
15
15
  if @docstr_infos.has_key? key
16
- construct_docstring(*@docstr_infos[key], true, is_print_xmldoc_summary)
16
+ construct_docstring(*@docstr_infos[key], true, is_static, is_print_xmldoc_summary)
17
17
  else
18
18
  "No docstring for #{class_cls.name}#{calc_separator(is_static)}#{method_name.to_s} method" +
19
19
  (method_owner.respond_to?(method_name) ? "" : "\nThere is no such method -- maybe a typo")
@@ -23,7 +23,7 @@ module KRPC
23
23
  def docstring_for_procedure(service_name, procedure_name, is_print_xmldoc_summary = true)
24
24
  key = [service_name, procedure_name].hash
25
25
  if @procedure_docstr_infos.has_key? key
26
- construct_docstring(service_name, '.', procedure_name, *@procedure_docstr_infos[key][3..-1], false, is_print_xmldoc_summary)
26
+ construct_docstring(service_name, '.', procedure_name, *@procedure_docstr_infos[key][3..-1], false, false, is_print_xmldoc_summary)
27
27
  else
28
28
  "No docstring for #{service_name}.#{procedure_name} procedure"
29
29
  end
@@ -58,7 +58,7 @@ module KRPC
58
58
  is_static ? '.' : '#'
59
59
  end
60
60
 
61
- def construct_docstring(namespace, separator, name, param_names, param_types, param_default, return_type, xmldoc, is_hide_this_param, is_print_xmldoc_summary)
61
+ def construct_docstring(namespace, separator, name, param_names, param_types, param_default, return_type, xmldoc, is_hide_this_param, is_prepend_client_param, is_print_xmldoc_summary)
62
62
  xmldoc = Nokogiri::XML(xmldoc)
63
63
  xmldoc_summary = xmlElements2str(xmldoc.xpath("doc/summary").children, :light_blue, :light_green, :light_red)
64
64
 
@@ -79,11 +79,12 @@ module KRPC
79
79
 
80
80
  param_infos = param_names.zip(param_types.map{|x| type2str(x)}, param_default)
81
81
  param_infos.shift if is_hide_this_param && param_names[0] == "this"
82
+ param_infos.unshift ["client", "Client", :no_default_value] if is_prepend_client_param
82
83
  if param_infos.empty?
83
84
  params = ""
84
85
  else
85
86
  params = "\n" + param_infos.map.with_index do |pi, i|
86
- "\t#{pi[0].light_green} :#{pi[1]}" + (pi[2].nil? ? "" : " = #{pi[2]}".magenta) \
87
+ "\t#{pi[0].light_green} :#{pi[1]}" + (pi[2] == :no_default_value ? "" : " = #{pi[2].inspect}".magenta) \
87
88
  + (param_infos.size == i+1 ? "" : ",") \
88
89
  + xmldoc_params[pi[0]].to_s
89
90
  end.join("\n") + "\n"
@@ -20,25 +20,25 @@ module KRPC
20
20
  encode_value(remote_oid, TypeStore["uint64"])
21
21
  elsif type.is_a?(Types::ListType)
22
22
  msg = TypeStore["KRPC.List"].ruby_type.new
23
- msg.items = obj.map{|x| encode(x, type.value_type)}.to_a
23
+ msg.items = obj.map{|x| encode( TypeStore.coerce_to(x, type.value_type), type.value_type )}.to_a
24
24
  msg.serialize_to_string
25
25
  elsif type.is_a?(Types::DictionaryType)
26
26
  entry_type = TypeStore["KRPC.DictionaryEntry"].ruby_type
27
27
  msg = TypeStore["KRPC.Dictionary"].ruby_type.new
28
28
  msg.entries = obj.map do |k,v|
29
29
  entry = entry_type.new
30
- entry.key = encode(k, type.key_type)
31
- entry.value = encode(v, type.value_type)
30
+ entry.key = encode( TypeStore.coerce_to(k, type.key_type), type.key_type )
31
+ entry.value = encode( TypeStore.coerce_to(v, type.value_type), type.value_type )
32
32
  entry
33
33
  end
34
34
  msg.serialize_to_string
35
35
  elsif type.is_a?(Types::SetType)
36
36
  msg = TypeStore["KRPC.Set"].ruby_type.new
37
- msg.items = obj.map{|x| encode(x, type.value_type)}.to_a
37
+ msg.items = obj.map{|x| encode( TypeStore.coerce_to(x, type.value_type), type.value_type )}.to_a
38
38
  msg.serialize_to_string
39
39
  elsif type.is_a?(Types::TupleType)
40
40
  msg = TypeStore["KRPC.Tuple"].ruby_type.new
41
- msg.items = obj.zip(type.value_types).map{|x,t| encode(x, t)}.to_a
41
+ msg.items = obj.zip(type.value_types).map{|x,t| encode( TypeStore.coerce_to(x, t), t )}.to_a
42
42
  msg.serialize_to_string
43
43
  else raise(RuntimeError, "Cannot encode object #{obj} of type #{type}")
44
44
  end
@@ -28,6 +28,10 @@ module KRPC
28
28
  super("wrong number of arguments (#{args_count} for #{valid_params_str})", sig)
29
29
  end
30
30
 
31
+ def with_arguments_count_incremented_by(args_count_increment)
32
+ self.class.new(args_count + args_count_increment, (valid_params_count_range.min + args_count_increment)..(valid_params_count_range.max + args_count_increment), signature)
33
+ end
34
+
31
35
  def with_signature(sig)
32
36
  self.class.new(args_count, valid_params_count_range, sig)
33
37
  end
@@ -5,8 +5,6 @@ require 'colorize'
5
5
 
6
6
  module KRPC
7
7
  module Gen
8
- AvailableToClassAndInstanceModuleName = "AvailableToClassAndInstance"
9
-
10
8
  class << self
11
9
  def service_gen_module(service_name)
12
10
  const_get_or_create(service_name, Module.new)
@@ -28,52 +26,71 @@ module KRPC
28
26
  values.map{|ev| [ev.name.underscore.to_sym, ev.value]}.to_h
29
27
  end
30
28
  end
31
-
29
+
32
30
  def add_rpc_method(cls, method_name, service_name, proc, *options)
33
31
  is_static = options.include? :static
34
32
  prepend_self_to_args = options.include? :prepend_self_to_args
35
- target_module = is_static ? cls.const_get_or_create(AvailableToClassAndInstanceModuleName, Module.new) : cls
36
33
  param_names, param_types, param_default, return_type = parse_procedure(proc)
37
34
  method_name = method_name.underscore
35
+ args = [cls, method_name, param_default, param_names, param_types, prepend_self_to_args, proc, return_type, service_name]
36
+
37
+ define_rpc_method(*args)
38
+ define_static_rpc_method(*args) if is_static
39
+ add_stream_constructing_proc(*args) unless options.include? :no_stream
40
+ Doc.add_docstring_info(is_static, cls, method_name, service_name, proc.name, param_names, param_types, param_default, return_type: return_type, xmldoc: proc.documentation)
41
+ end
38
42
 
39
- transform_exceptions = Proc.new do |method_owner, &block|
40
- begin
41
- block.call
42
- rescue ArgumentsNumberErrorSig => err
43
- sig = Doc.docstring_for_method(method_owner, method_name, false)
44
- if prepend_self_to_args then raise ArgumentsNumberErrorSig.new(err.args_count - 1, (err.valid_params_count_range.min-1)..(err.valid_params_count_range.max-1), sig)
45
- else raise err.with_signature(sig) end
46
- rescue ArgumentErrorSig => err
47
- raise err.with_signature(Doc.docstring_for_method(method_owner, method_name, false))
43
+ def transform_exceptions(method_owner, method_name, prepend_self_to_args, &block)
44
+ begin
45
+ block.call
46
+ rescue ArgumentsNumberErrorSig => err
47
+ err = err.with_signature(Doc.docstring_for_method(method_owner, method_name, false))
48
+ if prepend_self_to_args then raise err.with_arguments_count_incremented_by(-1)
49
+ elsif method_owner.is_a?(Class) then raise err.with_arguments_count_incremented_by(1)
50
+ else raise err end
51
+ rescue ArgumentErrorSig => err
52
+ raise err.with_signature(Doc.docstring_for_method(method_owner, method_name, false))
53
+ end
54
+ end
55
+
56
+ private #----------------------------------
57
+
58
+ def define_static_rpc_method(cls, method_name, param_default, param_names, param_types, prepend_self_to_args, proc, return_type, service_name)
59
+ cls.instance_eval do
60
+ define_singleton_method method_name do |*args|
61
+ Gen.transform_exceptions(cls, method_name, prepend_self_to_args) do
62
+ raise ArgumentErrorSig.new("missing argument for parameter \"client\"") if args.count < 1
63
+ raise ArgumentErrorSig.new("argument for parameter \"client\" must be a #{KRPC::Client.name} -- got #{args.first.inspect} of type #{args.first.class}") unless args.first.is_a?(KRPC::Client)
64
+ client = args.shift
65
+ kwargs = args.extract_kwargs!
66
+ client.execute_rpc(service_name, proc.name, args, kwargs, param_names, param_types, param_default, return_type: return_type)
67
+ end
48
68
  end
49
69
  end
70
+ end
50
71
 
51
- # Define method
52
- target_module.instance_eval do
72
+ def define_rpc_method(cls, method_name, param_default, param_names, param_types, prepend_self_to_args, proc, return_type, service_name)
73
+ cls.instance_eval do
53
74
  define_method method_name do |*args|
54
- transform_exceptions.call(self) do
75
+ Gen.transform_exceptions(self, method_name, prepend_self_to_args) do
55
76
  kwargs = args.extract_kwargs!
56
77
  args = [self] + args if prepend_self_to_args
57
- self.client.rpc(service_name, proc.name, args, kwargs, param_names, param_types, param_default, return_type: return_type)
78
+ self.client.execute_rpc(service_name, proc.name, args, kwargs, param_names, param_types, param_default, return_type: return_type)
58
79
  end
59
80
  end
60
81
  end
61
- # Add stream-constructing Proc
62
- unless options.include? :no_stream
63
- cls.stream_constructors[method_name] = Proc.new do |this, *args, **kwargs|
64
- transform_exceptions.call(this) do
65
- req_args = prepend_self_to_args ? [this] + args : args
66
- request = this.client.build_request(service_name, proc.name, req_args, kwargs, param_names, param_types, param_default)
67
- this.client.streams_manager.create_stream(request, return_type, this.method(method_name), *args, **kwargs)
68
- end
82
+ end
83
+
84
+ def add_stream_constructing_proc(cls, method_name, param_default, param_names, param_types, prepend_self_to_args, proc, return_type, service_name)
85
+ cls.stream_constructors[method_name] = Proc.new do |this, *args, **kwargs|
86
+ Gen.transform_exceptions(this, method_name, prepend_self_to_args) do
87
+ req_args = prepend_self_to_args ? [this] + args : args
88
+ request = this.client.build_request(service_name, proc.name, req_args, kwargs, param_names, param_types, param_default)
89
+ this.client.streams_manager.create_stream(request, return_type, this.method(method_name), *args, **kwargs)
69
90
  end
70
91
  end
71
- # Add docstring info
72
- Doc.add_docstring_info(is_static, cls, method_name, service_name, proc.name, param_names, param_types, param_default, return_type: return_type, xmldoc: proc.documentation)
73
92
  end
74
93
 
75
- private #----------------------------------
76
-
77
94
  def parse_procedure(proc)
78
95
  param_names = proc.parameters.map{|p| p.name.underscore}
79
96
  param_types = proc.parameters.map.with_index do |p,i|
@@ -82,7 +99,7 @@ module KRPC
82
99
  param_default = proc.parameters.zip(param_types).map do |param, type|
83
100
  if param.has_field?("default_argument")
84
101
  Decoder.decode(param.default_argument, type, :clientless)
85
- else nil
102
+ else :no_default_value
86
103
  end
87
104
  end
88
105
  return_type = if proc.has_field?("return_type")
@@ -98,19 +115,9 @@ module KRPC
98
115
  end
99
116
  end
100
117
 
101
- module AvailableToClassAndInstanceMethodsHandler
102
- def add_methods_available_to_class_and_instance
103
- if const_defined? AvailableToClassAndInstanceModuleName
104
- extend const_get(AvailableToClassAndInstanceModuleName)
105
- include const_get(AvailableToClassAndInstanceModuleName)
106
- end
107
- end
108
- end
109
-
110
118
  ##
111
119
  # Base class for service-defined class types.
112
120
  class ClassBase
113
- extend AvailableToClassAndInstanceMethodsHandler
114
121
  include Doc::SuffixMethods
115
122
  include Streaming::StreamConstructors
116
123
 
@@ -55,12 +55,6 @@ module KRPC
55
55
  end
56
56
  end
57
57
 
58
- # Add methods available to class and instance in service class & service' classes
59
- service_class.add_methods_available_to_class_and_instance
60
- mod = Gen.service_gen_module(service_name)
61
- service_classes = mod.constants.map{|c| mod.const_get(c)}.select {|c| c.is_a? Class}
62
- service_classes.each(&:add_methods_available_to_class_and_instance)
63
-
64
58
  # Return service class
65
59
  service_class
66
60
  end
@@ -70,7 +64,6 @@ module KRPC
70
64
  ##
71
65
  # Base class for service objects, created at runtime using information received from the server.
72
66
  class ServiceBase
73
- extend Gen::AvailableToClassAndInstanceMethodsHandler
74
67
  include Doc::SuffixMethods
75
68
  include Streaming::StreamConstructors
76
69
 
@@ -8,7 +8,7 @@ require 'set'
8
8
  module KRPC
9
9
  module Types
10
10
  PROTOBUF_VALUE_TYPES = ["double", "float", "int32", "int64", "uint32", "uint64", "bool", "string", "bytes"]
11
- RUBY_VALUE_TYPES = [Float, Integer, Boolean, String]
11
+ RUBY_VALUE_TYPES = [Float, Integer, Boolean, String, Array]
12
12
  PROTOBUF_TO_RUBY_VALUE_TYPE = {
13
13
  "double" => Float,
14
14
  "float" => Float,
@@ -18,7 +18,7 @@ module KRPC
18
18
  "uint64" => Integer,
19
19
  "bool" => Boolean,
20
20
  "string" => String,
21
- "bytes" => String
21
+ "bytes" => Array
22
22
  }
23
23
  PROTOBUF_TO_MESSAGE_TYPE = ProtobufUtils.create_PB_to_PB_message_class_hash("KRPC")
24
24
 
@@ -37,6 +37,7 @@ module KRPC
37
37
  elsif type_string.start_with? "Dictionary(" || type_string == "Dictionary" then DictionaryType.new(type_string)
38
38
  elsif type_string.start_with? "Set(" || type_string == "Set" then SetType.new(type_string)
39
39
  elsif type_string.start_with? "Tuple(" || type_string == "Tuple" then TupleType.new(type_string)
40
+ elsif type_string == "Test.TestEnum" then ValueType.new("int32")
40
41
  else # A message type (eg. type_string = "KRPC.List" or "KRPC.Services")
41
42
  raise(ValueError, "\"#{type_string}\" is not a valid type string") unless /^[A-Za-z0-9_\.]+$/ =~ type_string
42
43
  if PROTOBUF_TO_MESSAGE_TYPE.has_key? type_string
@@ -97,11 +98,15 @@ module KRPC
97
98
  raise(ValueError, "Failed to coerce value #{value.to_s} of type #{value.class} to type #{type}")
98
99
  end
99
100
  # Numeric types
100
- if type.ruby_type == Float && value.respond_to?(:to_f)
101
+ if type.ruby_type == Float && ( value.kind_of?(Float) || value.to_s.numeric? )
101
102
  return value.to_f
102
- elsif type.ruby_type == Integer && value.respond_to?(:to_i)
103
+ elsif type.ruby_type == Integer && ( value.kind_of?(Integer) || value.to_s.integer? )
103
104
  return value.to_i
104
105
  end
106
+ # Convert value type to string
107
+ if type.is_a?(ValueType) && type.ruby_type == String
108
+ return value.to_s
109
+ end
105
110
  raise(ValueError, "Failed to coerce value #{value.to_s} of type #{value.class} to type #{type}")
106
111
  end
107
112