snapi 0.0.2
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 +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: []
|