krpc 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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