msfl 0.0.1.pre.dev → 0.0.1.pre.qa
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.
- checksums.yaml +4 -4
- data/lib/msfl.rb +28 -3
- data/lib/msfl/configuration.rb +9 -0
- data/lib/msfl/datasets.rb +1 -0
- data/lib/msfl/datasets/base.rb +79 -0
- data/lib/msfl/datasets/movies.rb +13 -0
- data/lib/msfl/parsers.rb +1 -0
- data/lib/msfl/parsers/json.rb +26 -0
- data/lib/msfl/types.rb +1 -0
- data/lib/msfl/types/set.rb +7 -0
- data/lib/msfl/validators.rb +1 -0
- data/lib/msfl/validators/definitions.rb +1 -0
- data/lib/msfl/validators/definitions/hash_key.rb +36 -0
- data/lib/msfl/validators/semantic.rb +155 -0
- metadata +27 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5192c432f2b0fa92fdc52f791a6d1db94e0e7273
|
|
4
|
+
data.tar.gz: 369cf6c5d51a3b2f996b070a70894a6fd952b7f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dadc0e105e90e00c982424d332a2cada2733a676cf9c26f0d65ab3c5a79e4ffe94c78b87903c493aa070ae0c80f813eb1bf6313cd81531593911fde0f0c3b1bf
|
|
7
|
+
data.tar.gz: 86c9aac383c3a267e9344ca3f081b4b1e9fd8c461ca5e86a5962e3811476dad04ac918c521cc9dc0011811e5029caca9ec14858bd9b83ad313a8c0a1c45d6561
|
data/lib/msfl.rb
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
require 'msfl/parsers'
|
|
2
|
+
require 'msfl/types'
|
|
3
|
+
require 'msfl/validators'
|
|
4
|
+
require 'msfl/configuration'
|
|
5
|
+
require 'msfl/datasets'
|
|
6
|
+
|
|
7
|
+
module MSFL
|
|
8
|
+
class << self
|
|
9
|
+
# Allows the user to set configuration options
|
|
10
|
+
# by yielding the configuration block
|
|
11
|
+
#
|
|
12
|
+
# @param opts [Hash] an optional hash of options, supported options are `reset: true`
|
|
13
|
+
# @param block [Block] an optional configuration block
|
|
14
|
+
# @return [Configuration] the current configuration object
|
|
15
|
+
def configure(opts = {}, &block)
|
|
16
|
+
if opts.has_key?(:reset) && opts[:reset]
|
|
17
|
+
@configuration = nil
|
|
18
|
+
end
|
|
19
|
+
yield(configuration) if block_given?
|
|
20
|
+
configuration
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns the singleton class's configuration object
|
|
24
|
+
#
|
|
25
|
+
# @return [Configuration] the current configuration object
|
|
26
|
+
def configuration
|
|
27
|
+
@configuration ||= Configuration.new
|
|
28
|
+
end
|
|
4
29
|
end
|
|
5
30
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative 'datasets/base'
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module MSFL
|
|
2
|
+
module Datasets
|
|
3
|
+
class Base
|
|
4
|
+
|
|
5
|
+
include Validators::Definitions::HashKey
|
|
6
|
+
|
|
7
|
+
# The descendant class MUST override this method otherwise all field validations will fail
|
|
8
|
+
#
|
|
9
|
+
# @return [Array] the fields in the dataset
|
|
10
|
+
def fields
|
|
11
|
+
raise NoMethodError, "Descendants of MSFL::Datasets::Base are required to implement the #fields method"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# The descendant class may override this method to control the operators that are supported for the dataset
|
|
15
|
+
# - Note that this can only be used to reduce the number of supported operators (you can't add new operators
|
|
16
|
+
# here, without first adding them to MSFL::Validators::Definitions::HashKey#hash_key_operators)
|
|
17
|
+
def operators
|
|
18
|
+
hash_key_operators
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Method not implemented at this time
|
|
22
|
+
#
|
|
23
|
+
# @param obj [Object] the object that should be type checked based on the field argument
|
|
24
|
+
# @param field [Symbol] which field should the object be checked for conformity
|
|
25
|
+
# @param errors [Array] an array of validation errors - empty indicates that no errors have been encountered
|
|
26
|
+
# @return [Array] errors merged with any validation errors encountered in validating the set
|
|
27
|
+
#
|
|
28
|
+
# @example:
|
|
29
|
+
# foo.investors_validate_type_conforms("abc", :total_funding) => # there is one more error in errors
|
|
30
|
+
# # because the type of total_funding must be an integer
|
|
31
|
+
#
|
|
32
|
+
def validate_type_conforms(obj, field, errors)
|
|
33
|
+
errors
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Method not implemented at this time
|
|
37
|
+
# Returns true if the object conforms to the types supported by the indicated field
|
|
38
|
+
#
|
|
39
|
+
# @param obj [Object] the object that should be type checked based on the field argument
|
|
40
|
+
# @param field [Symbol] which field should the object be checked for conformity
|
|
41
|
+
def type_conforms?(obj, field)
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Method not implemented at this time
|
|
46
|
+
#
|
|
47
|
+
#
|
|
48
|
+
# @param operator [Symbol] the operator that we want to know if the particular field supports it
|
|
49
|
+
# @param field [Symbol] which field should the operator be checked for conformity
|
|
50
|
+
# @param errors [Array] an array of validation errors - empty indicates that no errors have been encountered
|
|
51
|
+
# @return [Array] errors merged with any validation errors encountered in validating the set
|
|
52
|
+
def validate_operator_conforms(operator, field, errors)
|
|
53
|
+
errors
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Method not implemented at this time
|
|
57
|
+
#
|
|
58
|
+
#
|
|
59
|
+
# @param value [Object] the precoerced value (the value must be correctly typed)
|
|
60
|
+
# that should be checked for validity based on the field, if the value does not conform an
|
|
61
|
+
# exception will be raised
|
|
62
|
+
# @param field [Symbol] which field should the value be checked for conformity
|
|
63
|
+
# @param errors [Array] an array of validation errors - empty indicates that no errors have been encountered
|
|
64
|
+
# @return [Array] errors merged with any validation errors encountered in validating the set
|
|
65
|
+
#
|
|
66
|
+
#
|
|
67
|
+
# @example:
|
|
68
|
+
# foo.investors_value_conforms(-6000, :total_funding) => # there is one more error in errors
|
|
69
|
+
# # because the funding cannot be negative
|
|
70
|
+
#
|
|
71
|
+
# @example:
|
|
72
|
+
# foo.investors_value_conforms(12345, :total_funding) => # errors is unchanged
|
|
73
|
+
#
|
|
74
|
+
def validate_value_conforms(value, field, errors)
|
|
75
|
+
errors
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module MSFL
|
|
4
|
+
module Datasets
|
|
5
|
+
# This is a fake dataset definition that shows the structure for composing your own and is used for testing
|
|
6
|
+
# msfl
|
|
7
|
+
class Movies < ::MSFL::Datasets::Base
|
|
8
|
+
def fields
|
|
9
|
+
[:title, :rating, :description, :earnings]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/msfl/parsers.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative 'parsers/json'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
module MSFL
|
|
3
|
+
module Parsers
|
|
4
|
+
class JSON
|
|
5
|
+
def self.parse(json)
|
|
6
|
+
obj = ::JSON.parse(json)
|
|
7
|
+
obj = arrays_to_sets obj
|
|
8
|
+
obj
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.arrays_to_sets(obj)
|
|
12
|
+
obj = Types::Set.new obj if obj.is_a?(::Array)
|
|
13
|
+
if obj.respond_to? :each
|
|
14
|
+
if obj.is_a?(::Hash)
|
|
15
|
+
result = {}
|
|
16
|
+
obj.each { |key, val| result[key.to_sym] = arrays_to_sets val }
|
|
17
|
+
elsif obj.is_a?(Types::Set)
|
|
18
|
+
result = Types::Set.new obj.map { |value| arrays_to_sets value }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
result ||= obj
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/msfl/types.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative 'types/set'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative 'validators/semantic'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative 'definitions/hash_key'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module MSFL
|
|
2
|
+
module Validators
|
|
3
|
+
module Definitions
|
|
4
|
+
module HashKey
|
|
5
|
+
|
|
6
|
+
def valid_hash_key?(key)
|
|
7
|
+
valid_hash_keys.include? key
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def valid_hash_keys
|
|
11
|
+
hash_key_operators.concat self.dataset.fields
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Operators still needing parsing: ellipsis2, tilda
|
|
15
|
+
def hash_key_operators
|
|
16
|
+
[
|
|
17
|
+
:and, # logical AND
|
|
18
|
+
:or, # logical OR
|
|
19
|
+
:in, # IN
|
|
20
|
+
:between, # inclusive range for integers, dates, and date times
|
|
21
|
+
:start, # a range bound inclusively to the left
|
|
22
|
+
:end, # a range bound inclusively to the right
|
|
23
|
+
:ellipsis2, # alias to :between
|
|
24
|
+
:tilda, # alias to :between, :start, and :end depending on usage
|
|
25
|
+
:eq, # ==
|
|
26
|
+
:lt, # <
|
|
27
|
+
:lte, # <=
|
|
28
|
+
:gt, # >
|
|
29
|
+
:gte, # >=
|
|
30
|
+
:neg, # logical negation
|
|
31
|
+
]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
require_relative 'definitions'
|
|
2
|
+
|
|
3
|
+
module MSFL
|
|
4
|
+
module Validators
|
|
5
|
+
class Semantic
|
|
6
|
+
# Load definitions
|
|
7
|
+
include Definitions::HashKey
|
|
8
|
+
|
|
9
|
+
attr_accessor :dataset, :errors, :current_field, :current_operator
|
|
10
|
+
|
|
11
|
+
BOOLEAN_OPERATORS = [:and, :or]
|
|
12
|
+
ENUMERATION_OPERATORS = [:in]
|
|
13
|
+
|
|
14
|
+
def initialize(attributes = nil, opts = {})
|
|
15
|
+
@dataset = MSFL.configuration.datasets.first.new unless MSFL.configuration.datasets.empty?
|
|
16
|
+
@dataset ||= Datasets::Base.new
|
|
17
|
+
@current_field = nil
|
|
18
|
+
@current_operator = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns true if the object is valid, false if the object is invalid
|
|
22
|
+
# An array of hashes of errors is available at #errors
|
|
23
|
+
#
|
|
24
|
+
# This method is not meant to be called recursively, the private method recursive_validate is used
|
|
25
|
+
# for this purpose
|
|
26
|
+
#
|
|
27
|
+
# @param hash [Hash] the object to be validated
|
|
28
|
+
# @param errors [Array] optionally provide an array that contains errors from previous validators in the
|
|
29
|
+
# validation chain
|
|
30
|
+
def validate(hash, errors = [], opts = {})
|
|
31
|
+
errors << "Object to validate must be a Hash" unless hash.is_a?(Hash)
|
|
32
|
+
recursive_validate hash, errors, opts
|
|
33
|
+
@errors = errors
|
|
34
|
+
result = true if @errors.empty?
|
|
35
|
+
result ||= false
|
|
36
|
+
result
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the result of merging errors with any newly encountered errors found in validating the hash
|
|
40
|
+
#
|
|
41
|
+
# @param hash [Hash] the Hash to validate
|
|
42
|
+
# @param errors [Array] an array of validation errors - empty indicates that no errors have been encountered
|
|
43
|
+
# @param opts [Hash] the options hash
|
|
44
|
+
# @return [Array] errors merged with any validation errors encountered in validating the hash
|
|
45
|
+
def validate_hash(hash, errors, opts)
|
|
46
|
+
# set current field
|
|
47
|
+
current_field = nil
|
|
48
|
+
# validate the keys and values
|
|
49
|
+
hash.each do |k, value|
|
|
50
|
+
key = k.to_sym
|
|
51
|
+
# validate the current hash key using broad hash key validation
|
|
52
|
+
errors << "Hash key encountered that is broadly invalid." unless valid_hash_key?(key)
|
|
53
|
+
|
|
54
|
+
# validate that the hash key is supported as an operator or dataset field
|
|
55
|
+
|
|
56
|
+
# if they key is an operator validate the dataset supports the operator for the current field
|
|
57
|
+
#
|
|
58
|
+
# if the key is a field of the dataset then we need to validate that the _value_ conforms to the dataset
|
|
59
|
+
# specific validation rules for that field
|
|
60
|
+
#
|
|
61
|
+
# if they key is neither an operator nor a field we raise an ArgumentError
|
|
62
|
+
#
|
|
63
|
+
# Then make a recursive call to validate on the value so that it and its elements are validated
|
|
64
|
+
# later I might be able to optimize this by only making the recursive call for Sets and Hashes
|
|
65
|
+
#
|
|
66
|
+
if dataset.operators.include? key
|
|
67
|
+
dataset.validate_operator_conforms key, current_field, errors
|
|
68
|
+
opts[:parent_operator] = key
|
|
69
|
+
elsif dataset.fields.include? key
|
|
70
|
+
current_field = key
|
|
71
|
+
dataset.validate_type_conforms value, current_field, errors
|
|
72
|
+
dataset.validate_value_conforms value, current_field, errors
|
|
73
|
+
opts[:parent_field] = current_field
|
|
74
|
+
else
|
|
75
|
+
errors << "Encountered hash key that is neither an operator nor a property of the dataset"
|
|
76
|
+
end
|
|
77
|
+
recursive_validate value, errors, opts
|
|
78
|
+
end
|
|
79
|
+
errors
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Acts as a helper method that forwards validation requests for sets to the right handler based on the
|
|
83
|
+
# :parent_operator option's value
|
|
84
|
+
#
|
|
85
|
+
# @param set [MSFL::Types::Set] the set to validate
|
|
86
|
+
# @param errors [Array] the existing array of validation errors
|
|
87
|
+
# @param opts [Hash] the options hash
|
|
88
|
+
# @return [Array] the errors array argument with any additional validation errors appended
|
|
89
|
+
def validate_set(set, errors, opts)
|
|
90
|
+
error_message =
|
|
91
|
+
"Validate set requires the :parent_operator option be set and represented in either the BOOLEAN_OPERATORS
|
|
92
|
+
or ENUMERATION_OPERATORS constant"
|
|
93
|
+
errors << error_message unless opts.has_key?(:parent_operator)
|
|
94
|
+
|
|
95
|
+
if BOOLEAN_OPERATORS.include?(opts[:parent_operator])
|
|
96
|
+
validate_boolean_set set, errors, opts
|
|
97
|
+
elsif ENUMERATION_OPERATORS.include?(opts[:parent_operator])
|
|
98
|
+
validate_enumeration_set set, errors, opts
|
|
99
|
+
else
|
|
100
|
+
errors << error_message
|
|
101
|
+
end
|
|
102
|
+
errors
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Returns the result of merging errors with any newly encountered errors found in validating the set
|
|
106
|
+
#
|
|
107
|
+
# @param set [MSFL::Types::Set] the set to validate
|
|
108
|
+
# @param errors [Array] an array of validation errors - empty indicates that no errors have been encountered
|
|
109
|
+
# @return [Array] errors merged with any validation errors encountered in validating the set
|
|
110
|
+
def validate_boolean_set(set, errors, opts)
|
|
111
|
+
# Every member needs to be a hash
|
|
112
|
+
set.each do |value|
|
|
113
|
+
errors << "Every member of a boolean set must be a Hash" unless value.is_a?(Hash)
|
|
114
|
+
# recursively call validate on each member
|
|
115
|
+
recursive_validate value, errors, opts
|
|
116
|
+
end
|
|
117
|
+
errors
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Validates a set of enumerationd scalar values
|
|
121
|
+
def validate_enumeration_set(set, errors, opts)
|
|
122
|
+
current_field = opts[:parent_field] if opts.has_key?(:parent_field)
|
|
123
|
+
current_field ||= nil
|
|
124
|
+
errors << "Validate enumeration set requires the parent_field option to be set" if current_field.nil?
|
|
125
|
+
set.each do |value|
|
|
126
|
+
# this isn't quite right, it feels dirty to use :each
|
|
127
|
+
errors << "No members of an enumeration set may permit iteration across itself" if value.respond_to?(:each)
|
|
128
|
+
dataset.validate_type_conforms value, current_field, errors
|
|
129
|
+
dataset.validate_value_conforms value, current_field, errors
|
|
130
|
+
end
|
|
131
|
+
errors
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def recursive_validate(obj, errors, opts)
|
|
137
|
+
# store a copy of parent_operator and parent_field to restore after recursion
|
|
138
|
+
parent_operator = opts[:parent_operator] if opts.has_key?(:parent_operator)
|
|
139
|
+
parent_operator ||= nil
|
|
140
|
+
parent_field = opts[:parent_field] if opts.has_key?(:parent_field)
|
|
141
|
+
parent_field ||= nil
|
|
142
|
+
if obj.is_a?(Hash)
|
|
143
|
+
validate_hash obj, errors, opts
|
|
144
|
+
elsif obj.is_a?(Types::Set)
|
|
145
|
+
validate_set obj, errors, opts
|
|
146
|
+
end
|
|
147
|
+
opts.delete :parent_operator
|
|
148
|
+
opts[:parent_operator] = parent_operator unless parent_operator.nil?
|
|
149
|
+
opts.delete :parent_field
|
|
150
|
+
opts[:parent_field] = parent_field unless parent_field.nil?
|
|
151
|
+
errors
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: msfl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.1.pre.
|
|
4
|
+
version: 0.0.1.pre.qa
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Courtland Caldwell
|
|
@@ -10,6 +10,20 @@ bindir: bin
|
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2015-03-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: json
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ~>
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.7'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ~>
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.7'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: rake
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -88,6 +102,18 @@ extensions: []
|
|
|
88
102
|
extra_rdoc_files: []
|
|
89
103
|
files:
|
|
90
104
|
- lib/msfl.rb
|
|
105
|
+
- lib/msfl/configuration.rb
|
|
106
|
+
- lib/msfl/datasets.rb
|
|
107
|
+
- lib/msfl/datasets/base.rb
|
|
108
|
+
- lib/msfl/datasets/movies.rb
|
|
109
|
+
- lib/msfl/parsers.rb
|
|
110
|
+
- lib/msfl/parsers/json.rb
|
|
111
|
+
- lib/msfl/types.rb
|
|
112
|
+
- lib/msfl/types/set.rb
|
|
113
|
+
- lib/msfl/validators.rb
|
|
114
|
+
- lib/msfl/validators/definitions.rb
|
|
115
|
+
- lib/msfl/validators/definitions/hash_key.rb
|
|
116
|
+
- lib/msfl/validators/semantic.rb
|
|
91
117
|
homepage: https://github.com/caldwecr/msfl
|
|
92
118
|
licenses:
|
|
93
119
|
- MIT
|