definition 0.1.0.rc1

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