sullivan 0.0.1

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 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: