snapi 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +47 -0
- data/README.md +92 -0
- data/lib/snapi.rb +31 -0
- data/lib/snapi/argument.rb +148 -0
- data/lib/snapi/capability.rb +130 -0
- data/lib/snapi/errors.rb +30 -0
- data/lib/snapi/function.rb +73 -0
- data/lib/snapi/sinatra_extension.rb +42 -0
- data/lib/snapi/validator.rb +133 -0
- data/lib/snapi/version.rb +3 -0
- data/spec/argument_spec.rb +128 -0
- data/spec/basic_capability_spec.rb +160 -0
- data/spec/function_spec.rb +35 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/validator_spec.rb +33 -0
- metadata +78 -0
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
backports (3.3.4)
|
5
|
+
coderay (1.0.9)
|
6
|
+
diff-lcs (1.2.4)
|
7
|
+
method_source (0.8.2)
|
8
|
+
multi_json (1.8.0)
|
9
|
+
pry (0.9.12.2)
|
10
|
+
coderay (~> 1.0.5)
|
11
|
+
method_source (~> 0.8)
|
12
|
+
slop (~> 3.4)
|
13
|
+
rack (1.5.2)
|
14
|
+
rack-protection (1.5.0)
|
15
|
+
rack
|
16
|
+
rack-test (0.6.2)
|
17
|
+
rack (>= 1.0)
|
18
|
+
rspec (2.14.1)
|
19
|
+
rspec-core (~> 2.14.0)
|
20
|
+
rspec-expectations (~> 2.14.0)
|
21
|
+
rspec-mocks (~> 2.14.0)
|
22
|
+
rspec-core (2.14.5)
|
23
|
+
rspec-expectations (2.14.3)
|
24
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
25
|
+
rspec-mocks (2.14.3)
|
26
|
+
sinatra (1.4.3)
|
27
|
+
rack (~> 1.4)
|
28
|
+
rack-protection (~> 1.4)
|
29
|
+
tilt (~> 1.3, >= 1.3.4)
|
30
|
+
sinatra-contrib (1.4.1)
|
31
|
+
backports (>= 2.0)
|
32
|
+
multi_json
|
33
|
+
rack-protection
|
34
|
+
rack-test
|
35
|
+
sinatra (~> 1.4.0)
|
36
|
+
tilt (~> 1.3)
|
37
|
+
slop (3.4.6)
|
38
|
+
tilt (1.4.1)
|
39
|
+
|
40
|
+
PLATFORMS
|
41
|
+
ruby
|
42
|
+
|
43
|
+
DEPENDENCIES
|
44
|
+
pry
|
45
|
+
rspec
|
46
|
+
sinatra
|
47
|
+
sinatra-contrib
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Snapi
|
2
|
+
|
3
|
+
Snapi is a modular API functionality definition tool.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
This hasn't been put on Rubygems yet so you still have to build the gem
|
8
|
+
manually for now.
|
9
|
+
|
10
|
+
```sh
|
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+`
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
Simple Exmaple:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'snapi'
|
24
|
+
require 'json'
|
25
|
+
|
26
|
+
class ScannerLibrary
|
27
|
+
def self.scan(args)
|
28
|
+
#
|
29
|
+
# _| _ _|_|_ _ _|_|_ o __ _
|
30
|
+
# (_|(_) |_| |(/_ |_| | | | |(_|
|
31
|
+
# __|
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Scanner
|
36
|
+
include Snapi::Capability
|
37
|
+
function :scan do |fn|
|
38
|
+
fn.argument :target do |arg|
|
39
|
+
arg.required true
|
40
|
+
arg.list true
|
41
|
+
arg.type :string
|
42
|
+
arg.format :address
|
43
|
+
end
|
44
|
+
fn.argument :port do |arg|
|
45
|
+
arg.type :string
|
46
|
+
end
|
47
|
+
fn.return :structured
|
48
|
+
end
|
49
|
+
library ScannerLibrary
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
## Sinatra Extension
|
54
|
+
|
55
|
+
A Sinatra application can be extended with this functionality as follows.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class MyAPi < Sinatra::Base
|
59
|
+
register Sinatra::Namespace
|
60
|
+
|
61
|
+
namespace Snapi.capability_root do
|
62
|
+
register Snapi::SinatraExtension
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
When loaded this application will offer the following routes:
|
68
|
+
|
69
|
+
```
|
70
|
+
# /plugins/
|
71
|
+
# /plugins/scanner/
|
72
|
+
# /plugins/scanner/scan/
|
73
|
+
```
|
74
|
+
|
75
|
+
## Project Goals & Name
|
76
|
+
|
77
|
+
I literally woke up in the middle of the night with the idea for a modular
|
78
|
+
Sinatra Api plugin system and wrote down the name "Snapi" which is a
|
79
|
+
contraction of "Sinatra Api". It might better be spelled "SnApi" but I prefer
|
80
|
+
"Snapi" because it snake cases to `snapi`, not `sn_api`. I like the concept of
|
81
|
+
snap-in modularity.
|
82
|
+
|
83
|
+
As of now the only real behavior is that of declaring functions and arguments
|
84
|
+
for a class as a way of doing meta-programming funcationality / arrity
|
85
|
+
disclosure.
|
86
|
+
|
87
|
+
The ultimate goal being for an API to be able to define itself dynamically and
|
88
|
+
dislose that functionality to remote systems and even do things like:
|
89
|
+
|
90
|
+
* Dynamic Form Generation
|
91
|
+
* API generation (TODO `snapi generate hosts plugin:crud`)
|
92
|
+
* Self Documentation
|
data/lib/snapi.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# include all the things!
|
2
|
+
require "snapi/validator"
|
3
|
+
require "snapi/errors"
|
4
|
+
require "snapi/argument"
|
5
|
+
require "snapi/function"
|
6
|
+
require "snapi/capability"
|
7
|
+
require "snapi/version"
|
8
|
+
|
9
|
+
module Snapi
|
10
|
+
@@capabilities = {}
|
11
|
+
|
12
|
+
def self.capabilities
|
13
|
+
@@capabilities || {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.register_capability(klass)
|
17
|
+
@@capabilities[klass.namespace] = klass
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.valid_capabilities
|
21
|
+
@@capabilities.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.capability_root
|
25
|
+
"/plugins/?"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# This depends on Snapi module being defined as above
|
30
|
+
require "snapi/sinatra_extension"
|
31
|
+
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Snapi
|
2
|
+
# Arguments are an intrinsic part of Capability and Function declaration
|
3
|
+
# in that Capabilities are made up of Functions and Functions may have
|
4
|
+
# one or more Arguments defined for them.
|
5
|
+
#
|
6
|
+
# Arguments are a code representation of a meta-programming way of defining
|
7
|
+
# what arguments or paramater SHOULD or MUST be passed to a method
|
8
|
+
# which represents a function on the Capabilities library class
|
9
|
+
class Argument
|
10
|
+
|
11
|
+
# @attributes tracks the various meta-attributes which can be
|
12
|
+
# set for the argument record. This initializer simply sets that
|
13
|
+
# value as
|
14
|
+
def initialize
|
15
|
+
@attributes = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Allow the record to behave like a hash by giving access to @attributes
|
19
|
+
# via [] getter
|
20
|
+
#
|
21
|
+
# @params key, key to query @attributes with
|
22
|
+
def [](key)
|
23
|
+
@attributes[key]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Allow the record to behave like a hash by giving access to @attributes
|
27
|
+
# via []= setter
|
28
|
+
#
|
29
|
+
# Validates the key requested to set is included in the valid_attributes
|
30
|
+
# white list and then uses the uses the various setter methods below to
|
31
|
+
# set the value.
|
32
|
+
#
|
33
|
+
# @param key, attribute name
|
34
|
+
# @param value, value to set
|
35
|
+
def []=(key, value)
|
36
|
+
raise InvalidArgumentAttributeError unless valid_attributes.include?(key)
|
37
|
+
send(key, value)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the @attributes hash
|
41
|
+
#
|
42
|
+
# @returns Hash
|
43
|
+
def attributes
|
44
|
+
@attributes
|
45
|
+
end
|
46
|
+
|
47
|
+
# Whitelist of attribute names
|
48
|
+
#
|
49
|
+
# @returns Array of Symbols
|
50
|
+
def valid_attributes
|
51
|
+
[:default_value, :format, :list, :required, :type, :values]
|
52
|
+
end
|
53
|
+
|
54
|
+
# DSL Setter
|
55
|
+
# Set a default value for the argument if one is not provided
|
56
|
+
#
|
57
|
+
# @param val, Value to use in case one isn't provided,
|
58
|
+
def default_value(val)
|
59
|
+
raise InvalidStringError unless val.class == String
|
60
|
+
@attributes[:default_value] = val
|
61
|
+
end
|
62
|
+
|
63
|
+
# DSL Setter
|
64
|
+
# Set a format the check string types against using the
|
65
|
+
# format_types outlined in Snapi::Validator
|
66
|
+
#
|
67
|
+
# @param format, Symbol of format to match against
|
68
|
+
def format(format)
|
69
|
+
raise InvalidFormatError unless Validator.valid_regex_format?(format)
|
70
|
+
@attributes[:format] = format
|
71
|
+
end
|
72
|
+
|
73
|
+
# DSL Setter
|
74
|
+
# Is the argument a list of options? If true it will be assumed that
|
75
|
+
# this argument will be an array of objects of the sort set as type
|
76
|
+
#
|
77
|
+
# @param bool, Boolean value
|
78
|
+
def list(bool)
|
79
|
+
raise InvalidBooleanError unless [true,false].include? bool
|
80
|
+
@attributes[:list] = bool
|
81
|
+
end
|
82
|
+
|
83
|
+
# DSL Setter
|
84
|
+
# Is the argument a required?
|
85
|
+
#
|
86
|
+
# @param bool, Boolean value
|
87
|
+
def required(bool)
|
88
|
+
raise InvalidBooleanError unless [true,false].include? bool
|
89
|
+
@attributes[:required] = bool
|
90
|
+
end
|
91
|
+
|
92
|
+
# DSL Setter
|
93
|
+
# What type of value is this argument. This will impact the way in which
|
94
|
+
# this argument value gets validated later on
|
95
|
+
#
|
96
|
+
# Valid types are: :boolean, :enum, :string, :number, :timestamp
|
97
|
+
#
|
98
|
+
# @param type, Symbol indicating type
|
99
|
+
def type(type)
|
100
|
+
valid_types = [:boolean, :enum, :string, :number, :timestamp]
|
101
|
+
raise InvalidTypeError unless valid_types.include?(type)
|
102
|
+
@attributes[:type] = type
|
103
|
+
end
|
104
|
+
|
105
|
+
# DSL Setter
|
106
|
+
# What are the values that can be selected? This only applies to :enum typed arguments
|
107
|
+
# and allow the argument to define a list of valid values to select from for this.
|
108
|
+
#
|
109
|
+
# In a form this would map to a select box. Alternative to using a :string argument
|
110
|
+
# with a format to validate against.
|
111
|
+
#
|
112
|
+
# Basically creates a whitelist of values for this argument.
|
113
|
+
#
|
114
|
+
# @param values, Array
|
115
|
+
def values(values)
|
116
|
+
raise InvalidValuesError unless values.class == Array
|
117
|
+
@attributes[:values] = values
|
118
|
+
end
|
119
|
+
|
120
|
+
# Check if a value provided will suffice for the way
|
121
|
+
# this argument is defined.
|
122
|
+
#
|
123
|
+
# @param input, Just about anything...
|
124
|
+
# @returns Boolean. true if valid
|
125
|
+
def valid_input?(input)
|
126
|
+
case @attributes[:type]
|
127
|
+
when :boolean
|
128
|
+
[true,false].include?(input)
|
129
|
+
when :enum
|
130
|
+
raise MissingValuesError unless @attributes[:values]
|
131
|
+
raise InvalidValuesError unless @attributes[:values].class == Array
|
132
|
+
|
133
|
+
@attributes[:values].include?(input)
|
134
|
+
when :string
|
135
|
+
format = @attributes[:format] || :anything
|
136
|
+
Validator.valid_input?(format, input)
|
137
|
+
when :number
|
138
|
+
[Integer, Fixnum].include?(input.class)
|
139
|
+
when :timestamp
|
140
|
+
# TODO timestamp pending
|
141
|
+
raise PendingBranchError
|
142
|
+
else
|
143
|
+
false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Snapi
|
2
|
+
# This class is exists as a way of definining an API capability
|
3
|
+
# using a handy DSL to define functions, their arguments, arity,
|
4
|
+
# validations and return types.
|
5
|
+
module Capability
|
6
|
+
|
7
|
+
# When this module is included into a given Class
|
8
|
+
# this will wbe run. It will extend the clas with
|
9
|
+
# the methods found in the the Snapi::Capability::ClassMethods
|
10
|
+
# module and register the class in question with the Snapi
|
11
|
+
# module.
|
12
|
+
#
|
13
|
+
# @params klass, Class
|
14
|
+
def self.included(klass)
|
15
|
+
klass.extend(ClassMethods)
|
16
|
+
Snapi.register_capability(klass)
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
# modify the class to track @functions and @library_class
|
21
|
+
# at the class level
|
22
|
+
class << self
|
23
|
+
attr_accessor :functions, :library_class
|
24
|
+
end
|
25
|
+
|
26
|
+
# Getter to query the internally tracked list of supported
|
27
|
+
# functions.
|
28
|
+
#
|
29
|
+
# @returns Hash, @functions or new Hash
|
30
|
+
def functions
|
31
|
+
@functions || {}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Getter to query the class which is responsible for having
|
35
|
+
# methods which map to the names of the functions.
|
36
|
+
#
|
37
|
+
# @returns Class, @library_class || self
|
38
|
+
def library_class
|
39
|
+
@library_class || self
|
40
|
+
end
|
41
|
+
|
42
|
+
# DSL setter to add a function to the @functions hash
|
43
|
+
#
|
44
|
+
# @params name, Name of function being defined
|
45
|
+
# @params Block to modify function
|
46
|
+
# @returns Hash of function data
|
47
|
+
def function(name)
|
48
|
+
raise InvalidFunctionNameError unless Validator.valid_input?(:snapi_function_name, name)
|
49
|
+
fn = Function.new
|
50
|
+
if block_given?
|
51
|
+
yield(fn)
|
52
|
+
end
|
53
|
+
@functions ||= {}
|
54
|
+
@functions[name] = fn
|
55
|
+
end
|
56
|
+
|
57
|
+
# DSL Setter to define the ruby class (or module) which
|
58
|
+
# contains methods that should map to the function names
|
59
|
+
# in the @functions hash
|
60
|
+
#
|
61
|
+
# @params klass, Class which default to self
|
62
|
+
# @returns Class
|
63
|
+
def library(klass=self)
|
64
|
+
@library_class = klass
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Convert the class name to a snake-cased symbol namespace
|
69
|
+
# representation of class name for use in namespacing
|
70
|
+
#
|
71
|
+
# @returns Symbol, snake_cased
|
72
|
+
def namespace
|
73
|
+
self.name
|
74
|
+
.split('::').last
|
75
|
+
.scan(/([A-Z]+[a-z0-9]+)/)
|
76
|
+
.flatten.map(&:downcase)
|
77
|
+
.join("_")
|
78
|
+
.to_sym
|
79
|
+
end
|
80
|
+
|
81
|
+
# Helper to conver the class itself to a hash representation
|
82
|
+
# including the functions hash keyed off the namespace
|
83
|
+
#
|
84
|
+
# @returns Hash
|
85
|
+
def to_hash
|
86
|
+
fn_hash = {}
|
87
|
+
functions.each {|k,v| fn_hash[k] = v.to_hash } if functions
|
88
|
+
{
|
89
|
+
self.namespace => fn_hash
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
# Helper to check if a function call to the capability would
|
94
|
+
# be valid with the given arguments
|
95
|
+
#
|
96
|
+
# @param function Symbolic name to reference the function out of the @functions hash
|
97
|
+
# @param args hash of arguments
|
98
|
+
# @returns Boolean
|
99
|
+
def valid_function_call?(function, args)
|
100
|
+
return false unless functions[function]
|
101
|
+
functions[function].valid_args?(args)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Accepts arguments and sends the args to the library class
|
105
|
+
# version of the function assuming that the function has been declared
|
106
|
+
# and the library class also includes that function
|
107
|
+
#
|
108
|
+
# @param function Symbolic name to reference the function out of the @functions hash
|
109
|
+
# @param args hash of arguments
|
110
|
+
# @returns God only knows, my friend, God only know
|
111
|
+
def run_function(function,args)
|
112
|
+
raise InvalidFunctionCallError unless valid_function_call?(function, args)
|
113
|
+
raise LibraryClassMissingFunctionError unless valid_library_class?
|
114
|
+
library_class.send(function,args)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Test if the set library class is valid based on mapping
|
118
|
+
# the keys of the @functions hash against the methods available
|
119
|
+
# to the @library_class
|
120
|
+
#
|
121
|
+
# @returns Boolean, true if @library_class offers all the methods needed
|
122
|
+
def valid_library_class?
|
123
|
+
self.functions.keys.each do |function_name|
|
124
|
+
return false unless library_class.methods.include?(function_name)
|
125
|
+
end
|
126
|
+
true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/lib/snapi/errors.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Snapi
|
2
|
+
#
|
3
|
+
#
|
4
|
+
# _________________ ___________ _______
|
5
|
+
# | ___| ___ \ ___ \ _ | ___ \___ / |
|
6
|
+
# | |__ | |_/ / |_/ / | | | |_/ / / /| |
|
7
|
+
# | __|| /| /| | | | / / / | |
|
8
|
+
# | |___| |\ \| |\ \\ \_/ / |\ \./ /__|_|
|
9
|
+
# \____/\_| \_\_| \_|\___/\_| \_\_____(_)
|
10
|
+
#
|
11
|
+
#
|
12
|
+
#
|
13
|
+
#
|
14
|
+
InvalidArgumentAttributeError = Class.new(StandardError)
|
15
|
+
InvalidBooleanError = Class.new(StandardError)
|
16
|
+
InvalidCapabilityError = Class.new(StandardError)
|
17
|
+
InvalidFormatError = Class.new(StandardError)
|
18
|
+
InvalidFunctionCallError = Class.new(StandardError)
|
19
|
+
InvalidFunctionNameError = Class.new(StandardError)
|
20
|
+
InvalidReturnTypeError = Class.new(StandardError)
|
21
|
+
InvalidStringError = Class.new(StandardError)
|
22
|
+
InvalidTypeError = Class.new(StandardError)
|
23
|
+
InvalidValuesError = Class.new(StandardError)
|
24
|
+
LibraryClassMissingFunctionError = Class.new(StandardError)
|
25
|
+
MissingValuesError = Class.new(StandardError)
|
26
|
+
|
27
|
+
|
28
|
+
# TODO remove
|
29
|
+
PendingBranchError = Class.new(StandardError)
|
30
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Snapi
|
2
|
+
# Functions are a core part of capability declaration as a
|
3
|
+
# capability is basically a collection of functions.
|
4
|
+
#
|
5
|
+
# Functions take arguments and return a pre-defined type of
|
6
|
+
# data structure.
|
7
|
+
#
|
8
|
+
# Right now Functions are structs which accept arguments
|
9
|
+
# and return_type messages as well as a few DSL methods
|
10
|
+
# to help define them dynamically
|
11
|
+
#
|
12
|
+
Function = Struct.new(:arguments, :return_type ) do
|
13
|
+
|
14
|
+
|
15
|
+
# DSL setter to define a the meta information for an
|
16
|
+
# argument for the Function
|
17
|
+
#
|
18
|
+
# Yields the argument to a given block for nice defition
|
19
|
+
#
|
20
|
+
# @params name
|
21
|
+
def argument(name)
|
22
|
+
arg = Argument.new
|
23
|
+
if block_given?
|
24
|
+
yield(arg)
|
25
|
+
end
|
26
|
+
self.arguments = {} if self.arguments == nil
|
27
|
+
self.arguments[name] = arg
|
28
|
+
end
|
29
|
+
|
30
|
+
# DSL Setter to define the type of data returned by this function
|
31
|
+
#
|
32
|
+
# TODO currently allows types of 'none', 'raw' or 'structured' but
|
33
|
+
# should support 'structured[stucture_type']
|
34
|
+
#
|
35
|
+
# @params type of returned data
|
36
|
+
def return(type)
|
37
|
+
valid = %w{ none raw structured }.include? type.to_s
|
38
|
+
raise InvalidReturnTypeError unless valid
|
39
|
+
|
40
|
+
self.return_type = type
|
41
|
+
end
|
42
|
+
|
43
|
+
# Hash representation of function including argument meta-data
|
44
|
+
# as a hash.
|
45
|
+
#
|
46
|
+
# @returns Hash representation of Function
|
47
|
+
def to_hash
|
48
|
+
args_hash = {}
|
49
|
+
arguments.each { |k,v| args_hash[k] = v.attributes } if arguments
|
50
|
+
{ return_type: return_type }.merge args_hash
|
51
|
+
end
|
52
|
+
|
53
|
+
# This method accepts a hash (as from a web request) which it
|
54
|
+
# uses to compare against its argument definitions in order to
|
55
|
+
# do some upfront validation before trying to run the function
|
56
|
+
# names as defined in the library_class
|
57
|
+
#
|
58
|
+
# TODO: build up an errors hash to disclose what the issue is
|
59
|
+
#
|
60
|
+
# @params args Hash
|
61
|
+
# @returns Boolean
|
62
|
+
def valid_args?(args={})
|
63
|
+
valid = false
|
64
|
+
arguments.each do |name, argument|
|
65
|
+
if argument[:required]
|
66
|
+
return false if args[name] == nil
|
67
|
+
end
|
68
|
+
valid = argument.valid_input?(args[name])
|
69
|
+
end
|
70
|
+
return valid
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'sinatra/contrib'
|
3
|
+
|
4
|
+
module Snapi
|
5
|
+
# TODO document, test, make more robust
|
6
|
+
module SinatraExtension
|
7
|
+
extend Sinatra::Extension
|
8
|
+
|
9
|
+
get "/?" do
|
10
|
+
JSON.generate(Snapi.capabilities)
|
11
|
+
end
|
12
|
+
|
13
|
+
get "/:capability/?" do
|
14
|
+
@capability = params.delete("capability").to_sym
|
15
|
+
|
16
|
+
unless Snapi.valid_capabilities.include?(@capability)
|
17
|
+
raise InvalidCapabilityError
|
18
|
+
end
|
19
|
+
|
20
|
+
JSON.generate(Snapi.capabilities[@capability].to_hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
get "/:capability/:function/?" do
|
24
|
+
@capability = params.delete("capability").to_sym
|
25
|
+
@function = params.delete("function").to_sym
|
26
|
+
|
27
|
+
unless Snapi.valid_capabilities.include?(@capability)
|
28
|
+
raise InvalidCapabilityError
|
29
|
+
end
|
30
|
+
|
31
|
+
unless Snapi.capabilities[@capability].valid_function_call?(@function,params)
|
32
|
+
raise InvalidFunctionCallError
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO add response_wrapper which ensures that the return data from the
|
36
|
+
# function matches the type declared in the capabilities function defitition
|
37
|
+
response = Snapi.capabilities[@capability].run_function(@function,params)
|
38
|
+
response.class == String ? response : JSON.generate(response)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Snapi
|
2
|
+
|
3
|
+
# Helpful module to check strings against named Regexp patterns which
|
4
|
+
# are stored in here.
|
5
|
+
module Validator
|
6
|
+
|
7
|
+
# Core method of the module which attempts to check if a provided
|
8
|
+
# string matches any of the regex's as identified by the key
|
9
|
+
#
|
10
|
+
# @params key, Symbol key which maps to one of the keys in the validation_regex method below
|
11
|
+
# @params string, String to check
|
12
|
+
# @returns Boolean, true if string checks out
|
13
|
+
def valid_input?(key,string)
|
14
|
+
raise InvalidFormatError unless valid_regex_format?(key)
|
15
|
+
|
16
|
+
boolarray = validation_regex[key].map do |regxp|
|
17
|
+
(string =~ regxp) == 0 ? true : false
|
18
|
+
end
|
19
|
+
|
20
|
+
return true if boolarray.include?(true)
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
# A helper to get get the keys off the validation_regex hash
|
25
|
+
#
|
26
|
+
# @returns Array of symbols
|
27
|
+
def format_types
|
28
|
+
validation_regex.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
# A helper to validate that the requested symbols is a valid
|
32
|
+
# type of format we can check against
|
33
|
+
#
|
34
|
+
# @param format, Symbol to check
|
35
|
+
# @returns Boolean, true if the requested format is in format_types array
|
36
|
+
def valid_regex_format?(format)
|
37
|
+
format_types.include?(format)
|
38
|
+
end
|
39
|
+
|
40
|
+
# A helper dictionary which returns and array of valid Regexp patterns
|
41
|
+
# in exchange for a valid key
|
42
|
+
#
|
43
|
+
# @returns Hash, dictionary of symbols and Regexp arrays
|
44
|
+
def validation_regex
|
45
|
+
{
|
46
|
+
:address => [HOSTNAME_REGEX, DOMAIN_REGEX, IP_V4_REGEX, IP_V6_REGEX],
|
47
|
+
:anything => [/.*/],
|
48
|
+
:bool => [TRUEFALSE_REGEX],
|
49
|
+
:command => [SIMPLE_COMMAND_REGEX],
|
50
|
+
:cron => [CRON_REGEX],
|
51
|
+
:gsm_adapter => [ADAPTER_REGEX],
|
52
|
+
:hostname => [HOSTNAME_REGEX],
|
53
|
+
:interface => [INTERFACE_REGEX],
|
54
|
+
:ip => [IP_V4_REGEX, IP_V6_REGEX],
|
55
|
+
:ipv6 => [IP_V6_REGEX],
|
56
|
+
:ipv4 => [IP_V4_REGEX],
|
57
|
+
:mac => [MAC_REGEX],
|
58
|
+
:snapi_function_name => [SNAPI_FUNCTION_NAME],
|
59
|
+
:string => [STRING_REGEX],
|
60
|
+
:on_off => [ON_OFF_REGEX],
|
61
|
+
:port => [PORT_REGEX],
|
62
|
+
:uri => [URI_REGEX],
|
63
|
+
:username => [HOSTNAME_REGEX],
|
64
|
+
:password => [NUM_LETTERS_SP_CHARS]
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
module_function :valid_input?, :validation_regex, :format_types, :valid_regex_format?
|
69
|
+
|
70
|
+
############################################################################
|
71
|
+
# ______ _____ _ _ _____ _ ______
|
72
|
+
# | ___ \ ___| | | | _ | | | _ \
|
73
|
+
# | |_/ / |__ | |_| | | | | | | | | |
|
74
|
+
# | ___ \ __|| _ | | | | | | | | |
|
75
|
+
# | |_/ / |___| | | \ \_/ / |___| |/ /
|
76
|
+
# \____/\____/\_| |_/\___/\_____/___( )
|
77
|
+
# |/
|
78
|
+
#
|
79
|
+
# _____ _ _ _____ ______ ___ _____ _____ ___________ _ _ _____
|
80
|
+
# |_ _| | | || ___| | ___ \/ _ \_ _|_ _| ___| ___ \ \ | |/ ___|
|
81
|
+
# | | | |_| || |__ | |_/ / /_\ \| | | | | |__ | |_/ / \| |\ `--.
|
82
|
+
# | | | _ || __| | __/| _ || | | | | __|| /| . ` | `--. \
|
83
|
+
# | | | | | || |___ | | | | | || | | | | |___| |\ \| |\ |/\__/ /
|
84
|
+
# \_/ \_| |_/\____/ \_| \_| |_/\_/ \_/ \____/\_| \_\_| \_/\____/
|
85
|
+
#
|
86
|
+
#
|
87
|
+
#
|
88
|
+
# Note base on lib/shells/gsm.rb
|
89
|
+
ADAPTER_REGEX = /^(unlocked_gsm|verizon_virgin_mobile|tmobile)$/
|
90
|
+
#
|
91
|
+
# Note set in lib/shells/scheduler.rb
|
92
|
+
CRON_REGEX = /^(1_minute|5_minutes|15_minutes|60_minutes)$/
|
93
|
+
#
|
94
|
+
# Source:
|
95
|
+
SIMPLE_COMMAND_REGEX = /^([a-zA-Z0-9\.\s\-\_\/\\\,\=]+)*$/i
|
96
|
+
#
|
97
|
+
# Source: RFC952, RFC1034
|
98
|
+
HOSTNAME_REGEX = /^[a-z0-9-]{1,63}$/i
|
99
|
+
#
|
100
|
+
# Source: RFC952, RFC1034, does not take max length into account
|
101
|
+
DOMAIN_REGEX = /^([a-z0-9-]{1,63}\.)*[a-z0-9-]{1,63}$/i
|
102
|
+
#
|
103
|
+
# validate the name for a function name
|
104
|
+
SNAPI_FUNCTION_NAME = /[a-z0-9_]/
|
105
|
+
#
|
106
|
+
INTERFACE_REGEX = /^([a-z]+)+([0-9]+)+$/i
|
107
|
+
#
|
108
|
+
# IPV4 Source: http://answers.oreilly.com/topic/318-how-to-match-ipv4-addresses-with-regular-expressions/
|
109
|
+
IP_V4_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
110
|
+
#
|
111
|
+
# IPV6 Source: https://gist.github.com/294476
|
112
|
+
IP_V6_REGEX = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
|
113
|
+
#
|
114
|
+
# Mac Source: http://www.ruby-forum.com/topic/139401
|
115
|
+
MAC_REGEX = /^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/i
|
116
|
+
#
|
117
|
+
# SIMPLE ON / OFF (CHECKBOXES)
|
118
|
+
ON_OFF_REGEX = /^(on|off)$/i
|
119
|
+
#
|
120
|
+
# Source:
|
121
|
+
STRING_REGEX = /^([a-zA-Z0-9\.\-\_]+)*$/i
|
122
|
+
#
|
123
|
+
# TRUE OR FALSE
|
124
|
+
TRUEFALSE_REGEX = /^(true|false)$/i
|
125
|
+
#
|
126
|
+
# mmmmmmmm
|
127
|
+
PORT_REGEX = /^(6[0-5][0-5][0-3][0-5]|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[1-9])$/
|
128
|
+
#
|
129
|
+
NUM_LETTERS_SP_CHARS = /^[\w\[\]\!\@\#\$\%\^\&\*\(\)\{\}\:\;\<\>\+\-]*$/i
|
130
|
+
#
|
131
|
+
URI_REGEX = /https?:\/\/[\S]+/
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Snapi::Argument do
|
4
|
+
it "is a class" do
|
5
|
+
subject.class.class.should == Class
|
6
|
+
end
|
7
|
+
|
8
|
+
it "has an attributes hash" do
|
9
|
+
subject.attributes.should == {}
|
10
|
+
end
|
11
|
+
|
12
|
+
it "responds to hash style queries" do
|
13
|
+
subject[:does_not_exist].should == nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "responds to hash style queries" do
|
17
|
+
subject[:default_value] = "some value"
|
18
|
+
subject[:default_value].should == subject.attributes[:default_value]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "protects from illegal keys being set" do
|
22
|
+
begin
|
23
|
+
subject[:dude_why_would_you_do_this] = "WHO CARES WHAT THIS IS BECAUSE IT WILL NOT GET SET"
|
24
|
+
rescue => error
|
25
|
+
error.class.should == Snapi::InvalidArgumentAttributeError
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can have default_value set" do
|
30
|
+
subject.default_value("test").should == "test"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "can have required set" do
|
34
|
+
subject.required(true).should == true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can have list set" do
|
38
|
+
subject.list(true).should == true
|
39
|
+
end
|
40
|
+
|
41
|
+
it "can have type set" do
|
42
|
+
subject.type(:string).should == :string
|
43
|
+
end
|
44
|
+
|
45
|
+
it "can return a hash of its own options" do
|
46
|
+
subject.attributes.keys.sort.should == []
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "can validated types such as" do
|
50
|
+
it ":boolean" do
|
51
|
+
a = Snapi::Argument.new
|
52
|
+
a.type :boolean
|
53
|
+
a.valid_input?(true).should == true
|
54
|
+
a.valid_input?(false).should == true
|
55
|
+
a.valid_input?(String).should == false
|
56
|
+
a.valid_input?("String").should == false
|
57
|
+
a.valid_input?({}).should == false
|
58
|
+
a.valid_input?([]).should == false
|
59
|
+
end
|
60
|
+
|
61
|
+
it ":string" do
|
62
|
+
a = Snapi::Argument.new
|
63
|
+
a.type :string
|
64
|
+
a.valid_input?(10.0).should == false
|
65
|
+
a.valid_input?(100).should == false
|
66
|
+
a.valid_input?(false).should == false
|
67
|
+
a.valid_input?([]).should == false
|
68
|
+
a.valid_input?({}).should == false
|
69
|
+
a.valid_input?(String).should == false
|
70
|
+
a.valid_input?("String").should == true
|
71
|
+
a.valid_input?(true).should == false
|
72
|
+
end
|
73
|
+
|
74
|
+
it ":string with format" do
|
75
|
+
a = Snapi::Argument.new
|
76
|
+
a.type :string
|
77
|
+
a.format :ip
|
78
|
+
a.valid_input?(10.0).should == false
|
79
|
+
a.valid_input?(100).should == false
|
80
|
+
a.valid_input?("192.168.1.1").should == true
|
81
|
+
a.valid_input?(false).should == false
|
82
|
+
a.valid_input?([]).should == false
|
83
|
+
a.valid_input?({}).should == false
|
84
|
+
a.valid_input?("String").should == false
|
85
|
+
a.valid_input?(String).should == false
|
86
|
+
a.valid_input?(true).should == false
|
87
|
+
end
|
88
|
+
|
89
|
+
it ":enum with values" do
|
90
|
+
a = Snapi::Argument.new
|
91
|
+
a.type :enum
|
92
|
+
a.values [:a,:b,:c]
|
93
|
+
a.valid_input?(10.0).should == false
|
94
|
+
a.valid_input?(100).should == false
|
95
|
+
a.valid_input?(:a).should == true
|
96
|
+
a.valid_input?(:b).should == true
|
97
|
+
a.valid_input?(:c).should == true
|
98
|
+
a.valid_input?(false).should == false
|
99
|
+
a.valid_input?([]).should == false
|
100
|
+
a.valid_input?({}).should == false
|
101
|
+
a.valid_input?("String").should == false
|
102
|
+
a.valid_input?(String).should == false
|
103
|
+
a.valid_input?(true).should == false
|
104
|
+
end
|
105
|
+
|
106
|
+
it ":number" do
|
107
|
+
a = Snapi::Argument.new
|
108
|
+
a.type :number
|
109
|
+
a.valid_input?(100000000).should == true
|
110
|
+
a.valid_input?(10.0).should == false
|
111
|
+
a.valid_input?(100).should == true
|
112
|
+
a.valid_input?(:a).should == false
|
113
|
+
a.valid_input?(:b).should == false
|
114
|
+
a.valid_input?(:c).should == false
|
115
|
+
a.valid_input?(false).should == false
|
116
|
+
a.valid_input?([]).should == false
|
117
|
+
a.valid_input?({}).should == false
|
118
|
+
a.valid_input?("String").should == false
|
119
|
+
a.valid_input?(String).should == false
|
120
|
+
a.valid_input?(true).should == false
|
121
|
+
end
|
122
|
+
|
123
|
+
it ":timestamp" do
|
124
|
+
pending
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Snapi::BasicCapability do
|
4
|
+
describe "has a namespace which" do
|
5
|
+
it "can convert its class name into a route namespace" do
|
6
|
+
subject.class.namespace.should == :basic_capability
|
7
|
+
end
|
8
|
+
|
9
|
+
it "passes this to inherited BasicCapability objects" do
|
10
|
+
LadyRainicornAndPrinceMonochromocorn = Class.new(subject.class)
|
11
|
+
LadyRainicornAndPrinceMonochromocorn.namespace.should == :lady_rainicorn_and_prince_monochromocorn
|
12
|
+
end
|
13
|
+
|
14
|
+
it "can return a hash representation of itself" do
|
15
|
+
class PrinceLemonGrab < Snapi::BasicCapability
|
16
|
+
function :summon_zombies do |fn|
|
17
|
+
fn.return :raw
|
18
|
+
end
|
19
|
+
end
|
20
|
+
lemon_grab = {:prince_lemon_grab => {:summon_zombies => { :return_type => :raw}}}
|
21
|
+
|
22
|
+
PrinceLemonGrab.to_hash.should == lemon_grab
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "DSL" do
|
27
|
+
it "can take a function" do
|
28
|
+
class PrincessBubblegum < Snapi::BasicCapability
|
29
|
+
function :create_candy_person do |fn|
|
30
|
+
fn.argument :candy_base do |arg|
|
31
|
+
arg.default_value "sugar"
|
32
|
+
arg.format :anything
|
33
|
+
arg.list true
|
34
|
+
arg.required true
|
35
|
+
arg.type :enum
|
36
|
+
end
|
37
|
+
fn.return :structured
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
expected_return = {
|
42
|
+
:create_candy_person => {
|
43
|
+
:return_type =>:structured,
|
44
|
+
:candy_base => {
|
45
|
+
:default_value => "sugar",
|
46
|
+
:format => :anything,
|
47
|
+
:required => true,
|
48
|
+
:list => true,
|
49
|
+
:type => :enum,
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
PrincessBubblegum.to_hash.should == { PrincessBubblegum.namespace => expected_return}
|
54
|
+
end
|
55
|
+
|
56
|
+
it "doesn't shared functions between inherited classes" do
|
57
|
+
class FinnTheHuman < Snapi::BasicCapability
|
58
|
+
function :enchyridion do |fn|
|
59
|
+
fn.return :raw
|
60
|
+
end
|
61
|
+
end
|
62
|
+
class JakeTheDog < Snapi::BasicCapability
|
63
|
+
function :beemo do |fn|
|
64
|
+
fn.return :raw
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
FinnTheHuman.functions[:enchyridion].should_not == nil
|
69
|
+
FinnTheHuman.functions[:beemo].should == nil
|
70
|
+
|
71
|
+
JakeTheDog.functions[:enchyridion].should == nil
|
72
|
+
JakeTheDog.functions[:beemo].should_not == nil
|
73
|
+
|
74
|
+
Snapi::BasicCapability.functions.should == {}
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "tracks a :library class or module which provides methods as defined by the function block" do
|
80
|
+
it "defaults to self" do
|
81
|
+
class TheLich < Snapi::BasicCapability
|
82
|
+
end
|
83
|
+
TheLich.library_class.should == TheLich
|
84
|
+
end
|
85
|
+
it "can be set via self.library " do
|
86
|
+
class BillysLittleFriend
|
87
|
+
def help_somebody
|
88
|
+
end
|
89
|
+
end
|
90
|
+
class BillyTheHero < Snapi::BasicCapability
|
91
|
+
library BillysLittleFriend
|
92
|
+
function :help_somebody
|
93
|
+
end
|
94
|
+
BillyTheHero.library_class.should == BillysLittleFriend
|
95
|
+
end
|
96
|
+
it "can validate if the library has the valid methods" do
|
97
|
+
class BillysLittleFriend
|
98
|
+
def self.help_somebody
|
99
|
+
end
|
100
|
+
end
|
101
|
+
class BillyTheHero < Snapi::BasicCapability
|
102
|
+
library BillysLittleFriend
|
103
|
+
function :help_somebody
|
104
|
+
end
|
105
|
+
class FrankTheVillain < Snapi::BasicCapability
|
106
|
+
library BillysLittleFriend
|
107
|
+
function :hurt_somebody
|
108
|
+
end
|
109
|
+
BillyTheHero.valid_library_class?.should == true
|
110
|
+
FrankTheVillain.valid_library_class?.should == false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "can run a function with a hash of arguments" do
|
115
|
+
class IceKing < Snapi::BasicCapability
|
116
|
+
function :ice_attack do |fn|
|
117
|
+
fn.argument :victim do |arg|
|
118
|
+
arg.required true
|
119
|
+
arg.type :string
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "validates a hash of arguments against a function" do
|
125
|
+
IceKing.valid_function_call?(:icicle, {:victim => "Gunther"}).should == false
|
126
|
+
IceKing.valid_function_call?(:ice_attack, {}).should == false
|
127
|
+
IceKing.valid_function_call?(:ice_attack, {:victim => "Gunther"}).should == true
|
128
|
+
end
|
129
|
+
|
130
|
+
it "raises errors when invalid args or function are sent" do
|
131
|
+
begin
|
132
|
+
IceKing.run_function(:icicle, {:victim => "Gunther"}).should == false
|
133
|
+
rescue => e
|
134
|
+
e.class.should == Snapi::InvalidFunctionCallError
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
it "raises errors when the library class does not support the requested library " do
|
139
|
+
begin
|
140
|
+
IceKing.run_function(:ice_attack, {:victim => "Gunther"}).should == false
|
141
|
+
rescue => e
|
142
|
+
e.class.should == Snapi::LibraryClassMissingFunctionError
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
it "runs the function if available in the library class" do
|
147
|
+
class IceWand
|
148
|
+
def self.ice_attack(args={})
|
149
|
+
"ZAP #{args[:victim].upcase}!"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class IceKing
|
154
|
+
library IceWand
|
155
|
+
end
|
156
|
+
|
157
|
+
IceKing.run_function(:ice_attack, {:victim => "Gunther"}).should == "ZAP GUNTHER!"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Snapi::Function do
|
4
|
+
it "can take an argument" do
|
5
|
+
subject.argument :test do |arg|
|
6
|
+
arg.default_value "test"
|
7
|
+
arg.list true
|
8
|
+
arg.type :string
|
9
|
+
end
|
10
|
+
|
11
|
+
subject.argument :test2 do |arg|
|
12
|
+
arg.default_value "testing more"
|
13
|
+
arg.required true
|
14
|
+
arg.type :string
|
15
|
+
end
|
16
|
+
|
17
|
+
subject.arguments[:test][:required].should == nil
|
18
|
+
subject.arguments[:test2][:required].should == true
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
it "can validate a ruby hash of keys and values against its arguments" do
|
23
|
+
subject.argument :argument do |arg|
|
24
|
+
arg.default_value "test"
|
25
|
+
arg.required true
|
26
|
+
arg.type :string
|
27
|
+
end
|
28
|
+
|
29
|
+
subject.valid_args?({}).should == false
|
30
|
+
subject.valid_args?({:not_argument => "test value"}).should == false
|
31
|
+
subject.valid_args?({:argument => "test value"}).should == true
|
32
|
+
# ignores
|
33
|
+
subject.valid_args?({:argument => "test value", :not => :relevant}).should == true
|
34
|
+
end
|
35
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
describe "Snapi::Validator" do
|
3
|
+
it "is a module" do
|
4
|
+
Snapi::Validator.class.should == Module
|
5
|
+
end
|
6
|
+
|
7
|
+
it "includes an array of format types it can validate" do
|
8
|
+
Snapi::Validator.format_types.class.should == Array
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can identify valid format types" do
|
12
|
+
test_format = Snapi::Validator.format_types.shuffle.first
|
13
|
+
Snapi::Validator.valid_regex_format?(test_format).should == true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can identify invalid format types" do
|
17
|
+
test_format = :no_frakin_way_this_key_exists_seriously_dude
|
18
|
+
Snapi::Validator.valid_regex_format?(test_format).should == false
|
19
|
+
end
|
20
|
+
|
21
|
+
it "offers an array of regular expressions in exchange for a format type" do
|
22
|
+
test_format = Snapi::Validator.format_types.shuffle.first
|
23
|
+
v_reg = Snapi::Validator.validation_regex[test_format]
|
24
|
+
v_reg.class.should == Array
|
25
|
+
v_reg.first.class.should == Regexp
|
26
|
+
end
|
27
|
+
|
28
|
+
it "will attempt to confirm if a string matches the specified regex" do
|
29
|
+
Snapi::Validator.valid_input?( :ip, "192.168.10.129" ).should == true
|
30
|
+
Snapi::Validator.valid_input?( :ip, "192.168.10.256" ).should == false
|
31
|
+
Snapi::Validator.valid_input?( :ip, "Jake the Dog" ).should == false
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: snapi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Gabe Koss
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Modular Capability Disclosure DSL
|
31
|
+
email: gabe@pwnieexpress.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- lib/snapi.rb
|
37
|
+
- lib/snapi/argument.rb
|
38
|
+
- lib/snapi/validator.rb
|
39
|
+
- lib/snapi/function.rb
|
40
|
+
- lib/snapi/version.rb
|
41
|
+
- lib/snapi/sinatra_extension.rb
|
42
|
+
- lib/snapi/capability.rb
|
43
|
+
- lib/snapi/errors.rb
|
44
|
+
- README.md
|
45
|
+
- CHANGELOG
|
46
|
+
- Gemfile.lock
|
47
|
+
- Gemfile
|
48
|
+
- spec/basic_capability_spec.rb
|
49
|
+
- spec/validator_spec.rb
|
50
|
+
- spec/spec_helper.rb
|
51
|
+
- spec/function_spec.rb
|
52
|
+
- spec/argument_spec.rb
|
53
|
+
homepage: https://github.com/pwnieexpress/snapi
|
54
|
+
licenses:
|
55
|
+
- MIT
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.8.25
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: Snapi
|
78
|
+
test_files: []
|