snapi 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/snapi.png)](http://badge.fury.io/rb/snapi) [![Code Climate](https://codeclimate.com/github/pwnieexpress/snapi.png)](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
|