sullivan 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b993257c0f51eb41768a9c272798cd6ec6caa160
4
+ data.tar.gz: 5e15408676c3ad43fb4645581120072914ae60aa
5
+ SHA512:
6
+ metadata.gz: da18cbc61e3cff2bb8e1715126a9e46dd8ead724d719a5c8534eb3717074fac16568ac9127bc461aeb32515d30981400e925e3c7c4e4511abef429f51ce8e675
7
+ data.tar.gz: 13dece6a3657c7e653b87db7847ef42ad6faee4db2c6ee70cc52d62295ab05f08c4781bb3f5913843dee6a4499a6805797a168bd849cb9475773c71ef5ad34d8
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sullivan.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Go Daddy Operating Company, LLC
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Sullivan
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'sullivan'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install sullivan
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( http://github.com/<my-github-username>/sullivan/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/lib/sullivan.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Sullivan
2
+ def self.validation(&block)
3
+ DSL.new.instance_eval(&block)
4
+ end
5
+
6
+ class DSL < BasicObject
7
+ def method_missing(method_name, *args)
8
+ constant_name = DSL.camelize(method_name.to_s)
9
+
10
+ if ::Sullivan::Validations.const_defined?(constant_name)
11
+ klass = ::Sullivan::Validations.const_get(constant_name)
12
+ klass.new(*args)
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def self.camelize(string)
19
+ string.split('_').map(&:capitalize).join
20
+ end
21
+ end
22
+ end
23
+
24
+ require 'sullivan/validations/hash.rb'
25
+ require 'sullivan/validations/kind_of.rb'
26
+ require 'sullivan/validations/many.rb'
27
+ require 'sullivan/validations/optional.rb'
28
+ require 'sullivan/validations/string_matching.rb'
@@ -0,0 +1,35 @@
1
+ module Sullivan
2
+ module Validations
3
+ class Hash
4
+ def initialize(validations)
5
+ @validations = validations
6
+ end
7
+
8
+ def validate(hash)
9
+ return 'must be a hash' unless hash.respond_to?(:to_hash)
10
+ hash = hash.to_hash
11
+
12
+ errors = @validations.each_with_object({}) { |(key, validation), h|
13
+ error = validation.validate(hash[key])
14
+ h[key] = error unless error.nil?
15
+ }
16
+
17
+ errors.merge! (hash.keys - @validations.keys).each_with_object({}) { |unexpected_key, h| h[unexpected_key] = 'is unexpected' }
18
+
19
+ errors unless errors.empty?
20
+ end
21
+ end
22
+
23
+ class StringHash < Hash
24
+ def initialize(validations)
25
+ super stringify_keys(validations)
26
+ end
27
+
28
+ private
29
+
30
+ def stringify_keys(hash)
31
+ hash.each_with_object({}) { |(k, v), h| h[k.to_s] = v }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ module Sullivan
2
+ module Validations
3
+ class KindOf
4
+ def initialize(klass)
5
+ @klass = klass
6
+ end
7
+
8
+ def validate(value)
9
+ unless value.kind_of?(@klass)
10
+ "must be a kind of #{@klass}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ module Sullivan
2
+ module Validations
3
+ class Many
4
+ def initialize(validation, at_least: nil, at_most: nil)
5
+ @validation = validation
6
+ @at_least = at_least
7
+ @at_most = at_most
8
+ end
9
+
10
+ def validate(values)
11
+ return 'must be an array' unless values.respond_to?(:map)
12
+ return "must contain at least #{num_elements(@at_least)}" if @at_least && values.size < @at_least
13
+ return "must contain at most #{num_elements(@at_most)}" if @at_most && values.size > @at_most
14
+
15
+ errors = values.map do |value|
16
+ @validation.validate(value)
17
+ end
18
+
19
+ errors unless errors.all?(&:nil?)
20
+ end
21
+
22
+ private
23
+
24
+ def num_elements(n)
25
+ "#{n} #{n == 1 ? 'element' : 'elements'}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ module Sullivan
2
+ module Validations
3
+ class Optional
4
+ def initialize(validation)
5
+ @validation = validation
6
+ end
7
+
8
+ def validate(value)
9
+ unless value.nil?
10
+ error = @validation.validate(value)
11
+ if error
12
+ error.respond_to?(:to_str) ? "#{error.to_str}, if present" : error
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Sullivan
2
+ module Validations
3
+ class StringMatching
4
+ def initialize(regex, error: nil)
5
+ @regex = regex
6
+ @custom_error = error
7
+ end
8
+
9
+ def validate(value)
10
+ error unless value.respond_to?(:to_str) && value.to_str =~ @regex
11
+ end
12
+
13
+ private
14
+
15
+ def error
16
+ @custom_error || "must be a string matching #{@regex.inspect}"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Sullivan
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,78 @@
1
+ require 'sullivan/validations/hash'
2
+ require 'sullivan/validations/kind_of'
3
+
4
+ describe Sullivan::Validations::Hash do
5
+ def hash(validations)
6
+ Sullivan::Validations::Hash.new(validations)
7
+ end
8
+
9
+ let(:kind_of_string) { Sullivan::Validations::KindOf.new(String) }
10
+
11
+ it 'accepts a hash with matching keys' do
12
+ error = hash(a: kind_of_string).validate({a: 'a string'})
13
+ expect(error).to be_nil
14
+ end
15
+
16
+ it 'does not accept a hash with non-matching keys' do
17
+ error = hash(a: kind_of_string).validate({a: 1})
18
+ expect(error).to eq({a: 'must be a kind of String'})
19
+ end
20
+
21
+ it 'does not accept a hash with extra keys' do
22
+ error = hash({}).validate({a: 'a string'})
23
+ expect(error).to eq({a: 'is unexpected'})
24
+ end
25
+
26
+ it 'does not accept a hash with missing keys' do
27
+ error = hash(a: kind_of_string).validate({})
28
+ expect(error).to eq({a: 'must be a kind of String'})
29
+ end
30
+
31
+ it 'does not accept a non-hash' do
32
+ error = hash({a: kind_of_string}).validate('a')
33
+ expect(error).to eq('must be a hash')
34
+ end
35
+
36
+ it 'nests' do
37
+ error = hash(a: hash(b: kind_of_string)).validate({a: {b: 1}})
38
+ expect(error).to eq({a: {b: 'must be a kind of String'}})
39
+ end
40
+ end
41
+
42
+ describe Sullivan::Validations::StringHash do
43
+ def string_hash(validations)
44
+ Sullivan::Validations::StringHash.new(validations)
45
+ end
46
+
47
+ let(:kind_of_string) { Sullivan::Validations::KindOf.new(String) }
48
+
49
+ it 'accepts a hash with matching keys' do
50
+ error = string_hash(a: kind_of_string).validate({'a' => 'a string'})
51
+ expect(error).to be_nil
52
+ end
53
+
54
+ it 'does not accept a hash with non-matching keys' do
55
+ error = string_hash(a: kind_of_string).validate({'a' => 1})
56
+ expect(error).to eq({'a' => 'must be a kind of String'})
57
+ end
58
+
59
+ it 'does not accept a hash with extra keys' do
60
+ error = string_hash({}).validate({'a' => 'a string'})
61
+ expect(error).to eq({'a' => 'is unexpected'})
62
+ end
63
+
64
+ it 'does not accept a hash with missing keys' do
65
+ error = string_hash(a: kind_of_string).validate({})
66
+ expect(error).to eq({'a' => 'must be a kind of String'})
67
+ end
68
+
69
+ it 'does not accept a non-hash' do
70
+ error = string_hash({'a' => kind_of_string}).validate('a')
71
+ expect(error).to eq('must be a hash')
72
+ end
73
+
74
+ it 'nests' do
75
+ error = string_hash(a: string_hash(b: kind_of_string)).validate({'a' => {'b' => 1}})
76
+ expect(error).to eq({'a' => {'b' => 'must be a kind of String'}})
77
+ end
78
+ end
@@ -0,0 +1,21 @@
1
+ require 'sullivan/validations/kind_of'
2
+
3
+ describe Sullivan::Validations::KindOf do
4
+ let(:validation) { Sullivan::Validations::KindOf.new(String) }
5
+
6
+ it 'accepts an object of the given type' do
7
+ error = validation.validate("a string")
8
+ expect(error).to be_nil
9
+ end
10
+
11
+ it 'accepts an object of a subtype of the given type' do
12
+ special_string_class = Class.new(String)
13
+ error = validation.validate(special_string_class.new("a string"))
14
+ expect(error).to be_nil
15
+ end
16
+
17
+ it 'does not accept an object of an unrelated type' do
18
+ error = validation.validate(1)
19
+ expect(error).to eq("must be a kind of String")
20
+ end
21
+ end
@@ -0,0 +1,67 @@
1
+ require 'sullivan/validations/many'
2
+ require 'sullivan/validations/kind_of'
3
+ require 'sullivan/validations/hash'
4
+
5
+ describe Sullivan::Validations::Many do
6
+ def many(validation, **opts)
7
+ Sullivan::Validations::Many.new(validation, **opts)
8
+ end
9
+
10
+ def hash(validations)
11
+ Sullivan::Validations::Hash.new(validations)
12
+ end
13
+
14
+ let(:kind_of_string) { Sullivan::Validations::KindOf.new(String) }
15
+
16
+ it 'accepts an empty array' do
17
+ error = many(kind_of_string).validate([])
18
+ expect(error).to be_nil
19
+ end
20
+
21
+ it 'accepts an array of matching elements' do
22
+ error = many(kind_of_string).validate(['a', 'b'])
23
+ expect(error).to be_nil
24
+ end
25
+
26
+ it 'does not accept an array including a non-matching element' do
27
+ error = many(kind_of_string).validate(['a', 2])
28
+ expect(error).to eq([nil, 'must be a kind of String'])
29
+ end
30
+
31
+ it 'accepts an array of matching hashes' do
32
+ error = many(hash(v: kind_of_string)).validate([{v: 'a'}, {v: 'b'}])
33
+ expect(error).to be_nil
34
+ end
35
+
36
+ it 'does not accept an array including a non-matching element' do
37
+ error = many(hash(v: kind_of_string)).validate([{v: 'a'}, {v: 2}])
38
+ expect(error).to eq([nil, {v: 'must be a kind of String'}])
39
+ end
40
+
41
+ it 'does not accept a non-enumerable' do
42
+ error = many(hash(v: kind_of_string)).validate('a')
43
+ expect(error).to eq('must be an array')
44
+ end
45
+
46
+ context "with size requirements" do
47
+ it 'does not accept an array with too few elements' do
48
+ error = many(kind_of_string, at_least: 1).validate([])
49
+ expect(error).to eq('must contain at least 1 element')
50
+ end
51
+
52
+ it 'accepts an array with enough elements' do
53
+ error = many(kind_of_string, at_least: 1).validate(['a'])
54
+ expect(error).to be_nil
55
+ end
56
+
57
+ it 'does not accept an array with too many elements' do
58
+ error = many(kind_of_string, at_most: 2).validate(['a', 'b', 'c'])
59
+ expect(error).to eq('must contain at most 2 elements')
60
+ end
61
+
62
+ it 'accepts an array with few enough elements' do
63
+ error = many(kind_of_string, at_most: 2).validate(['a', 'b'])
64
+ expect(error).to be_nil
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,33 @@
1
+ require 'sullivan/validations/optional'
2
+
3
+ describe Sullivan::Validations::Optional do
4
+ def optional(validation)
5
+ Sullivan::Validations::Optional.new(validation)
6
+ end
7
+
8
+ def hash(validations)
9
+ Sullivan::Validations::Hash.new(validations)
10
+ end
11
+
12
+ let(:kind_of_string) { Sullivan::Validations::KindOf.new(String) }
13
+
14
+ it 'should accept a missing value' do
15
+ error = optional(kind_of_string).validate(nil)
16
+ expect(error).to be_nil
17
+ end
18
+
19
+ it 'should accept a present, matching value' do
20
+ error = optional(kind_of_string).validate('foo')
21
+ expect(error).to be_nil
22
+ end
23
+
24
+ it 'should reject a present, non-matching value' do
25
+ error = optional(kind_of_string).validate(123)
26
+ expect(error).to eq('must be a kind of String, if present')
27
+ end
28
+
29
+ it "doesn't try to add 'if present' to structured errors" do
30
+ error = optional(hash(a: kind_of_string)).validate(a: 123)
31
+ expect(error).to eq(a: 'must be a kind of String')
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ require 'sullivan/validations/string_matching'
2
+
3
+ describe Sullivan::Validations::StringMatching do
4
+ def string_matching(validation, **opts)
5
+ Sullivan::Validations::StringMatching.new(validation, **opts)
6
+ end
7
+
8
+ it "accepts a string which matches" do
9
+ error = string_matching(/please/).validate('Gimme, please.')
10
+ expect(error).to be_nil
11
+ end
12
+
13
+ it "does not accept a string which doesn't match" do
14
+ error = string_matching(/please/).validate('Gimme.')
15
+ expect(error).to eq('must be a string matching /please/')
16
+ end
17
+
18
+ it "takes a custom message" do
19
+ error = string_matching(/abc/, error: 'must contain the magic word').validate('Gimme.')
20
+ expect(error).to eq('must contain the magic word')
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ require 'sullivan'
2
+
3
+ describe Sullivan do
4
+ describe ".validation" do
5
+ it "provides a convenient way to access the built-in validations" do
6
+ v = Sullivan.validation do
7
+ hash(
8
+ string_matching: string_matching(/\Al(ol)+\z/, error: "must be be a laugh"),
9
+ kind_of: kind_of(Numeric),
10
+ )
11
+ end
12
+
13
+ error = v.validate({})
14
+
15
+ expect(error[:string_matching]).to eq("must be be a laugh")
16
+ expect(error[:kind_of]).to eq("must be a kind of Numeric")
17
+ end
18
+ end
19
+ end
data/sullivan.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sullivan/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sullivan"
8
+ spec.version = Sullivan::VERSION
9
+ spec.authors = ["Peter Jaros"]
10
+ spec.email = ["peter.a.jaros@gmail.com"]
11
+ spec.summary = %q{A simple, composable way to validate the structure of data.}
12
+ spec.description = %q{A simple, composable way to validate the structure of data. "Form ever follows function. This is the law." — Louis Henry Sullivan}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~> 3.1.5"
24
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sullivan
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Peter Jaros
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.1.5
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.1.5
55
+ description: A simple, composable way to validate the structure of data. "Form ever
56
+ follows function. This is the law." — Louis Henry Sullivan
57
+ email:
58
+ - peter.a.jaros@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - lib/sullivan.rb
69
+ - lib/sullivan/validations/hash.rb
70
+ - lib/sullivan/validations/kind_of.rb
71
+ - lib/sullivan/validations/many.rb
72
+ - lib/sullivan/validations/optional.rb
73
+ - lib/sullivan/validations/string_matching.rb
74
+ - lib/sullivan/version.rb
75
+ - spec/sullivan/validations/hash_spec.rb
76
+ - spec/sullivan/validations/kind_of_spec.rb
77
+ - spec/sullivan/validations/many_spec.rb
78
+ - spec/sullivan/validations/optional_spec.rb
79
+ - spec/sullivan/validations/string_matching_spec.rb
80
+ - spec/sullivan_spec.rb
81
+ - sullivan.gemspec
82
+ homepage: ''
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.2.0
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: A simple, composable way to validate the structure of data.
106
+ test_files:
107
+ - spec/sullivan/validations/hash_spec.rb
108
+ - spec/sullivan/validations/kind_of_spec.rb
109
+ - spec/sullivan/validations/many_spec.rb
110
+ - spec/sullivan/validations/optional_spec.rb
111
+ - spec/sullivan/validations/string_matching_spec.rb
112
+ - spec/sullivan_spec.rb
113
+ has_rdoc: