definition 0.1.0.rc1

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
+ SHA256:
3
+ metadata.gz: 5d4178b9d52b38f362ceeefc4e4b593c08bc1db136f39f43791c146890eebeec
4
+ data.tar.gz: 35a2df7826b4d9e77fa425711fcbff1ba1024438b355240a5aa5ee79a535e2d5
5
+ SHA512:
6
+ metadata.gz: 1f249e44ce43c29716f6943a8373399797d928f1796d42f6ead9f13261595e297cf4dc37418ef99d53717f6c57a5988ae78c01bab3c5cd91e227b3e150b7efae
7
+ data.tar.gz: 413f8ccf960d01faad85221108feda1b3c7ac41a0766c76d36872e58484f65d2c043a1f71d6fdde2de05814908b1325caeb0e2953f46281f625fe26eefc7f3db
data/.approvals ADDED
File without changes
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /bin/
11
+ *.swp
12
+ spec/examples.txt
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format p
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,77 @@
1
+ require:
2
+ - rubocop-rspec
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 2.3
6
+
7
+ Metrics/LineLength:
8
+ Max: 119
9
+ Exclude:
10
+ - Guardfile
11
+ - Gemfile
12
+ IgnoreCopDirectives: true
13
+
14
+ Metrics/BlockLength:
15
+ Exclude:
16
+ - '*.gemspec'
17
+ - '**/*.rake'
18
+ - 'spec/**/*'
19
+
20
+ Metrics/MethodLength:
21
+ Max: 11
22
+
23
+
24
+ Style/Documentation:
25
+ Enabled: false
26
+
27
+ Style/AlignHash:
28
+ EnforcedColonStyle: table
29
+ EnforcedHashRocketStyle: table
30
+
31
+ Layout/AlignHash:
32
+ EnforcedColonStyle: table
33
+ EnforcedHashRocketStyle: table
34
+
35
+ Style/HashSyntax:
36
+ EnforcedStyle: ruby19_no_mixed_keys
37
+
38
+ Style/MultilineOperationIndentation:
39
+ Description: Checks indentation of binary operations that span more than one line.
40
+ EnforcedStyle: indented
41
+
42
+ Layout/MultilineOperationIndentation:
43
+ Description: Checks indentation of binary operations that span more than one line.
44
+ EnforcedStyle: indented
45
+
46
+ Style/RaiseArgs:
47
+ EnforcedStyle: compact
48
+
49
+ Style/SignalException:
50
+ EnforcedStyle: only_raise
51
+
52
+ Style/StringLiterals:
53
+ EnforcedStyle: double_quotes
54
+
55
+
56
+
57
+ RSpec/DescribeClass:
58
+ Exclude:
59
+ - spec/integration/**/*
60
+
61
+ RSpec/MessageSpies:
62
+ Enabled: false
63
+
64
+ RSpec/ExampleLength:
65
+ Enabled: false
66
+
67
+ RSpec/NamedSubject:
68
+ Enabled: false
69
+
70
+ RSpec/NestedGroups:
71
+ Enabled: false
72
+
73
+ RSpec/MultipleExpectations:
74
+ Enabled: false
75
+
76
+ RSpec/MessageExpectation:
77
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ rvm:
3
+ - jruby-9.1.17.0 # ruby 2.3
4
+ - jruby-9.2.0.0 # ruby 2.5
5
+ - jruby-head
6
+ - 2.3.0
7
+ - 2.4.0
8
+ - 2.5.0
9
+ - ruby-head
10
+ jobs:
11
+ include:
12
+ - stage: linting
13
+ rvm: ruby-head
14
+ script: bundle exec rake rubocop
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in definition.gemspec
6
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A sample Guardfile
4
+ # More info at https://github.com/guard/guard#readme
5
+
6
+ ## Uncomment and set this to only include directories you want to watch
7
+ directories(%w[lib spec])
8
+ .select { |d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist") }
9
+
10
+ ## Note: if you are using the `directories` clause above and you are not
11
+ ## watching the project directory ('.'), then you will want to move
12
+ ## the Guardfile to a watched dir and symlink it back, e.g.
13
+ #
14
+ # $ mkdir config
15
+ # $ mv Guardfile config/
16
+ # $ ln -s config/Guardfile .
17
+ #
18
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
+
20
+ # Note: The cmd option is now required due to the increasing number of ways
21
+ # rspec may be run, below are examples of the most common uses.
22
+ # * bundler: 'bundle exec rspec'
23
+ # * bundler binstubs: 'bin/rspec'
24
+ # * spring: 'bin/rspec' (This will use spring if running and you have
25
+ # installed the spring binstubs per the docs)
26
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
27
+ # * 'just' rspec: 'rspec'
28
+
29
+ guard :rspec, cmd: "bundle exec rspec" do # rubocop:disable Metrics/BlockLength
30
+ require "guard/rspec/dsl"
31
+ dsl = Guard::RSpec::Dsl.new(self)
32
+
33
+ # Feel free to open issues for suggestions and improvements
34
+
35
+ # RSpec files
36
+ rspec = dsl.rspec
37
+ watch(rspec.spec_helper) { rspec.spec_dir }
38
+ watch(rspec.spec_support) { rspec.spec_dir }
39
+ watch(rspec.spec_files)
40
+
41
+ # Ruby files
42
+ ruby = dsl.ruby
43
+ dsl.watch_spec_files_for(ruby.lib_files)
44
+
45
+ # Rails files
46
+ rails = dsl.rails(view_extensions: %w[erb haml slim])
47
+ dsl.watch_spec_files_for(rails.app_files)
48
+ dsl.watch_spec_files_for(rails.views)
49
+
50
+ watch(rails.controllers) do |m|
51
+ [
52
+ rspec.spec.call("routing/#{m[1]}_routing"),
53
+ rspec.spec.call("controllers/#{m[1]}_controller"),
54
+ rspec.spec.call("acceptance/#{m[1]}")
55
+ ]
56
+ end
57
+
58
+ # Rails config changes
59
+ watch(rails.spec_helper) { rspec.spec_dir }
60
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
61
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
62
+
63
+ # Capybara features specs
64
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
65
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
66
+
67
+ # Turnip features and steps
68
+ watch(%r{^spec/acceptance/(.+)\.feature$})
69
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
70
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
71
+ end
72
+ end
data/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # Definition
2
+
3
+ [![Build Status](https://travis-ci.org/Goltergaul/definition.svg?branch=master)][travis]
4
+ [![Gem Version](https://badge.fury.io/rb/definition.svg)][rubygems]
5
+
6
+ Simple and composable validation and coercion of data structures
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'definition'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install definition
23
+
24
+ ## Usage
25
+
26
+ Definitions can be used to validate data structures like for example Hashes:
27
+
28
+ ```ruby
29
+ schema = Definition.Keys do
30
+ required :first_name, Definition.Type(String)
31
+ required :last_name, Definition.Type(String)
32
+ optional :birthday, Definition.Type(Date)
33
+ end
34
+
35
+ conform_result = schema.conform({first_name: "John", last_name: "Doe", birthday: Date.today})
36
+ conform_result.passed? # => true
37
+
38
+ conform_result = schema.conform({first_name: "John", last_name: "Doe", birthday: "2018/02/09"})
39
+ conform_result.passed? # => false
40
+ conform_result.error_message # => hash fails validation for key birthday: { Is of type String instead of Date }
41
+ ```
42
+
43
+ But it can also transform those data structures at the same time. The following
44
+ example shows how a Unix timestamp in milliseconds can be transformed to a Time
45
+ object while validating:
46
+
47
+ ```ruby
48
+ milliseconds_time_definition = Definition.Lambda(:milliseconds_time) do |value|
49
+ conform_with(Time.at(value.to_r / 1000).utc) if value.is_a?(Integer)
50
+ end
51
+
52
+ schema = Definition.Keys do
53
+ required :title, Definition.Type(String)
54
+ required :body, Definition.Type(String)
55
+ optional :publication_date, milliseconds_time_definition
56
+ end
57
+
58
+ conform_result = schema.conform({title: "My first blog post", body: "Shortest one ever!", publication_date: 1546170180339})
59
+ conform_result.passed? # => true
60
+ conform_result.value # => {title: "My first blog post", body: "Shortest one ever!", publication_date: 2018-12-30 11:43:00 UTC}
61
+ ```
62
+
63
+ Because definitions do not only validate input but also transform input, we use
64
+ the term `conform` which stands for validation and coercion.
65
+
66
+ ### Conforming Hashes
67
+
68
+ Hashes can be conformed by using the `Keys` definition. It allows you to configure
69
+ required and optional attributes. The first argument of `required` and `optional`
70
+ takes either Symbols or Strings. If you use a Symbol, then the validated Hash
71
+ needs to have a Symbol key with that name, otherwise a string key.
72
+
73
+ The key definition will also fail if the input value contains extra keys.
74
+
75
+ ```ruby
76
+ Definition.Keys do
77
+ required :title, Definition.Type(String)
78
+ optional :publication_date, Definition.Type(Date)
79
+ end
80
+ ```
81
+
82
+ ### Validating types
83
+
84
+ This will validate that the value is of the specified type.
85
+
86
+ ```ruby
87
+ Definition.Type(String)
88
+ Definition.Type(Float)
89
+ Definition.Type(MyClass)
90
+
91
+ Definition.Type(MyClass).conform(0.1).passed? # => false
92
+ Definition.Type(MyClass).conform(MyClass.new).passed? # => true
93
+ ```
94
+
95
+ ### Conforming types
96
+
97
+ This will validate that the value is of the specified type. But if its not it will
98
+ try to coerce it into that type. This Definition works only with primitive types.
99
+
100
+ ```ruby
101
+ Definition.CoercibleType(String) # Uses String() to coerce values
102
+ Definition.CoercibleType(Float) # Uses Float() to coerce values
103
+
104
+ Definition.CoercibleType(Float).conform("0.1").passed? # => true
105
+ Definition.CoercibleType(Float).conform("0.1").value # => 0.1
106
+ ```
107
+
108
+ ### Combining multiple definitions with "And"
109
+
110
+ ```ruby
111
+ Definition.And(definition1, definition2)
112
+ ```
113
+
114
+ This definition will only conform if all definitions conform. The definitions will
115
+ be processed from left to right and the output of the previous will be the input
116
+ of the next.
117
+
118
+ ### Combining multiple definitions with "Or"
119
+
120
+ ```ruby
121
+ Definition.Or(definition1, definition2)
122
+ ```
123
+
124
+ This definition will conform if at least one definition conforms. The definitions will
125
+ be processed from left to right and stop as soon as a definition conforms. The output
126
+ of that definition will be the output of the Or definition.
127
+
128
+ ### Conforming array values with "Each"
129
+
130
+ ```ruby
131
+ Definition.Each(item_definition)
132
+
133
+ Definition.Each(Definition.Type(Integer)).conform([1,2,3,"4"]).error_message
134
+ # => Not all items conform with each: { Item "4" did not conform to each: { Is of type String instead of Integer } }
135
+ ```
136
+
137
+ This definition will only conform if all elements of the value conform to the
138
+ `item_definition`.
139
+
140
+ ### Conforming with custom lambda functions
141
+
142
+ ```ruby
143
+ Definition.Lambda(:password) do |value|
144
+ matches = Regexp.new(/^
145
+ (?=.*[a-z]) # should contain at least one lower case letter
146
+ (?=.*[A-Z]) # should contain at least one upper case letter
147
+ (?=.*\d) # should contain at least one digit
148
+ .{6,50} # should be between 6 and 50 characters long
149
+ $/x).match(value.to_s)
150
+ conform_with(value) if matches
151
+ end
152
+ ```
153
+
154
+ This definition can be used to build any custom validation or coercion you want.
155
+ The example above makes sure that a password conforms with a set of rules.
156
+
157
+ The block gets the input value as argument and you can do any transformation or
158
+ validation on it that you want. If you determine that the value is valid, then
159
+ you must call `conform_with` and pass it the value you want to return. This can
160
+ either be the original value or any transformed version of it. By not calling
161
+ `conform_with` you tell the definition to fail for the current input value.
162
+
163
+ The first argument of `Definition.Lambda` is a name you can give this definition.
164
+ It will only be used in the error message to make it more readable.
165
+
166
+ ### Composing Definitions
167
+
168
+ Definitions are reusable and can be easily composed:
169
+
170
+ ```ruby
171
+ country_code_definition = Definition.Lambda(:iso_county_code) do |value|
172
+ if iso_code = IsoCountryCodes.find(value)
173
+ conform_with(iso_code.alpha2)
174
+ end
175
+ end
176
+
177
+ address_definition = Definition.Keys do
178
+ required :street, Definition.Type(String)
179
+ required :postal_code, Definition.Type(String)
180
+ required :country_code, country_code_definition
181
+ end
182
+
183
+ order = Definition.Keys do
184
+ required :user, user_definition
185
+ required :invoice_address, address_definition
186
+ required :shipping_address, address_definition
187
+ end
188
+ ```
189
+
190
+ ### Examples
191
+
192
+ Check out the [integration specs](./spec/integration) for more usage examples.
193
+
194
+ ## Development
195
+
196
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
197
+
198
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
199
+
200
+ ## Contributing
201
+
202
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Goltergaul/definition.
203
+
204
+ [travis]: https://travis-ci.org/Goltergaul/definition
205
+ [rubygems]: https://rubygems.org/gems/definition
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
9
+
10
+ task :init do
11
+ Rake::Task["rubocop:install"].execute
12
+ end
13
+
14
+ require "rubocop/rake_task"
15
+ RuboCop::RakeTask.new
16
+ namespace :rubocop do
17
+ desc "Install Rubocop as pre-commit hook"
18
+ task :install do
19
+ require "rubocop_runner"
20
+ RubocopRunner.install
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "definition/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "definition"
9
+ spec.version = Definition::VERSION
10
+ spec.authors = ["Dominik Goltermann"]
11
+ spec.email = ["dominik@goltermann.cc"]
12
+
13
+ spec.summary = "Simple and composable validation and coercion of data structures inspired by clojure specs"
14
+ spec.homepage = "https://github.com/Goltergaul/definition"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "approvals", "~> 0.0"
25
+ spec.add_development_dependency "awesome_print"
26
+ spec.add_development_dependency "benchmark-ips"
27
+ spec.add_development_dependency "bundler", "~> 1.13"
28
+ spec.add_development_dependency "fuubar"
29
+ spec.add_development_dependency "guard"
30
+ spec.add_development_dependency "guard-rspec"
31
+ spec.add_development_dependency "pry"
32
+ spec.add_development_dependency "rake", "~> 10.0"
33
+ spec.add_development_dependency "rspec", "~> 3.0"
34
+ spec.add_development_dependency "rspec-its", "~> 1.2"
35
+ spec.add_development_dependency "rubocop"
36
+ spec.add_development_dependency "rubocop-rspec"
37
+ spec.add_development_dependency "rubocop_runner"
38
+ spec.add_development_dependency "timecop"
39
+ end