jsonrpc2 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.markdown +176 -0
- data/Rakefile +2 -0
- data/example/config.ru +34 -0
- data/jsonrpc2.gemspec +55 -0
- data/lib/jsonrpc2.rb +4 -0
- data/lib/jsonrpc2/accept.rb +67 -0
- data/lib/jsonrpc2/auth.rb +66 -0
- data/lib/jsonrpc2/client.rb +59 -0
- data/lib/jsonrpc2/html.rb +137 -0
- data/lib/jsonrpc2/interface.rb +298 -0
- data/lib/jsonrpc2/textile.rb +131 -0
- data/lib/jsonrpc2/types.rb +174 -0
- data/lib/jsonrpc2/version.rb +5 -0
- metadata +156 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
module JSONRPC2
|
2
|
+
# Types are checked against textual descriptions of the contents:
|
3
|
+
#
|
4
|
+
# * String - A string
|
5
|
+
# * Number - Any kind of number
|
6
|
+
# * Integer - Integer value
|
7
|
+
# * Boolean - true or false
|
8
|
+
# * true - True value
|
9
|
+
# * false - False value
|
10
|
+
# * null - nil value
|
11
|
+
# * Object - An object
|
12
|
+
# * Array - An array
|
13
|
+
# * Array [Type] - An array of type Type
|
14
|
+
# * Value (or Any or void) - Any value of any type
|
15
|
+
# * CustomType - A defined custom object type
|
16
|
+
module Types
|
17
|
+
module_function
|
18
|
+
|
19
|
+
# Checks that object is of given type (and that any custom types
|
20
|
+
# comply with interface)
|
21
|
+
#
|
22
|
+
# @param [Interface] interface API class
|
23
|
+
# @param [String] type_string Description of type(s) - comma
|
24
|
+
# separated if more than one
|
25
|
+
# @param object Value to check check type
|
26
|
+
# @return [Boolean] true if ok
|
27
|
+
def valid?(interface, type_string, object)
|
28
|
+
res = type_string.split(/,/).any? do |type|
|
29
|
+
case type
|
30
|
+
when 'String'
|
31
|
+
object.is_a?(String)
|
32
|
+
when 'Number'
|
33
|
+
object.kind_of?(Numeric)
|
34
|
+
when 'true'
|
35
|
+
object == true
|
36
|
+
when 'false'
|
37
|
+
object == false
|
38
|
+
when 'Boolean'
|
39
|
+
object == true || object == false
|
40
|
+
when 'null'
|
41
|
+
object.nil?
|
42
|
+
when 'Integer'
|
43
|
+
object.kind_of?(Numeric) && (object.to_i.to_f == object.to_f)
|
44
|
+
when 'Object'
|
45
|
+
object.is_a?(Hash)
|
46
|
+
when 'Array'
|
47
|
+
object.is_a?(Array)
|
48
|
+
when /\AArray\[(.*)\]\z/
|
49
|
+
object.is_a?(Array) && object.all? { |value| valid?(interface, $1, value) }
|
50
|
+
when 'Value', 'Any', 'void'
|
51
|
+
true
|
52
|
+
else # Custom type
|
53
|
+
subset = (type[-1] == ?*)
|
54
|
+
type = type[0...-1] if subset
|
55
|
+
|
56
|
+
custom = interface.types[type]
|
57
|
+
#STDERR.puts "Type Info: #{custom.inspect}"
|
58
|
+
if custom
|
59
|
+
custom.valid_object?(interface, object, subset)
|
60
|
+
else
|
61
|
+
raise "Invalid/unknown type: #{type} for #{interface.name}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
#STDERR.puts "#{interface.name}: #{type} - #{object.inspect} - #{res ? "OK" : "FAIL"}"
|
66
|
+
res
|
67
|
+
end
|
68
|
+
|
69
|
+
# Checks that param hash is valid for API call
|
70
|
+
#
|
71
|
+
# @param [Interface] interface API class
|
72
|
+
# @param [String] method Method name
|
73
|
+
# @param [Hash] data params hash to check
|
74
|
+
# @return [Boolean] true if ok
|
75
|
+
def valid_params?(interface, method, data)
|
76
|
+
about = interface.about[method.to_s]
|
77
|
+
return true if about.nil? # No defined params
|
78
|
+
|
79
|
+
params = (about[:params] || [])
|
80
|
+
param_names = params.map { |param| param[:name] }
|
81
|
+
|
82
|
+
if params.empty? && data.empty?
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
|
86
|
+
extra_keys = data.keys - param_names
|
87
|
+
unless extra_keys.empty?
|
88
|
+
raise "Extra parameters #{extra_keys.inspect} for #{method}."
|
89
|
+
end
|
90
|
+
|
91
|
+
params.each do |param|
|
92
|
+
if data.has_key?(param[:name])
|
93
|
+
value = data[param[:name].to_s]
|
94
|
+
unless valid?(interface, param[:type], value)
|
95
|
+
raise "'#{param[:name]}' should be of type #{param[:type]}, was #{value.class.name}"
|
96
|
+
end
|
97
|
+
elsif ! param[:required]
|
98
|
+
next true
|
99
|
+
else
|
100
|
+
raise "Missing parameter: '#{param[:name]}' of type #{param[:type]} for #{method}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Checks that result is valid for API call
|
106
|
+
#
|
107
|
+
# @param [Interface] interface API class
|
108
|
+
# @param [String] method Method name
|
109
|
+
# @param [Hash] value Value to check
|
110
|
+
# @return [Boolean] true if ok
|
111
|
+
def valid_result?(interface, method, value)
|
112
|
+
about = interface.about[method.to_s]
|
113
|
+
return true if about.nil? # Undefined
|
114
|
+
if about[:returns].nil?
|
115
|
+
return value.nil?
|
116
|
+
end
|
117
|
+
valid?(interface, about[:returns][:type], value) or
|
118
|
+
raise "Invalid return type: should have been #{about[:returns][:type]}, was #{value.class.name}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Description of JSON object
|
123
|
+
class JsonObjectType
|
124
|
+
attr_accessor :name, :fields
|
125
|
+
def initialize(name, fields)
|
126
|
+
@name, @fields = name, fields
|
127
|
+
@required = true
|
128
|
+
end
|
129
|
+
|
130
|
+
# Check that #object Hash is valid version of this type
|
131
|
+
def valid_object?(interface, object, subset = false)
|
132
|
+
object.keys.all? { |key| fields.any? { |field| field[:name] == key } } &&
|
133
|
+
fields.all? { |field| (object.keys.include?(field[:name]) &&
|
134
|
+
Types.valid?(interface, field[:type], object[field[:name]])) || subset || (! field[:required]) }
|
135
|
+
end
|
136
|
+
|
137
|
+
# Add field of #name and #type to type description
|
138
|
+
def field(name, type, desc, options={})
|
139
|
+
@fields << { :name => name, :type => type, :desc => desc, :required => @required }.merge(options)
|
140
|
+
end
|
141
|
+
# Shortcut to define string field
|
142
|
+
def string name, desc, options={}; field(name, 'String', desc, options); end
|
143
|
+
# Shortcut to define number field
|
144
|
+
def number name, desc, options={}; field(name, 'Number', desc, options); end
|
145
|
+
# Shortcut to define integer field
|
146
|
+
def integer name, desc, options={}; field(name, 'Integer', desc, options); end
|
147
|
+
# Shortcut to define boolean field
|
148
|
+
def boolean name, desc, options={}; field(name, 'Boolean', desc, options); end
|
149
|
+
|
150
|
+
# Make fields defined in block optional by default
|
151
|
+
def optional(&block)
|
152
|
+
old_required = @required
|
153
|
+
begin
|
154
|
+
@required = false
|
155
|
+
yield(self)
|
156
|
+
ensure
|
157
|
+
@required = old_required
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Make fields defined in block required by default
|
162
|
+
def required(&block)
|
163
|
+
old_required = @required
|
164
|
+
begin
|
165
|
+
@required = true
|
166
|
+
yield(self)
|
167
|
+
ensure
|
168
|
+
@required = old_required
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jsonrpc2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Geoff Youngs
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-07-17 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: httpclient
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: json
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: RedCloth
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
description: |+
|
63
|
+
JSON-RPC2 server DSL - allows APIs to be created as mountable Rack applications
|
64
|
+
with inline documentation, authentication and type checking.
|
65
|
+
|
66
|
+
e.g.
|
67
|
+
|
68
|
+
class Calculator < JSONRPC2::Interface
|
69
|
+
title "JSON-RPC2 Calculator"
|
70
|
+
introduction "This interface allows basic maths calculations via JSON-RPC2"
|
71
|
+
auth_with JSONRPC2::BasicAuth.new({'user' => 'secretword'})
|
72
|
+
|
73
|
+
section 'Simple Ops' do
|
74
|
+
desc 'Multiply two numbers'
|
75
|
+
param 'a', 'Number', 'a'
|
76
|
+
param 'b', 'Number', 'b'
|
77
|
+
result 'Number', 'a * b'
|
78
|
+
def mul args
|
79
|
+
args['a'] * args['b']
|
80
|
+
end
|
81
|
+
|
82
|
+
desc 'Add numbers'
|
83
|
+
example "Calculate 1 + 1 = 2", :params => { 'a' => 1, 'b' => 1}, :result => 2
|
84
|
+
|
85
|
+
param 'a', 'Number', 'First number'
|
86
|
+
param 'b', 'Number', 'Second number'
|
87
|
+
optional 'c', 'Number', 'Third number'
|
88
|
+
result 'Number', 'a + b + c'
|
89
|
+
def sum args
|
90
|
+
val = args['a'] + args['b']
|
91
|
+
val += args['c'] if args['c']
|
92
|
+
val
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
email:
|
98
|
+
- git@intersect-uk.co.uk
|
99
|
+
executables: []
|
100
|
+
|
101
|
+
extensions: []
|
102
|
+
|
103
|
+
extra_rdoc_files: []
|
104
|
+
|
105
|
+
files:
|
106
|
+
- .gitignore
|
107
|
+
- Gemfile
|
108
|
+
- README.markdown
|
109
|
+
- Rakefile
|
110
|
+
- example/config.ru
|
111
|
+
- jsonrpc2.gemspec
|
112
|
+
- lib/jsonrpc2.rb
|
113
|
+
- lib/jsonrpc2/accept.rb
|
114
|
+
- lib/jsonrpc2/auth.rb
|
115
|
+
- lib/jsonrpc2/client.rb
|
116
|
+
- lib/jsonrpc2/html.rb
|
117
|
+
- lib/jsonrpc2/interface.rb
|
118
|
+
- lib/jsonrpc2/textile.rb
|
119
|
+
- lib/jsonrpc2/types.rb
|
120
|
+
- lib/jsonrpc2/version.rb
|
121
|
+
homepage: http://github.com/geoffyoungs/jsonrpc2
|
122
|
+
licenses: []
|
123
|
+
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
hash: 3
|
135
|
+
segments:
|
136
|
+
- 0
|
137
|
+
version: "0"
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
hash: 3
|
144
|
+
segments:
|
145
|
+
- 0
|
146
|
+
version: "0"
|
147
|
+
requirements: []
|
148
|
+
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 1.8.15
|
151
|
+
signing_key:
|
152
|
+
specification_version: 3
|
153
|
+
summary: JSON-RPC2 server DSL
|
154
|
+
test_files: []
|
155
|
+
|
156
|
+
has_rdoc:
|