pax_global_header00006660000000000000000000000064140414560360014515gustar00rootroot0000000000000052 comment=19707f6cbad656108077587cf971879851058e44 dry-logic-1.2.0/000077500000000000000000000000001404145603600134065ustar00rootroot00000000000000dry-logic-1.2.0/.devtools/000077500000000000000000000000001404145603600153235ustar00rootroot00000000000000dry-logic-1.2.0/.devtools/templates/000077500000000000000000000000001404145603600173215ustar00rootroot00000000000000dry-logic-1.2.0/.devtools/templates/changelog.erb000066400000000000000000000013711404145603600217440ustar00rootroot00000000000000 <% releases.each_with_index do |r, idx| %> ## <%= r.version %> <%= r.date %> <% if r.summary %> <%= r.summary %> <% end %> <% if r.added? %> ### Added <% r.added.each do |log| %> - <%= log %> <% end %> <% end %> <% if r.fixed? %> ### Fixed <% r.fixed.each do |log| %> - <%= log %> <% end %> <% end %> <% if r.changed? %> ### Changed <% r.changed.each do |log| %> - <%= log %> <% end %> <% end %> <% curr_ver = r.date ? "v#{r.version}" : 'master' %> <% prev_rel = releases[idx + 1] %> <% if prev_rel %> <% ver_range = "v#{prev_rel.version}...#{curr_ver}" %> [Compare <%=ver_range%>](https://github.com/dry-rb/<%= project.name %>/compare/<%=ver_range%>) <% end %> <% end %> dry-logic-1.2.0/.devtools/templates/release.erb000066400000000000000000000013171404145603600214350ustar00rootroot00000000000000 <% if latest_release.summary %> <%= latest_release.summary %> <% end %> <% if latest_release.added? %> ### Added <% latest_release.added.each do |log| %> - <%= log %> <% end %> <% end %> <% if latest_release.fixed? %> ### Fixed <% latest_release.fixed.each do |log| %> - <%= log %> <% end %> <% end %> <% if latest_release.changed? %> ### Changed <% latest_release.changed.each do |log| %> - <%= log %> <% end %> <% end %> <% if previous_release %> <% ver_range = "v#{previous_release.version}...v#{latest_release.version}" %> [Compare <%=ver_range%>](https://github.com/dry-rb/<%= project.name %>/compare/<%=ver_range%>) <% end %> dry-logic-1.2.0/.github/000077500000000000000000000000001404145603600147465ustar00rootroot00000000000000dry-logic-1.2.0/.github/FUNDING.yml000066400000000000000000000000441404145603600165610ustar00rootroot00000000000000github: [jodosha, solnic, timriley] dry-logic-1.2.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001404145603600171315ustar00rootroot00000000000000dry-logic-1.2.0/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000007521404145603600215450ustar00rootroot00000000000000--- name: "\U0001F41B Bug report" about: See CONTRIBUTING.md for more information title: '' labels: bug, help wanted assignees: '' --- ## Describe the bug A clear and concise description of what the bug is. ## To Reproduce Provide detailed steps to reproduce, **an executable script would be best**. ## Expected behavior A clear and concise description of what you expected to happen. ## My environment - Affects my production application: **YES/NO** - Ruby version: ... - OS: ... dry-logic-1.2.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002361404145603600211220ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Community Support url: https://discourse.dry-rb.org about: Please ask and answer questions here. dry-logic-1.2.0/.github/SUPPORT.md000066400000000000000000000004441404145603600164460ustar00rootroot00000000000000## Support If you need help with any of the dry-rb libraries, feel free to ask questions on our [discussion forum](https://discourse.dry-rb.org/). This is the best place to seek help. Make sure to search for a potential solution in past threads before posting your question. Thanks! :heart: dry-logic-1.2.0/.github/workflows/000077500000000000000000000000001404145603600170035ustar00rootroot00000000000000dry-logic-1.2.0/.github/workflows/ci.yml000066400000000000000000000047011404145603600201230ustar00rootroot00000000000000# this file is managed by dry-rb/devtools project name: ci "on": push: paths: - ".github/workflows/ci.yml" - "lib/**" - "*.gemspec" - "spec/**" - "Rakefile" - "Gemfile" - "Gemfile.devtools" - ".rubocop.yml" - "project.yml" pull_request: branches: - master create: jobs: tests: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - "3.0" - "2.7" - "2.6" - "2.5" - "jruby" include: - ruby: "3.0" coverage: "true" env: COVERAGE: ${{matrix.coverage}} COVERAGE_TOKEN: ${{secrets.CODACY_PROJECT_TOKEN}} steps: - name: Checkout uses: actions/checkout@v1 - name: Install package dependencies run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS" - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} - name: Install latest bundler run: | gem install bundler --no-document bundle config set without 'tools benchmarks docs' - name: Bundle install run: bundle install --jobs 4 --retry 3 - name: Run all tests run: bundle exec rake - name: Run codacy-coverage-reporter uses: codacy/codacy-coverage-reporter-action@master if: env.COVERAGE == 'true' && env.COVERAGE_TOKEN != '' with: project-token: ${{secrets.CODACY_PROJECT_TOKEN}} coverage-reports: coverage/coverage.xml release: runs-on: ubuntu-latest if: contains(github.ref, 'tags') && github.event_name == 'create' needs: tests env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{secrets.GH_PAT}} steps: - uses: actions/checkout@v1 - name: Install package dependencies run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS" - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - name: Install dependencies run: gem install ossy --no-document - name: Trigger release workflow run: | tag=$(echo $GITHUB_REF | cut -d / -f 3) ossy gh w dry-rb/devtools release --payload "{\"tag\":\"$tag\",\"sha\":\"${{github.sha}}\",\"tag_creator\":\"$GITHUB_ACTOR\",\"repo\":\"$GITHUB_REPOSITORY\",\"repo_name\":\"${{github.event.repository.name}}\"}" dry-logic-1.2.0/.github/workflows/docsite.yml000066400000000000000000000032651404145603600211660ustar00rootroot00000000000000# this file is managed by dry-rb/devtools project name: docsite on: push: paths: - docsite/** - .github/workflows/docsite.yml branches: - master - release-** tags: jobs: update-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - run: | git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* - name: Set up Ruby uses: actions/setup-ruby@v1 with: ruby-version: "2.6.x" - name: Set up git user run: | git config --local user.email "dry-bot@dry-rb.org" git config --local user.name "dry-bot" - name: Install dependencies run: gem install ossy --no-document - name: Update release branches run: | branches=`git log --format=%B -n 1 $GITHUB_SHA | grep "docsite:release-" || echo "nothing"` if [[ ! $branches -eq "nothing" ]]; then for b in $branches do name=`echo $b | ruby -e 'puts gets[/:(.+)/, 1].gsub(/\s+/, "")'` echo "merging $GITHUB_SHA to $name" git checkout -b $name --track origin/$name echo `git log -n 1` git cherry-pick $GITHUB_SHA -m 1 done git push --all "https://dry-bot:${{secrets.GH_PAT}}@github.com/$GITHUB_REPOSITORY.git" git checkout master else echo "no need to update branches" fi - name: Trigger dry-rb.org deploy env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{secrets.GH_PAT}} run: ossy github workflow dry-rb/dry-rb.org ci dry-logic-1.2.0/.github/workflows/sync_configs.yml000066400000000000000000000033421404145603600222140ustar00rootroot00000000000000# this file is managed by dry-rb/devtools project name: sync on: repository_dispatch: push: branches: - "master" jobs: main: runs-on: ubuntu-latest if: (github.event_name == 'repository_dispatch' && github.event.action == 'sync_configs') || github.event_name != 'repository_dispatch' env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{ secrets.GH_PAT }} steps: - name: Checkout ${{github.repository}} uses: actions/checkout@v1 - name: Checkout devtools uses: actions/checkout@v2 with: repository: dry-rb/devtools path: tmp/devtools - name: Setup git user run: | git config --local user.email "dry-bot@dry-rb.org" git config --local user.name "dry-bot" - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - name: Install dependencies run: gem install ossy --no-document - name: Compile file templates run: tmp/devtools/bin/compile-templates - name: Update workflow files from devtools run: tmp/devtools/bin/sync-workflows - name: Update configuration files from devtools run: tmp/devtools/bin/sync-shared-files - name: Update changelog.yml from commit run: tmp/devtools/bin/update-changelog-from-commit $GITHUB_SHA - name: Compile CHANGELOG.md run: tmp/devtools/bin/compile-changelog - name: Commit run: | git add -A git commit -m "[devtools] sync" || echo "nothing to commit" - name: Push changes run: | git pull --rebase origin master git push https://dry-bot:${{secrets.GH_PAT}}@github.com/${{github.repository}}.git HEAD:master dry-logic-1.2.0/.gitignore000066400000000000000000000001101404145603600153660ustar00rootroot00000000000000.DS_Store coverage /.bundle vendor/bundle tmp/ pkg/ .idea/ Gemfile.lock dry-logic-1.2.0/.rspec000066400000000000000000000000701404145603600145200ustar00rootroot00000000000000--color --require spec_helper --order random --warnings dry-logic-1.2.0/.rubocop.yml000066400000000000000000000047501404145603600156660ustar00rootroot00000000000000# this file is managed by dry-rb/devtools project AllCops: TargetRubyVersion: 2.7 Exclude: - spec/support/coverage.rb - spec/support/warnings.rb - spec/support/rspec_options.rb - Gemfile.devtools - "*.gemspec" Layout/SpaceAroundMethodCallOperator: Enabled: false Layout/SpaceInLambdaLiteral: Enabled: false Layout/MultilineMethodCallIndentation: Enabled: true EnforcedStyle: indented Layout/FirstArrayElementIndentation: EnforcedStyle: consistent Layout/SpaceInsideHashLiteralBraces: Enabled: true EnforcedStyle: no_space EnforcedStyleForEmptyBraces: no_space Layout/LineLength: Max: 100 Exclude: - "spec/**/*_spec.rb" Lint/BooleanSymbol: Enabled: false Lint/ConstantDefinitionInBlock: Exclude: - "spec/**/*_spec.rb" Lint/RaiseException: Enabled: false Lint/StructNewOverride: Enabled: false Lint/SuppressedException: Exclude: - "spec/spec_helper.rb" Naming/PredicateName: Enabled: false Naming/FileName: Exclude: - "lib/*-*.rb" Naming/MethodName: Enabled: false Naming/MemoizedInstanceVariableName: Enabled: false Metrics/MethodLength: Enabled: false Metrics/ClassLength: Enabled: false Metrics/BlockLength: Enabled: false Metrics/AbcSize: Max: 25 Metrics/CyclomaticComplexity: Enabled: true Max: 12 Style/ExponentialNotation: Enabled: false Style/HashEachMethods: Enabled: false Style/HashTransformKeys: Enabled: false Style/HashTransformValues: Enabled: false Style/AccessModifierDeclarations: Enabled: false Style/Alias: Enabled: true EnforcedStyle: prefer_alias_method Style/AsciiComments: Enabled: false Style/BlockDelimiters: Enabled: false Style/ClassAndModuleChildren: Exclude: - "spec/**/*_spec.rb" Style/ConditionalAssignment: Enabled: false Style/DateTime: Enabled: false Style/Documentation: Enabled: false Style/EachWithObject: Enabled: false Style/FormatString: Enabled: false Style/GuardClause: Enabled: false Style/IfUnlessModifier: Enabled: false Style/Lambda: Enabled: false Style/LambdaCall: Enabled: false Style/ParallelAssignment: Enabled: false Style/StabbyLambdaParentheses: Enabled: false Style/StringLiterals: Enabled: true EnforcedStyle: double_quotes ConsistentQuotesInMultiline: false Style/StringLiteralsInInterpolation: Enabled: true EnforcedStyle: double_quotes Style/SymbolArray: Exclude: - "spec/**/*_spec.rb" Style/TrailingUnderscoreVariable: Enabled: false Style/MultipleComparison: Enabled: false dry-logic-1.2.0/CHANGELOG.md000066400000000000000000000202041404145603600152150ustar00rootroot00000000000000 ## 1.2.0 2021-04-26 ### Added - Add predicate and operation builder DSL (@oleander) [Compare v1.1.1...v1.2.0](https://github.com/dry-rb/dry-logic/compare/v1.1.1...v1.2.0) ## 1.1.1 2021-04-14 ### Fixed - Fixed a crash under jruby caused by arg splatting in Binary operations (@flash-gordon) [Compare v1.1.0...v1.1.1](https://github.com/dry-rb/dry-logic/compare/v1.1.0...v1.1.1) ## 1.1.0 2020-12-26 ### Fixed - Nested `Check` operations no longer crash under MRI 3.0 (@oleander) ### Changed - Switched to equalizer from dry-core (@solnic) [Compare v1.0.8...v1.1.0](https://github.com/dry-rb/dry-logic/compare/v1.0.8...v1.1.0) ## 1.0.8 2020-09-28 ### Fixed - Better Ruby 3 support with fixed specialization for rules of negative arity (@flash-gordon) [Compare v1.0.7...v1.0.8](https://github.com/dry-rb/dry-logic/compare/v1.0.7...v1.0.8) ## 1.0.7 2020-08-13 ### Added - A new `uri?` predicate that you can use to verify `URI` strings, ie `uri?("https", "https://dry-rb.org")` (@nerburish) - New predicates: `uuid_v1?`, `uuid_v2?`, `uuid_v3?` and `uuid_v5?` (via #75) (@jamesbrauman) [Compare v1.0.6...v1.0.7](https://github.com/dry-rb/dry-logic/compare/v1.0.6...v1.0.7) ## 1.0.6 2020-02-10 ### Fixed - Made the regexp used by `uuid_v4?` more secure (@kml) [Compare v1.0.5...v1.0.6](https://github.com/dry-rb/dry-logic/compare/v1.0.5...v1.0.6) ## 1.0.5 2019-11-07 ### Fixed - Make `format?` tolerant to `nil` values. It already worked like that before, but starting Ruby 2.7 it would produce warnings. Now it won't. Don't rely on this behavior, it's only added to make tests pass in dry-schema. Use explicit type checks instead (@flash-gordon) [Compare v1.0.4...v1.0.5](https://github.com/dry-rb/dry-logic/compare/v1.0.4...v1.0.5) ## 1.0.4 2019-11-06 ### Fixed - Fix keyword warnings (@flash-gordon) [Compare v1.0.3...v1.0.4](https://github.com/dry-rb/dry-logic/compare/v1.0.3...v1.0.4) ## 1.0.3 2019-08-01 ### Added - `bytesize?` predicate (@bmalinconico) - `min_bytesize?` predicate (@bmalinconico) - `max_bytesize? predicate (@bmalinconico) ### Changed - Min ruby version was set to `>= 2.4.0` (@flash-gordon) [Compare v1.0.2...v1.0.3](https://github.com/dry-rb/dry-logic/compare/v1.0.2...v1.0.3) ## 1.0.2 2019-06-14 Re-pushed 1.0.1 after dry-schema 1.2.0 release. [Compare v1.0.1...v1.0.2](https://github.com/dry-rb/dry-logic/compare/v1.0.1...v1.0.2) ## 1.0.1 2019-06-04 This release was removed from rubygems because it broke dry-schema. ### Added - `uuid_v4?` predicate (radar) - `respond_to?` predicate (waiting-for-dev) [Compare v1.0.0...v1.0.1](https://github.com/dry-rb/dry-logic/compare/v1.0.0...v1.0.1) ## 1.0.0 2019-04-23 ### Changed - Version bump to `1.0.0` (flash-gordon) [Compare v0.6.1...v1.0.0](https://github.com/dry-rb/dry-logic/compare/v0.6.1...v1.0.0) ## 0.6.1 2019-04-18 ### Fixed - Fix a regression in dry-validation 0.x for argument-less predicates (flash-gordon) [Compare v0.6.0...v0.6.1](https://github.com/dry-rb/dry-logic/compare/v0.6.0...v0.6.1) ## 0.6.0 2019-04-04 ### Added - Generating hints can be disabled by building `Operations::And` with `hints: false` option set (solnic) ### Changed - `Rule` construction has been optimized so that currying and application is multiple-times faster (flash-gordon) [Compare v0.5.0...v0.6.0](https://github.com/dry-rb/dry-logic/compare/v0.5.0...v0.6.0) ## 0.5.0 2019-01-29 ### Added - `:nil?` predicate (`none?` is now an alias) (solnic) ### Fixed - `Operation::Key#ast` will now return a correct AST with non-Undefined inputs (solnic) [Compare v0.4.2...v0.5.0](https://github.com/dry-rb/dry-logic/compare/v0.4.2...v0.5.0) ## 0.4.2 2017-09-15 ### Added - New `:case?` predicate matches a value against the given object with `#===` (flash-gordon) - New `:is?` predicate checks objects identity (using `#equal?`) (flash-gordon) ### Fixed - A bug with using custom predicates within a standalone module in `dry-validation` (flash-gordon) [Compare v0.4.1...v0.4.2](https://github.com/dry-rb/dry-logic/compare/v0.4.1...v0.4.2) ## 0.4.1 2017-01-23 ### Fixed - Warnings on MRI 2.4.0 are gone (jtippett) ### Changed - Predicates simply reuse other predicate methods instead of referring to them via `#[]` (georgemillo) [Compare v0.4.0...v0.4.1](https://github.com/dry-rb/dry-logic/compare/v0.4.0...v0.4.1) ## 0.4.0 2016-09-21 This is a partial rewrite focused on internal clean up and major performance improvements. This is also the beginning of the work to make this library first-class rather than "just" a rule backend for dry-validation and dry-types. ### Added - `Rule#[]` which applies a rule and always returns `true` or `false` (solnic) - `Rule#bind` which returns a rule with its predicate bound to a given object (solnic) - `Rule#eval_args` which evaluates unbound-methods-args in the context of a given object (solnic) - `Logic.Rule` builder function (solnic) - Nice `#inspect` on rules and operation objects (solnic) ### Changed - [BRAEKING] New result API (solnic) - [BREAKING] `Predicate` is now `Rule::Predicate` (solnic) - [BREAKING] `Rule::Conjunction` is now `Operation::And` (solnic) - [BREAKING] `Rule::Disjunction` is now `Operation::Or` (solnic) - [BREAKING] `Rule::ExlusiveDisjunction` is now `Operation::Xor` (solnic) - [BREAKING] `Rule::Implication` is now `Operation::Implication` (solnic) - [BREAKING] `Rule::Set` is now `Operation::Set` (solnic) - [BREAKING] `Rule::Each` is now `Operation::Each` (solnic) - [BREAKING] `Rule.new` accepts a predicate function as its first arg now (solnic) - [BREAKING] `Rule#name` is now `Rule#id` (solnic) - `Rule#parameters` is public now (solnic) [Compare v0.3.0...v0.4.0](https://github.com/dry-rb/dry-logic/compare/v0.3.0...v0.4.0) ## 0.3.0 2016-07-01 ### Added - `:type?` predicate imported from dry-types (solnic) - `Rule#curry` interface (solnic) ### Changed - Predicates AST now includes information about args (names & possible values) (fran-worley + solnic) - Predicates raise errors when they are called with invalid arity (fran-worley + solnic) - Rules no longer evaluate input twice when building result objects (solnic) [Compare v0.2.3...v0.3.0](https://github.com/dry-rb/dry-logic/compare/v0.2.3...v0.3.0) ## 0.2.3 2016-05-11 ### Added - `not_eql?`, `includes?`, `excludes?` predicates (fran-worley) ### Changed - Renamed `inclusion?` to `included_in?` and deprecated `inclusion?` (fran-worley) - Renamed `exclusion?` to `excluded_from?` and deprecated `exclusion?` (fran-worley) [Compare v0.2.2...v0.2.3](https://github.com/dry-rb/dry-logic/compare/v0.2.2...v0.2.3) ## 0.2.2 2016-03-30 ### Added - `number?`, `odd?`, `even?` predicates (fran-worley) [Compare v0.2.1...v0.2.2](https://github.com/dry-rb/dry-logic/compare/v0.2.1...v0.2.2) ## 0.2.1 2016-03-20 ### Fixed - Result AST for `Rule::Each` correctly maps elements with eql inputs (solnic) [Compare v0.2.0...v0.2.1](https://github.com/dry-rb/dry-logic/compare/v0.2.0...v0.2.1) ## 0.2.0 2016-03-11 ### Changed - Entire AST has been redefined (solnic) [Compare v0.1.4...v0.2.0](https://github.com/dry-rb/dry-logic/compare/v0.1.4...v0.2.0) ## 0.1.4 2016-01-27 ### Added - Support for hash-names in `Check` and `Result` which can properly resolve input from nested results (solnic) [Compare v0.1.3...v0.1.4](https://github.com/dry-rb/dry-logic/compare/v0.1.3...v0.1.4) ## 0.1.3 2016-01-27 ### Added - Support for resolving input from `Rule::Result` (solnic) ### Changed - `Check` and `Result` carry original input(s) (solnic) [Compare v0.1.2...v0.1.3](https://github.com/dry-rb/dry-logic/compare/v0.1.2...v0.1.3) ## 0.1.2 2016-01-19 ### Fixed - `xor` returns wrapped results when used against another result-rule (solnic) [Compare v0.1.1...v0.1.2](https://github.com/dry-rb/dry-logic/compare/v0.1.1...v0.1.2) ## 0.1.1 2016-01-18 ### Added - `Rule::Attr` which can be applied to a data object with attr readers (SunnyMagadan) - `Rule::Result` which can be applied to a result object (solnic) - `true?` and `false?` predicates (solnic) [Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-logic/compare/v0.1.0...v0.1.1) ## 0.1.0 2016-01-11 Code extracted from dry-validation 0.4.1 dry-logic-1.2.0/CODEOWNERS000066400000000000000000000000121404145603600147720ustar00rootroot00000000000000* @solnic dry-logic-1.2.0/CODE_OF_CONDUCT.md000066400000000000000000000026611404145603600162120ustar00rootroot00000000000000# Contributor Code of Conduct As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.4.0, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct) dry-logic-1.2.0/CONTRIBUTING.md000066400000000000000000000026461404145603600156470ustar00rootroot00000000000000# Issue Guidelines ## Reporting bugs If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated. ## Reporting feature requests Report a feature request **only after discussing it first on [discourse.dry-rb.org](https://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature. ## Reporting questions, support requests, ideas, concerns etc. **PLEASE DON'T** - use [discourse.dry-rb.org](http://discourse.dry-rb.org) instead. # Pull Request Guidelines A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc. Other requirements: 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue. 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style. 3) Add API documentation if it's a new feature 4) Update API documentation if it changes an existing feature 5) Bonus points for sending a PR which updates user documentation in the `docsite` directory # Asking for help If these guidelines aren't helpful, and you're stuck, please post a message on [discourse.dry-rb.org](https://discourse.dry-rb.org). dry-logic-1.2.0/Gemfile000066400000000000000000000003431404145603600147010ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" eval_gemfile "Gemfile.devtools" gemspec group :tools do gem "pry-byebug", platform: :mri gem "benchmark-ips", platform: :mri gem "hotch", platform: :mri end dry-logic-1.2.0/Gemfile.devtools000066400000000000000000000007131404145603600165400ustar00rootroot00000000000000# frozen_string_literal: true # this file is managed by dry-rb/devtools project git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } group :test do gem "simplecov", require: false, platforms: :ruby gem "simplecov-cobertura", require: false, platforms: :ruby gem "rexml", require: false gem "warning" if RUBY_VERSION >= "2.4.0" end group :tools do # this is the same version that we use on codacy gem "rubocop", "~> 1.12" end dry-logic-1.2.0/LICENSE000066400000000000000000000020731404145603600144150ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-2021 dry-rb team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dry-logic-1.2.0/README.md000066400000000000000000000021231404145603600146630ustar00rootroot00000000000000[gem]: https://rubygems.org/gems/dry-logic [actions]: https://github.com/dry-rb/dry-logic/actions [codacy]: https://www.codacy.com/gh/dry-rb/dry-logic [chat]: https://dry-rb.zulipchat.com [inchpages]: http://inch-ci.org/github/dry-rb/dry-logic # dry-logic [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat] [![Gem Version](https://badge.fury.io/rb/dry-logic.svg)][gem] [![CI Status](https://github.com/dry-rb/dry-logic/workflows/ci/badge.svg)][actions] [![Codacy Badge](https://api.codacy.com/project/badge/Grade/3ac6ea12c2dd42beb36dc3abe63d9606)][codacy] [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/3ac6ea12c2dd42beb36dc3abe63d9606)][codacy] [![Inline docs](http://inch-ci.org/github/dry-rb/dry-logic.svg?branch=master)][inchpages] ## Links * [User documentation](http://dry-rb.org/gems/dry-logic) * [API documentation](http://rubydoc.info/gems/dry-logic) ## Supported Ruby versions This library officially supports the following Ruby versions: * MRI >= `2.5` * jruby >= `9.2` ## License See `LICENSE` file. dry-logic-1.2.0/Rakefile000066400000000000000000000004401404145603600150510ustar00rootroot00000000000000#!/usr/bin/env rake # frozen_string_literal: true require "bundler/gem_tasks" $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "lib")) require "rspec/core" require "rspec/core/rake_task" task default: :spec desc "Run all specs in spec directory" RSpec::Core::RakeTask.new(:spec) dry-logic-1.2.0/benchmarks/000077500000000000000000000000001404145603600155235ustar00rootroot00000000000000dry-logic-1.2.0/benchmarks/builder.rb000066400000000000000000000013351404145603600175000ustar00rootroot00000000000000# frozen_string_literal: true require "benchmark/ips" require "dry/logic" require "dry/logic/builder" def regular user_present = Dry::Logic::Rule::Predicate.new(Dry::Logic::Predicates[:key?]).curry(:user) min_age = Dry::Logic::Rule::Predicate.new(Dry::Logic::Predicates[:gt?]).curry(18) has_min_age = Dry::Logic::Operations::Key.new(min_age, name: %i[user age]) user_present & has_min_age end def builder Dry::Logic::Builder.call do key?(:user) & key(name: %i[user age]) { gt?(18) } end end Benchmark.ips do |x| x.report("builder") do builder.(user: {age: 19}) builder.(user: {age: 18}) end x.report("regular") do regular.(user: {age: 18}) regular.(user: {age: 19}) end x.compare! end dry-logic-1.2.0/benchmarks/rule_application.rb000066400000000000000000000017441404145603600214100ustar00rootroot00000000000000# frozen_string_literal: true require_relative "setup" unless Dry::Logic::Rule.respond_to?(:build) Dry::Logic::Rule.singleton_class.alias_method(:build, :new) end predicates = Dry::Logic::Predicates type_check = Dry::Logic::Rule.build(predicates[:type?]).curry(Integer) int_check = Dry::Logic::Rule.build(predicates[:int?]) key_check = Dry::Logic::Rule.build(predicates[:key?]).curry(:user) with_user = { user: {} } without_user = {} comparison = Dry::Logic::Rule.build(predicates[:gteq?]).curry(18) Benchmark.ips do |x| x.report("type check - success") { type_check.(0) } x.report("type check - failure") { type_check.("0") } x.report("int check - success") { int_check.(0) } x.report("int check - failure") { int_check.("0") } x.report("key check - success") { key_check.(with_user) } x.report("key check - failure") { key_check.(without_user) } x.report("comparison - success") { comparison.(20) } x.report("comparison - failure") { comparison.(17) } x.compare! end dry-logic-1.2.0/benchmarks/setup.rb000066400000000000000000000003231404145603600172060ustar00rootroot00000000000000# frozen_string_literal: true require "benchmark/ips" require "hotch" ENV["HOTCH_VIEWER"] ||= "open" require "dry/logic" require "dry/logic/predicates" def profile(&block) Hotch(filter: "Dry", &block) end dry-logic-1.2.0/bin/000077500000000000000000000000001404145603600141565ustar00rootroot00000000000000dry-logic-1.2.0/bin/.gitkeep000066400000000000000000000000001404145603600155750ustar00rootroot00000000000000dry-logic-1.2.0/bin/console000077500000000000000000000002531404145603600155460ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'bundler/setup' require 'dry/logic' require 'dry/logic/predicates' include Dry::Logic require 'irb' IRB.start dry-logic-1.2.0/changelog.yml000066400000000000000000000147241404145603600160700ustar00rootroot00000000000000--- - version: 1.2.0 summary: date: '2021-04-26' fixed: added: - Add predicate and operation builder DSL (@oleander) changed: - version: 1.1.1 summary: date: '2021-04-14' fixed: - Fixed a crash under jruby caused by arg splatting in Binary operations (@flash-gordon) - version: 1.1.0 summary: date: '2020-12-26' fixed: - Nested `Check` operations no longer crash under MRI 3.0 (@oleander) added: changed: - Switched to equalizer from dry-core (@solnic) - version: 1.0.8 date: '2020-09-28' fixed: - Better Ruby 3 support with fixed specialization for rules of negative arity (@flash-gordon) - version: 1.0.7 summary: date: '2020-08-13' fixed: added: - A new `uri?` predicate that you can use to verify `URI` strings, ie `uri?("https", "https://dry-rb.org")` (@nerburish) - 'New predicates: `uuid_v1?`, `uuid_v2?`, `uuid_v3?` and `uuid_v5?` (via #75) (@jamesbrauman)' - version: 1.0.6 summary: date: '2020-02-10' fixed: - Made the regexp used by `uuid_v4?` more secure (@kml) added: changed: - version: 1.0.5 date: '2019-11-07' fixed: - Make `format?` tolerant to `nil` values. It already worked like that before, but starting Ruby 2.7 it would produce warnings. Now it won't. Don't rely on this behavior, it's only added to make tests pass in dry-schema. Use explicit type checks instead (@flash-gordon) - version: 1.0.4 date: '2019-11-06' fixed: - Fix keyword warnings (@flash-gordon) - version: 1.0.3 date: '2019-08-01' added: - "`bytesize?` predicate (@bmalinconico)" - "`min_bytesize?` predicate (@bmalinconico)" - "`max_bytesize? predicate (@bmalinconico)" changed: - Min ruby version was set to `>= 2.4.0` (@flash-gordon) - version: 1.0.2 date: '2019-06-14' summary: Re-pushed 1.0.1 after dry-schema 1.2.0 release. - version: 1.0.1 date: '2019-06-04' added: - "`uuid_v4?` predicate (radar)" - "`respond_to?` predicate (waiting-for-dev)" summary: This release was removed from rubygems because it broke dry-schema. - version: 1.0.0 date: '2019-04-23' changed: - Version bump to `1.0.0` (flash-gordon) - version: 0.6.1 date: '2019-04-18' fixed: - Fix a regression in dry-validation 0.x for argument-less predicates (flash-gordon) - version: 0.6.0 date: '2019-04-04' added: - 'Generating hints can be disabled by building `Operations::And` with `hints: false` option set (solnic)' changed: - "`Rule` construction has been optimized so that currying and application is multiple-times faster (flash-gordon)" - version: 0.5.0 date: '2019-01-29' added: - "`:nil?` predicate (`none?` is now an alias) (solnic)" fixed: - "`Operation::Key#ast` will now return a correct AST with non-Undefined inputs (solnic)" - version: 0.4.2 date: '2017-09-15' added: - New `:case?` predicate matches a value against the given object with `#===` (flash-gordon) - New `:is?` predicate checks objects identity (using `#equal?`) (flash-gordon) fixed: - A bug with using custom predicates within a standalone module in `dry-validation` (flash-gordon) - version: 0.4.1 date: '2017-01-23' changed: - Predicates simply reuse other predicate methods instead of referring to them via `#[]` (georgemillo) fixed: - Warnings on MRI 2.4.0 are gone (jtippett) - version: 0.4.0 date: '2016-09-21' summary: This is a partial rewrite focused on internal clean up and major performance improvements. This is also the beginning of the work to make this library first-class rather than "just" a rule backend for dry-validation and dry-types. added: - "`Rule#[]` which applies a rule and always returns `true` or `false` (solnic)" - "`Rule#bind` which returns a rule with its predicate bound to a given object (solnic)" - "`Rule#eval_args` which evaluates unbound-methods-args in the context of a given object (solnic)" - "`Logic.Rule` builder function (solnic)" - Nice `#inspect` on rules and operation objects (solnic) changed: - "[BRAEKING] New result API (solnic)" - "[BREAKING] `Predicate` is now `Rule::Predicate` (solnic)" - "[BREAKING] `Rule::Conjunction` is now `Operation::And` (solnic)" - "[BREAKING] `Rule::Disjunction` is now `Operation::Or` (solnic)" - "[BREAKING] `Rule::ExlusiveDisjunction` is now `Operation::Xor` (solnic)" - "[BREAKING] `Rule::Implication` is now `Operation::Implication` (solnic)" - "[BREAKING] `Rule::Set` is now `Operation::Set` (solnic)" - "[BREAKING] `Rule::Each` is now `Operation::Each` (solnic)" - "[BREAKING] `Rule.new` accepts a predicate function as its first arg now (solnic)" - "[BREAKING] `Rule#name` is now `Rule#id` (solnic)" - "`Rule#parameters` is public now (solnic)" - version: 0.3.0 date: '2016-07-01' added: - "`:type?` predicate imported from dry-types (solnic)" - "`Rule#curry` interface (solnic)" changed: - Predicates AST now includes information about args (names & possible values) (fran-worley + solnic) - Predicates raise errors when they are called with invalid arity (fran-worley + solnic) - Rules no longer evaluate input twice when building result objects (solnic) - version: 0.2.3 date: '2016-05-11' added: - "`not_eql?`, `includes?`, `excludes?` predicates (fran-worley)" changed: - Renamed `inclusion?` to `included_in?` and deprecated `inclusion?` (fran-worley) - Renamed `exclusion?` to `excluded_from?` and deprecated `exclusion?` (fran-worley) - version: 0.2.2 date: '2016-03-30' added: - "`number?`, `odd?`, `even?` predicates (fran-worley)" - version: 0.2.1 date: '2016-03-20' fixed: - Result AST for `Rule::Each` correctly maps elements with eql inputs (solnic) - version: 0.2.0 date: '2016-03-11' changed: - Entire AST has been redefined (solnic) - version: 0.1.4 date: '2016-01-27' added: - |- Support for hash-names in `Check` and `Result` which can properly resolve input from nested results (solnic) - version: 0.1.3 date: '2016-01-27' added: - Support for resolving input from `Rule::Result` (solnic) changed: - "`Check` and `Result` carry original input(s) (solnic)" - version: 0.1.2 date: '2016-01-19' fixed: - "`xor` returns wrapped results when used against another result-rule (solnic)" - version: 0.1.1 date: '2016-01-18' added: - "`Rule::Attr` which can be applied to a data object with attr readers (SunnyMagadan)" - "`Rule::Result` which can be applied to a result object (solnic)" - "`true?` and `false?` predicates (solnic)" - version: 0.1.0 date: '2016-01-11' summary: Code extracted from dry-validation 0.4.1 dry-logic-1.2.0/docsite/000077500000000000000000000000001404145603600150405ustar00rootroot00000000000000dry-logic-1.2.0/docsite/source/000077500000000000000000000000001404145603600163405ustar00rootroot00000000000000dry-logic-1.2.0/docsite/source/builder.html.md000066400000000000000000000004451404145603600212560ustar00rootroot00000000000000--- title: Builder layout: gem-single name: dry-logic --- Use Dry Logic's builder evaluate predicates and operations ``` ruby require "dry/logic/builder" is_zero = Dry::Logic::Builder.call do lt?(0) ^ gt?(0) end is_zero.call(0).success? # => true is_zero.call(1).success? # => false ``` dry-logic-1.2.0/docsite/source/custom-predicates.html.md000066400000000000000000000012101404145603600232520ustar00rootroot00000000000000--- title: Custom predicates layout: gem-single name: dry-logic --- Define custom predicates using the `predicate` method. Use the build in builder to evaluate and define your predicate. ``` ruby extend Dry::Logic::Builder build do predicate :divisible_with? do |num, input| (input % num).zero? end end ``` Then create your predicate ``` ruby is_divisible_with_ten = build do divisible_with?(10) end ``` Here, `10` represents the first argument `num` to `divisible_with?`. ``` ruby # 10 & 5 is passed as {input} to your method is_divisible_with_ten.call(10).success? # => true is_divisible_with_ten.call(5).success? # => false ``` dry-logic-1.2.0/docsite/source/index.html.md000066400000000000000000000025031404145603600207340ustar00rootroot00000000000000--- title: Introduction description: Predicate logic with composable rules layout: gem-single type: gem name: dry-logic sections: - predicates - operations - custom-predicates - builder --- Predicate logic and rule composition used by: * [dry-types](https://github.com/dry-rb/dry-types) for constrained types * [dry-validation](https://github.com/dry-rb/dry-validation) for composing validation rules * your project...? ## Synopsis ``` ruby require 'dry/logic' require 'dry/logic/predicates' include Dry::Logic # Rule::Predicate will only apply its predicate to its input, that’s all # require input to have the key :user user_present = Rule::Predicate.new(Predicates[:key?]).curry(:user) # curry allows us to prepare predicates with args, without the input # require value to be greater than 18 min_18 = Rule::Predicate.new(Predicates[:gt?]).curry(18) # use the min_18 predicate on the value of user[:age] has_min_age = Operations::Key.new(min_18, name: [:user, :age]) user_rule = user_present & has_min_age user_rule.(user: { age: 19 }).success? # => true user_rule.(user: { age: 18 }).success? # => false user_rule.(user: { age: 'seventeen' }) # => ArgumentError: comparison of String with 18 failed user_rule.(user: { }) # => NoMethodError: undefined method `>' for nil:NilClass user_rule.({}).success? # => false ``` dry-logic-1.2.0/docsite/source/operations.html.md000066400000000000000000000103041404145603600220060ustar00rootroot00000000000000--- title: Operations layout: gem-single name: dry-logic --- Operations work on one or more predicates (see predicates) and can be invoked in conjunction with other operations. Use `Dry::Logic::Builder` to evaluate operations & predicates. ``` ruby extend Dry::Logic::Builder is_zero = build { eql?(0) } is_zero.call(10).success? ``` ### Or (`|`, `or`) > Returns true if one of its arguments is true. Similar to Ruby's `||` operator Argument 1 | Argument 2 | Result --- | --- | --- true | true | true true | false | true false | true | true false | false | false ``` ruby is_number = build do int? | float? | number? end is_number.call(1).success? # => true is_number.call(2.0).success? # => true is_number.call('3').success? # => false is_number.call('four').success? # => false ``` ### Implication (`>`, `then`, `implication`) > Similar to Ruby's `if then` expression Argument 1 | Argument 2 | Result --- | --- | --- true | true | true true | false | false false | true | false false | false | false ``` ruby is_empty = build do (attr?(:empty) > empty?) | nil? end is_empty.call("").success? # => true is_empty.call([]).success? # => true is_empty.call({}).success? # => true is_empty.call(nil).success? # => true is_empty.call("string").success? # => false is_empty.call(["array"]).success? # => false is_empty.call({key: "value"}).success? # => false ``` ### Exclusive or (`^`, `xor`) > Returns true if at most one of its arguments are valid; otherwise, false Argument 1 | Argument 2 | Result --- | --- | --- true | true | false true | false | true false | true | true false | false | false ``` ruby is_not_zero = build do lt?(0) ^ gt?(0) end is_not_zero.call(1).success? # => true is_not_zero.call(0).success? # => false is_not_zero.call(-1).success? # => true ``` ### And (`&`, `and`) > Returns true if both of its arguments are true. Similar to Ruby's `&&` operator Argument 1 | Argument 2 | Result --- | --- | --- true | true | true true | false | false false | true | false false | false | false ``` ruby is_middle_aged = build do gt?(30) & lt?(50) end is_child.call(20).success? # => false is_child.call(40).success? # => true is_child.call(60).success? # => true ``` ### Attribute (`attr`) ``` ruby is_middle_aged = build do attr name: :age do gt?(30) & lt?(50) end end Person = Struct.new(:age) is_middle_aged.call(Person.new(20)).success? # => false is_middle_aged.call(Person.new(40)).success? # => true is_middle_aged.call(Person.new(60)).success? # => false ``` ### Each (`each`) > True when any of the inputs is true when applied to the predicate. Similar to `Array#any?` ``` ruby is_only_odd = build do each { odd? } end is_only_odd.call([1, 3, 5]).success? # => true is_only_odd.call([4, 6, 8]).success? # => false ``` ### Set (`set`) > Applies input to an array of predicates. Returns true if all predicates yield true. Similar to `Array#all?` ``` ruby is_natrual_and_odd = build do set(int?, odd?, gt?(1)) end is_natrual_and_odd.call('5').success? # => false is_natrual_and_odd.call(5).success? # => true is_natrual_and_odd.call(-1).success? # => false ``` ### Negation (`negation`, `not`) > Returns true when predicate returns false. Similar to Ruby's `!` operator ``` ruby is_present = build do negation(empty?) end is_present.call([1]).success? # => true is_present.call([]).success? # => false is_present.call("A").success? # => true is_present.call("").success? # => false ``` ### Key (`key`) ``` ruby is_named = build do key name: [:user, :name] do str? & negation(empty?) end end is_named.call({ user: { name: "John" } }).success? # => true is_named.call({ user: { name: nil } }).success? # => false ``` ### Check (`check`) ``` ruby is_allowed_to_drive = build do check keys: [:age] do gt?(18) end end is_allowed_to_drive.call({ age: 30 }).success? # => true is_allowed_to_drive.call({ age: 10 }).success? # => false ``` Or when the predicate allows for more than one argument. ``` ruby is_speeding = build do check keys: [:speed, :limit] do lt? end end is_speeding.call({ speed: 100, limit: 50 }).success? # => true is_speeding.call({ speed: 40, limit: 50 }).success? # => false ``` Which is the same as this ``` ruby input[:limit].lt?(*input.values_at(:speed)) ``` dry-logic-1.2.0/docsite/source/predicates.html.md000066400000000000000000000300341404145603600217500ustar00rootroot00000000000000--- title: Predicates layout: gem-single name: dry-logic --- Predicates can be chained together using operations such as `&` and `|` (see the `operations` section). Take a look at the `builder` section on how to run the below examples using Dry Logic's builder. ### URI (`uri?`) > Returns true when the string input is a URI ``` ruby is_https_url = build { uri?(:https) } is_https_url = build { uri?(/https?/) } is_http_url = build { uri?(:http) } is_url = build { uri?([:http, :https]) } https_url = "https://example.com" http_url = "http://example.com" local_url = "example.local" is_https_url.call(https_url).success? # => true is_https_url.call(local_url).success? # => false is_http_url.call(http_url).success? # => true is_url.call(https_url).success? # => false ``` ### UUID (`uuid_v1?`, `uuid_v2?`, `uuid_v3?`, `uuid_v4?`, `uuid_v5?`) > Returns true when the string input is a UUID ``` ruby is_uuid_v1 = build { uuid_v1? } is_uuid_v2 = build { uuid_v2? } is_uuid_v3 = build { uuid_v3? } is_uuid_v4 = build { uuid_v4? } is_uuid_v5 = build { uuid_v5? } uuid1 = "554ef240-5433-11eb-ae93-0242ac130002" not_uuid = "" is_uuid_v1.call(uuid1).success? # => true is_uuid_v2.call(uuid1).success? # => false is_uuid_v3.call(not_uuid).success? # => false is_uuid_v4.call(uuid1).success? # => false is_uuid_v5.call(uuid1).success? # => false ``` ### Case expression (`case?`) > Similar to Ruby's `===` operator ``` ruby is_natrual = build { case?(1...) } is_natrual.call(1).success? # => true is_natrual.call(-1).success? # => false is_natrual.call("").success? # => false is_integer = build { case?(Integer) } is_integer.call(1).success? # => true is_integer.call("").success? # => false ``` ### Identity equality (`is?`) > Compare two values using `Object#object_id`. Similar to Ruby's `Object#equal?` ``` ruby is_nil = build { is?(nil) } is_nil.call(nil).success? # => true is_nil.call(:some).success? # => false is_very_specific = build { is?(Class.new) } is_very_specific.call(nil).success? # => false is_very_specific.call(:some).success? # => false ``` ### Equality (`eql?`) > Returns true when the input is equal to the provided value. Similar to `Object#eql?` ``` ruby is_zero = build { eql?(0) } is_zero.call(0).success? # => true is_zero.call(10).success? # => false ``` ### Inequality (`not_eql?`) > Returns true when the input does not equal the provided value. Similar to Ruby's `!=` operator ``` ruby is_present = build { not_eql?(nil) } is_present.call("hello").success? # => true is_present.call(nil).success? # => false ``` ### Included values (`includes?`) > Returns true when the input contains the provided value. Can be applied to values responding to `#include?` ``` ruby has_zeros = build { includes?(0) } has_zeros.call([0, 1, 2]).success? # => true has_zeros.call([-1, -2, -3]).success? # => false ``` ### Excluded values (`excludes?`) > The inverse of `includes?` ``` ruby no_zeroes = build { excludes?(0) } no_zeroes.call([1,2,3]).success? # => true no_zeroes.call([0, -1, -2]).success? # => false ``` ### Included in (`included_in?`) > Returns true when the input is included in the provided value. Can be applied to values responding to `#include?` ``` ruby is_natrual = build { included_in?(1...) } is_natrual.call(1).success? # => true is_natrual.call(0).success? # => false is_natrual.call(-1).success? # => false ``` ### Excluded from (`excluded_from?`) > Inverse of `included_in?` ``` is_negative = build { excluded_from?(0...) } is_negative.call(-1).success? # => true is_negative.call(0).success? # => false is_negative.call(1).success? # => false ``` ### Size (`size?`) > Can be applied values responding to `#size`, such as `Hash`, `Array`, and `String` ``` ruby is_empty = build { size?(0) } is_empty.call({}).success? # => true is_empty.call([]).success? # => true is_empty.call("").success? # => true is_empty.call({"1" => 2}).success? # => false is_empty.call([1]).success? # => false is_empty.call("1").success? # => false ``` ### Minimum size (`min_size?`) > Checks for a miminum size using `#size >= value`. See `#size?` ``` ruby is_present = build { min_size?(1) } is_present.call({"1" => 2}).success? # => true is_present.call([1]).success? # => true is_present.call("1").success? # => true is_present.call({}).success? # => false is_present.call([]).success? # => false is_present.call("").success? # => false ``` ### Maximum size (`max_size?`) > Checks for a maximum size using `#size <= value`. See #size? ``` ruby one_or_none = build { max_size?(1) } one_or_none.call({}).success? # => true one_or_none.call([1]).success? # => true one_or_none.call("A").success? # => true one_or_none.call({"A" => :a, "B" => :b}).success? # => false one_or_none.call([1,2]).success? # => false one_or_none.call("AB").success? # => false ``` ### Bytesize (`bytesize?`) > Can be applied values responding to `#bytesize`, such as `String` ``` ruby is_one = build { bytesize?(1) } is_one.call("A").success? # => true is_one.call("AB").success? # => false is_two_or_tree = build { bytesize?(2..3) } is_two_or_tree.call("ABC").success? # => true is_two_or_tree.call("ABCD").success? # => false is_four = build { bytesize?([4]) } is_four.call("ABCD").success? # => true is_four.call("ABC").success? # => false ``` ### Minimum byte size (`min_bytesize?`) > Checks for a minimum size using `#bytesize >= value`. See #bytesize? ``` ruby is_min_one = build { min_bytesize?(1) } is_min_one.call("A").success? # => true is_min_one.call("").success? # => false ``` ### Maximum byte size (`max_bytesize?`) > Checks for a maximum size using `#bytesize >= value`. See #bytesize? ``` ruby is_max_one = build { max_bytesize?(1) } is_max_one.call("A").success? # => true is_max_one.call("AB").success? # => false ``` ### Greater than (`gt?`) > Similar to Ruby's `>` operator ``` ruby can_vote = build { gt?(17) } can_vote.call(17).success? # => false can_vote.call(18).success? # => true can_vote.call(19).success? # => true ``` ### Greater or equal to (`gteq?`) > Returns true when input is greater than the provided value. Similar to Ruby's `>=` operator ``` ruby can_vote = build { gteq?(18) } can_vote.call(17).success? # => false can_vote.call(18).success? # => true can_vote.call(19).success? # => true ``` ### Less than (`lt?`) > Similar to Ruby's `<` operator ``` ruby can_work = build { lt?(65) } can_work.call(65).success? # => false can_work.call(64).success? # => true ``` ### Less or equal to (`lteq?`) > Returns true when input is less or equal to the provided value. Similar Ruby's `<=` operator ``` ruby can_work = build { lteq?(64) } can_work.call(65).success? # => false can_work.call(64).success? # => true ``` ### Odd (`odd?`) > Similar to `Integer#odd?` ``` ruby is_odd = build { odd? } is_odd.call(1).success? # => true is_odd.call(2).success? # => false ``` ### Even (`even?`) > Returns true if the input is even. Similar to Ruby's `Integer#even?` method ``` ruby is_even = build { even? } is_even.call(2).success? # => true is_even.call(1).success? # => false ``` ### Hash (`hash?`) > Returns true if the input is of type `Hash` ``` ruby is_hash = build { hash? } is_hash.call(Hash.new).success? # => true is_hash.call(Array.new).success? # => false ``` ### Array (`array?`) > Returns true if the input is of type `Array` ``` ruby is_array = build { array? } is_array.call(Array.new).success? # => true is_array.call(Hash.new).success? # => false ``` ### String (`str?`) > Returns true if the input is of type `String` ``` ruby is_string = build { str? } is_string.call(String.new).success? # => true is_string.call(Hash.new).success? # => false ``` ### Number (`number?`) > Returns true if the input is a number ``` ruby is_number = build { number? } is_number.call(4).success? # => true is_number.call(-4).success? # => true is_number.call(" 4").success? # => true is_number.call("-4").success? # => true is_number.call(4.0).success? # => true is_number.call('4').success? # => true is_number.call('4.0').success? # => true is_number.call("A4").success? # => false is_number.call("A-4").success? # => false is_number.call(nil).success? # => false is_number.call(:four).success? # => false is_number.call("four").success? # => false ``` ### Decimal (`decimal?`) > Returns true if the input is of type `BigDecimal` ``` ruby is_decimal = build { decimal? } is_decimal.call(BigDecimal(1)).success? # => true is_decimal.call(1).success? # => false ``` ### Float (`float?`) > Returns true if the input is of type `Float` ``` ruby is_float = build { float? } is_float.call(1.0).success? # => true is_float.call(1).success? # => false ``` ### Number (`num?`) > Returns true if the input is of type `Integer` ``` ruby is_num = build { num? } is_num.call(1).success? # => true is_num.call(1.0).success? # => false ``` ### Time (`time?`) > Returns true if the input is of `Time` ``` ruby is_time = build { time? } is_time.call(Time.new).success? # => true is_time.call("2 o'clock").success? # => false ``` ### DateTime (`date_time?`) > Returns true if the input is of type `DateTime.` ``` ruby is_date_time = build { date_time? } is_date_time.call(DateTime.new).success? # => true is_date_time.call("2 o'clock").success? # => false ``` ### Date (`date?`) > Returns true if the input is of type `Date` ``` ruby is_date = build { date? } is_date.call(Date.new).success? # => true is_date.call("1 year ago").success? # => false ``` ### Bool (`bool?`) > Checks if the input is equal to `true` or `false` ``` ruby is_bool = build { bool? } is_bool.call(true).success? # => true is_bool.call(false).success? # => true is_bool.call(:false).success? # => false is_bool.call("true").success? # => false ``` ### True / False (`true?`, `false?`) > Returns true if the input is `true` or `false` ``` ruby is_true = build { true? } is_false = build { false? } is_true.call(true).success? # => true is_true.call(false).success? # => false is_false.call(false).success? # => true is_true.call(true).success? # => false ``` ### Empty (`empty?`) > Returns true if the input is empty. Can be applied to all inputs responding to `#empty?` ``` ruby is_empty = build { empty? } is_empty.call(nil).success? # => true is_empty.call([]).success? # => true is_empty.call("").success? # => true is_empty.call({}).success? # => true is_empty.call({key: :value}).success? # => false is_empty.call([:array]).success? # => false is_empty.call("string").success? # => false is_empty.call(:symbol).success? # => false ``` ### Filled (`filled?`) > Returns true if the input is not empty. Can be applied to all inputs responding to `#empty?` ``` ruby is_filled = build { filled? } is_filled.call({key: :value}).success? # => true is_filled.call([:array]).success? # => true is_filled.call("string").success? # => true is_filled.call(:symbol).success? # => true is_filled.call(nil).success? # => false is_filled.call([]).success? # => false is_filled.call("").success? # => false is_filled.call({}).success? # => false ``` ### Attribute (`attr?`, `respond_to?`) > Returns true when the struct input responds to the given method. Similar to Ruby's `Object#respond_to?` method ``` ruby has_named = build { attr?(:name) } class Person < Struct.new(:age, :name) # Logic ... end class Car < Struct.new(:age, :brand) # Logic ... end is_named.call(Person.new(30, "John")).success? # => true is_named.call(Car.new(3, "Volvo")).success? # => false ``` ### Nil (`nil?`, `none?`) > Returns true when the input is equal to `nil`. Similar to Ruby's `nil?` method ``` ruby is_nil = build { nil? } is_nil.call(nil).success? # => true is_nil.call(:some).success? # => false ``` ### Key (`key?`) > Returns true if the hash input has contains the provided key. Similar to Ruby's `Hash#key?` method ``` ruby is_named = build { key?(:name) } is_named.call({ name: "John" }).success? # => true is_named.call({ age: 30 }).success? # => false ``` ### Format (`format?`) > Returns true when the input matches the provides regular expression. Similar to Ruby's `String#match` method ``` ruby is_email_ish = build { format?(/^\S+@\S+$/) } is_email_ish.call("hello@example.com") # => true is_email_ish.call("nope|failed.com") # => false ``` dry-logic-1.2.0/dry-logic.gemspec000066400000000000000000000026531404145603600166520ustar00rootroot00000000000000# frozen_string_literal: true # this file is managed by dry-rb/devtools project lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'dry/logic/version' Gem::Specification.new do |spec| spec.name = 'dry-logic' spec.authors = ["Piotr Solnica"] spec.email = ["piotr.solnica@gmail.com"] spec.license = 'MIT' spec.version = Dry::Logic::VERSION.dup spec.summary = "Predicate logic with rule composition" spec.description = spec.summary spec.homepage = 'https://dry-rb.org/gems/dry-logic' spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-logic.gemspec", "lib/**/*"] spec.bindir = 'bin' spec.executables = [] spec.require_paths = ['lib'] spec.metadata['allowed_push_host'] = 'https://rubygems.org' spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-logic/blob/master/CHANGELOG.md' spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-logic' spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-logic/issues' spec.required_ruby_version = ">= 2.5.0" # to update dependencies edit project.yml spec.add_runtime_dependency "concurrent-ruby", "~> 1.0" spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5" spec.add_development_dependency "bundler" spec.add_development_dependency "rake" spec.add_development_dependency "rspec" end dry-logic-1.2.0/examples/000077500000000000000000000000001404145603600152245ustar00rootroot00000000000000dry-logic-1.2.0/examples/basic.rb000066400000000000000000000006311404145603600166320ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic" require "dry/logic/predicates" include Dry::Logic user_present = Rule::Predicate.build(Predicates[:key?]).curry(:user) has_min_age = Operations::Key.new(Rule::Predicate.build(Predicates[:gt?]).curry(18), name: [:user, :age]) user_rule = user_present & has_min_age puts user_rule.(user: { age: 19 }).success? puts user_rule.(user: { age: 18 }).success? dry-logic-1.2.0/lib/000077500000000000000000000000001404145603600141545ustar00rootroot00000000000000dry-logic-1.2.0/lib/dry-logic.rb000066400000000000000000000000631404145603600163710ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic" dry-logic-1.2.0/lib/dry/000077500000000000000000000000001404145603600147525ustar00rootroot00000000000000dry-logic-1.2.0/lib/dry/logic.rb000066400000000000000000000003361404145603600163760ustar00rootroot00000000000000# frozen_string_literal: true # A collection of micro-libraries, each intended to encapsulate # a common task in Ruby module Dry module Logic end end require "dry/logic/rule/predicate" require "dry/logic/operations" dry-logic-1.2.0/lib/dry/logic/000077500000000000000000000000001404145603600160475ustar00rootroot00000000000000dry-logic-1.2.0/lib/dry/logic/appliable.rb000066400000000000000000000007611404145603600203310ustar00rootroot00000000000000# frozen_string_literal: true module Dry module Logic module Appliable def id options[:id] end def result options[:result] end def applied? !result.nil? end def success? result.equal?(true) end def failure? !success? end def to_ast if applied? && id [success? ? :success : :failure, [id, ast]] else ast end end end end end dry-logic-1.2.0/lib/dry/logic/builder.rb000066400000000000000000000043251404145603600200260ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic" require "singleton" require "delegate" module Dry module Logic autoload :Operations, "dry/logic/operations" autoload :Predicates, "dry/logic/predicates" module Builder IGNORED_OPERATIONS = %i[ Abstract Binary Unary ].freeze IGNORED_PREDICATES = [ :predicate ].freeze # Predicate and operation builder # # @block [Proc] # @return [Builder::Result] # @example Check if input is zero # is_zero = Dry::Logic::Builder.call do # negation { lt?(0) ^ gt?(0) } # end # # p is_zero.call(1) # => false # p is_zero.call(0) # => true # p is_zero.call(-1) # => false def call(&context) Context.instance.call(&context) end module_function :call alias_method :build, :call public :call, :build class Context include Dry::Logic include Singleton module Predicates include Logic::Predicates end # @see Builder#call def call(&context) instance_eval(&context) end # Defines custom predicate # # @name [Symbol] Name of predicate # @Context [Proc] def predicate(name, &context) if singleton_class.method_defined?(name) singleton_class.undef_method(name) end prerdicate = Rule::Predicate.new(context) define_singleton_method(name) do |*args| prerdicate.curry(*args) end end # Defines methods for operations and predicates def initialize Operations.constants(false).each do |name| next if IGNORED_OPERATIONS.include?(name) operation = Operations.const_get(name) define_singleton_method(name.downcase) do |*args, **kwargs, &block| operation.new(*call(&block), *args, **kwargs) end end Predicates::Methods.instance_methods(false).each do |name| unless IGNORED_PREDICATES.include?(name) predicate(name, &Predicates[name]) end end end end end end end dry-logic-1.2.0/lib/dry/logic/evaluator.rb000066400000000000000000000017331404145603600204020ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/equalizer" module Dry module Logic class Evaluator include Dry::Equalizer(:path) attr_reader :path class Set include Dry::Equalizer(:evaluators) attr_reader :evaluators def self.new(paths) super(paths.map { |path| Evaluator::Key.new(path) }) end def initialize(evaluators) @evaluators = evaluators end def call(input) evaluators.map { |evaluator| evaluator[input] } end alias_method :[], :call end class Key < Evaluator def call(input) path.reduce(input) { |a, e| a[e] } end alias_method :[], :call end class Attr < Evaluator def call(input) path.reduce(input) { |a, e| a.public_send(e) } end alias_method :[], :call end def initialize(path) @path = Array(path) end end end end dry-logic-1.2.0/lib/dry/logic/operations.rb000066400000000000000000000006551404145603600205650ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/and" require "dry/logic/operations/or" require "dry/logic/operations/xor" require "dry/logic/operations/implication" require "dry/logic/operations/negation" require "dry/logic/operations/key" require "dry/logic/operations/attr" require "dry/logic/operations/each" require "dry/logic/operations/set" require "dry/logic/operations/check" require "dry/logic/operators" dry-logic-1.2.0/lib/dry/logic/operations/000077500000000000000000000000001404145603600202325ustar00rootroot00000000000000dry-logic-1.2.0/lib/dry/logic/operations/abstract.rb000066400000000000000000000015731404145603600223700ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/constants" require "dry/core/equalizer" require "dry/logic/operators" module Dry module Logic module Operations class Abstract include Core::Constants include Dry::Equalizer(:rules, :options) include Operators attr_reader :rules attr_reader :options def initialize(*rules, **options) @rules = rules @options = options end def id options[:id] end def curry(*args) new(rules.map { |rule| rule.curry(*args) }, **options) end def new(rules, **new_options) self.class.new(*rules, **options, **new_options) end def with(new_options) new(rules, **options, **new_options) end def to_ast ast end end end end end dry-logic-1.2.0/lib/dry/logic/operations/and.rb000066400000000000000000000017571404145603600213330ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/binary" require "dry/logic/result" module Dry module Logic module Operations class And < Binary attr_reader :hints def initialize(*, **) super @hints = options.fetch(:hints, true) end def type :and end alias_method :operator, :type def call(input) left_result = left.(input) if left_result.success? right_result = right.(input) if right_result.success? Result::SUCCESS else Result.new(false, id) { right_result.ast(input) } end else Result.new(false, id) do left_ast = left_result.to_ast hints ? [type, [left_ast, [:hint, right.ast(input)]]] : left_ast end end end def [](input) left[input] && right[input] end end end end end dry-logic-1.2.0/lib/dry/logic/operations/attr.rb000066400000000000000000000004451404145603600215340ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/key" module Dry module Logic module Operations class Attr < Key def self.evaluator(name) Evaluator::Attr.new(name) end def type :attr end end end end end dry-logic-1.2.0/lib/dry/logic/operations/binary.rb000066400000000000000000000010351404145603600220420ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/abstract" module Dry module Logic module Operations class Binary < Abstract attr_reader :left attr_reader :right def initialize(left, right, **options) super @left = left @right = right end def ast(input = Undefined) [type, [left.ast(input), right.ast(input)]] end def to_s "#{left} #{operator.to_s.upcase} #{right}" end end end end end dry-logic-1.2.0/lib/dry/logic/operations/check.rb000066400000000000000000000022331404145603600216340ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/unary" require "dry/logic/evaluator" require "dry/logic/result" module Dry module Logic module Operations class Check < Unary attr_reader :evaluator def self.new(rule, **options) if options[:evaluator] super(rule, **options) else keys = options.fetch(:keys) evaluator = Evaluator::Set.new(keys) super(rule, **options, evaluator: evaluator) end end def initialize(*rules, **options) super @evaluator = options[:evaluator] end def type :check end def call(input) *head, tail = evaluator[input] result = rule.curry(*head).(tail) if result.success? Result::SUCCESS else Result.new(false, id) { [type, [options[:keys], result.to_ast]] } end end def [](input) rule[*evaluator[input].reverse] end def ast(input = Undefined) [type, [options[:keys], rule.ast(input)]] end end end end end dry-logic-1.2.0/lib/dry/logic/operations/each.rb000066400000000000000000000013171404145603600214610ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/unary" require "dry/logic/result" module Dry module Logic module Operations class Each < Unary def type :each end def call(input) results = input.map { |element| rule.(element) } success = results.all?(&:success?) Result.new(success, id) do failures = results .map .with_index { |result, idx| [:key, [idx, result.ast(input[idx])]] if result.failure? } .compact [:set, failures] end end def [](arr) arr.map { |input| rule[input] }.all? end end end end end dry-logic-1.2.0/lib/dry/logic/operations/implication.rb000066400000000000000000000013201404145603600230630ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/binary" require "dry/logic/result" module Dry module Logic module Operations class Implication < Binary def type :implication end def operator :then end def call(input) left_result = left.(input) if left_result.success? right_result = right.(input) Result.new(right_result.success?, id) { right_result.to_ast } else Result::SUCCESS end end def [](input) if left[input] right[input] else true end end end end end end dry-logic-1.2.0/lib/dry/logic/operations/key.rb000066400000000000000000000027051404145603600213530ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/unary" require "dry/logic/evaluator" require "dry/logic/result" module Dry module Logic module Operations class Key < Unary attr_reader :evaluator attr_reader :path def self.new(rules, **options) if options[:evaluator] super else name = options.fetch(:name) eval = options.fetch(:evaluator, evaluator(name)) super(rules, **options, evaluator: eval, path: name) end end def self.evaluator(name) Evaluator::Key.new(name) end def initialize(*rules, **options) super @evaluator = options[:evaluator] @path = options[:path] end def type :key end def call(hash) input = evaluator[hash] result = rule.(input) if result.success? Result::SUCCESS else Result.new(false, path) { [type, [path, result.to_ast]] } end end def [](hash) rule[evaluator[hash]] end def ast(input = Undefined) if input.equal?(Undefined) || !input.is_a?(Hash) [type, [path, rule.ast]] else [type, [path, rule.ast(evaluator[input])]] end end def to_s "#{type}[#{path}](#{rule})" end end end end end dry-logic-1.2.0/lib/dry/logic/operations/negation.rb000066400000000000000000000006231404145603600223640ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/unary" require "dry/logic/result" module Dry module Logic module Operations class Negation < Unary def type :not end def call(input) Result.new(rule.(input).failure?, id) { ast(input) } end def [](input) !rule[input] end end end end end dry-logic-1.2.0/lib/dry/logic/operations/or.rb000066400000000000000000000013541404145603600212020ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/binary" require "dry/logic/result" module Dry module Logic module Operations class Or < Binary def type :or end alias_method :operator, :type def call(input) left_result = left.(input) if left_result.success? Result::SUCCESS else right_result = right.(input) if right_result.success? Result::SUCCESS else Result.new(false, id) { [:or, [left_result.to_ast, right_result.to_ast]] } end end end def [](input) left[input] || right[input] end end end end end dry-logic-1.2.0/lib/dry/logic/operations/set.rb000066400000000000000000000014171404145603600213550ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/abstract" require "dry/logic/result" module Dry module Logic module Operations class Set < Abstract def type :set end def call(input) results = rules.map { |rule| rule.(input) } success = results.all?(&:success?) Result.new(success, id) do [type, results.select(&:failure?).map { |failure| failure.to_ast }] end end def [](input) rules.map { |rule| rule[input] }.all? end def ast(input = Undefined) [type, rules.map { |rule| rule.ast(input) }] end def to_s "#{type}(#{rules.map(&:to_s).join(", ")})" end end end end end dry-logic-1.2.0/lib/dry/logic/operations/unary.rb000066400000000000000000000006751404145603600217250ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/abstract" module Dry module Logic module Operations class Unary < Abstract attr_reader :rule def initialize(*rules, **options) super @rule = rules.first end def ast(input = Undefined) [type, rule.ast(input)] end def to_s "#{type}(#{rule})" end end end end end dry-logic-1.2.0/lib/dry/logic/operations/xor.rb000066400000000000000000000010411404145603600213630ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/operations/binary" require "dry/logic/result" module Dry module Logic module Operations class Xor < Binary def type :xor end alias_method :operator, :type def call(input) Result.new(self[input], id) { ast(input) } end def [](input) left[input] ^ right[input] end def ast(input = Undefined) [type, rules.map { |rule| rule.ast(input) }] end end end end end dry-logic-1.2.0/lib/dry/logic/operators.rb000066400000000000000000000007721404145603600204200ustar00rootroot00000000000000# frozen_string_literal: true module Dry module Logic module Operators def and(other) Operations::And.new(self, other) end alias_method :&, :and def or(other) Operations::Or.new(self, other) end alias_method :|, :or def xor(other) Operations::Xor.new(self, other) end alias_method :^, :xor def then(other) Operations::Implication.new(self, other) end alias_method :>, :then end end end dry-logic-1.2.0/lib/dry/logic/predicates.rb000066400000000000000000000127751404145603600205330ustar00rootroot00000000000000# frozen_string_literal: true require "bigdecimal" require "bigdecimal/util" require "date" module Dry module Logic module Predicates module Methods def [](name) method(name) end def type?(type, input) input.is_a?(type) end def nil?(input) input.nil? end alias_method :none?, :nil? def key?(name, input) input.key?(name) end def attr?(name, input) input.respond_to?(name) end def empty?(input) case input when String, Array, Hash then input.empty? when nil then true else false end end def filled?(input) !empty?(input) end def bool?(input) input.is_a?(TrueClass) || input.is_a?(FalseClass) end def date?(input) input.is_a?(Date) end def date_time?(input) input.is_a?(DateTime) end def time?(input) input.is_a?(Time) end def number?(input) true if Float(input) rescue ArgumentError, TypeError false end def int?(input) input.is_a?(Integer) end def float?(input) input.is_a?(Float) end def decimal?(input) input.is_a?(BigDecimal) end def str?(input) input.is_a?(String) end def hash?(input) input.is_a?(Hash) end def array?(input) input.is_a?(Array) end def odd?(input) input.odd? end def even?(input) input.even? end def lt?(num, input) input < num end def gt?(num, input) input > num end def lteq?(num, input) !gt?(num, input) end def gteq?(num, input) !lt?(num, input) end def size?(size, input) case size when Integer then size.equal?(input.size) when Range, Array then size.include?(input.size) else raise ArgumentError, "+#{size}+ is not supported type for size? predicate." end end def min_size?(num, input) input.size >= num end def max_size?(num, input) input.size <= num end def bytesize?(size, input) case size when Integer then size.equal?(input.bytesize) when Range, Array then size.include?(input.bytesize) else raise ArgumentError, "+#{size}+ is not supported type for bytesize? predicate." end end def min_bytesize?(num, input) input.bytesize >= num end def max_bytesize?(num, input) input.bytesize <= num end def inclusion?(list, input) ::Kernel.warn "inclusion is deprecated - use included_in instead." included_in?(list, input) end def exclusion?(list, input) ::Kernel.warn "exclusion is deprecated - use excluded_from instead." excluded_from?(list, input) end def included_in?(list, input) list.include?(input) end def excluded_from?(list, input) !list.include?(input) end def includes?(value, input) if input.respond_to?(:include?) input.include?(value) else false end rescue TypeError false end def excludes?(value, input) !includes?(value, input) end def eql?(left, right) left.eql?(right) end def is?(left, right) left.equal?(right) end def not_eql?(left, right) !left.eql?(right) end def true?(value) value.equal?(true) end def false?(value) value.equal?(false) end def format?(regex, input) !input.nil? && regex.match?(input) end def case?(pattern, input) pattern === input end def uuid_v1?(input) uuid_v1_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-1[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i format?(uuid_v1_format, input) end def uuid_v2?(input) uuid_v2_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-2[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i format?(uuid_v2_format, input) end def uuid_v3?(input) uuid_v3_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i format?(uuid_v3_format, input) end def uuid_v4?(input) uuid_v4_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i format?(uuid_v4_format, input) end def uuid_v5?(input) uuid_v5_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i format?(uuid_v5_format, input) end def uri?(schemes, input) uri_format = URI::DEFAULT_PARSER.make_regexp(schemes) format?(uri_format, input) end def respond_to?(method, input) input.respond_to?(method) end def predicate(name, &block) define_singleton_method(name, &block) end end extend Methods def self.included(other) super other.extend(Methods) end end end end dry-logic-1.2.0/lib/dry/logic/result.rb000066400000000000000000000031551404145603600177160ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/constants" module Dry module Logic class Result include Core::Constants SUCCESS = Class.new { def success? true end def failure? false end }.new.freeze attr_reader :success attr_reader :id attr_reader :serializer def initialize(success, id = nil, &block) @success = success @id = id @serializer = block end def success? success end def failure? !success? end def type success? ? :success : :failure end def ast(input = Undefined) serializer.(input) end def to_ast if id [type, [id, ast]] else ast end end def to_s visit(to_ast) end private def visit(ast) __send__(:"visit_#{ast[0]}", ast[1]) end def visit_predicate(node) name, args = node if args.empty? name.to_s else "#{name}(#{args.map(&:last).map(&:inspect).join(", ")})" end end def visit_and(node) left, right = node "#{visit(left)} AND #{visit(right)}" end def visit_or(node) left, right = node "#{visit(left)} OR #{visit(right)}" end def visit_xor(node) left, right = node "#{visit(left)} XOR #{visit(right)}" end def visit_not(node) "not(#{visit(node)})" end def visit_hint(node) visit(node) end end end end dry-logic-1.2.0/lib/dry/logic/rule.rb000066400000000000000000000046711404145603600173530ustar00rootroot00000000000000# frozen_string_literal: true require "concurrent/map" require "dry/core/constants" require "dry/core/equalizer" require "dry/logic/operations" require "dry/logic/result" require "dry/logic/rule/interface" module Dry module Logic def self.Rule(*args, **options, &block) if args.any? Rule.build(*args, **options) elsif block Rule.build(block, **options) end end class Rule include Core::Constants include Dry::Equalizer(:predicate, :options) include Operators attr_reader :predicate attr_reader :options attr_reader :args attr_reader :arity def self.interfaces @interfaces ||= ::Concurrent::Map.new end def self.specialize(arity, curried, base = Rule) base.interfaces.fetch_or_store([arity, curried]) do interface = Interface.new(arity, curried) klass = Class.new(base) { include interface } base.const_set("#{base.name.split("::").last}#{interface.name}", klass) klass end end def self.build(predicate, args: EMPTY_ARRAY, arity: predicate.arity, **options) specialize(arity, args.size).new(predicate, {args: args, arity: arity, **options}) end def initialize(predicate, options = EMPTY_HASH) @predicate = predicate @options = options @args = options[:args] || EMPTY_ARRAY @arity = options[:arity] || predicate.arity end def type :rule end def id options[:id] end def curry(*new_args) with(args: args + new_args) end def bind(object) if predicate.respond_to?(:bind) self.class.build(predicate.bind(object), **options) else self.class.build( -> *args { object.instance_exec(*args, &predicate) }, **options, arity: arity, parameters: parameters ) end end def eval_args(object) with(args: args.map { |arg| UnboundMethod === arg ? arg.bind(object).() : arg }) end def with(new_opts) self.class.build(predicate, **options, **new_opts) end def parameters options[:parameters] || predicate.parameters end def ast(input = Undefined) [:predicate, [id, args_with_names(input)]] end private def args_with_names(*input) parameters.map(&:last).zip(args + input) end end end end dry-logic-1.2.0/lib/dry/logic/rule/000077500000000000000000000000001404145603600170165ustar00rootroot00000000000000dry-logic-1.2.0/lib/dry/logic/rule/interface.rb000066400000000000000000000061111404145603600213020ustar00rootroot00000000000000# frozen_string_literal: true module Dry module Logic class Rule class Interface < ::Module SPLAT = ["*rest"].freeze attr_reader :arity attr_reader :curried def initialize(arity, curried) @arity = arity @curried = curried if !variable_arity? && curried > arity raise ArgumentError, "wrong number of arguments (#{curried} for #{arity})" end define_constructor if curried? if constant? define_constant_application else define_application end end def constant? arity.zero? end def variable_arity? arity.negative? end def curried? !curried.zero? end def unapplied if variable_arity? unapplied = arity.abs - 1 - curried if unapplied.negative? 0 else unapplied end else arity - curried end end def name if constant? "Constant" else arity_str = if variable_arity? "Variable#{arity.abs - 1}Arity" else "#{arity}Arity" end curried_str = if curried? "#{curried}Curried" else EMPTY_STRING end "#{arity_str}#{curried_str}" end end def define_constructor assignment = if curried.equal?(1) "@arg0 = @args[0]" else "#{curried_args.join(", ")} = @args" end module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def initialize(*) super #{assignment} end RUBY end def define_constant_application module_exec do def call(*) if @predicate[] Result::SUCCESS else Result.new(false, id) { ast } end end def [](*) @predicate[] end end end def define_application splat = variable_arity? ? SPLAT : EMPTY_ARRAY parameters = (unapplied_args + splat).join(", ") application = "@predicate[#{(curried_args + unapplied_args + splat).join(", ")}]" module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def call(#{parameters}) if #{application} Result::SUCCESS else Result.new(false, id) { ast(#{parameters}) } end end def [](#{parameters}) #{application} end RUBY end def curried_args @curried_args ||= ::Array.new(curried) { |i| "@arg#{i}" } end def unapplied_args @unapplied_args ||= ::Array.new(unapplied) { |i| "input#{i}" } end end end end end dry-logic-1.2.0/lib/dry/logic/rule/predicate.rb000066400000000000000000000011131404145603600212770ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/rule" module Dry module Logic class Rule::Predicate < Rule def self.specialize(arity, curried, base = Predicate) super end def type :predicate end def name predicate.name end def to_s if args.size > 0 "#{name}(#{args.map(&:inspect).join(", ")})" else name.to_s end end def ast(input = Undefined) [type, [name, args_with_names(input)]] end alias_method :to_ast, :ast end end end dry-logic-1.2.0/lib/dry/logic/rule_compiler.rb000066400000000000000000000034621404145603600212420ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/constants" require "dry/logic/rule" require "dry/logic/rule/predicate" module Dry module Logic class RuleCompiler include Core::Constants attr_reader :predicates def initialize(predicates) @predicates = predicates end def call(ast) ast.map { |node| visit(node) } end def visit(node) name, nodes = node send(:"visit_#{name}", nodes) end def visit_check(node) keys, predicate = node Operations::Check.new(visit(predicate), keys: keys) end def visit_not(node) Operations::Negation.new(visit(node)) end def visit_key(node) name, predicate = node Operations::Key.new(visit(predicate), name: name) end def visit_attr(node) name, predicate = node Operations::Attr.new(visit(predicate), name: name) end def visit_set(node) Operations::Set.new(*call(node)) end def visit_each(node) Operations::Each.new(visit(node)) end def visit_predicate(node) name, params = node predicate = Rule::Predicate.build(predicates[name]) if params.size > 1 args = params.map(&:last).reject { |val| val == Undefined } predicate.curry(*args) else predicate end end def visit_and(node) left, right = node visit(left).and(visit(right)) end def visit_or(node) left, right = node visit(left).or(visit(right)) end def visit_xor(node) left, right = node visit(left).xor(visit(right)) end def visit_implication(node) left, right = node visit(left).then(visit(right)) end end end end dry-logic-1.2.0/lib/dry/logic/version.rb000066400000000000000000000001311404145603600200540ustar00rootroot00000000000000# frozen_string_literal: true module Dry module Logic VERSION = "1.2.0" end end dry-logic-1.2.0/project.yml000066400000000000000000000005321404145603600155770ustar00rootroot00000000000000name: dry-logic codacy_id: 3ac6ea12c2dd42beb36dc3abe63d9606 gemspec: authors: ["Piotr Solnica"] email: ["piotr.solnica@gmail.com"] summary: "Predicate logic with rule composition" development_dependencies: - bundler - rake - rspec runtime_dependencies: - [concurrent-ruby, "~> 1.0"] - [dry-core, "~> 0.5", ">= 0.5"] dry-logic-1.2.0/spec/000077500000000000000000000000001404145603600143405ustar00rootroot00000000000000dry-logic-1.2.0/spec/integration/000077500000000000000000000000001404145603600166635ustar00rootroot00000000000000dry-logic-1.2.0/spec/integration/builder/000077500000000000000000000000001404145603600203115ustar00rootroot00000000000000dry-logic-1.2.0/spec/integration/builder/operation_spec.rb000066400000000000000000000200631404145603600236510ustar00rootroot00000000000000# frozen_string_literal: true require_relative "../../shared/operation" RSpec.describe "operations" do describe "nested" do let(:predicate) do Dry::Logic::Builder.call do check keys: [:person] do check keys: [:age] do gt?(50) & lt?(200) end end end end describe "success" do subject { predicate.call({person: {age: 100}}) } it { is_expected.to be_a_success } end describe "failure" do subject { predicate.call({person: {age: 10}}) } it { is_expected.not_to be_a_success } end end describe :check do describe "one path" do let(:expression) do lambda do |*| check keys: [:age] do gt?(50) end end end describe "success" do it_behaves_like "operation" do let(:input) { {age: 100} } let(:output) { true } end end describe "failure" do it_behaves_like "operation" do let(:input) { {age: 10} } let(:output) { false } end end end describe "two paths" do let(:expression) do lambda do |*| check keys: %i[speed limit] do gt? end end end describe "success" do it_behaves_like "operation" do let(:input) { {speed: 50, limit: 100} } let(:output) { true } end end describe "failure" do it_behaves_like "operation" do let(:input) { {speed: 100, limit: 50} } let(:output) { false } end end end end describe :implication do let(:expression) do lambda do |*| implication { [gt?(0), lt?(10)] } end end describe "[true => true]" do it_behaves_like "operation" do let(:input) { 5 } let(:output) { true } end end describe "[true => false]" do it_behaves_like "operation" do let(:input) { 20 } let(:output) { false } end end describe "[false => true]" do it_behaves_like "operation" do let(:input) { -5 } let(:output) { true } end end end describe :key do let(:expression) do lambda do |*| key name: %i[user age] do gt?(10) end end end describe "success" do it_behaves_like "operation" do let(:input) { {user: {age: 20}} } let(:output) { true } end end describe "failure" do it_behaves_like "operation" do let(:input) { {user: {age: 5}} } let(:output) { false } end end end describe :and do let(:expression) do lambda do |*| even?.and(int?) end end describe "success" do it_behaves_like "operation" do let(:input) { 2 } let(:output) { true } end end describe "failure" do it_behaves_like "operation" do let(:input) { 5 } let(:output) { false } end end end describe :or do let(:expression) do lambda do |*| even?.or(odd?) end end describe "success" do it_behaves_like "operation" do let(:input) { 10 } let(:output) { true } end it_behaves_like "operation" do let(:input) { 9 } let(:output) { true } end end end describe :negation do let(:expression) do lambda do |*| negation { even? } end end describe "success" do it_behaves_like "operation" do let(:input) { 9 } let(:output) { true } end end describe "failure" do it_behaves_like "operation" do let(:input) { 6 } let(:output) { false } end end end describe :set do let(:expression) do lambda do |*| set { [lt?(5), gt?(2)] } end end describe "success" do it_behaves_like "operation" do let(:input) { 3 } let(:output) { true } end end describe "success" do it_behaves_like "operation" do let(:input) { 7 } let(:output) { false } end end end describe :each do let(:expression) do lambda do |*| each { gt?(10) } end end describe "success" do it_behaves_like "operation" do let(:input) { [20, 30] } let(:output) { true } end end describe "failure" do it_behaves_like "operation" do let(:input) { [10, 20, 30] } let(:output) { false } end end end describe :xor do let(:expression) do lambda do |*| even?.xor(gt?(4)) end end describe "success" do it_behaves_like "operation" do let(:input) { 5 } let(:output) { true } end it_behaves_like "operation" do let(:input) { 2 } let(:output) { true } end end describe "failure" do it_behaves_like "operation" do let(:input) { 6 } let(:output) { false } end end end # describe :attr do # let(:expression) do # lambda do |*| # attr name: :age do # gt?(50) # end # end # end # # let(:person) { Struct.new(:age) } # # describe "success" do # it_behaves_like "operation" do # let(:input) { person.new(100) } # let(:output) { true } # end # end # # describe "failure" do # it_behaves_like "operation" do # let(:input) { person.new(0) } # let(:output) { false } # end # end # end describe "operators" do describe :& do let(:expression) do lambda do |*| even? & int? end end describe "success" do it_behaves_like "operation" do let(:input) { 2 } let(:output) { true } end end describe "failure" do it_behaves_like "operation" do let(:input) { 5 } let(:output) { false } end end end describe :^ do let(:expression) do lambda do |*| even? ^ gt?(4) end end describe "success" do it_behaves_like "operation" do let(:input) { 5 } let(:output) { true } end it_behaves_like "operation" do let(:input) { 2 } let(:output) { true } end end describe "failure" do it_behaves_like "operation" do let(:input) { 6 } let(:output) { false } end end end describe :> do let(:expression) do lambda do |*| gt?(0) > lt?(10) end end describe "[true => true]" do it_behaves_like "operation" do let(:input) { 5 } let(:output) { true } end end describe "[true => false]" do it_behaves_like "operation" do let(:input) { 20 } let(:output) { false } end end describe "[false => true]" do it_behaves_like "operation" do let(:input) { -5 } let(:output) { true } end end end describe :then do let(:expression) do lambda do |*| gt?(0).then(lt?(10)) end end describe "[true => true]" do it_behaves_like "operation" do let(:input) { 5 } let(:output) { true } end end describe "[true => false]" do it_behaves_like "operation" do let(:input) { 20 } let(:output) { false } end end describe "[false => true]" do it_behaves_like "operation" do let(:input) { -5 } let(:output) { true } end end end describe :| do let(:expression) do lambda do |*| even? | odd? end end describe "success" do it_behaves_like "operation" do let(:input) { 10 } let(:output) { true } end it_behaves_like "operation" do let(:input) { 9 } let(:output) { true } end end end end end dry-logic-1.2.0/spec/integration/builder/predicate_spec.rb000066400000000000000000000551341404145603600236200ustar00rootroot00000000000000# frozen_string_literal: true require_relative "../../shared/predicate" RSpec.describe "predicates" do let(:input) { described_class } describe :key? do let(:expression) { ->(*) { key?(:speed) } } describe "success" do let(:output) { true } describe({speed: 100}) do it_behaves_like "predicate" end end describe "failure" do let(:output) { false } describe({age: 50}) do it_behaves_like "predicate" end end end describe :format? do let(:expression) { ->(*) { format?(/^(A|B)$/) } } describe "success" do let(:output) { true } it_behaves_like "predicate" do let(:input) { "A" } end it_behaves_like "predicate" do let(:input) { "B" } end end describe "failure" do let(:output) { false } it_behaves_like "predicate" do let(:input) { "C" } end it_behaves_like "predicate" do let(:input) { "D" } end end end describe :type? do let(:expression) { ->(*) { type?(String) } } describe "success" do let(:output) { true } describe "string" do it_behaves_like "predicate" do let(:input) { "string" } end end end describe "failure" do let(:output) { false } it_behaves_like "predicate" do let(:input) { :symbol } end end end describe :nil? do let(:expression) { ->(*) { nil? } } describe "success" do let(:output) { true } it_behaves_like "predicate" do let(:input) { nil } end end describe "failure" do let(:output) { false } describe :symbol do it_behaves_like "predicate" end end end describe :attr? do let(:expression) { ->(*) { attr?(:name) } } describe "success" do let(:output) { true } describe Struct.new(:name).new("John") do it_behaves_like "predicate" end end describe "failure" do let(:output) { false } describe Struct.new(:age).new(50) do it_behaves_like "predicate" end end end describe :empty? do let(:expression) { ->(*) { empty? } } describe "success" do let(:output) { true } describe({}) do it_behaves_like "predicate" end describe String do it_behaves_like "predicate" do let(:input) { described_class.new } end end describe [] do it_behaves_like "predicate" end end describe "failure" do let(:output) { false } describe({key: "value"}) do it_behaves_like "predicate" end describe "string" do it_behaves_like "predicate" end describe nil do it_behaves_like "predicate" end describe [1, 2] do it_behaves_like "predicate" end end end describe :filled? do let(:expression) { ->(*) { filled? } } describe "success" do let(:output) { true } describe({key: "value"}) do it_behaves_like "predicate" end describe "string" do it_behaves_like "predicate" end describe nil do it_behaves_like "predicate" end describe [1, 2] do it_behaves_like "predicate" end end describe "failure" do let(:output) { false } describe({}) do it_behaves_like "predicate" end describe String do it_behaves_like "predicate" do let(:input) { described_class.new } end end describe [] do it_behaves_like "predicate" end end end describe :bool? do let(:expression) { ->(*) { bool? } } describe "success" do let(:output) { true } describe true do it_behaves_like "predicate" end describe false do it_behaves_like "predicate" end end describe "failure" do let(:output) { false } describe :symbol do it_behaves_like "predicate" end describe 5 do it_behaves_like "predicate" end end end describe :date? do let(:expression) { ->(*) { date? } } describe "success" do let(:output) { true } describe Date.new do it_behaves_like "predicate" end end describe "failure" do let(:output) { false } describe :symbol do it_behaves_like "predicate" end end end describe :date_time? do let(:expression) { ->(*) { date_time? } } describe "success" do let(:output) { true } it_behaves_like "predicate" do let(:input) { DateTime.new } end end describe "failure" do let(:output) { false } it_behaves_like "predicate" do let(:input) { :symbol } end end end describe :time? do let(:expression) { ->(*) { time? } } describe "success" do let(:output) { true } it_behaves_like "predicate" do let(:input) { Time.new } end end describe "failure" do let(:output) { false } it_behaves_like "predicate" do let(:input) { :symbol } end end end describe :number? do let(:expression) { ->(*) { number? } } describe "success" do let(:output) { true } it_behaves_like "predicate" do let(:input) { -4 } end it_behaves_like "predicate" do let(:input) { 10.0 } end it_behaves_like "predicate" do let(:input) { 10 } end end describe "failure" do let(:output) { false } it_behaves_like "predicate" do let(:input) { "A-4" } end it_behaves_like "predicate" do let(:input) { "A10" } end it_behaves_like "predicate" do let(:input) { nil } end it_behaves_like "predicate" do let(:input) { :nope } end end end describe :int? do let(:expression) { ->(*) { int? } } describe "success" do let(:output) { true } it_behaves_like "predicate" do let(:input) { 10 } end end describe "failure" do let(:output) { false } it_behaves_like "predicate" do let(:input) { 10.0 } end end end describe :float? do let(:expression) { ->(*) { float? } } describe "success" do let(:output) { true } it_behaves_like "predicate" do let(:input) { 1.0 } end end describe "success" do let(:output) { false } it_behaves_like "predicate" do let(:input) { 1 } end end end describe :decimal? do let(:expression) { ->(*) { decimal? } } describe "success" do let(:output) { true } it_behaves_like "predicate" do let(:input) { BigDecimal(1) } end end describe "failure" do let(:output) { false } it_behaves_like "predicate" do let(:input) { 10 } end end end describe :str? do let(:expression) { ->(*) { str? } } describe "success" do let(:output) { true } describe String do it_behaves_like "predicate" do let(:input) { described_class.new } end end end describe "failure" do let(:output) { false } describe Array do it_behaves_like "predicate" do let(:input) { described_class.new } end end end end describe :hash? do let(:expression) { ->(*) { hash? } } describe "success" do let(:output) { true } describe Hash do it_behaves_like "predicate" do let(:input) { described_class.new } end end end describe "failure" do let(:output) { false } describe Array do it_behaves_like "predicate" do let(:input) { described_class.new } end end end end describe :array? do let(:expression) { ->(*) { array? } } describe "success" do let(:output) { true } describe Array do it_behaves_like "predicate" do let(:input) { described_class.new } end end end describe "failure" do let(:output) { false } describe Hash do it_behaves_like "predicate" do let(:input) { described_class.new } end end end end describe :even? do let(:expression) { ->(*) { even? } } describe "success" do let(:output) { true } describe 10 do it_behaves_like "predicate" do let(:input) { described_class } end end end describe "failure" do let(:output) { false } describe 5 do it_behaves_like "predicate" do let(:input) { described_class } end end end end describe :odd? do let(:expression) { ->(*) { odd? } } describe "success" do let(:output) { true } describe 5 do it_behaves_like "predicate" do let(:input) { described_class } end end end describe "failure" do let(:output) { false } describe 10 do it_behaves_like "predicate" do let(:input) { described_class } end end end end describe :lt? do let(:expression) { ->(*) { lt?(10) } } describe "success" do let(:output) { true } describe 5 do it_behaves_like "predicate" do let(:input) { described_class } end end end describe "failure" do let(:output) { false } describe 200 do it_behaves_like "predicate" do let(:input) { described_class } end end end end describe :gt? do let(:expression) { ->(*) { gt?(10) } } describe "failure" do let(:output) { false } describe 5 do it_behaves_like "predicate" do let(:input) { described_class } end end end describe "success" do let(:output) { true } describe 200 do it_behaves_like "predicate" do let(:input) { described_class } end end end end describe :gteq? do let(:expression) { ->(*) { gteq?(10) } } describe "success" do let(:output) { true } describe 10 do it_behaves_like "predicate" do let(:input) { described_class } end end describe 11 do it_behaves_like "predicate" do let(:input) { described_class } end end end describe "failure" do let(:output) { false } describe 9 do it_behaves_like "predicate" do let(:input) { described_class } end end end end describe :lteq? do let(:expression) { ->(*) { lteq?(10) } } describe "success" do let(:output) { true } describe 9 do it_behaves_like "predicate" end end describe "failure" do let(:output) { false } describe 11 do it_behaves_like "predicate" end end end describe :size? do describe "success" do let(:output) { true } describe "Integer" do it_behaves_like "predicate" do let(:expression) { ->(*) { size?(2) } } let(:input) { [1, 2] } end end describe "Range" do it_behaves_like "predicate" do let(:expression) { ->(*) { size?(1..3) } } let(:input) { [1, 2] } end end describe "Array" do it_behaves_like "predicate" do let(:expression) { ->(*) { size?([3]) } } let(:input) { [1, 2, 3] } end end end describe "failure" do let(:output) { false } describe "Integer" do it_behaves_like "predicate" do let(:expression) { ->(*) { size?(2) } } let(:input) { [1] } end end describe "Range" do it_behaves_like "predicate" do let(:expression) { ->(*) { size?(1..3) } } let(:input) { [1, 2, 3, 4] } end end describe "Array" do it_behaves_like "predicate" do let(:expression) { ->(*) { size?([3]) } } let(:input) { [1, 2] } end end end end describe :min_size? do let(:expression) { ->(*) { min_size?(2) } } describe "success" do describe [1, 2, 3] do let(:output) { true } it_behaves_like "predicate" do let(:input) { described_class } end end end describe "failure" do describe [1] do let(:output) { false } it_behaves_like "predicate" do let(:input) { described_class } end end end end describe :max_size? do let(:expression) { ->(*) { max_size?(2) } } describe "success" do describe [1] do let(:output) { true } it_behaves_like "predicate" do let(:input) { described_class } end end end describe "failure" do describe [1, 2, 3] do let(:output) { false } it_behaves_like "predicate" do let(:input) { described_class } end end end end describe :bytesize? do describe "success" do let(:output) { true } describe "Integer" do let(:expression) { ->(*) { bytesize?(1) } } it_behaves_like "predicate" do let(:input) { "A" } end end describe "Range" do let(:expression) { ->(*) { bytesize?(2..3) } } it_behaves_like "predicate" do let(:input) { "AB" } end end describe "Array" do let(:expression) { ->(*) { bytesize?([2, 3]) } } it_behaves_like "predicate" do let(:input) { "AB" } end end end describe "failure" do let(:output) { false } describe "Integer" do let(:expression) { ->(*) { bytesize?(1) } } it_behaves_like "predicate" do let(:input) { "AB" } end end describe "Range" do let(:expression) { ->(*) { bytesize?(2..3) } } it_behaves_like "predicate" do let(:input) { "A" } end end describe "Array" do let(:expression) { ->(*) { bytesize?([2, 3]) } } it_behaves_like "predicate" do let(:input) { "A" } end end end end describe :min_bytesize? do let(:expression) { ->(*) { min_bytesize?(1) } } describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { "A" } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { "" } end end end describe :max_bytesize? do let(:expression) { ->(*) { max_bytesize?(1) } } describe "success" do it_behaves_like "predicate" do let(:output) { false } let(:input) { "AB" } end end describe "failure" do it_behaves_like "predicate" do let(:output) { true } let(:input) { "" } end end end describe :included_in? do let(:expression) { ->(*) { included_in?([1, 2, 3]) } } describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { 1 } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { 4 } end end end describe :excluded_from? do let(:expression) { ->(*) { excluded_from?([1, 2, 3]) } } describe "success" do it_behaves_like "predicate" do let(:output) { false } let(:input) { 1 } end end describe "failure" do it_behaves_like "predicate" do let(:output) { true } let(:input) { 4 } end end end describe :includes? do let(:expression) { ->(*) { includes?(1) } } describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { [1, 2, 3] } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { [2, 3, 4] } end end end describe :excludes? do describe Array do let(:expression) { ->(*) { excludes?(1) } } describe "success" do it_behaves_like "predicate" do let(:output) { false } let(:input) { described_class.new([1, 2, 3]) } end end describe "failure" do it_behaves_like "predicate" do let(:output) { true } let(:input) { described_class.new([2, 3, 4]) } end end end describe String do let(:expression) { ->(*) { excludes?("A") } } describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { described_class.new("B") } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { described_class.new("A") } end end end end describe "compare methods" do describe :eql? do let(:expression) { ->(*) { eql?(10) } } describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { 10 } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { 20 } end end end describe :not_eql? do let(:expression) { ->(*) { not_eql?(10) } } describe "success" do it_behaves_like "predicate" do let(:output) { false } let(:input) { 10 } end end describe "failure" do it_behaves_like "predicate" do let(:output) { true } let(:input) { 20 } end end end end describe :is? do describe "success" do it_behaves_like "predicate" do let(:expression) { ->(*) { is?(nil) } } let(:output) { true } let(:input) { nil } end end describe "failure" do it_behaves_like "predicate" do let(:expression) { ->(*) { is?(Class.new) } } let(:output) { false } let(:input) { Class.new } end end end describe "true? & false?" do describe :true? do let(:expression) { ->(*) { true? } } describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { true } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { false } end end end describe :false? do let(:expression) { ->(*) { false? } } describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { false } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { true } end end end end describe :case? do describe "Range" do let(:expression) { ->(*) { case?(5..10) } } describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { 6 } end end end describe "Fixnum" do let(:expression) { ->(*) { case?(Integer) } } describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { 10 } end end end end describe "uuid" do describe :uuid_v2? describe :uuid_v3? describe :uuid_v5? describe :uuid_v1? do let(:expression) do ->(*) { uuid_v1? } end describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { "554ef240-5433-11eb-ae93-0242ac130002" } end end end describe :uuid_v4? do let(:expression) do ->(*) { uuid_v4? } end describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { "f2711f00-5602-45c7-ae01-c94d285592c3" } end end end end describe :uri? do describe :http do let(:expression) do ->(*) { uri?(:http) } end describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { "http://google.com" } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { "https://google.com" } end end end describe [:http, :https] do let(:expression) do ->(*) { uri?(%w[http https]) } end describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { "https://google.com" } end it_behaves_like "predicate" do let(:output) { true } let(:input) { "http://google.com" } end end end describe Regexp do let(:expression) do ->(*) { uri?(/https?/) } end describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { "https://google.com" } end it_behaves_like "predicate" do let(:output) { true } let(:input) { "http://google.com" } end end end describe :https do let(:expression) do ->(*) { uri?(:https) } end describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { "https://google.com" } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { "http://google.com" } end end end end describe :respond_to? do let(:expression) do ->(*) { respond_to?(:awesome?) } end describe "success" do it_behaves_like "predicate" do let(:output) { true } let(:input) { Struct.new(:awesome?).new(true) } end end describe "failure" do it_behaves_like "predicate" do let(:output) { false } let(:input) { Struct.new(:not_awesome?).new(true) } end end end describe :predicate do before { extend Dry::Logic::Builder } before(:each) do build do predicate :divisible_with? do |num, input| (input % num).zero? end end end let(:expression) do lambda do |*| divisible_with?(10) end end describe "success" do it_behaves_like "predicate" do let(:input) { 10 } let(:output) { true } end end describe "success" do it_behaves_like "predicate" do let(:input) { 3 } let(:output) { false } end end end end dry-logic-1.2.0/spec/integration/result_spec.rb000066400000000000000000000036541404145603600215500ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Result do include_context "predicates" describe "#to_s" do shared_examples_for "string representation" do it "returns string representation" do expect(rule.(input).to_s).to eql(output) end end context "with a predicate" do let(:rule) { Dry::Logic::Rule::Predicate.build(gt?, args: [18]) } let(:input) { 17 } let(:output) { "gt?(18, 17)" } it_behaves_like "string representation" end context "with AND operation" do let(:rule) do Dry::Logic::Rule::Predicate.build(array?).and(Dry::Logic::Rule::Predicate.build(empty?)) end let(:input) { "" } let(:output) { 'array?("") AND empty?("")' } it_behaves_like "string representation" end context "with OR operation" do let(:rule) do Dry::Logic::Rule::Predicate.build(array?).or(Dry::Logic::Rule::Predicate.build(empty?)) end let(:input) { 123 } let(:output) { "array?(123) OR empty?(123)" } it_behaves_like "string representation" end context "with XOR operation" do let(:rule) do Dry::Logic::Rule::Predicate.build(array?).xor(Dry::Logic::Rule::Predicate.build(empty?)) end let(:input) { [] } let(:output) { "array?([]) XOR empty?([])" } it_behaves_like "string representation" end context "with THEN operation" do let(:rule) do Dry::Logic::Rule::Predicate.build(array?).then(Dry::Logic::Rule::Predicate.build(empty?)) end let(:input) { [1, 2, 3] } let(:output) { "empty?([1, 2, 3])" } it_behaves_like "string representation" end context "with NOT operation" do let(:rule) { Dry::Logic::Operations::Negation.new(Dry::Logic::Rule::Predicate.build(array?)) } let(:input) { "foo" } let(:output) { 'not(array?("foo"))' } it_behaves_like "string representation" end end end dry-logic-1.2.0/spec/integration/rule_spec.rb000066400000000000000000000030761404145603600211770ustar00rootroot00000000000000# frozen_string_literal: true require "dry-logic" RSpec.describe "Rules" do specify "defining an anonymous rule with an arbitrary predicate" do rule = Dry::Logic.Rule { |value| value.is_a?(Integer) } expect(rule.(1)).to be_success expect(rule[1]).to be(true) end specify "defining a conjunction" do rule = Dry::Logic.Rule(&:even?) & Dry::Logic.Rule { |v| v > 4 } expect(rule.(3)).to be_failure expect(rule.(4)).to be_failure expect(rule.(5)).to be_failure expect(rule.(6)).to be_success end specify "defining a disjunction" do rule = Dry::Logic.Rule { |v| v < 4 } | Dry::Logic.Rule { |v| v > 6 } expect(rule.(5)).to be_failure expect(rule.(3)).to be_success expect(rule.(7)).to be_success end specify "defining an implication" do rule = Dry::Logic.Rule(&:empty?) > Dry::Logic.Rule { |v| v.is_a?(Array) } expect(rule.("foo")).to be_success expect(rule.([1, 2])).to be_success expect(rule.([])).to be_success expect(rule.("")).to be_failure end specify "defining an exclusive disjunction" do rule = Dry::Logic.Rule(&:empty?) ^ Dry::Logic.Rule { |v| v.is_a?(Array) } expect(rule.("foo")).to be_failure expect(rule.([])).to be_failure expect(rule.([1, 2])).to be_success expect(rule.("")).to be_success end specify "defining a rule with options" do rule = Dry::Logic::Rule(id: :empty?) { |value| value.empty? } expect(rule.("foo")).to be_failure expect(rule.("")).to be_success expect(rule.ast("foo")).to eql([:predicate, [:empty?, [[:value, "foo"]]]]) end end dry-logic-1.2.0/spec/shared/000077500000000000000000000000001404145603600156065ustar00rootroot00000000000000dry-logic-1.2.0/spec/shared/operation.rb000066400000000000000000000004661404145603600201410ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/builder" RSpec.shared_examples "operation" do before { extend Dry::Logic::Builder } let(:operation) { build(&expression) } let(:args) { defined?(input) ? [input] : [] } subject { operation.call(*args).success? } it { is_expected.to eq(output) } end dry-logic-1.2.0/spec/shared/predicate.rb000066400000000000000000000005261404145603600200760ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/builder" # TODO: Merge with {operation}? RSpec.shared_examples "predicate" do before { extend Dry::Logic::Builder } let(:predicate) { build(&expression) } let(:args) { defined?(input) ? [input] : [] } subject { predicate.call(*args).success? } it { is_expected.to eq(output) } end dry-logic-1.2.0/spec/shared/predicates.rb000066400000000000000000000025571404145603600202670ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.shared_examples "predicates" do let(:nil?) { Dry::Logic::Predicates[:nil?] } let(:array?) { Dry::Logic::Predicates[:array?] } let(:empty?) { Dry::Logic::Predicates[:empty?] } let(:str?) { Dry::Logic::Predicates[:str?] } let(:true?) { Dry::Logic::Predicates[:true?] } let(:hash?) { Dry::Logic::Predicates[:hash?] } let(:int?) { Dry::Logic::Predicates[:int?] } let(:filled?) { Dry::Logic::Predicates[:filled?] } let(:min_size?) { Dry::Logic::Predicates[:min_size?] } let(:lt?) { Dry::Logic::Predicates[:lt?] } let(:gt?) { Dry::Logic::Predicates[:gt?] } let(:key?) { Dry::Logic::Predicates[:key?] } let(:attr?) { Dry::Logic::Predicates[:attr?] } let(:eql?) { Dry::Logic::Predicates[:eql?] } let(:size?) { Dry::Logic::Predicates[:size?] } let(:case?) { Dry::Logic::Predicates[:case?] } let(:equal?) { Dry::Logic::Predicates[:equal?] } end RSpec.shared_examples "a passing predicate" do let(:predicate) { Dry::Logic::Predicates[predicate_name] } it do arguments_list.each do |args| expect(predicate.call(*args)).to be(true) end end end RSpec.shared_examples "a failing predicate" do let(:predicate) { Dry::Logic::Predicates[predicate_name] } it do arguments_list.each do |args| expect(predicate.call(*args)).to be(false) end end end dry-logic-1.2.0/spec/shared/rule.rb000066400000000000000000000034711404145603600171070ustar00rootroot00000000000000# frozen_string_literal: true RSpec.shared_examples_for Dry::Logic::Rule do let(:arity) { 2 } let(:predicate) { double(:predicate, arity: arity, name: predicate_name) } let(:rule_type) { described_class } let(:predicate_name) { :good? } describe "#arity" do it "returns its predicate arity" do rule = rule_type.build(predicate) expect(rule.arity).to be(2) end end describe "#parameters" do it "returns a list of args with their names" do rule = rule_type.build(-> foo, bar { true }, args: [312]) expect(rule.parameters).to eql([%i[req foo], %i[req bar]]) end end describe "#call" do let(:arity) { 1 } it "returns success for valid input" do rule = rule_type.build(predicate) expect(predicate).to receive(:[]).with(2).and_return(true) expect(rule.(2)).to be_success end it "returns failure for invalid input" do rule = rule_type.build(predicate) expect(predicate).to receive(:[]).with(2).and_return(false) expect(rule.(2)).to be_failure end end describe "#[]" do let(:arity) { 1 } it "delegates to its predicate" do rule = rule_type.build(predicate) expect(predicate).to receive(:[]).with(2).and_return(true) expect(rule[2]).to be(true) end end describe "#curry" do it "returns a curried rule" do rule = rule_type.build(predicate).curry(3) expect(predicate).to receive(:[]).with(3, 2).and_return(true) expect(rule.args).to eql([3]) expect(rule.(2)).to be_success end it "raises argument error when arity does not match" do expect(predicate).to receive(:arity).and_return(2) expect { rule_type.build(predicate).curry(3, 2, 1) }.to raise_error( ArgumentError, "wrong number of arguments (3 for 2)" ) end end end dry-logic-1.2.0/spec/spec_helper.rb000066400000000000000000000010501404145603600171520ustar00rootroot00000000000000# frozen_string_literal: true require_relative "support/coverage" require_relative "support/warnings" require_relative "support/rspec_options" begin require "pry-byebug" rescue LoadError; end require "dry-logic" require "dry/core/constants" require "pathname" SPEC_ROOT = Pathname(__dir__) Dir[SPEC_ROOT.join("shared/**/*.rb")].each(&method(:require)) Dir[SPEC_ROOT.join("support/**/*.rb")].each(&method(:require)) RSpec.configure do |config| config.include Module.new { def undefined Dry::Core::Constants::Undefined end } end dry-logic-1.2.0/spec/support/000077500000000000000000000000001404145603600160545ustar00rootroot00000000000000dry-logic-1.2.0/spec/support/coverage.rb000066400000000000000000000004631404145603600201770ustar00rootroot00000000000000# frozen_string_literal: true # this file is managed by dry-rb/devtools if ENV["COVERAGE"] == "true" require "simplecov" require "simplecov-cobertura" SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter SimpleCov.start do add_filter "/spec/" enable_coverage :branch end end dry-logic-1.2.0/spec/support/mutant.rb000066400000000000000000000002551404145603600177130ustar00rootroot00000000000000# frozen_string_literal: true module Mutant class Selector class Expression < self def call(_subject) integration.all_tests end end end end dry-logic-1.2.0/spec/support/rspec_options.rb000066400000000000000000000007351404145603600212750ustar00rootroot00000000000000RSpec.configure do |config| # When no filter given, search and run focused tests config.filter_run_when_matching :focus # Disables rspec monkey patches (no reason for their existence tbh) config.disable_monkey_patching! # Run ruby in verbose mode config.warnings = true # Collect all failing expectations automatically, # without calling aggregate_failures everywhere config.define_derived_metadata do |meta| meta[:aggregate_failures] = true end end dry-logic-1.2.0/spec/support/warnings.rb000066400000000000000000000003701404145603600202310ustar00rootroot00000000000000# frozen_string_literal: true # this file is managed by dry-rb/devtools project require "warning" Warning.ignore(%r{rspec/core}) Warning.ignore(%r{rspec/mocks}) Warning.ignore(/codacy/) Warning[:experimental] = false if Warning.respond_to?(:[]) dry-logic-1.2.0/spec/unit/000077500000000000000000000000001404145603600153175ustar00rootroot00000000000000dry-logic-1.2.0/spec/unit/builder_spec.rb000066400000000000000000000011311404145603600203000ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Builder do describe "undefined methods" do it "raises NameError" do expect do described_class.call { does_not_exist } end.to raise_error(NameError, /does_not_exist/) end end describe "leakage" do context "given a module extending ::Builder" do subject do Module.new do extend Dry::Logic::Builder end end it { is_expected.not_to respond_to(:int?) } it { is_expected.to respond_to(:call) } it { is_expected.to respond_to(:build) } end end end dry-logic-1.2.0/spec/unit/operations/000077500000000000000000000000001404145603600175025ustar00rootroot00000000000000dry-logic-1.2.0/spec/unit/operations/and_spec.rb000066400000000000000000000037401404145603600216070ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::And do subject(:operation) { described_class.new(left, right) } include_context "predicates" let(:left) { Dry::Logic::Rule::Predicate.build(int?) } let(:right) { Dry::Logic::Rule::Predicate.build(gt?).curry(18) } describe "#call" do it "calls left and right" do expect(operation.(18)).to be_failure end end describe "#to_ast" do it "returns ast" do expect(operation.to_ast).to eql( [:and, [[:predicate, [:int?, [[:input, undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, undefined]]]]]] ) end it "returns result ast" do expect(operation.("18").to_ast).to eql( [:and, [[:predicate, [:int?, [[:input, "18"]]]], [:hint, [:predicate, [:gt?, [[:num, 18], [:input, "18"]]]]]]] ) expect(operation.with(hints: false).("18").to_ast).to eql( [:predicate, [:int?, [[:input, "18"]]]] ) expect(operation.(18).to_ast).to eql( [:predicate, [:gt?, [[:num, 18], [:input, 18]]]] ) end it "returns failure result ast" do expect(operation.with(id: :age).("18").to_ast).to eql( [:failure, [:age, [:and, [[:predicate, [:int?, [[:input, "18"]]]], [:hint, [:predicate, [:gt?, [[:num, 18], [:input, "18"]]]]]]]]] ) expect(operation.with(id: :age).(18).to_ast).to eql( [:failure, [:age, [:predicate, [:gt?, [[:num, 18], [:input, 18]]]]]] ) end end describe "#and" do let(:other) { Dry::Logic::Rule::Predicate.build(lt?).curry(30) } it "creates and with the other" do expect(operation.and(other).(31)).to be_failure end end describe "#or" do let(:other) { Dry::Logic::Rule::Predicate.build(lt?).curry(14) } it "creates or with the other" do expect(operation.or(other).(13)).to be_success end end describe "#to_s" do it "returns string representation" do expect(operation.to_s).to eql("int? AND gt?(18)") end end end dry-logic-1.2.0/spec/unit/operations/attr_spec.rb000066400000000000000000000016541404145603600220210ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::Attr do subject(:operation) do Dry::Logic::Operations::Attr.new(Dry::Logic::Rule::Predicate.build(str?), name: :name) end include_context "predicates" let(:model) { Struct.new(:name) } describe "#call" do it "applies predicate to the value" do expect(operation.(model.new("Jane"))).to be_success expect(operation.(model.new(nil))).to be_failure end end describe "#and" do let(:other) do Dry::Logic::Operations::Attr.new(Dry::Logic::Rule::Predicate.build(min_size?).curry(3), name: :name) end it "returns and where value is passed to the right" do present_and_string = operation.and(other) expect(present_and_string.(model.new("Jane"))).to be_success expect(present_and_string.(model.new("Ja"))).to be_failure expect(present_and_string.(model.new(1))).to be_failure end end end dry-logic-1.2.0/spec/unit/operations/check_spec.rb000066400000000000000000000041431404145603600221200ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::Check do include_context "predicates" describe "#call" do context "with 1-level nesting" do subject(:operation) do described_class.new(Dry::Logic::Rule::Predicate.build(eql?).curry(1), id: :compare, keys: [:num]) end it "applies predicate to args extracted from the input" do expect(operation.(num: 1)).to be_success expect(operation.(num: 2)).to be_failure end end context "with 2-levels nesting" do subject(:operation) do described_class.new( Dry::Logic::Rule::Predicate.build(eql?), id: :compare, keys: [[:nums, :left], [:nums, :right]] ) end it "applies predicate to args extracted from the input" do expect(operation.(nums: {left: 1, right: 1})).to be_success expect(operation.(nums: {left: 1, right: 2})).to be_failure end it "curries args properly" do result = operation.(nums: {left: 1, right: 2}) expect(result.to_ast).to eql( [:failure, [:compare, [:check, [ [[:nums, :left], [:nums, :right]], [:predicate, [:eql?, [[:left, 1], [:right, 2]]]] ]]]] ) end end context "with its output as input" do let(:gt?) { Dry::Logic::Predicates[:gt?] } let(:min) { Dry::Logic::Rule::Predicate.new(gt?).curry(18) } let(:inner) { described_class.new(min, keys: [:age]) } let(:outer) { described_class.new(inner, keys: [:person]) } subject { outer.call(input) } describe "success" do let(:input) { {person: {age: 20}} } it { is_expected.to be_a_success } end describe "failure" do let(:input) { {person: {age: 10}} } it { is_expected.not_to be_a_success } end end end describe "#to_ast" do subject(:operation) do described_class.new(Dry::Logic::Rule::Predicate.build(str?), keys: [:email]) end it "returns ast" do expect(operation.to_ast).to eql( [:check, [[:email], [:predicate, [:str?, [[:input, undefined]]]]]] ) end end end dry-logic-1.2.0/spec/unit/operations/each_spec.rb000066400000000000000000000026011404145603600217400ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::Each do subject(:operation) { described_class.new(is_string) } include_context "predicates" let(:is_string) { Dry::Logic::Rule::Predicate.build(str?) } describe "#call" do it "applies its rules to all elements in the input" do expect(operation.(["Address"])).to be_success expect(operation.([nil, "Address"])).to be_failure expect(operation.([:Address, "Address"])).to be_failure end end describe "#to_ast" do it "returns ast" do expect(operation.to_ast).to eql([:each, [:predicate, [:str?, [[:input, undefined]]]]]) end it "returns result ast" do expect(operation.([nil, 12, nil]).to_ast).to eql( [:set, [ [:key, [0, [:predicate, [:str?, [[:input, nil]]]]]], [:key, [1, [:predicate, [:str?, [[:input, 12]]]]]], [:key, [2, [:predicate, [:str?, [[:input, nil]]]]]] ]] ) end it "returns failure result ast" do expect(operation.with(id: :tags).([nil, "red", 12]).to_ast).to eql( [:failure, [:tags, [:set, [ [:key, [0, [:predicate, [:str?, [[:input, nil]]]]]], [:key, [2, [:predicate, [:str?, [[:input, 12]]]]]] ]]]] ) end end describe "#to_s" do it "returns string representation" do expect(operation.to_s).to eql("each(str?)") end end end dry-logic-1.2.0/spec/unit/operations/implication_spec.rb000066400000000000000000000015601404145603600233530ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::Implication do subject(:operation) { described_class.new(left, right) } include_context "predicates" let(:left) { Dry::Logic::Rule::Predicate.build(int?) } let(:right) { Dry::Logic::Rule::Predicate.build(gt?).curry(18) } describe "#call" do it "calls left and right" do expect(operation.("19")).to be_success expect(operation.(19)).to be_success expect(operation.(18)).to be_failure end end describe "#to_ast" do it "returns ast" do expect(operation.to_ast).to eql( [:implication, [[:predicate, [:int?, [[:input, undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, undefined]]]]]] ) end end describe "#to_s" do it "returns string representation" do expect(operation.to_s).to eql("int? THEN gt?(18)") end end end dry-logic-1.2.0/spec/unit/operations/key_spec.rb000066400000000000000000000073531404145603600216410ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::Key do subject(:operation) { described_class.new(predicate, name: :user) } include_context "predicates" let(:predicate) do Dry::Logic::Rule::Predicate.build(key?).curry(:age) end describe "#call" do context "with a plain predicate" do it "returns a success for valid input" do expect(operation.(user: {age: 18})).to be_success end it "returns a failure for invalid input" do result = operation.(user: {}) expect(result).to be_failure expect(result.to_ast).to eql( [:failure, [:user, [:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, {}]]]]]]]] ) end end context "with a set rule as predicate" do subject(:operation) do described_class.new(predicate, name: :address) end let(:predicate) do Dry::Logic::Operations::Set.new( Dry::Logic::Rule::Predicate.build(key?).curry(:city), Dry::Logic::Rule::Predicate.build(key?).curry(:zipcode) ) end it "applies set rule to the value that passes" do result = operation.(address: {city: "NYC", zipcode: "123"}) expect(result).to be_success end it "applies set rule to the value that fails" do result = operation.(address: {city: "NYC"}) expect(result).to be_failure expect(result.to_ast).to eql( [:failure, [:address, [:key, [:address, [:set, [ [:predicate, [:key?, [[:name, :zipcode], [:input, {city: "NYC"}]]]] ]]]]]] ) end end context "with an each rule as predicate" do subject(:operation) do described_class.new(predicate, name: :nums) end let(:predicate) do Dry::Logic::Operations::Each.new(Dry::Logic::Rule::Predicate.build(str?)) end it "applies each rule to the value that passses" do result = operation.(nums: %w[1 2 3]) expect(result).to be_success end it "applies each rule to the value that fails" do failure = operation.(nums: [1, "3", 3]) expect(failure).to be_failure expect(failure.to_ast).to eql( [:failure, [:nums, [:key, [:nums, [:set, [ [:key, [0, [:predicate, [:str?, [[:input, 1]]]]]], [:key, [2, [:predicate, [:str?, [[:input, 3]]]]]] ]]]]]] ) end end end describe "#to_ast" do it "returns ast" do expect(operation.to_ast).to eql( [:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, undefined]]]]]] ) end end describe "#ast" do it "returns ast without the input" do expect(operation.ast).to eql( [:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, undefined]]]]]] ) end it "returns ast with the input" do expect(operation.ast(user: "jane")).to eql( [:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, "jane"]]]]]] ) end end describe "#and" do subject(:operation) do described_class.new(Dry::Logic::Rule::Predicate.build(str?), name: [:user, :name]) end let(:other) do described_class.new(Dry::Logic::Rule::Predicate.build(filled?), name: [:user, :name]) end it "returns and rule where value is passed to the right" do present_and_string = operation.and(other) expect(present_and_string.(user: {name: "Jane"})).to be_success expect(present_and_string.(user: {})).to be_failure expect(present_and_string.(user: {name: 1})).to be_failure end end describe "#to_s" do it "returns string representation" do expect(operation.to_s).to eql("key[user](key?(:age))") end end end dry-logic-1.2.0/spec/unit/operations/negation_spec.rb000066400000000000000000000023741404145603600226530ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::Negation do subject(:operation) { described_class.new(is_int) } include_context "predicates" let(:is_int) { Dry::Logic::Rule::Predicate.build(int?) } describe "#call" do it "negates its rule" do expect(operation.("19")).to be_success expect(operation.(17)).to be_failure end context "double negation" do subject(:double_negation) { described_class.new(operation) } it "works as rule" do expect(double_negation.("19")).to be_failure expect(double_negation.(17)).to be_success end end end describe "#to_ast" do it "returns ast" do expect(operation.to_ast).to eql( [:not, [:predicate, [:int?, [[:input, undefined]]]]] ) end it "returns result ast" do expect(operation.(17).to_ast).to eql( [:not, [:predicate, [:int?, [[:input, 17]]]]] ) end it "returns result ast with an :id" do expect(operation.with(id: :age).(17).to_ast).to eql( [:failure, [:age, [:not, [:predicate, [:int?, [[:input, 17]]]]]]] ) end end describe "#to_s" do it "returns string representation" do expect(operation.to_s).to eql("not(int?)") end end end dry-logic-1.2.0/spec/unit/operations/or_spec.rb000066400000000000000000000040431404145603600214620ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::Or do subject(:operation) { described_class.new(left, right) } include_context "predicates" let(:left) { Dry::Logic::Rule::Predicate.build(nil?) } let(:right) { Dry::Logic::Rule::Predicate.build(gt?).curry(18) } let(:other) do Dry::Logic::Rule::Predicate.build(int?) & Dry::Logic::Rule::Predicate.build(lt?).curry(14) end describe "#call" do it "calls left and right" do expect(operation.(nil)).to be_success expect(operation.(19)).to be_success expect(operation.(18)).to be_failure end end describe "#to_ast" do it "returns ast" do expect(operation.to_ast).to eql( [:or, [ [:predicate, [:nil?, [[:input, undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, undefined]]]] ]] ) end it "returns result ast" do expect(operation.(17).to_ast).to eql( [:or, [ [:predicate, [:nil?, [[:input, 17]]]], [:predicate, [:gt?, [[:num, 18], [:input, 17]]]] ]] ) end it "returns failure result ast" do expect(operation.with(id: :age).(17).to_ast).to eql( [:failure, [:age, [:or, [ [:predicate, [:nil?, [[:input, 17]]]], [:predicate, [:gt?, [[:num, 18], [:input, 17]]]] ]]]] ) end end describe "#and" do it "creates and with the other" do expect(operation.and(other).(nil)).to be_failure expect(operation.and(other).(19)).to be_failure expect(operation.and(other).(13)).to be_failure expect(operation.and(other).(14)).to be_failure end end describe "#or" do it "creates or with the other" do expect(operation.or(other).(nil)).to be_success expect(operation.or(other).(19)).to be_success expect(operation.or(other).(13)).to be_success expect(operation.or(other).(14)).to be_failure end end describe "#to_s" do it "returns string representation" do expect(operation.to_s).to eql("nil? OR gt?(18)") end end end dry-logic-1.2.0/spec/unit/operations/set_spec.rb000066400000000000000000000022651404145603600216410ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::Set do subject(:operation) { described_class.new(is_int, gt_18) } include_context "predicates" let(:is_int) { Dry::Logic::Rule::Predicate.build(int?) } let(:gt_18) { Dry::Logic::Rule::Predicate.build(gt?, args: [18]) } describe "#call" do it "applies all its rules to the input" do expect(operation.(19)).to be_success expect(operation.(17)).to be_failure end end describe "#to_ast" do it "returns ast" do expect(operation.to_ast).to eql( [:set, [[:predicate, [:int?, [[:input, undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, undefined]]]]]] ) end it "returns result ast" do expect(operation.(17).to_ast).to eql( [:set, [[:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]] ) end it "returns result ast with an :id" do expect(operation.with(id: :age).(17).to_ast).to eql( [:failure, [:age, [:set, [[:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]]]] ) end end describe "#to_s" do it "returns string representation" do expect(operation.to_s).to eql("set(int?, gt?(18))") end end end dry-logic-1.2.0/spec/unit/operations/xor_spec.rb000066400000000000000000000033641404145603600216570ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Operations::Xor do subject(:operation) { described_class.new(left, right) } include_context "predicates" let(:left) { Dry::Logic::Rule::Predicate.build(array?) } let(:right) { Dry::Logic::Rule::Predicate.build(empty?) } let(:other) do Dry::Logic::Rule::Predicate.build(str?) end describe "#call" do it "calls left and right" do expect(operation.(nil)).to be_success expect(operation.("")).to be_success expect(operation.([])).to be_failure end end describe "#to_ast" do it "returns ast" do expect(operation.to_ast).to eql( [:xor, [[:predicate, [:array?, [[:input, undefined]]]], [:predicate, [:empty?, [[:input, undefined]]]]]] ) end it "returns result ast" do expect(operation.([]).to_ast).to eql( [:xor, [[:predicate, [:array?, [[:input, []]]]], [:predicate, [:empty?, [[:input, []]]]]]] ) end it "returns failure result ast" do expect(operation.with(id: :name).([]).to_ast).to eql( [:failure, [:name, [:xor, [[:predicate, [:array?, [[:input, []]]]], [:predicate, [:empty?, [[:input, []]]]]]]]] ) end end describe "#and" do it "creates conjunction with the other" do expect(operation.and(other).(nil)).to be_failure expect(operation.and(other).(19)).to be_failure expect(operation.and(other).("")).to be_success end end describe "#or" do it "creates disjunction with the other" do expect(operation.or(other).([])).to be_failure expect(operation.or(other).("")).to be_success end end describe "#to_s" do it "returns string representation" do expect(operation.to_s).to eql("array? XOR empty?") end end end dry-logic-1.2.0/spec/unit/predicates/000077500000000000000000000000001404145603600174425ustar00rootroot00000000000000dry-logic-1.2.0/spec/unit/predicates/array_spec.rb000066400000000000000000000014361404145603600221230ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#array?" do let(:predicate_name) { :array? } context "when value is an array" do let(:arguments_list) do [ [[]], [%w[other array]], [[123, "really", :blah]], [[]], [[nil]], [[false]], [[true]] ] end it_behaves_like "a passing predicate" end context "when value is not an array" do let(:arguments_list) do [ [""], [{}], [nil], [:symbol], [String], [1], [1.0], [true], [{}] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/attr_spec.rb000066400000000000000000000012771404145603600217620ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#attr?" do let(:predicate_name) { :attr? } context "when value responds to the attr name" do let(:arguments_list) do [ [:name, Struct.new(:name).new("John")], [:age, Struct.new(:age).new(18)] ] end it_behaves_like "a passing predicate" end context "with value does not respond to the attr name" do let(:arguments_list) do [ [:name, Struct.new(:age).new(18)], [:age, Struct.new(:name).new("Jill")] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/bool_spec.rb000066400000000000000000000012321404145603600217320ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#bool?" do let(:predicate_name) { :bool? } context "when value is a boolean" do let(:arguments_list) do [[true], [false]] end it_behaves_like "a passing predicate" end context "when value is not a bool" do let(:arguments_list) do [ [""], [[]], [{}], [nil], [:symbol], [String], [1], [0], ["true"], ["false"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/bytesize_spec.rb000066400000000000000000000017651404145603600226500ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#bytesize?" do let(:predicate_name) { :bytesize? } context "when value size is equal to n" do let(:arguments_list) do [ [4, "こa"], [1..8, "こa"] ] end it_behaves_like "a passing predicate" end context "when value size is greater than n" do let(:arguments_list) do [ [3, "こa"], [1..3, "こa"] ] end it_behaves_like "a failing predicate" end context "with value size is less than n" do let(:arguments_list) do [ [5, "こa"], [5..10, "こa"] ] end it_behaves_like "a failing predicate" end context "with an unsupported size" do it "raises an error" do expect { Dry::Logic::Predicates[:bytesize?].call("oops", 1) }.to raise_error(ArgumentError, /oops/) end end end end dry-logic-1.2.0/spec/unit/predicates/case_spec.rb000066400000000000000000000013101404145603600217070ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#case?" do let(:predicate_name) { :case? } context "when the value matches the pattern" do let(:arguments_list) do [ [11, 11], [:odd?.to_proc, 11], [/\Af/, "foo"], [Integer, 11] ] end it_behaves_like "a passing predicate" end context "when the value doesn't match the pattern" do let(:arguments_list) do [ [13, 14], [:odd?.to_proc, 12], [/\Af/, "bar"], [String, 11] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/date_spec.rb000066400000000000000000000011401404145603600217120ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#date?" do let(:predicate_name) { :date? } context "when value is a date" do let(:arguments_list) do [[Date.today]] end it_behaves_like "a passing predicate" end context "with value is not an integer" do let(:arguments_list) do [ [""], [[]], [{}], [nil], [:symbol], [String], [1] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/date_time_spec.rb000066400000000000000000000011601404145603600227320ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#date_time?" do let(:predicate_name) { :date_time? } context "when value is a datetime" do let(:arguments_list) do [[DateTime.now]] end it_behaves_like "a passing predicate" end context "with value is not an integer" do let(:arguments_list) do [ [""], [[]], [{}], [nil], [:symbol], [String], [1] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/decimal_spec.rb000066400000000000000000000011701404145603600223760ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#decimal?" do let(:predicate_name) { :decimal? } context "when value is a decimal" do let(:arguments_list) do [[1.2.to_d]] end it_behaves_like "a passing predicate" end context "with value is not an integer" do let(:arguments_list) do [ [""], [[]], [{}], [nil], [:symbol], [String], [1], [1.0] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/empty_spec.rb000066400000000000000000000013271404145603600221420ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#empty?" do let(:predicate_name) { :empty? } context "when value is empty" do let(:arguments_list) do [ [""], [[]], [{}], [nil] ] end it_behaves_like "a passing predicate" end context "with value is not empty" do let(:arguments_list) do [ ["Jill"], [[1, 2, 3]], [{name: "John"}], [true], [false], ["1"], ["0"], [:symbol], [String] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/eql_spec.rb000066400000000000000000000007121404145603600215620ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates, "#eql?" do let(:predicate_name) { :eql? } context "when value is equal to the arg" do let(:arguments_list) do [%w[Foo Foo]] end it_behaves_like "a passing predicate" end context "with value is not equal to the arg" do let(:arguments_list) do [%w[Bar Foo]] end it_behaves_like "a failing predicate" end end dry-logic-1.2.0/spec/unit/predicates/even_spec.rb000066400000000000000000000011051404145603600217330ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#even?" do let(:predicate_name) { :even? } context "when value is an odd int" do let(:arguments_list) do [ [13], [1], [1111] ] end it_behaves_like "a failing predicate" end context "with value is an even int" do let(:arguments_list) do [ [0], [2], [2222] ] end it_behaves_like "a passing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/excluded_from_spec.rb000066400000000000000000000014401404145603600236200ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#excluded_from?" do let(:predicate_name) { :excluded_from? } context "when value is not present in list" do let(:arguments_list) do [ [%w[Jill John], "Jack"], [1..2, 0], [1..2, 3], [[nil, false], true] ] end it_behaves_like "a passing predicate" end context "with value is present in list" do let(:arguments_list) do [ [%w[Jill John], "Jill"], [%w[Jill John], "John"], [1..2, 1], [1..2, 2], [[nil, false], nil], [[nil, false], false] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/excludes_spec.rb000066400000000000000000000023461404145603600226220ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#excludes?" do let(:predicate_name) { :excludes? } context "with input excludes value" do let(:arguments_list) do [ ["Jack", %w[Jill John]], [0, 1..2], [3, 1..2], ["foo", "Hello World"], [:foo, {bar: 0}], [true, [nil, false]] ] end it_behaves_like "a passing predicate" end context "with input of invalid type" do let(:arguments_list) do [ [2, 1], [1, nil], ["foo", 1], [1, "foo"], [1..2, "foo"], ["foo", 1..2], [:key, "foo"] ] end it_behaves_like "a passing predicate" end context "when input includes value" do let(:arguments_list) do [ ["Jill", %w[Jill John]], ["John", %w[Jill John]], [1, 1..2], [2, 1..2], ["Hello", "Hello World"], ["World", "Hello World"], [:bar, {bar: 0}], [nil, [nil, false]], [false, [nil, false]] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/false_spec.rb000066400000000000000000000012411404145603600220710ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#false?" do let(:predicate_name) { :false? } context "when value is false" do let(:arguments_list) do [[false]] end it_behaves_like "a passing predicate" end context "when value is not false" do let(:arguments_list) do [ [true], [""], [[]], [{}], [nil], [:symbol], [String], [1], [0], ["true"], ["false"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/filled_spec.rb000066400000000000000000000013331404145603600222400ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#filled?" do let(:predicate_name) { :filled? } context "when value is filled" do let(:arguments_list) do [ ["Jill"], [[1, 2, 3]], [{name: "John"}], [true], [false], ["1"], ["0"], [:symbol], [String] ] end it_behaves_like "a passing predicate" end context "with value is not filled" do let(:arguments_list) do [ [""], [[]], [{}], [nil] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/float_spec.rb000066400000000000000000000011341404145603600221050ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#float?" do let(:predicate_name) { :float? } context "when value is a float" do let(:arguments_list) do [[1.0]] end it_behaves_like "a passing predicate" end context "with value is not an integer" do let(:arguments_list) do [ [""], [[]], [{}], [nil], [:symbol], [String], [1] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/format_spec.rb000066400000000000000000000011521404145603600222700ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates, "#format?" do let(:predicate_name) { :format? } context "when value matches provided regexp" do let(:arguments_list) do [[/^F/, "Foo"]] end it_behaves_like "a passing predicate" end context "when value does not match provided regexp" do let(:arguments_list) do [[/^F/, "Bar"]] end it_behaves_like "a failing predicate" end context "when input is nil" do let(:arguments_list) do [[/^F/, nil]] end it_behaves_like "a failing predicate" end end dry-logic-1.2.0/spec/unit/predicates/gt_spec.rb000066400000000000000000000014111404145603600214100ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#gt?" do let(:predicate_name) { :gt? } context "when value is greater than n" do let(:arguments_list) do [ [13, 14], [13.37, 13.38] ] end it_behaves_like "a passing predicate" end context "when value is equal to n" do let(:arguments_list) do [ [13, 13], [13.37, 13.37] ] end it_behaves_like "a failing predicate" end context "with value is less than n" do let(:arguments_list) do [ [13, 12], [13.37, 13.36] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/gteq_spec.rb000066400000000000000000000014151404145603600217420ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#gteq?" do let(:predicate_name) { :gteq? } context "when value is greater than n" do let(:arguments_list) do [ [13, 14], [13.37, 13.38] ] end it_behaves_like "a passing predicate" end context "when value is equal to n" do let(:arguments_list) do [ [13, 13], [13.37, 13.37] ] end it_behaves_like "a passing predicate" end context "with value is less than n" do let(:arguments_list) do [ [13, 12], [13.37, 13.36] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/hash_spec.rb000066400000000000000000000013041404145603600217220ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#hash?" do let(:predicate_name) { :hash? } context "when value is a hash" do let(:arguments_list) do [ [{}], [foo: :bar], [{}] ] end it_behaves_like "a passing predicate" end context "when value is not a hash" do let(:arguments_list) do [ [""], [[]], [nil], [:symbol], [String], [1], [1.0], [true], [[]], [Hash] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/included_in_spec.rb000066400000000000000000000014341404145603600232600ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#included_in?" do let(:predicate_name) { :included_in? } context "when value is present in list" do let(:arguments_list) do [ [%w[Jill John], "Jill"], [%w[Jill John], "John"], [1..2, 1], [1..2, 2], [[nil, false], nil], [[nil, false], false] ] end it_behaves_like "a passing predicate" end context "with value is not present in list" do let(:arguments_list) do [ [%w[Jill John], "Jack"], [1..2, 0], [1..2, 3], [[nil, false], true] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/includes_spec.rb000066400000000000000000000010241404145603600226040ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates, "#is?" do let(:predicate_name) { :is? } let(:one) { Object.new } let(:two) { Object.new } context "when value is equal to the arg" do let(:arguments_list) do [[one, one], [:one, :one]] end it_behaves_like "a passing predicate" end context "with value is not equal to the arg" do let(:arguments_list) do [[one, two], [{}, {}]] end it_behaves_like "a failing predicate" end end dry-logic-1.2.0/spec/unit/predicates/int_spec.rb000066400000000000000000000011751404145603600215770ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#int?" do let(:predicate_name) { :int? } context "when value is an integer" do let(:arguments_list) do [ [1], [33], [7] ] end it_behaves_like "a passing predicate" end context "with value is not an integer" do let(:arguments_list) do [ [""], [[]], [{}], [nil], [:symbol], [String] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/key_spec.rb000066400000000000000000000011551404145603600215730ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#key?" do let(:predicate_name) { :key? } context "when key is present in value" do let(:arguments_list) do [ [:name, {name: "John"}], [:age, {age: 18}] ] end it_behaves_like "a passing predicate" end context "with key is not present in value" do let(:arguments_list) do [ [:name, {age: 18}], [:age, {name: "Jill"}] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/lt_spec.rb000066400000000000000000000014111404145603600214150ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#lt?" do let(:predicate_name) { :lt? } context "when value is less than n" do let(:arguments_list) do [ [13, 12], [13.37, 13.36] ] end it_behaves_like "a passing predicate" end context "when value is equal to n" do let(:arguments_list) do [ [13, 13], [13.37, 13.37] ] end it_behaves_like "a failing predicate" end context "with value is greater than n" do let(:arguments_list) do [ [13, 14], [13.37, 13.38] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/lteq_spec.rb000066400000000000000000000014151404145603600217470ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#lteq?" do let(:predicate_name) { :lteq? } context "when value is less than n" do let(:arguments_list) do [ [13, 12], [13.37, 13.36] ] end it_behaves_like "a passing predicate" end context "when value is equal to n" do let(:arguments_list) do [ [13, 13], [13.37, 13.37] ] end it_behaves_like "a passing predicate" end context "with value is greater than n" do let(:arguments_list) do [ [13, 14], [13.37, 13.38] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/max_bytesize_spec.rb000066400000000000000000000013521404145603600235050ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#max_bytesize?" do let(:predicate_name) { :max_bytesize? } context "when value size is less than n" do let(:arguments_list) do [ [5, "こa"] ] end it_behaves_like "a passing predicate" end context "when value size is equal to n" do let(:arguments_list) do [ [5, "こab"] ] end it_behaves_like "a passing predicate" end context "with value size is greater than n" do let(:arguments_list) do [ [5, "こabc"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/max_size_spec.rb000066400000000000000000000020161404145603600226170ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#max_size?" do let(:predicate_name) { :max_size? } context "when value size is less than n" do let(:arguments_list) do [ [3, [1, 2]], [5, "Jill"], [3, {1 => "st", 2 => "nd"}], [9, 1], [6, 1..5] ] end it_behaves_like "a passing predicate" end context "when value size is equal to n" do let(:arguments_list) do [ [2, [1, 2]], [4, "Jill"], [2, {1 => "st", 2 => "nd"}], [8, 1], [5, 1..5] ] end it_behaves_like "a passing predicate" end context "with value size is greater than n" do let(:arguments_list) do [ [1, [1, 2]], [3, "Jill"], [1, {1 => "st", 2 => "nd"}], [7, 1], [4, 1..5] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/min_bytesize_spec.rb000066400000000000000000000013471404145603600235070ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#min_bytesize?" do let(:predicate_name) { :min_bytesize? } context "when value size is greater than n" do let(:arguments_list) do [ [3, "こa"] ] end it_behaves_like "a passing predicate" end context "when value size is equal to n" do let(:arguments_list) do [ [5, "こab"] ] end it_behaves_like "a passing predicate" end context "with value size is less than n" do let(:arguments_list) do [ [5, "こ"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/min_size_spec.rb000066400000000000000000000020161404145603600226150ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#min_size?" do let(:predicate_name) { :min_size? } context "when value size is greater than n" do let(:arguments_list) do [ [1, [1, 2]], [3, "Jill"], [1, {1 => "st", 2 => "nd"}], [7, 1], [4, 1..5] ] end it_behaves_like "a passing predicate" end context "when value size is equal to n" do let(:arguments_list) do [ [2, [1, 2]], [4, "Jill"], [2, {1 => "st", 2 => "nd"}], [8, 1], [5, 1..5] ] end it_behaves_like "a passing predicate" end context "with value size is less than n" do let(:arguments_list) do [ [3, [1, 2]], [5, "Jill"], [3, {1 => "st", 2 => "nd"}], [9, 1], [6, 1..5] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/none_spec.rb000066400000000000000000000011161404145603600217370ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#nil?" do let(:predicate_name) { :nil? } context "when value is nil" do let(:arguments_list) { [[nil]] } it_behaves_like "a passing predicate" end context "when value is not nil" do let(:arguments_list) do [ [""], [true], [false], [0], [:symbol], [[]], [{}], [String] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/not_eql_spec.rb000066400000000000000000000007221404145603600224430ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates, "#not_eql?" do let(:predicate_name) { :not_eql? } context "when value is equal to the arg" do let(:arguments_list) do [%w[Foo Foo]] end it_behaves_like "a failing predicate" end context "with value is not equal to the arg" do let(:arguments_list) do [%w[Bar Foo]] end it_behaves_like "a passing predicate" end end dry-logic-1.2.0/spec/unit/predicates/number_spec.rb000066400000000000000000000013231404145603600222700ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#number?" do let(:predicate_name) { :number? } context "when value is numerical" do let(:arguments_list) do [ ["34"], ["1.000004"], ["0"], [4], ["-15.24"], [-3.5] ] end it_behaves_like "a passing predicate" end context "with value is not numerical" do let(:arguments_list) do [ [""], ["-14px"], ["10,150.00"], [nil], [:symbol], [String] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/odd_spec.rb000066400000000000000000000011031404145603600215420ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#odd?" do let(:predicate_name) { :odd? } context "when value is an odd int" do let(:arguments_list) do [ [13], [1], [1111] ] end it_behaves_like "a passing predicate" end context "with value is an even int" do let(:arguments_list) do [ [0], [2], [2222] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/respond_to_spec.rb000066400000000000000000000011501404145603600231520ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#respond_to?" do let(:predicate_name) { :respond_to? } context "when value responds to method" do let(:arguments_list) do [ [:method, Object], [:new, Hash] ] end it_behaves_like "a passing predicate" end context "when value does not respond to method" do let(:arguments_list) do [ [:foo, Object], [:bar, Hash] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/size_spec.rb000066400000000000000000000023061404145603600217540ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#size?" do let(:predicate_name) { :size? } context "when value size is equal to n" do let(:arguments_list) do [ [[8], 2], [4, "Jill"], [2, {1 => "st", 2 => "nd"}], [8, 8], [1..8, 5] ] end it_behaves_like "a passing predicate" end context "when value size is greater than n" do let(:arguments_list) do [ [[1, 2], 3], [5, "Jill"], [3, {1 => "st", 2 => "nd"}], [1, 9], [1..5, 6] ] end it_behaves_like "a failing predicate" end context "with value size is less than n" do let(:arguments_list) do [ [[1, 2], 1], [3, "Jill"], [1, {1 => "st", 2 => "nd"}], [1, 7], [1..5, 4] ] end it_behaves_like "a failing predicate" end context "with an unsupported size" do it "raises an error" do expect { Dry::Logic::Predicates[:size?].call("oops", 1) }.to raise_error(ArgumentError, /oops/) end end end end dry-logic-1.2.0/spec/unit/predicates/str_spec.rb000066400000000000000000000011371404145603600216130ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#str?" do let(:predicate_name) { :str? } context "when value is a string" do let(:arguments_list) do [ [""], ["John"] ] end it_behaves_like "a passing predicate" end context "with value is not a string" do let(:arguments_list) do [ [[]], [{}], [nil], [:symbol], [String] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/time_spec.rb000066400000000000000000000011361404145603600217400ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#time?" do let(:predicate_name) { :time? } context "when value is a time" do let(:arguments_list) do [[Time.now]] end it_behaves_like "a passing predicate" end context "with value is not an integer" do let(:arguments_list) do [ [""], [[]], [{}], [nil], [:symbol], [String], [1] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/true_spec.rb000066400000000000000000000012351404145603600217610ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#true?" do let(:predicate_name) { :true? } context "when value is true" do let(:arguments_list) do [[true]] end it_behaves_like "a passing predicate" end context "with value is not true" do let(:arguments_list) do [ [false], [""], [[]], [{}], [nil], [:symbol], [String], [1], [0], ["true"], ["false"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/type_spec.rb000066400000000000000000000014541404145603600217660ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#type?" do let(:predicate_name) { :type? } context "when value has a correct type" do let(:arguments_list) do [[TrueClass, true]] end it_behaves_like "a passing predicate" end context "with value is not true" do let(:arguments_list) do [ [TrueClass, false], [TrueClass, ""], [TrueClass, []], [TrueClass, {}], [TrueClass, nil], [TrueClass, :symbol], [TrueClass, String], [TrueClass, 1], [TrueClass, 0], [TrueClass, "true"], [TrueClass, "false"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/uri_spec.rb000066400000000000000000000020161404145603600215770ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#uri?" do let(:predicate_name) { :uri? } context "when value is a valid URI" do let(:arguments_list) do [ [nil, "https://github.com/dry-rb/dry-logic"], # without schemes param ["https", "https://github.com/dry-rb/dry-logic"], # with scheme param [%w[http https], "https://github.com/dry-rb/dry-logic"], # with schemes array ["mailto", "mailto:myemail@host.com"], # with mailto format ["urn", "urn:isbn:0451450523"] # with URN format ] end it_behaves_like "a passing predicate" end context "with value is not a valid URI" do let(:arguments_list) do [ ["http", "mailto:myemail@host.com"], # scheme not allowed [%w[http https], "ftp:://myftp.com"], # scheme not allowed ["", "not-a-uri-at-all"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/uuid_v1_spec.rb000066400000000000000000000016141404145603600223570ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#uuid_v1?" do let(:predicate_name) { :uuid_v1? } context "when value is a valid V1 UUID" do let(:arguments_list) do [["f2d26c57-e07c-1416-a749-57e937930e04"]] end it_behaves_like "a passing predicate" end context "with value is not a valid V1 UUID" do let(:arguments_list) do [ ["not-a-uuid-at-all\nf2d26c57-e07c-1416-a749-57e937930e04"], # V1 with invalid prefix ["f2d26c57-e07c-1416-a749-57e937930e04\nnot-a-uuid-at-all"], # V1 with invalid suffix ["f2d26c57-e07c-3416-a749-57e937930e04"], # wrong version number (3, not 1) ["20633928-6a07-41e9-a923-1681be663d3e"], # UUID V4 ["not-a-uuid-at-all"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/uuid_v2_spec.rb000066400000000000000000000016141404145603600223600ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#uuid_v2?" do let(:predicate_name) { :uuid_v2? } context "when value is a valid V1 UUID" do let(:arguments_list) do [["f2d26c57-e07c-2416-a749-57e937930e04"]] end it_behaves_like "a passing predicate" end context "with value is not a valid V4 UUID" do let(:arguments_list) do [ ["not-a-uuid-at-all\nf2d26c57-e07c-2416-a749-57e937930e04"], # V2 with invalid prefix ["f2d26c57-e07c-2416-a749-57e937930e04\nnot-a-uuid-at-all"], # V2 with invalid suffix ["f2d26c57-e07c-3416-a749-57e937930e04"], # wrong version number (3, not 2) ["20633928-6a07-11e9-a923-1681be663d3e"], # UUID V1 ["not-a-uuid-at-all"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/uuid_v3_spec.rb000066400000000000000000000016141404145603600223610ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#uuid_v3?" do let(:predicate_name) { :uuid_v3? } context "when value is a valid V3 UUID" do let(:arguments_list) do [["f2d26c57-e07c-3416-a749-57e937930e04"]] end it_behaves_like "a passing predicate" end context "with value is not a valid V4 UUID" do let(:arguments_list) do [ ["not-a-uuid-at-all\nf2d26c57-e07c-3416-a749-57e937930e04"], # V3 with invalid prefix ["f2d26c57-e07c-3416-a749-57e937930e04\nnot-a-uuid-at-all"], # V3 with invalid suffix ["f2d26c57-e07c-4416-a749-57e937930e04"], # wrong version number (4, not 3) ["20633928-6a07-11e9-a923-1681be663d3e"], # UUID V1 ["not-a-uuid-at-all"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/uuid_v4_spec.rb000066400000000000000000000016141404145603600223620ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#uuid_v4?" do let(:predicate_name) { :uuid_v4? } context "when value is a valid V4 UUID" do let(:arguments_list) do [["f2d26c57-e07c-4416-a749-57e937930e04"]] end it_behaves_like "a passing predicate" end context "with value is not a valid V4 UUID" do let(:arguments_list) do [ ["not-a-uuid-at-all\nf2d26c57-e07c-4416-a749-57e937930e04"], # V4 with invalid prefix ["f2d26c57-e07c-4416-a749-57e937930e04\nnot-a-uuid-at-all"], # V4 with invalid suffix ["f2d26c57-e07c-3416-a749-57e937930e04"], # wrong version number (3, not 4) ["20633928-6a07-11e9-a923-1681be663d3e"], # UUID V1 ["not-a-uuid-at-all"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates/uuid_v5_spec.rb000066400000000000000000000016141404145603600223630ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do describe "#uuid_v5?" do let(:predicate_name) { :uuid_v5? } context "when value is a valid V5 UUID" do let(:arguments_list) do [["f2d26c57-e07c-5416-a749-57e937930e04"]] end it_behaves_like "a passing predicate" end context "with value is not a valid V4 UUID" do let(:arguments_list) do [ ["not-a-uuid-at-all\nf2d26c57-e07c-5416-a749-57e937930e04"], # V5 with invalid prefix ["f2d26c57-e07c-5416-a749-57e937930e04\nnot-a-uuid-at-all"], # V5 with invalid suffix ["f2d26c57-e07c-3416-a749-57e937930e04"], # wrong version number (3, not 5) ["20633928-6a07-11e9-a923-1681be663d3e"], # UUID V1 ["not-a-uuid-at-all"] ] end it_behaves_like "a failing predicate" end end end dry-logic-1.2.0/spec/unit/predicates_spec.rb000066400000000000000000000010021404145603600207720ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/predicates" RSpec.describe Dry::Logic::Predicates do it "can be included in another module" do mod = Module.new { include Dry::Logic::Predicates } expect(mod[:key?]).to be_a(Method) end describe ".predicate" do it "defines a predicate method" do mod = Module.new { include Dry::Logic::Predicates predicate(:test?) do |foo| true end } expect(mod.test?("arg")).to be(true) end end end dry-logic-1.2.0/spec/unit/rule/000077500000000000000000000000001404145603600162665ustar00rootroot00000000000000dry-logic-1.2.0/spec/unit/rule/predicate_spec.rb000066400000000000000000000025771404145603600216000ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Rule::Predicate do subject(:rule) { described_class.build(predicate) } let(:predicate) { str? } include_context "predicates" it_behaves_like Dry::Logic::Rule describe "#name" do it "returns predicate identifier" do expect(rule.name).to be(:str?) end end describe "#to_ast" do context "without a result" do it "returns rule ast" do expect(rule.to_ast).to eql([:predicate, [:str?, [[:input, undefined]]]]) end it "returns :failure with an id" do email = rule.with(id: :email) expect(email.(11).to_ast).to eql([:failure, [:email, [:predicate, [:str?, [[:input, 11]]]]]]) end end context "with a result" do it "returns success" do expect(rule.("foo")).to be_success end it "returns failure ast" do expect(rule.(5).to_ast).to eql([:predicate, [:str?, [[:input, 5]]]]) end end context "with a zero-arity predicate" do let(:predicate) { Module.new { def self.test? true end } .method(:test?) } it "returns ast" do expect(rule.to_ast).to eql([:predicate, [:test?, []]]) end end end describe "#to_s" do it "returns string representation" do expect(rule.curry("foo").to_s).to eql('str?("foo")') end end end dry-logic-1.2.0/spec/unit/rule_compiler_spec.rb000066400000000000000000000061641404145603600215260ustar00rootroot00000000000000# frozen_string_literal: true require "dry/logic/rule_compiler" RSpec.describe Dry::Logic::RuleCompiler, "#call" do subject(:compiler) { described_class.new(predicates) } let(:predicates) { {key?: predicate, attr?: predicate, filled?: predicate, gt?: predicate, one: predicate} } let(:predicate) { double(:predicate, name: :test?, arity: 2).as_null_object } let(:rule) { Dry::Logic::Rule::Predicate.build(predicate) } let(:key_op) { Dry::Logic::Operations::Key.new(rule, name: :email) } let(:attr_op) { Dry::Logic::Operations::Attr.new(rule, name: :email) } let(:check_op) { Dry::Logic::Operations::Check.new(rule, keys: [:email]) } let(:not_key_op) { Dry::Logic::Operations::Negation.new(key_op) } let(:and_op) { key_op.curry(:email) & rule } let(:or_op) { key_op.curry(:email) | rule } let(:xor_op) { key_op.curry(:email) ^ rule } let(:set_op) { Dry::Logic::Operations::Set.new(rule) } let(:each_op) { Dry::Logic::Operations::Each.new(rule) } it "compiles key rules" do ast = [[:key, [:email, [:predicate, [:filled?, [[:input, undefined]]]]]]] rules = compiler.(ast) expect(rules).to eql([key_op]) end it "compiles attr rules" do ast = [[:attr, [:email, [:predicate, [:filled?, [[:input, undefined]]]]]]] rules = compiler.(ast) expect(rules).to eql([attr_op]) end it "compiles check rules" do ast = [[:check, [[:email], [:predicate, [:filled?, [[:input, undefined]]]]]]] rules = compiler.(ast) expect(rules).to eql([check_op]) end it "compiles attr rules" do ast = [[:attr, [:email, [:predicate, [:filled?, [[:input, undefined]]]]]]] rules = compiler.(ast) expect(rules).to eql([attr_op]) end it "compiles negated rules" do ast = [[:not, [:key, [:email, [:predicate, [:filled?, [[:input, undefined]]]]]]]] rules = compiler.(ast) expect(rules).to eql([not_key_op]) end it "compiles and rules" do ast = [ [ :and, [ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, undefined]]]]]], [:predicate, [:filled?, [[:input, undefined]]]] ] ] ] rules = compiler.(ast) expect(rules).to eql([and_op]) end it "compiles or rules" do ast = [ [ :or, [ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, undefined]]]]]], [:predicate, [:filled?, [[:input, undefined]]]] ] ] ] rules = compiler.(ast) expect(rules).to eql([or_op]) end it "compiles exclusive or rules" do ast = [ [ :xor, [ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, undefined]]]]]], [:predicate, [:filled?, [[:input, undefined]]]] ] ] ] rules = compiler.(ast) expect(rules).to eql([xor_op]) end it "compiles set rules" do ast = [[:set, [[:predicate, [:filled?, [[:input, nil]]]]]]] rules = compiler.(ast) expect(rules).to eql([set_op]) end it "compiles each rules" do ast = [[:each, [:predicate, [:filled?, [[:input, nil]]]]]] rules = compiler.(ast) expect(rules).to eql([each_op]) end end dry-logic-1.2.0/spec/unit/rule_spec.rb000066400000000000000000000151451404145603600176330ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Logic::Rule do subject(:rule) { described_class.build(predicate, **options) } let(:predicate) { -> { true } } let(:options) { {} } let(:schema) do Class.new do define_method(:class, Kernel.instance_method(:class)) def method_missing(m, *) if m.to_s.end_with?("?") self.class.new else super end end def to_proc -> value { value } end def arity 1 end def parameters [[:req, :value]] end end.new end it_behaves_like described_class describe ".new" do it "accepts an :id" do expect(described_class.build(predicate, id: :check_num).id).to be(:check_num) end end describe "with a function returning truthy value" do it "is successful for valid input" do expect(described_class.build(-> val { val }).("true")).to be_success end it "is not successful for invalid input" do expect(described_class.build(-> val { val }).(nil)).to be_failure end end describe "#ast" do it "returns predicate node with :id" do expect(described_class.build(-> value { true }).with(id: :email?).ast("oops")).to eql( [:predicate, [:email?, [[:value, "oops"]]]] ) end it "returns predicate node with undefined args" do expect(described_class.build(-> value { true }).with(id: :email?).ast).to eql( [:predicate, [:email?, [[:value, undefined]]]] ) end end describe "#type" do it "returns rule type" do expect(rule.type).to be(:rule) end end describe "#bind" do let(:bound) { rule.with(id: :bound).bind(object) } context "with an unbound method" do let(:predicate) { klass.instance_method(:test?) } let(:klass) { Class.new { def test? true end } } let(:object) { klass.new } it "returns a new rule with its predicate bound to a specific object" do expect(bound.()).to be_success end it "carries id" do expect(bound.id).to be(:bound) end end context "with an arbitrary block" do let(:predicate) { -> value { value == expected } } let(:object) { Class.new { def expected "test" end } .new } it "returns a new with its predicate executed in the context of the provided object" do expect(bound.("test")).to be_success expect(bound.("oops")).to be_failure end it "carries id" do expect(bound.id).to be(:bound) end it "stores arity" do expect(bound.options[:arity]).to be(rule.arity) end it "stores parameters" do expect(bound.options[:parameters]).to eql(rule.parameters) end end context "with a schema instance" do let(:object) { schema } let(:predicate) { schema } it "returns a new with its predicate executed in the context of the provided object" do expect(bound.(true)).to be_success expect(bound.(false)).to be_failure end end end describe "#eval_args" do context "with an unbound method" do let(:options) { {args: [1, klass.instance_method(:num), :foo], arity: 3} } let(:klass) { Class.new { def num 7 end } } let(:object) { klass.new } it "evaluates args in the context of the provided object" do expect(rule.eval_args(object).args).to eql([1, 7, :foo]) end end context "with a schema instance" do let(:options) { {args: [1, schema, :foo], arity: 3} } let(:object) { Object.new } it "returns a new with its predicate executed in the context of the provided object" do expect(rule.eval_args(object).args).to eql([1, schema, :foo]) end end end describe "arity specialization" do describe "0-arity rule" do let(:options) { {args: [1], arity: 1} } let(:predicate) { :odd?.to_proc } it "generates interface with the right arity" do expect(rule.method(:call).arity).to be_zero expect(rule.method(:[]).arity).to be_zero expect(rule[]).to be(true) expect(rule.()).to be_success end end describe "1-arity rule" do let(:options) { {args: [1], arity: 2} } let(:predicate) { -> a, b { a + b } } it "generates interface with the right arity" do expect(rule.method(:call).arity).to be(1) expect(rule.method(:[]).arity).to be(1) expect(rule[10]).to be(11) expect(rule.(1)).to be_success end end describe "currying" do let(:options) { {args: [], arity: 2} } let(:predicate) { -> a, b { a + b } } let(:rule) { super().curry(1) } it "generates correct arity on currying" do expect(rule.method(:call).arity).to be(1) expect(rule.method(:[]).arity).to be(1) expect(rule[10]).to be(11) expect(rule.(1)).to be_success end end describe "arbitrary arity" do let(:arity) { rand(1..20) } let(:curried) { rand(arity) } let(:options) { {args: [1] * curried, arity: arity} } let(:predicate) { double(:predicate) } it "generates correct arity" do expect(rule.method(:call).arity).to be(arity - curried) expect(rule.method(:[]).arity).to be(arity - curried) end end describe "-1 arity" do let(:options) { {args: [], arity: -1} } it "accepts variable number of arguments" do expect(rule.method(:call).arity).to be(-1) expect(rule.method(:[]).arity).to be(-1) end end describe "-2 arity" do let(:options) { {args: [], arity: -2} } it "accepts variable number of arguments" do expect(rule.method(:call).arity).to be(-2) expect(rule.method(:[]).arity).to be(-2) end context "curried 1" do let(:options) { {args: [1], arity: -2} } it "doesn't have required arguments" do expect(rule.method(:call).arity).to be(-1) expect(rule.method(:[]).arity).to be(-1) end end context "curried 2" do let(:options) { {args: [1, 2], arity: -2} } it "doesn't have required arguments" do expect(rule.method(:call).arity).to be(-1) expect(rule.method(:[]).arity).to be(-1) end end end describe "constants" do let(:options) { {args: [], arity: 0} } it "accepts variable number of arguments" do expect(rule.method(:call).arity).to be(-1) expect(rule.method(:[]).arity).to be(-1) end end end end