snapi 0.0.6 → 0.0.7
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.
- data/CHANGELOG +13 -0
- data/README.md +264 -40
- data/lib/core_ext/hash.rb +30 -0
- data/lib/snapi.rb +25 -4
- data/lib/snapi/argument.rb +5 -3
- data/lib/snapi/errors.rb +0 -1
- data/lib/snapi/function.rb +18 -7
- data/lib/snapi/sinatra_extension.rb +32 -21
- data/lib/snapi/sinatra_extension_helper.rb +39 -0
- data/lib/snapi/validator.rb +0 -10
- data/lib/snapi/version.rb +1 -1
- data/spec/argument_spec.rb +2 -2
- data/spec/basic_capability_spec.rb +12 -10
- data/spec/snapi_spec.rb +43 -0
- metadata +5 -2
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
0.0.7
|
|
2
|
+
-- remove support for Snapi.capability_root
|
|
3
|
+
-- Import deep_merge, and deep_merge! methods for Ruby
|
|
4
|
+
core Has class
|
|
5
|
+
-- Improve Snapi module API
|
|
6
|
+
-- Improvements to Sinatra Extension
|
|
7
|
+
-- Sinatra Extension helper with repsonse_wrapper method
|
|
8
|
+
-- to_hash method on function now includes and Array of argument
|
|
9
|
+
-- utilize default_value if set for a :string argument
|
|
10
|
+
-- remove useless regexes from validator
|
|
11
|
+
-- Improve spec tests
|
|
12
|
+
-- Improve Documentation in README.md
|
|
13
|
+
|
|
1
14
|
0.0.6
|
|
2
15
|
-- fix bug around @@capabilities overide in Sinatra Extension
|
|
3
16
|
0.0.5
|
data/README.md
CHANGED
|
@@ -2,55 +2,203 @@
|
|
|
2
2
|
|
|
3
3
|
Snapi is a modular API functionality definition tool.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](http://badge.fury.io/rb/snapi) [](https://codeclimate.com/github/pwnieexpress/snapi)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
manually for now.
|
|
7
|
+
## Usage
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
git clone git@github.com:pwnieexpress/snapi.git
|
|
12
|
-
cd snapi
|
|
13
|
-
gem build snapi.gemspec
|
|
14
|
-
gem install snapi
|
|
15
|
-
```
|
|
16
|
-
This has only been used on `1.9.3` but is should run fine on `1.9+` and `2.x+`
|
|
9
|
+
### Installation
|
|
17
10
|
|
|
18
|
-
|
|
11
|
+
Install by simply typing `gem install snapi`. This has only been tested on
|
|
12
|
+
`1.9.3` but is should run fine on `1.9+` and `2.x+`
|
|
13
|
+
|
|
14
|
+
### Snapi
|
|
15
|
+
|
|
16
|
+
The main access point to the functionality that has been defined is through the
|
|
17
|
+
top level `Snapi` module.
|
|
18
|
+
|
|
19
|
+
#### Functionality Disclosure
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
`Snapi` is intended to provided *functionality disclosure* so that different
|
|
22
|
+
APIs can be defined for different purposes quickly and in a way that self
|
|
23
|
+
documents and organizes the functionality.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Capabilities
|
|
27
|
+
|
|
28
|
+
Capabilities are core to Snapis API organizational structure. A capability
|
|
29
|
+
represents a collection of functions and can, for many intents and purposes, be
|
|
30
|
+
thought of as a kind of module.
|
|
31
|
+
|
|
32
|
+
To create a capability start with a Ruby class and include the
|
|
33
|
+
`Snapi::Capability` module.
|
|
21
34
|
|
|
22
35
|
```ruby
|
|
23
36
|
require 'snapi'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
|
|
38
|
+
class SayHello
|
|
39
|
+
include Snapi::Capability
|
|
40
|
+
function :hello_world
|
|
41
|
+
|
|
42
|
+
# Note, even if this doesn't require any external args it still requires an
|
|
43
|
+
# arity of 1.
|
|
44
|
+
def self.hello_world(params)
|
|
45
|
+
"Hello World"
|
|
32
46
|
end
|
|
33
47
|
end
|
|
48
|
+
```
|
|
34
49
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
#### Working with Snapi Capabilities
|
|
51
|
+
|
|
52
|
+
`Snapi` provides a number of helpful ways to view and access and work with
|
|
53
|
+
capabilities.
|
|
54
|
+
|
|
55
|
+
##### Check for the presence of a capability:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
Snapi.has_capability?(:capability)
|
|
59
|
+
#=> true / false
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
##### Access a specific capability by name:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
Snapi[:capability]
|
|
66
|
+
#=> the class in question
|
|
67
|
+
```
|
|
68
|
+
##### See all capabilities
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
Snapi.capabilities
|
|
72
|
+
#=> Hash of Capability information
|
|
73
|
+
|
|
74
|
+
Snapi.valid_capabilities
|
|
75
|
+
#=> Array of the names of valid capabilities
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Functions
|
|
79
|
+
|
|
80
|
+
Functions are declared inside the capability class. At a minimum the functions
|
|
81
|
+
take a name which maps to a class method defined on the capability class.
|
|
82
|
+
|
|
83
|
+
The minimal function as declared above looks like:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
function :hello_world
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
:exclamation: *Note:* class methods which serve functions demand having an
|
|
90
|
+
arity of 1. They should expect a hash containing keyed values which map to the
|
|
91
|
+
arguments defined in the `Snapi` function definition.
|
|
92
|
+
|
|
93
|
+
#### Arguments
|
|
94
|
+
|
|
95
|
+
Functions are handy but they are much more useful when you begin to declare
|
|
96
|
+
arguments for them. Lets define a more interested method for our capability.
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
function :hello do |fn|
|
|
100
|
+
fn.argument :friend do |arg|
|
|
101
|
+
arg.required true
|
|
102
|
+
arg.type :string
|
|
48
103
|
end
|
|
49
|
-
library ScannerLibrary
|
|
50
104
|
end
|
|
51
105
|
```
|
|
52
106
|
|
|
53
|
-
|
|
107
|
+
Now we can create more dynamic method to serve this function.
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
def self.hello(params)
|
|
111
|
+
"Hello #{params[:friend]}"
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
##### Argument Attributes
|
|
116
|
+
|
|
117
|
+
Arguments can collect a bit of meta information as they are being defined.
|
|
118
|
+
|
|
119
|
+
###### type
|
|
120
|
+
|
|
121
|
+
Arguments can come in the following types:
|
|
122
|
+
|
|
123
|
+
* `:boolean`
|
|
124
|
+
* `:enum`: *not currently implemented*
|
|
125
|
+
* `:string`
|
|
126
|
+
* `:number`
|
|
127
|
+
* `:timestamp`: *not currently implemented*
|
|
128
|
+
|
|
129
|
+
###### default_value
|
|
130
|
+
|
|
131
|
+
Provide a default value for non-required `:string` typed arguments.
|
|
132
|
+
|
|
133
|
+
###### format
|
|
134
|
+
|
|
135
|
+
Provide an expected validation format for `:string` typed arguments.
|
|
136
|
+
|
|
137
|
+
The following formats are currently implemented:
|
|
138
|
+
|
|
139
|
+
* `:address`: a valid hostname, domain name, IPv4 or IPv6 address
|
|
140
|
+
* `:anything`: Any string. This is the default used. (regex: `/.*/`)
|
|
141
|
+
* `:bool`: A boolean value string containing `'true'` or `'false'`.
|
|
142
|
+
* `:command`: Simple command regex containing alphanumeric, characters and basic punctionation
|
|
143
|
+
* `:hostname`: A hostname
|
|
144
|
+
* `:interface`: Simple interface validation like `<string><number>`
|
|
145
|
+
* `:ip`, `ipv6`, `ipv4`: IP addresses
|
|
146
|
+
* `:mac`: MAC Address
|
|
147
|
+
* `:snapi_function_name`: Valid function name for Snapi
|
|
148
|
+
* `:on_off`: A string containing `on` or `off`
|
|
149
|
+
* `:port`: A valid network port (1-65535)
|
|
150
|
+
* `:uri`: Simplistic URI validation
|
|
151
|
+
|
|
152
|
+
###### list
|
|
153
|
+
|
|
154
|
+
The `argument.list` attribute takes a boolean value and indicates that the
|
|
155
|
+
argument should come as an array of elements, rather than a single one.
|
|
156
|
+
|
|
157
|
+
This is mostly useful as meta-information for the purposes of functionality
|
|
158
|
+
disclosure and does not
|
|
159
|
+
|
|
160
|
+
:exclamation: *Note* list arguments are currently *not* validated even if
|
|
161
|
+
defined as such.
|
|
162
|
+
|
|
163
|
+
###### required
|
|
164
|
+
|
|
165
|
+
Expecting `true` or `false` the required argument indicates to snapi if the argument MUST be included.
|
|
166
|
+
|
|
167
|
+
###### values
|
|
168
|
+
|
|
169
|
+
*Pending...*
|
|
170
|
+
|
|
171
|
+
###### description
|
|
172
|
+
|
|
173
|
+
*Pending...*
|
|
174
|
+
|
|
175
|
+
#### Return Type
|
|
176
|
+
|
|
177
|
+
A return type can be defined for a function.
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
fn.return :structured
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Valid types:
|
|
184
|
+
|
|
185
|
+
* `:structured`: Indicates structured output of some type
|
|
186
|
+
* `:raw`: indicates a hash containing shell command run information like `:stdout` or `:exitstatus`
|
|
187
|
+
* `:none`: indicates an unformated string output
|
|
188
|
+
|
|
189
|
+
### Library Class
|
|
190
|
+
|
|
191
|
+
The `library` option on a capability can be used to make `Snapi` expect its
|
|
192
|
+
defined functions to be served from another class.
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
library ExternalRubyClass
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
:exclamation: *Note:* This class *must* have valid class methods with an arity
|
|
199
|
+
of 1 for each function declared in the capability defition.
|
|
200
|
+
|
|
201
|
+
### Sinatra Extension
|
|
54
202
|
|
|
55
203
|
A Sinatra application can be extended with this functionality as follows.
|
|
56
204
|
|
|
@@ -58,7 +206,7 @@ A Sinatra application can be extended with this functionality as follows.
|
|
|
58
206
|
class MyAPi < Sinatra::Base
|
|
59
207
|
register Sinatra::Namespace
|
|
60
208
|
|
|
61
|
-
namespace
|
|
209
|
+
namespace "/snapi" do
|
|
62
210
|
register Snapi::SinatraExtension
|
|
63
211
|
end
|
|
64
212
|
end
|
|
@@ -67,9 +215,84 @@ end
|
|
|
67
215
|
When loaded this application will offer the following routes:
|
|
68
216
|
|
|
69
217
|
```
|
|
70
|
-
# /
|
|
71
|
-
# /
|
|
72
|
-
# /
|
|
218
|
+
# /snapi/
|
|
219
|
+
# /snapi/say_hello/
|
|
220
|
+
# /snapi/say_hello/hello_world
|
|
221
|
+
# /snapi/say_hello/hello
|
|
222
|
+
...
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### The API
|
|
226
|
+
|
|
227
|
+
If we run the [sample app](https://github.com/pwnieexpress/snapi/blob/develop/readme_sample.rb) we can make the following requests as either GET or
|
|
228
|
+
POST:
|
|
229
|
+
|
|
230
|
+
##### http://localhost:4567/snapi
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
{
|
|
234
|
+
"status":200,
|
|
235
|
+
"data":{
|
|
236
|
+
"say_hello":{
|
|
237
|
+
"hello_world":{
|
|
238
|
+
"return_type":null,
|
|
239
|
+
"arguments":[
|
|
240
|
+
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
"hello":{
|
|
244
|
+
"return_type":null,
|
|
245
|
+
"arguments":[
|
|
246
|
+
{
|
|
247
|
+
"name":"friend",
|
|
248
|
+
"required":false,
|
|
249
|
+
"type":"string"
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
"execution_time":2.602e-05
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
##### http://localhost:4567/snapi/hello_world
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
{
|
|
263
|
+
"status":200,
|
|
264
|
+
"data":{
|
|
265
|
+
"hello_world":{
|
|
266
|
+
"return_type":null,
|
|
267
|
+
"arguments":[
|
|
268
|
+
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
"hello":{
|
|
272
|
+
"return_type":null,
|
|
273
|
+
"arguments":[
|
|
274
|
+
{
|
|
275
|
+
"name":"friend",
|
|
276
|
+
"required":true,
|
|
277
|
+
"type":"string"
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"execution_time":3.4522e-05
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
##### http://localhost:4567/snapi/say_hello/hello?friend=Snapi
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
{
|
|
290
|
+
"status":200,
|
|
291
|
+
"data":{
|
|
292
|
+
"result":"Hello Snapi"
|
|
293
|
+
},
|
|
294
|
+
"execution_time":0.000346298
|
|
295
|
+
}
|
|
73
296
|
```
|
|
74
297
|
|
|
75
298
|
## Project Goals & Name
|
|
@@ -88,5 +311,6 @@ The ultimate goal being for an API to be able to define itself dynamically and
|
|
|
88
311
|
dislose that functionality to remote systems and even do things like:
|
|
89
312
|
|
|
90
313
|
* Dynamic Form Generation
|
|
91
|
-
*
|
|
314
|
+
* CLI Tool / Option Parser
|
|
92
315
|
* Self Documentation
|
|
316
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# The core Hash class
|
|
2
|
+
# This adds the deep_merge method to this core class which is used to join nested hashes
|
|
3
|
+
class Hash
|
|
4
|
+
|
|
5
|
+
# Non destructive version of deep_merge using a dup
|
|
6
|
+
#
|
|
7
|
+
# @param [Hash] the hash to be merged with
|
|
8
|
+
# @returns [Hash] A copy of a new hash merging the hash
|
|
9
|
+
# this was called on and the param hash
|
|
10
|
+
def deep_merge(other_hash)
|
|
11
|
+
dup.deep_merge!(other_hash)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Recusively merges hashes into each other. Any value that is not a Hash will
|
|
15
|
+
# be overridden with the value in the other hash.
|
|
16
|
+
#
|
|
17
|
+
# @param [Hash] the hash to be merged with
|
|
18
|
+
# @returns [Hash] A copy of itself with the new hash merged in
|
|
19
|
+
def deep_merge!(other)
|
|
20
|
+
raise ArgumentError unless other.is_a?(Hash)
|
|
21
|
+
|
|
22
|
+
other.each do |k, v|
|
|
23
|
+
self[k] = (self[k].is_a?(Hash) && self[k].is_a?(Hash)) ? self[k].deep_merge(v) : v
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
data/lib/snapi.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# include all the things!
|
|
2
|
+
require "core_ext/hash"
|
|
3
|
+
|
|
2
4
|
require "snapi/validator"
|
|
3
5
|
require "snapi/errors"
|
|
4
6
|
require "snapi/argument"
|
|
@@ -10,7 +12,11 @@ module Snapi
|
|
|
10
12
|
@@capabilities = {}
|
|
11
13
|
|
|
12
14
|
def self.capabilities
|
|
13
|
-
@@capabilities
|
|
15
|
+
@@capabilities
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.[](key)
|
|
19
|
+
@@capabilities[key]
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
def self.register_capability(klass)
|
|
@@ -21,11 +27,26 @@ module Snapi
|
|
|
21
27
|
@@capabilities.keys
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
def self.
|
|
25
|
-
|
|
30
|
+
def self.capability_hash
|
|
31
|
+
valid_capabilities.each_with_object({}) do |cap,coll|
|
|
32
|
+
coll[cap] = Snapi[cap].to_hash
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.has_capability?(capability)
|
|
37
|
+
valid_capabilities.include?(capability)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.supports?(capability,function,params)
|
|
41
|
+
if Snapi.has_capability?(capability) &&
|
|
42
|
+
Snapi[capability].valid_function_call?(function, params)
|
|
43
|
+
true
|
|
44
|
+
else
|
|
45
|
+
false
|
|
46
|
+
end
|
|
26
47
|
end
|
|
27
48
|
end
|
|
28
49
|
|
|
29
50
|
# This depends on Snapi module being defined as above
|
|
51
|
+
require "snapi/sinatra_extension_helper"
|
|
30
52
|
require "snapi/sinatra_extension"
|
|
31
|
-
|
data/lib/snapi/argument.rb
CHANGED
|
@@ -50,7 +50,8 @@ module Snapi
|
|
|
50
50
|
def valid_attributes
|
|
51
51
|
[
|
|
52
52
|
:default_value, :format, :list,
|
|
53
|
-
:required, :type, :values, :name,
|
|
53
|
+
:required, :type, :values, :name,
|
|
54
|
+
:description
|
|
54
55
|
]
|
|
55
56
|
end
|
|
56
57
|
|
|
@@ -79,6 +80,7 @@ module Snapi
|
|
|
79
80
|
#
|
|
80
81
|
# @param bool, Boolean value
|
|
81
82
|
def list(bool)
|
|
83
|
+
#TODO work out kinks in list behavior...
|
|
82
84
|
raise InvalidBooleanError unless [true,false].include? bool
|
|
83
85
|
@attributes[:list] = bool
|
|
84
86
|
end
|
|
@@ -132,7 +134,6 @@ module Snapi
|
|
|
132
134
|
when :enum
|
|
133
135
|
raise MissingValuesError unless @attributes[:values]
|
|
134
136
|
raise InvalidValuesError unless @attributes[:values].class == Array
|
|
135
|
-
|
|
136
137
|
@attributes[:values].include?(input)
|
|
137
138
|
when :string
|
|
138
139
|
format = @attributes[:format] || :anything
|
|
@@ -141,7 +142,8 @@ module Snapi
|
|
|
141
142
|
[Integer, Fixnum].include?(input.class)
|
|
142
143
|
when :timestamp
|
|
143
144
|
# TODO timestamp pending
|
|
144
|
-
raise PendingBranchError
|
|
145
|
+
# raise PendingBranchError
|
|
146
|
+
true
|
|
145
147
|
else
|
|
146
148
|
false
|
|
147
149
|
end
|
data/lib/snapi/errors.rb
CHANGED
data/lib/snapi/function.rb
CHANGED
|
@@ -45,9 +45,9 @@ module Snapi
|
|
|
45
45
|
#
|
|
46
46
|
# @returns Hash representation of Function
|
|
47
47
|
def to_hash
|
|
48
|
-
|
|
49
|
-
arguments.each { |k,v|
|
|
50
|
-
{ return_type: return_type }
|
|
48
|
+
args = []
|
|
49
|
+
arguments.each { |k,v| args << {name: k }.merge(v.attributes) } if arguments
|
|
50
|
+
{ return_type: return_type, arguments: args }
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
# This method accepts a hash (as from a web request) which it
|
|
@@ -61,12 +61,23 @@ module Snapi
|
|
|
61
61
|
# @returns Boolean
|
|
62
62
|
def valid_args?(args={})
|
|
63
63
|
valid = false
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
|
|
65
|
+
if arguments
|
|
66
|
+
arguments.each do |name, argument|
|
|
67
|
+
if argument[:required]
|
|
68
|
+
return false if args[name] == nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if argument[:default_value] && !args[name]
|
|
72
|
+
args[name] = argument[:default_value]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
valid = argument.valid_input?(args[name])
|
|
67
76
|
end
|
|
68
|
-
|
|
77
|
+
else
|
|
78
|
+
valid = true
|
|
69
79
|
end
|
|
80
|
+
|
|
70
81
|
return valid
|
|
71
82
|
end
|
|
72
83
|
end
|
|
@@ -3,42 +3,53 @@ require 'sinatra/contrib'
|
|
|
3
3
|
|
|
4
4
|
module Snapi
|
|
5
5
|
module SinatraExtension
|
|
6
|
+
|
|
6
7
|
extend Sinatra::Extension
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
helpers Snapi::SinatraExtensionHelper
|
|
10
|
+
|
|
11
|
+
# Hello darkness, my old friend
|
|
12
|
+
def self.get_or_post(url,&block)
|
|
13
|
+
get(url,&block)
|
|
14
|
+
post(url,&block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
get_or_post "/?" do
|
|
18
|
+
response_wrapper do
|
|
19
|
+
Snapi.capability_hash
|
|
12
20
|
end
|
|
13
|
-
JSON.generate(capabilities)
|
|
14
21
|
end
|
|
15
22
|
|
|
16
|
-
|
|
23
|
+
get_or_post "/:capability/?" do
|
|
17
24
|
@capability = params.delete("capability").to_sym
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
response_wrapper do
|
|
27
|
+
if Snapi.has_capability?(@capability)
|
|
28
|
+
Snapi[@capability].to_hash
|
|
29
|
+
else
|
|
30
|
+
raise InvalidCapabilityError
|
|
31
|
+
end
|
|
21
32
|
end
|
|
22
|
-
|
|
23
|
-
JSON.generate(Snapi.capabilities[@capability].to_hash)
|
|
24
33
|
end
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
get_or_post "/:capability/:function/?" do
|
|
27
36
|
@capability = params.delete("capability").to_sym
|
|
28
37
|
@function = params.delete("function").to_sym
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
raise InvalidCapabilityError
|
|
32
|
-
end
|
|
39
|
+
response_wrapper do
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
unless Snapi.supports?(@capability, @function, params)
|
|
42
|
+
raise InvalidFunctionCallError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
response = Snapi[@capability].run_function(@function,params)
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
unless response.class == Hash
|
|
48
|
+
response = { raw_result: response }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
response
|
|
52
|
+
end
|
|
42
53
|
end
|
|
43
54
|
end
|
|
44
55
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'digest/sha2'
|
|
2
|
+
|
|
3
|
+
module Snapi
|
|
4
|
+
module SinatraExtensionHelper
|
|
5
|
+
# Helper that handles wrapping all API data requests in a standard format
|
|
6
|
+
# that includes status and error messages (if there were any).
|
|
7
|
+
#
|
|
8
|
+
# @param data [Hash] Singular or multiple data objects the client
|
|
9
|
+
# requested, should include a JSON schema attribute to allow for response
|
|
10
|
+
# validation.
|
|
11
|
+
# @param response_code [Fixnum] HTTP Status code to return
|
|
12
|
+
# @param errors [Array<String>] List of errors that occured while processing
|
|
13
|
+
# the request.
|
|
14
|
+
def response_wrapper(data = {}, response_code = 200, errors = [])
|
|
15
|
+
time_taken = nil
|
|
16
|
+
|
|
17
|
+
if block_given?
|
|
18
|
+
time_start = Time.now
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
data = data.deep_merge(yield)
|
|
22
|
+
rescue Exception => e
|
|
23
|
+
response_code = 500
|
|
24
|
+
errors << "#{e.class.name}: #{e.backtrace}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
time_end = Time.now
|
|
28
|
+
time_taken = (time_end - time_start)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
response = { status: response_code, data: data }
|
|
32
|
+
response[:errors] = errors unless errors.empty?
|
|
33
|
+
response[:execution_time] = time_taken unless time_taken.nil?
|
|
34
|
+
|
|
35
|
+
# Use halt to prevent all further processing
|
|
36
|
+
halt(response_code, response.to_json)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/snapi/validator.rb
CHANGED
|
@@ -47,7 +47,6 @@ module Snapi
|
|
|
47
47
|
:anything => [/.*/],
|
|
48
48
|
:bool => [TRUEFALSE_REGEX],
|
|
49
49
|
:command => [SIMPLE_COMMAND_REGEX],
|
|
50
|
-
:cron => [CRON_REGEX],
|
|
51
50
|
:gsm_adapter => [ADAPTER_REGEX],
|
|
52
51
|
:hostname => [HOSTNAME_REGEX],
|
|
53
52
|
:interface => [INTERFACE_REGEX],
|
|
@@ -56,12 +55,9 @@ module Snapi
|
|
|
56
55
|
:ipv4 => [IP_V4_REGEX],
|
|
57
56
|
:mac => [MAC_REGEX],
|
|
58
57
|
:snapi_function_name => [SNAPI_FUNCTION_NAME],
|
|
59
|
-
:string => [STRING_REGEX],
|
|
60
58
|
:on_off => [ON_OFF_REGEX],
|
|
61
59
|
:port => [PORT_REGEX],
|
|
62
60
|
:uri => [URI_REGEX],
|
|
63
|
-
:username => [HOSTNAME_REGEX],
|
|
64
|
-
:password => [NUM_LETTERS_SP_CHARS]
|
|
65
61
|
}
|
|
66
62
|
end
|
|
67
63
|
|
|
@@ -88,9 +84,6 @@ module Snapi
|
|
|
88
84
|
# Note base on lib/shells/gsm.rb
|
|
89
85
|
ADAPTER_REGEX = /^(unlocked_gsm|verizon_virgin_mobile|tmobile)$/
|
|
90
86
|
#
|
|
91
|
-
# Note set in lib/shells/scheduler.rb
|
|
92
|
-
CRON_REGEX = /^(1_minute|5_minutes|15_minutes|60_minutes)$/
|
|
93
|
-
#
|
|
94
87
|
# Source:
|
|
95
88
|
SIMPLE_COMMAND_REGEX = /^([a-zA-Z0-9\.\s\-\_\/\\\,\=]+)*$/i
|
|
96
89
|
#
|
|
@@ -117,9 +110,6 @@ module Snapi
|
|
|
117
110
|
# SIMPLE ON / OFF (CHECKBOXES)
|
|
118
111
|
ON_OFF_REGEX = /^(on|off)$/i
|
|
119
112
|
#
|
|
120
|
-
# Source:
|
|
121
|
-
STRING_REGEX = /^([a-zA-Z0-9\.\-\_]+)*$/i
|
|
122
|
-
#
|
|
123
113
|
# TRUE OR FALSE
|
|
124
114
|
TRUEFALSE_REGEX = /^(true|false)$/i
|
|
125
115
|
#
|
data/lib/snapi/version.rb
CHANGED
data/spec/argument_spec.rb
CHANGED
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe Snapi::Argument do
|
|
4
4
|
it "is a class" do
|
|
5
|
-
|
|
5
|
+
Snapi::Argument.class.should == Class
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
it "has an attributes hash" do
|
|
@@ -46,7 +46,7 @@ describe Snapi::Argument do
|
|
|
46
46
|
subject.attributes.keys.sort.should == []
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
describe "can
|
|
49
|
+
describe "can validate types such as" do
|
|
50
50
|
it ":boolean" do
|
|
51
51
|
a = Snapi::Argument.new
|
|
52
52
|
a.type :boolean
|
|
@@ -17,7 +17,7 @@ describe Snapi::BasicCapability do
|
|
|
17
17
|
fn.return :raw
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
|
-
lemon_grab = {:summon_zombies
|
|
20
|
+
lemon_grab = {:summon_zombies=>{:return_type=>:raw, :arguments=>[]}}
|
|
21
21
|
|
|
22
22
|
PrinceLemonGrab.to_hash.should == lemon_grab
|
|
23
23
|
end
|
|
@@ -40,17 +40,19 @@ describe Snapi::BasicCapability do
|
|
|
40
40
|
|
|
41
41
|
expected_return = {
|
|
42
42
|
:create_candy_person => {
|
|
43
|
-
:return_type
|
|
44
|
-
:
|
|
43
|
+
:return_type => :structured,
|
|
44
|
+
:arguments => [{
|
|
45
|
+
:name => :candy_base,
|
|
45
46
|
:default_value => "sugar",
|
|
46
|
-
:format
|
|
47
|
-
:
|
|
48
|
-
:
|
|
49
|
-
:type
|
|
50
|
-
|
|
51
|
-
}
|
|
47
|
+
:format => :anything,
|
|
48
|
+
:list => true,
|
|
49
|
+
:required => true,
|
|
50
|
+
:type => :enum
|
|
51
|
+
}]
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
PrincessBubblegum.to_hash.should == expected_return
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
it "doesn't shared functions between inherited classes" do
|
data/spec/snapi_spec.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Snapi do
|
|
4
|
+
it "is a module" do
|
|
5
|
+
Snapi.class.should == Module
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "can return a hash of registered capabilities" do
|
|
9
|
+
Snapi.capabilities.class.should == Hash
|
|
10
|
+
Snapi.capabilities[:basic_capability].should == Snapi::BasicCapability
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "provides direct hash style access to its capabilities" do
|
|
14
|
+
Snapi[:basic_capability].should == Snapi.capabilities[:basic_capability]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "allows other ruby classes to register as capabilities" do
|
|
18
|
+
TmpClass = Class.new(Snapi::BasicCapability)
|
|
19
|
+
|
|
20
|
+
Snapi.capabilities.length.should == 1
|
|
21
|
+
Snapi.register_capability(TmpClass)
|
|
22
|
+
|
|
23
|
+
Snapi.capabilities.length.should == 2
|
|
24
|
+
Snapi[:tmp_class].should == TmpClass
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "provides a list of known capabilities" do
|
|
28
|
+
Snapi.valid_capabilities.class.should == Array
|
|
29
|
+
Snapi.valid_capabilities.should == [:basic_capability, :tmp_class]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "provides a hash containing a full functionality disclosure" do
|
|
33
|
+
hash = Snapi.capability_hash
|
|
34
|
+
hash.class.should == Hash
|
|
35
|
+
hash[:tmp_class].should == TmpClass.to_hash
|
|
36
|
+
hash[:basic_capability].should == Snapi::BasicCapability.to_hash
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "can test is a capability is valid" do
|
|
40
|
+
Snapi.has_capability?(:basic_capability).should == true
|
|
41
|
+
Snapi.has_capability?(:sexy_ninja).should == false # :(
|
|
42
|
+
end
|
|
43
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: snapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.7
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2014-03-16 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rspec
|
|
@@ -34,12 +34,14 @@ extensions: []
|
|
|
34
34
|
extra_rdoc_files: []
|
|
35
35
|
files:
|
|
36
36
|
- lib/snapi.rb
|
|
37
|
+
- lib/core_ext/hash.rb
|
|
37
38
|
- lib/snapi/argument.rb
|
|
38
39
|
- lib/snapi/validator.rb
|
|
39
40
|
- lib/snapi/function.rb
|
|
40
41
|
- lib/snapi/version.rb
|
|
41
42
|
- lib/snapi/sinatra_extension.rb
|
|
42
43
|
- lib/snapi/capability.rb
|
|
44
|
+
- lib/snapi/sinatra_extension_helper.rb
|
|
43
45
|
- lib/snapi/errors.rb
|
|
44
46
|
- README.md
|
|
45
47
|
- CHANGELOG
|
|
@@ -47,6 +49,7 @@ files:
|
|
|
47
49
|
- Gemfile.lock
|
|
48
50
|
- Gemfile
|
|
49
51
|
- spec/basic_capability_spec.rb
|
|
52
|
+
- spec/snapi_spec.rb
|
|
50
53
|
- spec/validator_spec.rb
|
|
51
54
|
- spec/spec_helper.rb
|
|
52
55
|
- spec/function_spec.rb
|