dry-types-1.2.2/0000755000175000017500000000000013617303242013401 5ustar utkarshutkarshdry-types-1.2.2/CODE_OF_CONDUCT.md0000644000175000017500000000266113617303242016205 0ustar utkarshutkarsh# 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-types-1.2.2/CONTRIBUTING.md0000644000175000017500000000312413617303242015632 0ustar utkarshutkarsh# 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, don't link to a discussion thread, and instead summarize what was discussed. ## 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 to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides # 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) or join [our chat](https://dry-rb.zulipchat.com). dry-types-1.2.2/.rubocop.yml0000644000175000017500000000245613617303242015662 0ustar utkarshutkarsh# this file is managed by dry-rb/devtools project AllCops: TargetRubyVersion: 2.4 Style/EachWithObject: Enabled: false Style/StringLiterals: Enabled: true EnforcedStyle: single_quotes Style/Alias: Enabled: false Style/LambdaCall: Enabled: false Style/StabbyLambdaParentheses: Enabled: false Style/FormatString: Enabled: false Style/Documentation: Enabled: false Layout/SpaceInLambdaLiteral: Enabled: false Layout/MultilineMethodCallIndentation: Enabled: true EnforcedStyle: indented Metrics/LineLength: Max: 100 Metrics/MethodLength: Max: 22 Metrics/ClassLength: Max: 150 Metrics/AbcSize: Max: 20 Metrics/BlockLength: Enabled: false Metrics/CyclomaticComplexity: Enabled: true Max: 10 Lint/BooleanSymbol: Enabled: false Style/AccessModifierDeclarations: Enabled: false Style/BlockDelimiters: Enabled: false Layout/IndentFirstArrayElement: EnforcedStyle: consistent Style/ClassAndModuleChildren: Exclude: - "spec/**/*_spec.rb" Lint/HandleExceptions: Exclude: - "spec/spec_helper.rb" Naming/FileName: Exclude: - "lib/dry-*.rb" Style/SymbolArray: Exclude: - "spec/**/*_spec.rb" Style/ConditionalAssignment: Enabled: false Naming/MethodName: Enabled: false Style/AsciiComments: Enabled: false Style/DateTime: Enabled: false dry-types-1.2.2/.yardopts0000644000175000017500000000026313617303242015250 0ustar utkarshutkarsh--title 'dry-types' --query '@api.text != "private"' --embed-mixins --output doc --readme README.md --files CHANGELOG.md --markup markdown --markup-provider=redcarpet lib/**/*.rb dry-types-1.2.2/Rakefile0000644000175000017500000000070113617303242015044 0ustar utkarshutkarsh# frozen_string_literal: true require 'bundler/gem_tasks' require 'rspec/core/rake_task' task :run_specs do require 'rspec/core' types_result = RSpec::Core::Runner.run(['spec/dry']) RSpec.clear_examples Dry::Types.load_extensions(:maybe) ext_result = RSpec::Core::Runner.run(['spec']) exit [types_result, ext_result].max end task default: :run_specs require 'yard' require 'yard/rake/yardoc_task' YARD::Rake::YardocTask.new(:doc) dry-types-1.2.2/CHANGELOG.md0000644000175000017500000010106213617303242015212 0ustar utkarshutkarsh# 1.2.2 2019-12-14 ## Fixed - `Types.Contructor` doesn't re-wrap class instances implementing type interface, this fixes some quirks in dry-struct (flash-gordon) ## Changed - Types now use immutable equalizers. This should improve performance in certain cases e.g. in ROM (flash-gordon) - Attempting to use non-symbol keys in hash schemas raises an error. We always supported only symbols as keys but there was no check, now it'll throw an argument error. If you want to convert strings to symbols, use `Hash#with_key_transform` (flash-gordon) - Params and JSON types accept Time/Date/Datetime instances and boolean values. This can be useful in tests but we discourage you from relying on this behavior in production code. For example, building structs with `Params` types is considered a smell. There are dedicated tools for coercion, namely dry-schema and dry-validation. Be responsible user of dry-types! ❤ (flash-gordon) [Compare v1.2.1...v1.2.2](https://github.com/dry-rb/dry-types/compare/v1.2.1...v1.2.2) # 1.2.1 2019-11-07 ## Fixed - Fix keyword warnings reported by Ruby 2.7 (flash-gordon) - Error type in failing case in `Array::Member` (esparta) [Compare v1.2.0...v1.2.1](https://github.com/dry-rb/dry-types/compare/v1.2.0...v1.2.1) # 1.2.0 2019-10-06 ## Changed - `Dry::Types.[]` used to work with classes, now it's deprecated (flash-gordon) ## Fixed - Bug with using a `Bool`-named struct as a schema key (flash-gordon) - A bunch of issues related to using `meta` on complex types (flash-gordon) - `Types.Constructor(...)` returns a `Types::Array` as it should (flash-gordon) ## Added - `Optional::Params` types that coerce empty strings to `nil` (flash-gordon) ```ruby Dry::Types['optional.params.integer'].('') # => nil Dry::Types['optional.params.integer'].('140') # => 140 Dry::Types['optional.params.integer'].('asd') # => exception! ``` Keep in mind, `Dry::Types['optional.params.integer']` and `Dry::Types['params.integer'].optional` are not the same, the latter doesn't handle empty strings. - Predicate inferrer was ported from dry-schema (authored by solnic) ```ruby require 'dry/types/predicate_inferrer' Dry::Types::PredicateInferrer.new[Types::String] # => [:str?] Dry::Types::PredicateInferrer.new[Types::String | Types::Integer] # => [[[:str?], [:int?]]] ``` Note that the API of the predicate inferrer can change in the stable version, it's dictated by the needs of dry-schema so it should be considered as semi-stable. If you depend on it, write specs covering the desired behavior. Another option is copy-and-paste the whole thing to your project. - Primitive inferrer was ported from dry-schema (authored by solnic) ```ruby require 'dry/types/primitive_inferrer' Dry::Types::PrimitiveInferrer.new[Types::String] # => [String] Dry::Types::PrimitiveInferrer.new[Types::String | Types::Integer] # => [String, Integer] Dry::Types::PrimitiveInferrer.new[Types::String.optional] # => [NilClass, String] ``` The primitive inferrer should be stable by now, you can rely on it. - The `monads` extension adds `Dry::Types::Result#to_monad`. This makes it compatible with do notation from dry-monads. Load it with `Dry::Types.load_extensions(:monads)` (skryukov) ```ruby Types = Dry.Types Dry::Types.load_extensions(:monads) class AddTen include Dry::Monads[:result, :do] def call(input) integer = yield Types::Coercible::Integer.try(input) Success(integer + 10) end end ``` [Compare v1.1.1...v1.2.0](https://github.com/dry-rb/dry-types/compare/v1.1.1...v1.2.0) # 1.1.1 2019-07-26 ## Fixed - A bug where meta was lost for lax array types (flash-gordon) [Compare v1.1.0...v1.1.1](https://github.com/dry-rb/dry-types/compare/v1.1.0...v1.1.1) # 1.1.0 2019-07-02 ## Added - New builder method `Interface` constructs a type which accepts objects that respond to the given methods (waiting-for-dev) ```ruby Types = Dry.Types() Types::Callable = Types.Interface(:call) Types::Callable.valid?(Object.new) # => false Types::Callable.valid?(proc {}) # => true ``` - New types: `coercible.symbol`, `params.symbol`, and `json.symbol`, all use `.to_sym` for coercion (waiting-for-dev) ## Fixed - Converting schema keys to maybe types (flash-gordon) - Using `Schema#key` and `Array#member` on constuctors (flash-gordon) - Using `meta(omittable: true)` within `transform_types` works again but produces a warning, please migrate to `.omittable` or `.required(false)` (flash-gordon) - Bug with a constructror defined on top of enum (flash-gordon) [Compare v1.0.1...v1.1.0](https://github.com/dry-rb/dry-types/compare/v1.0.1...v1.1.0) # 1.0.1 2019-06-04 ## Added - In a case of failure the constructor block can now pass a different value (flash-gordon) ```ruby not_empty_string = Types::String.constructor do |value, &failure| value.strip.empty? ? failure.(nil) : value.strip end not_empty_string.(' ') { |v| v } # => nil not_empty_string.lax.(' ') # => nil not_empty_string.lax.(' foo ') # => "foo" ``` - `Schema#strict` now accepts an boolean argument. If `fales` is passed this will turn a strict schema into a non-strict one (flash-gordon) [Compare v1.0.0...v1.0.1](https://github.com/dry-rb/dry-types/compare/v1.0.0...v1.0.1) # 1.0.0 2019-04-23 ## Changed - [BREAKING] Behavior of built-in constructor types was changed to be more strict. They will always raise an error on failed coercion (flash-gordon) Compare: ```ruby # 0.15.0 Types::Params::Integer.('foo') # => "foo" # 1.0.0 Types::Params::Integer.('foo') # => Dry::Types::CoercionError: invalid value for Integer(): "foo" ``` To handle coercion errors `Type#call` now yields a block: ```ruby Types::Params::Integer.('foo') { :invalid } # => :invalid ``` This makes work with coercions more straightforward and way faster. - [BREAKING] Safe types were renamed to Lax, this name better serves their purpose. The previous name is available but prints a warning (flash-gordon) - [BREAKING] Metadata is now pushed down to the decorated type. It is not likely you will notice a difference but this a breaking change that enables some use cases in rom related to the usage of default types in relations (flash-gordon) - Nominal types are now completely unconstrained. This fixes some inconsistencies when using them with constraints. `Nominal#try` will always return a successful result, for the previous behavior use `Nominal#try_coerce` or switch to strict types with passing a block to `#call` (flash-gordon) ## Performance improvements - During the work on this release, a lot of performance improvements were made. dry-types 1.0 combined with dry-logic 1.0 are multiple times faster than dry-types 0.15 and dry-logic 0.5 for common cases including constraints checking and coercion (flash-gordon) ## Added - API for custom constructor types was enhanced. If you pass your own callable to `.constructor` it can have a block in its signature. If a block is passed, you must call it on failed coercion, otherwise raise a type coercion error (flash-gordon) Example: ```ruby proc do |input, &block| if input.is_a? String Integer(input, 10) else Integer(input) end rescue ArgumentError, TypeError => error if block block.call else raise Dry::Types::CoercionError.new( error.message, backtrace: error.backtrace ) end end ``` This makes the exception handling your job so that dry-types won't have to catch and re-wrap all possible errors (this is not safe, generally speaking). - Types now can be converted to procs thus you can pass them as blocks (flash-gordon) ```ruby %w(1 2 3).map(&Types::Coercible::Integer) # => [1, 2, 3] ``` [Compare v0.15.0...v1.0.0](https://github.com/dry-rb/dry-types/compare/v0.15.0...v1.0.0) # 0.15.0 2019-03-22 ## Changed - [BREAKING] Internal representation of hash schemas was changed to be a simple list of key types (flash-gordon) `Dry::Types::Hash#with_type_transform` now yields a key type instead of type + name: ```ruby Dry::Types['strict.hash'].with_type_transform { |key| key.name == :age ? key.required(false) : key } ``` - [BREAKING] Definition types were renamed to nominal (flash-gordon) - [BREAKING] Top-level types returned by `Dry::Types.[]` are now strict (flash-gordon) ```ruby # before Dry::Types['integer'] # => #]> # now Dry::Types['integer'] # => rule=[type?(Integer)]>]> # you can still access nominal types using namespace Dry::Types['nominal.integer'] # => #]> ``` - [BREAKING] Default values are not evaluated if the decorated type returns `nil`. They are triggered on `Undefined` instead (GustavoCaso + flash-gordon) - [BREAKING] Support for old hash schemas was fully removed. This makes dry-types not compatible with dry-validation < 1.0 (flash-gordon) - `Dry::Types.module` is deprecated in favor of `Dry.Types` (flash-gordon) Keep in mind `Dry.Types` uses strict types for top-level names, that is after ```ruby module Types include Dry.Types end ``` `Types::Integer` is a strict type. If you want it to be nominal, use `include Dry.Types(default: :nominal)`. See other options below. - `params.integer` now always converts strings to decimal numbers, this means `09` will be coerced to `9` (threw an error before) (skryukov) - Ruby 2.3 is EOL and not officially supported. It may work but we don't test it. ## Added - Improved string representation of types (flash-gordon) ```ruby Dry::Types['nominal.integer'] # => #]> Dry::Types['params.integer'] # => # fn=Dry::Types::Coercions::Params.to_int>]> Dry::Types['hash'].schema(age?: 'integer') # => # rule=[type?(Integer)]>}> rule=[type?(Hash)]>]> Dry::Types['array'] # => # rule=[type?(Integer)]>> rule=[type?(Array)]>]> ``` - Options for the list of types you want to import with `Dry.Types` (flash-gordon) Cherry-pick only certain types: ```ruby module Types include Dry.Types(:strict, :nominal, :coercible) end Types.constants # => [:Strict, :Nominal, :Coercible] ``` Change default top-level types: ```ruby module Types include Dry.Types(default: :coercible) end # => # fn=Kernel.Integer>]> ``` Rename type namespaces: ```ruby module Types include Dry.Types(strict: :Strong, coercible: :Kernel) end ``` - Optional keys for schemas can be provided with ?-ending symbols (flash-gordon) ```ruby Dry::Types['hash'].schema(name: 'string', age?: 'integer') ``` - Another way of making keys optional is setting `required: false` to meta. In fact, it is the preferable way if you have to store this information in `meta`, otherwise use the Key's API (see below) (flash-gordon) ```ruby Dry::Types['hash'].schema( name: Dry::Types['string'], age: Dry::Types['integer'].meta(required: false) ) ``` - Key types have API for making keys omittable and back (flash-gordon) ```ruby # defining a base schema with optional keys lax_hash = Dry::Types['hash'].with_type_transform { |key| key.required(false) } # same as lax_hash = Dry::Types['hash'].with_type_transform(&:omittable) # keys in user_schema are not required user_schema = lax_hash.schema(name: 'string', age: 'integer') ``` - `Type#optional?` now recognizes more cases where `nil` is an allowed value (flash-gordon) - `Constructor#{prepend,append}` with `<<` and `>>` as aliases. `Constructor#append` works the same way `Constructor#constrcutor` does. `Constuctor#prepend` chains functions in the reverse order, see examples (flash-gordon) ```ruby to_int = Types::Coercible::Integer inc = to_int.append { |x| x + 2 } inc.("1") # => "1" -> 1 -> 3 inc = to_int.prepend { |x| x + "2" } inc.("1") # => "1" -> "12" -> 12 ``` - Partial schema application for cases when you want to validate only a subset of keys (flash-gordon) This is useful when you want to update a key or two in an already-validated hash. A perfect example is `Dry::Struct#new` where this feature is now used. ```ruby schema = Dry::Types['hash'].schema(name: 'string', age: 'integer') value = schema.(name: 'John', age: 20) update = schema.apply({ age: 21 }, skip_missing: true) value.merge(update) ``` ## Fixed - `Hash::Map` now behaves as a constrained type if its values are constrained (flash-gordon) - `coercible.integer` now doesn't blow up on invalid strings (exterm) [Compare v0.14.0...v0.15.0](https://github.com/dry-rb/dry-types/compare/v0.14.0...v0.15.0) # v0.14.1 2019-03-25 ## Fixed - `coercible.integer` now doesn't blow up on invalid strings (exterm) [Compare v0.14.0...v0.14.1](https://github.com/dry-rb/dry-types/compare/v0.14.0...v0.14.1) # v0.14.0 2019-01-29 ## Changed - [BREAKING] Support for Ruby 2.2 was dropped. It reached EOL on March 31, 2018. - `dry-logic` was updated to `~> 0.5` (solnic) ## Fixed - `valid?` works correctly with constructors now (cgeorgii) [Compare v0.13.4...v0.14.0](https://github.com/dry-rb/dry-types/compare/v0.13.4...v0.14.0) # v0.13.4 2018-12-21 ## Fixed - Fixed warnings about keyword arguments from Ruby 2.6. See https://bugs.ruby-lang.org/issues/14183 for all the details (flash-gordon) # v0.13.3 2018-11-25 ## Fixed - `Dry::Types::Hash#try` returns `Failure` instead of throwing an exception on missing keys (GustavoCaso) [Compare v0.13.2...v0.13.3](https://github.com/dry-rb/dry-types/compare/v0.13.2...v0.13.3) # v0.13.2 2018-05-30 ## Fixed - `Defaults#valid?` now works fine when passing `Dry::Core::Constans::Undefined` as value (GustavoCaso) - `valid?` for constructor types wrapping `Sum`s (GustavoCaso) [Compare v0.13.1...v0.13.2](https://github.com/dry-rb/dry-types/compare/v0.13.1...v0.13.2) # v0.13.1 2018-05-28 ## Fixed - Defaults now works fine with meta (GustavoCaso) - Defaults are now re-decorated properly (flash-gordon) ## Added - `params.int` was added to make the upgrade process in dry-validation smoother (available after you `require 'dry/types/compat/int'`) (flash-gordon) [Compare v0.13.0...v0.13.1](https://github.com/dry-rb/dry-types/compare/v0.13.0...v0.13.1) # v0.13.0 2018-05-03 ## Changed - [BREAKING] Renamed `Types::Form` to `Types::Params`. You can opt-in the former name with `require 'dry/types/compat/form_types'`. It will be dropped in the next release (ndrluis) - [BREAKING] The `Int` types was renamed to `Integer`, this was the only type named differently from the standard Ruby classes so it has been made consistent. The former name is available with `require 'dry/types/compat/int'` (GustavoCaso + flash-gordon) - [BREAKING] Default types are not evaluated on `nil`. Default values are evaluated _only_ if no value were given. ```ruby type = Types::Strict::String.default("hello") type[nil] # => constraint error type[] # => "hello" ``` This change allowed to greatly simplify hash schemas, make them a lot more flexible yet predictable (see below). - [BREAKING] `Dry::Types.register_class` was removed, `Dry::Types.register` was made private API, do not register your types in the global `dry-types` container, use a module instead, e.g. `Types` (flash-gordon) - [BREAKING] Enum types don't accept value index anymore. Instead, explicit mapping is supported, see below (flash-gordon) ## Added - Hash schemas were rewritten. The old API is still around but is going to be deprecated and removed before 1.0. The new API is simpler and more flexible. Instead of having a bunch of predefined schemas you can build your own by combining the following methods: 1. `Schema#with_key_transform`—transforms keys of input hashes, for things like symbolizing etc. 2. `Schema#strict`—makes a schema intolerant to unknown keys. 3. `Hash#with_type_transform`—transforms member types with an arbitrary block. For instance, ```ruby optional_keys = Types::Hash.with_type_transform { |t, _key| t.optional } schema = optional_keys.schema(name: 'strict.string', age: 'strict.int') schema.(name: "Jane", age: nil) # => {name: "Jane", age: nil} ``` Note that by default all keys are required, if a key is expected to be absent, add to the corresponding type's meta `omittable: true`: ```ruby intolerant = Types::Hash.schema(name: Types::Strict::String) intolerant[{}] # => Dry::Types::MissingKeyError tolerant = Types::Hash.schema(name: Types::Strict::String.meta(omittable: true)) tolerant[{}] # => {} tolerant_with_default = Types::Hash.schema(name: Types::Strict::String.meta(omittable: true).default("John")) tolerant[{}] # => {name: "John"} ``` The new API is composable in a natural way: ```ruby TOLERANT = Types::Hash.with_type_transform { |t| t.meta(omittable: true) }.freeze user = TOLERANT.schema(name: 'strict.string', age: 'strict.int') user.(name: "Jane") # => {name: "Jane"} TOLERANT_SYMBOLIZED = TOLERANT.with_key_transform(&:to_sym) user_sym = TOLERANT_SYMBOLIZED.schema(name: 'strict.string', age: 'strict.int') user_sym.("name" => "Jane") # => {name: "Jane"} ``` (flash-gordon) - `Types.Strict` is an alias for `Types.Instance` (flash-gordon) ```ruby strict_range = Types.Strict(Range) strict_range == Types.Instance(Range) # => true ``` - `Enum#include?` is an alias to `Enum#valid?` (d-Pixie + flash-gordon) - `Range` was added (GustavoCaso) - `Array` types filter out `Undefined` values, if you have an array type with a constructor type as its member, the constructor now can return `Dry::Types::Undefined` to indicate empty value: ```ruby filter_empty_strings = Types::Strict::Array.of( Types::Strict::String.constructor { |input| input.to_s.yield_self { |s| s.empty? ? Dry::Types::Undefined : s } } ) filter_empty_strings.(["John", nil, "", "Jane"]) # => ["John", "Jane"] ``` - `Types::Map` was added for homogeneous hashes, when only types of keys and values are known in advance, not specific key names (fledman + flash-gordon) ```ruby int_to_string = Types::Hash.map('strict.integer', 'strict.string') int_to_string[0 => 'foo'] # => { 0 => "foo" } int_to_string[0 => 1] # Dry::Types::MapError: input value 1 for key 0 is invalid: type?(String, 1) ``` - Enum supports mappings (bolshakov + flash-gordon) ```ruby dict = Types::Strict::String.enum('draft' => 0, 'published' => 10, 'archived' => 20) dict['published'] # => 'published' dict[10] # => 'published' ``` ## Fixed - Fixed applying constraints to optional type, i.e. `.optional.constrained` works correctly (flash-gordon) - Fixed enum working with optionals (flash-gordon) ## Internal - Dropped the `dry-configurable` dependency (GustavoCaso) - The gem now uses `dry-inflector` for inflections instead of `inflecto` (GustavoCaso) [Compare v0.12.2...v0.13.0](https://github.com/dry-rb/dry-types/compare/v0.12.2...v0.13.0) # v0.12.2 2017-11-04 ## Fixed - The type compiler was fixed for simple rules such as used for strict type checks (flash-gordon) - Fixed an error on `Dry::Types['json.decimal'].try(nil)` (nesaulov) - Fixed an error on calling `try` on an array type built of constrained types (flash-gordon) - Implemented `===` for enum types (GustavoCaso) [Compare v0.12.1...v0.12.2](https://github.com/dry-rb/dry-types/compare/v0.12.1...v0.12.2) # v0.12.1 2017-10-11 ## Fixed - `Constructor#try` rescues `ArgumentError` (raised in cases like `Integer('foo')`) (flash-gordon) - `#constructor` works correctly for default and enum types (solnic) - Optional sum types work correctly in `safe` mode (GustavoCaso) - The equalizer of constrained types respects meta (flash-gordon) [Compare v0.12.0...v0.12.1](https://github.com/dry-rb/dry-types/compare/v0.12.0...v0.12.1) # v0.12.0 2017-09-15 ## Added - A bunch of shortcut methods for constructing types to the autogenerated module, e.g. `Types.Constructor(String, &:to_s)` (flash-gordon) ## Deprecated - `Types::Array#member` was deprecated in favor of `Types::Array#of` (flash-gordon) [Compare v0.11.1...v0.12.0](https://github.com/dry-rb/dry-types/compare/v0.11.1...v0.12.0) # v0.11.1 2017-08-14 ## Changed - Constructors are now equalized using `fn` and `meta` too (flash-gordon) ## Fixed - Fixed `Constructor#name` with `Sum`-types (flash-gordon) [Compare v0.11.0...v0.11.1](https://github.com/dry-rb/dry-types/compare/v0.11.0...v0.11.1) # v0.11.0 2017-06-30 ## Added - `#to_ast` available for all type objects (GustavoCaso) - `Types::Array#of` as an alias for `#member` (maliqq) - Detailed failure objects are passed to results which improves constraint violation messages (GustavoCaso) [Compare v0.10.3...v0.11.0](https://github.com/dry-rb/dry-types/compare/v0.10.3...v0.11.0) # v0.10.3 2017-05-06 ## Added - Callable defaults accept the underlying type (v-kolesnikov) [Compare v0.10.2...v0.10.3](https://github.com/dry-rb/dry-types/compare/v0.10.2...v0.10.3) # v0.10.2 2017-04-28 ## Fixed - Fixed `Type#optional?` for sum types (flash-gordon) [Compare v0.10.1...v0.10.2](https://github.com/dry-rb/dry-types/compare/v0.10.1...v0.10.2) # v0.10.1 2017-04-28 ## Added - `Type#optional?` returns true if type is Sum and left is nil (GustavoCaso) - `Type#pristine` returns a type without `meta` (flash-gordon) ## Fixed - `meta` is used in type equality again (solnic) - `Any` works correctly with meta again (flash-gordon) [Compare v0.10.0...v0.10.1](https://github.com/dry-rb/dry-types/compare/v0.10.0...v0.10.1) # v0.10.0 2017-04-26 ## Added - Types can be used in `case` statements now (GustavoCaso) ## Fixed - Return original value when Date.parse raises a RangeError (jviney) ## Changed - Meta data are now stored separately from options (flash-gordon) - `Types::Object` was renamed to `Types::Any` (flash-gordon) [Compare v0.9.4...v0.10.0](https://github.com/dry-rb/dry-types/compare/v0.9.4...v0.10.0) # v0.9.4 2017-01-24 ## Added - Added `Types::Object` which passes an object of any type (flash-gordon) [Compare v0.9.3...v0.9.4](https://github.com/dry-rb/dry-types/compare/v0.9.3...v0.9.4) # v0.9.3 2016-12-03 ## Fixed - Updated to dry-core >= 0.2.1 (ruby warnings are gone) (flash-gordon) [Compare v0.9.2...v0.9.3](https://github.com/dry-rb/dry-types/compare/v0.9.2...v0.9.3) # v0.9.2 2016-11-13 ## Added - Support for `"Y"` and `"N"` as `true` and `false` values, respectively (scare21410) ## Changed - Optimized object allocation in hash schemas, resulting in up to 25% speed boost (davydovanton) [Compare v0.9.1...v0.9.2](https://github.com/dry-rb/dry-types/compare/v0.9.1...v0.9.2) # v0.9.1 2016-11-04 ## Fixed - `Hash#strict_with_defaults` properly evaluates callable defaults (bolshakov) ## Changed - `Hash#weak` accepts Hash-descendants again (solnic) [Compare v0.9.0...v0.9.1](https://github.com/dry-rb/dry-types/compare/v0.9.0...v0.9.1) # v0.9.0 2016-09-21 ## Added - `Hash#strict_with_defaults` which validates presence of all required keys and respects default types for missing _values_ (backus) - `Type#constrained?` method (flash-gordon) ## Fixed - Summing two constrained types works correctly (flash-gordon) - `Types::Array::Member#valid?` in cases where member type is a constraint (solnic) - `Hash::Schema#try` handles exceptions properly and returns a failure object (solnic) ## Changed - [BREAKING] Renamed `Hash##{schema=>permissive}` (backus) - [BREAKING] `dry-monads` dependency was made optional, Maybe types are available after `Dry::Types.load_extensions(:maybe)` (flash-gordon) - [BREAKING] `Dry::Types::Struct` and `Dry::Types::Value` have been extracted to [`dry-struct`](https://github.com/dry-rb/dry-struct) (backus) - `Types::Form::Bool` supports upcased true/false values (kirs) - `Types::Form::{Date,DateTime,Time}` fail gracefully for invalid input (padde) - ice_nine dependency has been dropped as it was required by Struct only (flash-gordon) [Compare v0.8.1...v0.9.0](https://github.com/dry-rb/dry-types/compare/v0.8.1...v0.9.0) # v0.8.1 2016-07-13 ## Fixed - Compiler no longer chokes on type nodes without args (solnic) - Removed `bin/console` from gem package (solnic) [Compare v0.8.0...v0.8.1](https://github.com/dry-rb/dry-types/compare/v0.8.0...v0.8.1) # v0.8.0 2016-07-01 ## Added - `Struct` now implements `Type` interface so ie `SomeStruct | String` works now (flash-gordon) - `:weak` Hash constructor which can partially coerce a hash even when it includes invalid values (solnic) - Types include `Dry::Equalizer` now (flash-gordon) ## Fixed - `Struct#to_hash` descends into arrays too (nepalez) - `Default#with` works now (flash-gordon) ## Changed - `:symbolized` hash schema is now based on `:weak` schema (solnic) - `Struct::Value` instances are now **deeply frozen** via ice_nine (backus) [Compare v0.7.2...v0.8.0](https://github.com/dry-rb/dry-types/compare/v0.7.2...v0.8.0) # v0.7.2 2016-05-11 ## Fixed - `Bool#default` gladly accepts `false` as its value (solnic) - Creating an empty schema with input processor no longer fails (lasseebert) ## Changed - Allow multiple calls to meta (solnic) - Allow capitalised versions of true and false values for boolean coercions (nil0bject) - Replace kleisli with dry-monads (flash-gordon) - Use coercions from Kernel (flash-gordon) - Decimal coercions now work with Float (flash-gordon) - Coerce empty strings in form posts to blank arrays and hashes (timriley) - update to use dry-logic v0.2.3 (fran-worley) [Compare v0.7.1...v0.7.2](https://github.com/dry-rb/dry-types/compare/v0.7.1...v0.7.2) # v0.7.1 2016-04-06 ## Added - `JSON::*` types with JSON-specific coercions (coop) ## Fixed - Schema is properly inherited in Struct (backus) - `constructor_type` is properly inherited in Struct (fbernier) [Compare v0.7.0...v0.7.1](https://github.com/dry-rb/dry-types/compare/v0.7.0...v0.7.1) # v0.7.0 2016-03-30 Major focus of this release is to make complex type composition possible and improving constraint errors to be more meaningful. ## Added - `Type#try` interface that tries to process the input and return a result object which can be either a success or failure (solnic) - `#meta` interface for setting arbitrary meta data on types (solnic) - `ConstraintError` has a message which includes information about the predicate which failed ie `nil violates constraints (type?(String) failed)` (solnic) - `Struct` uses `Dry::Equalizer` too, just like `Value` (AMHOL) - `Sum::Constrained` which has a disjunction rule built from its types (solnic) - Compiler supports `[:constructor, [primitive, fn_proc]]` nodes (solnic) - Compiler supports building schema-less `form.hash` types (solnic) ## Fixed - `Sum` now supports complex types like `Array` or `Hash` with member types and/or constraints (solnic) - `Default#constrained` will properly wrap a new constrained type (solnic) ## Changed - [BREAKING] Renamed `Type#{optional=>maybe}` (AMHOL) - [BREAKING] `Type#optional(other)` builds a sum: `Strict::Nil | other` (AMHOL) - [BREAKING] Type objects are now frozen (solnic) - [BREAKING] `Value` instances are frozen (AMHOL) - `Array` is no longer a constructor and has a `Array::Member` subclass (solnic) - `Hash` is no longer a constructor and is split into `Hash::Safe`, `Hash::Strict` and `Hash::Symbolized` (solnic) - `Constrained` has now a `Constrained::Coercible` subclass which will try to apply its type prior applying its rule (solnic) - `#maybe` uses `Strict::Nil` now (solnic) - `Type#default` will raise if `nil` was passed for `Maybe` type (solnic) - `Hash` with a schema will set maybe values for missing keys or nils (flash-gordon) [Compare v0.6.0...v0.7.0](https://github.com/dry-rb/dry-types/compare/v0.6.0...v0.7.0) # v0.6.0 2016-03-16 Renamed from `dry-data` to `dry-types` and: ## Added - `Dry::Types.module` which returns a namespace for inclusion which has all built-in types defined as constants (solnic) - `Hash#schema` supports default values now (solnic) - `Hash#symbolized` passes through keys that are already symbols (solnic) - `Struct.new` uses an empty hash by default as input (solnic) - `Struct.constructor_type` macro can be used to change attributes constructor (solnic) - `default` accepts a block now for dynamic values (solnic) - `Types.register_class` accepts a second arg which is the name of the class' constructor method, defaults to `:new` (solnic) ## Fixed - `Struct` will simply pass-through the input if it is already a struct (solnic) - `default` will raise if a value violates constraints (solnic) - Evaluating a default value tries to use type's constructor which makes it work with types that may coerce an input into nil (solnic) - `enum` works just fine with integer-values (solnic) - `enum` + `default` works just fine (solnic) - `Optional` no longer responds to `primitive` as it makes no sense since there's no single primitive for an optional value (solnic) - `Optional` passes-through a value which is already a maybe (solnic) ## Changed - `Dry::Types::Definition` is now the base type definition object (solnic) - `Dry::Types::Constructor` is now a type definition with a constructor function (solnic) [Compare v0.5.1...v0.6.0](https://github.com/dry-rb/dry-types/compare/v0.5.1...v0.6.0) # v0.5.1 2016-01-11 ## Added - `Dry::Data::Type#safe` for types which can skip constructor when primitive does not match input's class (solnic) - `form.array` and `form.hash` safe types (solnic) [Compare v0.5.0...v0.5.1](https://github.com/dry-rb/dry-types/compare/v0.5.0...v0.5.1) # v0.5.0 2016-01-11 ## Added - `Type#default` interface for defining a type with a default value (solnic) ## Changed - [BREAKING] `Dry::Data::Type.new` accepts constructor and _options_ now (solnic) - Renamed `Dry::Data::Type::{Enum,Constrained}` => `Dry::Data::{Enum,Constrained}` (solnic) - `dry-logic` is now a dependency for constrained types (solnic) - Constrained types are now always available (solnic) - `strict.*` category uses constrained types with `:type?` predicate (solnic) - `SumType#call` no longer needs to rescue from `TypeError` (solnic) ## Fixed - `attribute` raises proper error when type definition is missing (solnic) [Compare v0.4.2...v0.5.0](https://github.com/dry-rb/dry-types/compare/v0.4.2...v0.5.0) # v0.4.2 2015-12-27 ## Added - Support for arrays in type compiler (solnic) ## Changed - Array member uses type objects now rather than just their constructors (solnic) [Compare v0.4.1...v0.4.2](https://github.com/dry-rb/dry-types/compare/v0.4.1...v0.4.2) # v0.4.0 2015-12-11 ## Added - Support for sum-types with constraint type (solnic) - `Dry::Data::Type#optional` for defining optional types (solnic) ## Changed - `Dry::Data['optional']` was **removed** in favor of `Dry::Data::Type#optional` (solnic) [Compare v0.3.2...v0.4.0](https://github.com/dry-rb/dry-types/compare/v0.3.2...v0.4.0) # v0.3.2 2015-12-10 ## Added - `Dry::Data::Value` which works like a struct but is a value object with equalizer (solnic) ## Fixed - Added missing require for `dry-equalizer` (solnic) [Compare v0.3.1...v0.3.2](https://github.com/dry-rb/dry-types/compare/v0.3.1...v0.3.2) # v0.3.1 2015-12-09 ## Changed - Removed require of constrained type and make it optional (solnic) [Compare v0.3.0...v0.3.1](https://github.com/dry-rb/dry-types/compare/v0.3.0...v0.3.1) # v0.3.0 2015-12-09 ## Added - `Type#constrained` interface for defining constrained types (solnic) - `Dry::Data` can be configured with a type namespace (solnic) - `Dry::Data.finalize` can be used to define types as constants under configured namespace (solnic) - `Dry::Data::Type#enum` for defining an enum from a specific type (solnic) - New types: `symbol` and `class` along with their `strict` versions (solnic) [Compare v0.2.1...v0.3.0](https://github.com/dry-rb/dry-types/compare/v0.2.1...v0.3.0) # v0.2.1 2015-11-30 ## Added - Type compiler supports nested hashes now (solnic) ## Fixed - `form.bool` sum is using correct right-side `form.false` type (solnic) ## Changed - Improved structure of the ast (solnic) [Compare v0.2.0...v0.2.1](https://github.com/dry-rb/dry-types/compare/v0.2.0...v0.2.1) # v0.2.0 2015-11-29 ## Added - `form.nil` which coerces empty strings to `nil` (solnic) - `bool` sum-type (true | false) (solnic) - Type compiler supports sum-types now (solnic) ## Changed - Constructing optional types uses the new `Dry::Data["optional"]` built-in type (solnic) [Compare v0.1.0...v0.2.0](https://github.com/dry-rb/dry-types/compare/v0.1.0...v0.2.0) # v0.1.0 2015-11-27 ## Added - `form.*` coercible types (solnic) - `Type::Hash#strict` for defining hashes with a strict schema (solnic) - `Type::Hash#symbolized` for defining hashes that will symbolize keys (solnic) - `Dry::Data.register_class` short-cut interface for registering a class and setting its `.new` method as the constructor (solnic) - `Dry::Data::Compiler` for building a type from a simple ast (solnic) [Compare v0.0.1...HEAD](https://github.com/dry-rb/dry-types/compare/v0.0.1...HEAD) # v0.0.1 2015-10-05 First public release dry-types-1.2.2/.codeclimate.yml0000644000175000017500000000025013617303242016450 0ustar utkarshutkarsh# this file is managed by dry-rb/devtools project version: "2" exclude_patterns: - "benchmarks/" - "examples/" - "spec/" plugins: rubocop: enabled: true dry-types-1.2.2/.gitignore0000644000175000017500000000013513617303242015370 0ustar utkarshutkarsh/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ log/ dry-types-1.2.2/Gemfile0000644000175000017500000000121013617303242014666 0ustar utkarshutkarsh# frozen_string_literal: true source 'https://rubygems.org' git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } gemspec gem 'dry-logic', github: 'dry-rb/dry-logic', branch: 'master' if ENV['DRY_LOGIC_FROM_MASTER'].eql?('true') group :test do platform :mri do gem 'simplecov', require: false end gem 'dry-struct' end group :tools do gem 'pry-byebug', platform: :mri gem 'rubocop' gem 'ossy', github: 'solnic/ossy', branch: 'master' end group :benchmarks do platform :mri do gem 'attrio' gem 'benchmark-ips' gem 'dry-struct' gem 'fast_attributes' gem 'hotch' gem 'virtus' end end dry-types-1.2.2/README.md0000644000175000017500000000304113617303242014656 0ustar utkarshutkarsh[gem]: https://rubygems.org/gems/dry-types [ci]: https://github.com/dry-rb/dry-types/actions?query=workflow%3Aci [codeclimate]: https://codeclimate.com/github/dry-rb/dry-types [inchpages]: http://inch-ci.org/github/dry-rb/dry-types [chat]: https://dry-rb.zulipchat.com # dry-types [![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-types.svg)][gem] [![Build Status](https://github.com/dry-rb/dry-types/workflows/ci/badge.svg)][ci] [![Code Climate](https://codeclimate.com/github/dry-rb/dry-types/badges/gpa.svg)][codeclimate] [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-types/badges/coverage.svg)][codeclimate] [![Inline docs](http://inch-ci.org/github/dry-rb/dry-types.svg?branch=master)][inchpages] ## Links - [Documentation](http://dry-rb.org/gems/dry-types) ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake run_specs` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/dry-rb/dry-types. dry-types-1.2.2/.rspec0000644000175000017500000000005613617303242014517 0ustar utkarshutkarsh--color --require spec_helper --order random dry-types-1.2.2/docsite/0000755000175000017500000000000013617303242015033 5ustar utkarshutkarshdry-types-1.2.2/docsite/source/0000755000175000017500000000000013617303242016333 5ustar utkarshutkarshdry-types-1.2.2/docsite/source/extensions.html.md0000644000175000017500000000044613617303242022023 0ustar utkarshutkarsh--- title: Extensions layout: gem-single name: dry-types sections: - maybe - monads --- `dry-types` can be extended with extension. Those extensions are loaded with `Dry::Types.load_extensions`. Available extensions: - [Maybe](docs::extensions/maybe) - [Monads](docs::extensions/monads) dry-types-1.2.2/docsite/source/array-with-member.html.md0000644000175000017500000000036613617303242023161 0ustar utkarshutkarsh--- title: Array With Member layout: gem-single name: dry-types --- The built-in array type supports defining the member's type: ``` ruby PostStatuses = Types::Array.of(Types::Coercible::String) PostStatuses[[:foo, :bar]] # ["foo", "bar"] ``` dry-types-1.2.2/docsite/source/optional-values.html.md0000644000175000017500000000153413617303242022745 0ustar utkarshutkarsh--- title: Type Attributes layout: gem-single name: dry-types --- Types themselves have optional attributes you can apply to get further functionality. ### Append `.optional` to a _Type_ to allow `nil` By default, nil values raise an error: ``` ruby Types::Strict::String[nil] # => raises Dry::Types::ConstraintError ``` Add `.optional` and `nil` values become valid: ```ruby optional_string = Types::Strict::String.optional optional_string[nil] # => nil optional_string['something'] # => "something" optional_string[123] # raises Dry::Types::ConstraintError ``` `Types::String.optional` is just syntactic sugar for `Types::Strict::Nil | Types::Strict::String`. ### Handle optional values using Monads See [Maybe](docs::extensions/maybe) extension for another approach to handling optional values by returning a [_Monad_](/gems/dry-monads/) object. dry-types-1.2.2/docsite/source/hash-schemas.html.md0000644000175000017500000001051313617303242022164 0ustar utkarshutkarsh--- title: Hash Schemas layout: gem-single name: dry-types --- It is possible to define a type for a hash with a known set of keys and corresponding value types. Let's say you want to describe a hash containing the name and the age of a user: ```ruby # using simple kernel coercions user_hash = Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer) user_hash[name: 'Jane', age: '21'] # => { name: 'Jane', age: 21 } # :name left untouched and :age was coerced to Integer ``` If a value doesn't conform to the type, an error is raised: ```ruby user_hash[name: :Jane, age: '21'] # => Dry::Types::SchemaError: :Jane (Symbol) has invalid type # for :name violates constraints (type?(String, :Jane) failed) ``` All keys are required by default: ```ruby user_hash[name: 'Jane'] # => Dry::Types::MissingKeyError: :age is missing in Hash input ``` Extra keys are omitted by default: ```ruby user_hash[name: 'Jane', age: '21', city: 'London'] # => { name: 'Jane', age: 21 } ``` ### Default values Default types are **only** evaluated if the corresponding key is missing in the input: ```ruby user_hash = Types::Hash.schema( name: Types::String, age: Types::Integer.default(18) ) user_hash[name: 'Jane'] # => { name: 'Jane', age: 18 } # nil violates the constraint user_hash[name: 'Jane', age: nil] # => Dry::Types::SchemaError: nil (NilClass) has invalid type # for :age violates constraints (type?(Integer, nil) failed) ``` In order to evaluate default types on `nil`, wrap your type with a constructor and map `nil` to `Dry::Types::Undefined`: ```ruby user_hash = Types::Hash.schema( name: Types::String, age: Types::Integer. default(18). constructor { |value| value.nil? ? Dry::Types::Undefined : value } ) user_hash[name: 'Jane', age: nil] # => { name: 'Jane', age: 18 } ``` The process of converting types to constructors like that can be automated, see "Type transformations" below. ### Optional keys By default, all keys are required to present in the input. You can mark a key as optional by adding `?` to its name: ```ruby user_hash = Types::Hash.schema(name: Types::String, age?: Types::Integer) user_hash[name: 'Jane'] # => { name: 'Jane' } ``` ### Extra keys All keys not declared in the schema are silently ignored. This behavior can be changed by calling `.strict` on the schema: ```ruby user_hash = Types::Hash.schema(name: Types::String).strict user_hash[name: 'Jane', age: 21] # => Dry::Types::UnknownKeysError: unexpected keys [:age] in Hash input ``` ### Transforming input keys Keys are supposed to be symbols but you can attach a key tranformation to a schema, e.g. for converting strings into symbols: ```ruby user_hash = Types::Hash.schema(name: Types::String).with_key_transform(&:to_sym) user_hash['name' => 'Jane'] # => { name: 'Jane' } ``` ### Inheritance Hash schemas can be inherited in a sense you can define a new schema based on an existing one. Declared keys will be merged, key and type transformations will be preserved. The `strict` option is also passed to the new schema if present. ```ruby # Building an empty base schema StrictSymbolizingHash = Types::Hash.schema({}).strict.with_key_transform(&:to_sym) user_hash = StrictSymbolizingHash.schema( name: Types::String ) user_hash['name' => 'Jane'] # => { name: 'Jane' } user_hash['name' => 'Jane', 'city' => 'London'] # => Dry::Types::UnknownKeysError: unexpected keys [:city] in Hash input ``` ### Transforming types A schema can transform types with a block. For example, the following code makes all keys optional: ```ruby user_hash = Types::Hash.with_type_transform { |type| type.required(false) }.schema( name: Types::String, age: Types::Integer ) user_hash[name: 'Jane'] # => { name: 'Jane' } user_hash[{}] # => {} ``` Type transformations work perfectly with inheritance, you don't have to define same rules more than once: ```ruby SymbolizeAndOptionalSchema = Types::Hash .schema({}) .with_key_transform(&:to_sym) .with_type_transform { |type| type.required(false) } user_hash = SymbolizeAndOptionalSchema.schema( name: Types::String, age: Types::Integer ) user_hash['name' => 'Jane'] ``` You can check key name by calling `.name` on the type argument: ```ruby Types::Hash.with_type_transform do |key| if key.name.to_s.end_with?('_at') key.constructor { |v| Time.iso8601(v) } else key end end ``` dry-types-1.2.2/docsite/source/custom-types.html.md0000644000175000017500000000412613617303242022277 0ustar utkarshutkarsh--- title: Custom Types layout: gem-single name: dry-types --- There are a bunch of helpers for building your own types based on existing classes and values. These helpers are automatically defined if you're imported types in a module. ### `Types.Instance` `Types.Instance` builds a type that checks if a value has the given class. ```ruby range_type = Types.Instance(Range) range_type[1..2] # => 1..2 ``` ### `Types.Value` `Types.Value` builds a type that checks a value for equality (using `==`). ```ruby valid = Types.Value('valid') valid['valid'] # => 'valid' valid['invalid'] # => Dry::Types::ConstraintError: "invalid" violates constraints (eql?("valid", "invalid") failed) ``` ### `Types.Constant` `Types.Constant` builds a type that checks a value for identity (using `equal?`). ```ruby valid = Types.Constant(:valid) valid[:valid] # => :valid valid[:invalid] # => Dry::Types::ConstraintError: :invalid violates constraints (is?(:valid, :invalid) failed) ``` ### `Types.Constructor` `Types.Constructor` builds a new constructor type for the given class. By default uses the `new` method as a constructor. ```ruby user_type = Types.Constructor(User) # It is equivalent to User.new(name: 'John') user_type[name: 'John'] # Using a block user_type = Types.Constructor(User) { |values| User.new(values) } ``` ### `Types.Nominal` `Types.Nominal` wraps the given class with a simple definition without any behavior attached. ```ruby int = Types.Nominal(Integer) int[1] # => 1 # The type doesn't have any checks int['one'] # => 'one' ``` ### `Types.Hash` `Types.Hash` builds a new hash schema. ```ruby # In the full form Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer) # Using Types.Hash() Types.Hash(:permissive, name: Types::String, age: Types::Coercible::Integer) ``` ### `Types.Array` `Types.Array` is a shortcut for `Types::Array.of` ```ruby ListOfStrings = Types.Array(Types::String) ``` ### `Types.Interface` `Types.Interface` builds a type that checks a value responds to given methods. ```ruby Callable = Types.Interface(:call) Contact = Types.Interface(:name, :phone) ```dry-types-1.2.2/docsite/source/enum.html.md0000644000175000017500000000375613617303242020577 0ustar utkarshutkarsh--- title: Enum layout: gem-single name: dry-types --- In many cases you may want to define an enum. For example, in a blog application a post may have a finite list of statuses. Apart from accessing the current status value, it is useful to have all possible values accessible too. Furthermore, an enum can be a map from, e.g., strings to integers. This is useful for mapping externally-provided integer values to human-readable strings without explicit conversions, see examples. ``` ruby require 'dry-types' require 'dry-struct' module Types include Dry.Types() end class Post < Dry::Struct Statuses = Types::String.enum('draft', 'published', 'archived') attribute :title, Types::String attribute :body, Types::String attribute :status, Statuses end # enum values are frozen, let's be paranoid, doesn't hurt and have potential to # eliminate silly bugs Post::Statuses.values.frozen? # => true Post::Statuses.values.all?(&:frozen?) # => true Post::Statuses['draft'] # => "draft" # it'll raise if something silly was passed in Post::Statuses['something silly'] # => Dry::Types::ConstraintError: "something silly" violates constraints # nil is considered as something silly too Post::Statuses[nil] # => Dry::Types::ConstraintError: nil violates constraints ``` Note that if you want to define an enum type with a default, you must call `.default` *before* calling `.enum`, not the other way around: ```ruby # this is the correct usage: Dry::Types::String.default('red').enum('blue', 'green', 'red') # this will raise an error: Dry::Types::String.enum('blue', 'green', 'red').default('red') ``` ### Mappings A classic example is mapping integers coming from somewhere (API/database/etc) to something more understandable: ```ruby class Cell < Dry::Struct attribute :state, Types::String.enum('locked' => 0, 'open' => 1) end Cell.new(state: 'locked') # => # # Integers are accepted too Cell.new(state: 0) # => # Cell.new(state: 1) # => # ``` dry-types-1.2.2/docsite/source/built-in-types.html.md0000644000175000017500000000723213617303242022511 0ustar utkarshutkarsh--- title: Built-in Types layout: gem-single name: dry-types --- Built-in types are grouped under 6 categories: - `nominal` - base type definitions with a primitive class and options - `strict` - constrained types with a primitive type check applied to input - `coercible` - types with constructors using kernel coercions - `params` - types with constructors performing non-strict coercions specific to HTTP parameters - `json` - types with constructors performing non-strict coercions specific to JSON - `maybe` - types accepting either nil or a specific primitive type ### Categories Assuming you included `Dry::Types` ([see instructions](docs::getting-started)) in a module called `Types`: * Nominal types: - `Types::Nominal::Any` - `Types::Nominal::Nil` - `Types::Nominal::Symbol` - `Types::Nominal::Class` - `Types::Nominal::True` - `Types::Nominal::False` - `Types::Nominal::Bool` - `Types::Nominal::Integer` - `Types::Nominal::Float` - `Types::Nominal::Decimal` - `Types::Nominal::String` - `Types::Nominal::Date` - `Types::Nominal::DateTime` - `Types::Nominal::Time` - `Types::Nominal::Array` - `Types::Nominal::Hash` * `Strict` types will raise an error if passed a value of the wrong type: - `Types::Strict::Nil` - `Types::Strict::Symbol` - `Types::Strict::Class` - `Types::Strict::True` - `Types::Strict::False` - `Types::Strict::Bool` - `Types::Strict::Integer` - `Types::Strict::Float` - `Types::Strict::Decimal` - `Types::Strict::String` - `Types::Strict::Date` - `Types::Strict::DateTime` - `Types::Strict::Time` - `Types::Strict::Array` - `Types::Strict::Hash` > All types in the `strict` category are [constrained](docs::constraints) by a type-check that is applied to make sure that the input is an instance of the primitive: ``` ruby Types::Strict::Integer[1] # => 1 Types::Strict::Integer['1'] # => raises Dry::Types::ConstraintError ``` * `Coercible` types will attempt to cast values to the correct class using kernel coercion methods: - `Types::Coercible::String` - `Types::Coercible::Integer` - `Types::Coercible::Float` - `Types::Coercible::Decimal` - `Types::Coercible::Array` - `Types::Coercible::Hash` * Types suitable for `Params` param processing with coercions: - `Types::Params::Nil` - `Types::Params::Date` - `Types::Params::DateTime` - `Types::Params::Time` - `Types::Params::True` - `Types::Params::False` - `Types::Params::Bool` - `Types::Params::Integer` - `Types::Params::Float` - `Types::Params::Decimal` - `Types::Params::Array` - `Types::Params::Hash` * Types suitable for `JSON` processing with coercions: - `Types::JSON::Nil` - `Types::JSON::Date` - `Types::JSON::DateTime` - `Types::JSON::Time` - `Types::JSON::Decimal` - `Types::JSON::Array` - `Types::JSON::Hash` * `Maybe` strict types: - `Types::Maybe::Strict::Class` - `Types::Maybe::Strict::String` - `Types::Maybe::Strict::Symbol` - `Types::Maybe::Strict::True` - `Types::Maybe::Strict::False` - `Types::Maybe::Strict::Integer` - `Types::Maybe::Strict::Float` - `Types::Maybe::Strict::Decimal` - `Types::Maybe::Strict::Date` - `Types::Maybe::Strict::DateTime` - `Types::Maybe::Strict::Time` - `Types::Maybe::Strict::Array` - `Types::Maybe::Strict::Hash` * `Maybe` coercible types: - `Types::Maybe::Coercible::String` - `Types::Maybe::Coercible::Integer` - `Types::Maybe::Coercible::Float` - `Types::Maybe::Coercible::Decimal` - `Types::Maybe::Coercible::Array` - `Types::Maybe::Coercible::Hash` > `Maybe` types are not available by default - they must be loaded using `Dry::Types.load_extensions(:maybe)`. See [Maybe extension](docs::extensions/maybe) for more information. dry-types-1.2.2/docsite/source/default-values.html.md0000644000175000017500000000536013617303242022545 0ustar utkarshutkarsh--- title: Default Values layout: gem-single name: dry-types --- A type with a default value will return the configured value when the input is not defined: ``` ruby PostStatus = Types::String.default('draft') PostStatus[] # "draft" PostStatus["published"] # "published" PostStatus[true] # raises ConstraintError ``` It works with a callable value: ``` ruby CallableDateTime = Types::DateTime.default { DateTime.now } CallableDateTime[] # => # CallableDateTime[] # => # ``` `Dry::Types::Undefined` can be passed explicitly as a missing value: ```ruby PostStatus = Types::String.default('draft') PostStatus[Dry::Types::Undefined] # "draft" ``` It also receives the type constructor as an argument: ```ruby CallableDateTime = Types::DateTime.constructor(&:to_datetime).default { |type| type[Time.now] } CallableDateTime[Time.now] # => # CallableDateTime[Date.today] # => # CallableDateTime[] # => # ``` **Be careful:** types will return the **same instance** of the default value every time. This may cause problems if you mutate the returned value after receiving it: ```ruby default_0 = PostStatus.() # => "draft" default_1 = PostStatus.() # => "draft" # Both variables point to the same string: default_0.object_id == default_1.object_id # => true # Mutating the string will change the default value of type: default_0 << '_mutated' PostStatus.(nil) # => "draft_mutated" # not "draft" ``` You can guard against these kind of errors by calling `freeze` when setting the default: ```ruby PostStatus = Types::Params::String.default('draft'.freeze) default = PostStatus.() default << 'attempt to mutate default' # => RuntimeError: can't modify frozen string # If you really want to mutate it, call `dup` on it first: default = default.dup default << "this time it'll work" ``` **Warning on using with constrained types**: If the value passed to the `.default` block does not match the type constraints, this will not throw an exception, because it is not passed to the constructor and will be used as is. ```ruby CallableDateTime = Types::DateTime.constructor(&:to_datetime).default { Time.now } CallableDateTime[Time.now] # => # CallableDateTime[Date.today] # => # CallableDateTime[] # => 2017-05-06 00:50:15 +0300 ``` dry-types-1.2.2/docsite/source/extensions/0000755000175000017500000000000013617303242020532 5ustar utkarshutkarshdry-types-1.2.2/docsite/source/extensions/maybe.html.md0000644000175000017500000000302313617303242023112 0ustar utkarshutkarsh--- title: Maybe layout: gem-single name: dry-types --- The [dry-monads gem](/gems/dry-monads/) provides approach to handling optional values by returning a [_Monad_](/gems/dry-monads/) object. This allows you to pass your type to a `Maybe(x)` block that only executes if `x` returns `Some` or `None`. > NOTE: Requires the [dry-monads gem](/gems/dry-monads/) to be loaded. 1. Load the `:maybe` extension in your application. ```ruby require 'dry-types' Dry::Types.load_extensions(:maybe) module Types include Dry.Types() end ``` 2. Append `.maybe` to a _Type_ to return a _Monad_ object ```ruby x = Types::Maybe::Strict::Integer[nil] Maybe(x) { puts(x) } x = Types::Maybe::Coercible::String[nil] Maybe(x) { puts(x) } x = Types::Maybe::Strict::Integer[123] Maybe(x) { puts(x) } x = Types::Maybe::Strict::String[123] Maybe(x) { puts(x) } ``` ```ruby Types::Maybe::Strict::Integer[nil] # None Types::Maybe::Strict::Integer[123] # Some(123) Types::Maybe::Coercible::Float[nil] # None Types::Maybe::Coercible::Float['12.3'] # Some(12.3) # 'Maybe' types can also accessed by calling '.maybe' on a regular type: Types::Strict::Integer.maybe # equivalent to Types::Maybe::Strict::Integer ``` You can define your own optional types: ``` ruby maybe_string = Types::Strict::String.maybe maybe_string[nil] # => None maybe_string[nil].fmap(&:upcase) # => None maybe_string['something'] # => Some('something') maybe_string['something'].fmap(&:upcase) # => Some('SOMETHING') maybe_string['something'].fmap(&:upcase).value_or('NOTHING') # => "SOMETHING" ``` dry-types-1.2.2/docsite/source/extensions/monads.html.md0000644000175000017500000000262513617303242023305 0ustar utkarshutkarsh--- title: Monads layout: gem-single name: dry-types --- The monads extension makes `Dry::Types::Result` objects compatible with `dry-monads`. To enable the extension: ```ruby require 'dry/types' Dry::Types.load_extensions(:monads) ``` After loading the extension, you can leverage monad API: ```ruby Types = Dry.Types() result = Types::String.try('Jane') result.class #=> Dry::Types::Result::Success monad = result.to_monad monad.class #=> Dry::Monads::Result::Success monad.value! # => 'Jane' result = Types::String.try(nil) result.class #=> Dry::Types::Result::Failure monad = result.to_monad monad.class #=> Dry::Monads::Result::Failure monad.failure # => [#, nil] Types::String.try(nil) .to_monad .fmap { |result| puts "passed: #{result.inspect}" } .or { |error, input| puts "input '#{input.inspect}' failed with error: #{error.to_s}" } ``` This can be useful when used with `dry-monads` and the [`do` notation](/gems/dry-monads/1.0/do-notation/): ```ruby require 'dry/types' Types = Dry.Types() Dry::Types.load_extensions(:monads) class AddTen include Dry::Monads[:result, :do] def call(input) integer = yield Types::Coercible::Integer.try(input) Success(integer + 10) end end add_ten = AddTen.new add_ten.call(10) # => Success(20) add_ten.call('integer') # => Failure([#, "integer"]) ``` dry-types-1.2.2/docsite/source/sum.html.md0000644000175000017500000000112613617303242020424 0ustar utkarshutkarsh--- title: Sum layout: gem-single name: dry-types order: 7 --- You can specify sum types using `|` operator, it is an explicit way of defining what the valid types of a value are. For example `dry-types` defines the `Bool` type which is a sum consisting of the `True` and `False` types, expressed as `Types::True | Types::False`. Another common case is defining that something can be either `nil` or something else: ``` ruby nil_or_string = Types::Nil | Types::String nil_or_string[nil] # => nil nil_or_string["hello"] # => "hello" nil_or_string[123] # raises Dry::Types::ConstraintError ``` dry-types-1.2.2/docsite/source/index.html.md0000644000175000017500000001060413617303242020730 0ustar utkarshutkarsh--- title: Introduction layout: gem-single type: gem name: dry-types sections: - getting-started - built-in-types - optional-values - default-values - sum - constraints - hash-schemas - array-with-member - enum - map - custom-types - extensions --- `dry-types` is a simple and extendable type system for Ruby; useful for value coercions, applying constraints, defining complex structs or value objects and more. It was created as a successor to [Virtus](https://github.com/solnic/virtus). ### Example usage ```ruby require 'dry-types' require 'dry-struct' module Types include Dry.Types() end User = Dry.Struct(name: Types::String, age: Types::Integer) User.new(name: 'Bob', age: 35) # => # ``` See [Built-in Types](docs::built-in-types/) for a full list of available types. By themselves, the basic type definitions like `Types::String` and `Types::Integer` don't do anything except provide documentation about which type an attribute is expected to have. However, there are many more advanced possibilities: - `Strict` types will raise an error if passed an attribute of the wrong type: ```ruby class User < Dry::Struct attribute :name, Types::Strict::String attribute :age, Types::Strict::Integer end User.new(name: 'Bob', age: '18') # => Dry::Struct::Error: [User.new] "18" (String) has invalid type for :age ``` - `Coercible` types will attempt to convert an attribute to the correct class using Ruby's built-in coercion methods: ```ruby class User < Dry::Struct attribute :name, Types::Coercible::String attribute :age, Types::Coercible::Integer end User.new(name: 'Bob', age: '18') # => # User.new(name: 'Bob', age: 'not coercible') # => ArgumentError: invalid value for Integer(): "not coercible" ``` - Use `.optional` to denote that an attribute can be `nil` (see [Optional Values](docs::optional-values)): ```ruby class User < Dry::Struct attribute :name, Types::String attribute :age, Types::Integer.optional end User.new(name: 'Bob', age: nil) # => # # name is not optional: User.new(name: nil, age: 18) # => Dry::Struct::Error: [User.new] nil (NilClass) has invalid type for :name # keys must still be present: User.new(name: 'Bob') # => Dry::Struct::Error: [User.new] :age is missing in Hash input ``` - Add custom constraints (see [Constraints](docs::constraints.html)): ```ruby class User < Dry::Struct attribute :name, Types::Strict::String attribute :age, Types::Strict::Integer.constrained(gteq: 18) end User.new(name: 'Bob', age: 17) # => Dry::Struct::Error: [User.new] 17 (Fixnum) has invalid type for :age ``` - Add custom metadata to a type: ```ruby class User < Dry::Struct attribute :name, Types::String attribute :age, Types::Integer.meta(info: 'extra info about age') end ``` - Pass values directly to `Dry::Types` without creating an object using `[]`: ```ruby Types::Strict::String["foo"] # => "foo" Types::Strict::String["10000"] # => "10000" Types::Coercible::String[10000] # => "10000" Types::Strict::String[10000] # Dry::Types::ConstraintError: 1000 violates constraints ``` ### Features * Support for [constrained types](docs::constraints) * Support for [optional values](docs::optional-values) * Support for [default values](docs::default-values) * Support for [sum types](docs::sum) * Support for [enums](docs::enum) * Support for [hash type with type schemas](docs::hash-schemas) * Support for [array type with members](docs::array-with-member) * Support for arbitrary meta information * Support for typed struct objects via [dry-struct](/gems/dry-struct) * Types are [categorized](docs::built-in-types), which is especially important for optimized and dedicated coercion logic * Types are composable and reusable objects * No const-missing magic and complicated const lookups * Roughly 6-10 x faster than Virtus ### Use cases `dry-types` is suitable for many use-cases, for example: * Value coercions * Processing arrays * Processing hashes with explicit schemas * Defining various domain-specific information shared between multiple parts of your application * Annotating objects ### Other gems using dry-types `dry-types` is often used as a low-level abstraction. The following gems use it already: * [dry-struct](/gems/dry-struct) * [dry-initializer](/gems/dry-initializer) * [Hanami](http://hanamirb.org) * [rom-rb](http://rom-rb.org) * [Trailblazer](http://trailblazer.to) dry-types-1.2.2/docsite/source/getting-started.html.md0000644000175000017500000000252413617303242022730 0ustar utkarshutkarsh--- title: Getting Started layout: gem-single name: dry-types --- ### Using `Dry::Types` in Your Application 1. Make `Dry::Types` available to the application by creating a namespace that includes `Dry::Types`: ```ruby module Types include Dry.Types() end ``` 2. Reload the environment, & type `Types::Coercible::String` in the ruby console to confirm it worked: ``` ruby Types::Coercible::String # => #> ``` ### Creating Your First Type 1. Define a struct's types by passing the name & type to the `attribute` method: ```ruby class User < Dry::Struct attribute :name, Types::String end ``` 2. Define [Custom Types](docs::custom-types) in the `Types` module, then pass the name & type to `attribute`: ```ruby module Types include Dry.Types() Email = String.constrained(format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i) Age = Integer.constrained(gt: 18) end class User < Dry::Struct attribute :name, Types::String attribute :email, Types::Email attribute :age, Types::Age end ``` 3. Use a `Dry::Struct` as a type: ```ruby class Message < Dry::Struct attribute :body, Types::String attribute :to, User end ``` dry-types-1.2.2/docsite/source/map.html.md0000644000175000017500000000077513617303242020406 0ustar utkarshutkarsh--- title: Map layout: gem-single name: dry-types --- `Map` describes a homogeneous hashmap. This means only types of keys and values are known. You can simply imagine a map input as a list of key-value pairs. ```ruby int_float_hash = Types::Hash.map(Types::Integer, Types::Float) int_float_hash[100 => 300.0, 42 => 70.0] # => {100=>300.0, 42=>70.0} # Only accepts mappings of integers to floats int_float_hash[name: 'Jane'] # => Dry::Types::MapError: input key :name is invalid: type?(Integer, :name) ``` dry-types-1.2.2/docsite/source/constraints.html.md0000644000175000017500000000164613617303242022176 0ustar utkarshutkarsh--- title: Constraints layout: gem-single name: dry-types --- You can create constrained types that will use validation rules to check that the input is not violating any of the configured constraints. You can treat it as a lower level guarantee that you're not instantiating objects that are broken. All types support the constraints API, but not all constraints are suitable for a particular primitive, it's up to you to set up constraints that make sense. Under the hood it uses [`dry-logic`](/gems/dry-logic) and all of its predicates are supported. ``` ruby string = Types::String.constrained(min_size: 3) string['foo'] # => "foo" string['fo'] # => Dry::Types::ConstraintError: "fo" violates constraints email = Types::String.constrained( format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i ) email["jane@doe.org"] # => "jane@doe.org" email["jane"] # => Dry::Types::ConstraintError: "jane" violates constraints ``` dry-types-1.2.2/LICENSE0000644000175000017500000000207313617303242014410 0ustar utkarshutkarshThe MIT License (MIT) Copyright (c) 2015-2019 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-types-1.2.2/log/0000755000175000017500000000000013617303242014162 5ustar utkarshutkarshdry-types-1.2.2/log/.gitkeep0000644000175000017500000000000013617303242015601 0ustar utkarshutkarshdry-types-1.2.2/dry-types.gemspec0000644000175000017500000000417113617303242016711 0ustar utkarshutkarsh# frozen_string_literal: true lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'dry/types/version' Gem::Specification.new do |spec| spec.name = 'dry-types' spec.version = Dry::Types::VERSION.dup spec.authors = ['Piotr Solnica'] spec.email = ['piotr.solnica@gmail.com'] spec.license = 'MIT' spec.summary = 'Type system for Ruby supporting coercions, constraints and complex types like structs, value objects, enums etc.' spec.description = spec.summary spec.homepage = 'https://github.com/dry-rb/dry-types' # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or # delete this section to allow pushing this gem to any host. if spec.respond_to?(:metadata) spec.metadata['allowed_push_host'] = 'https://rubygems.org' spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md' spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-types' spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-types/issues' else raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' end spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - ['bin/console', 'bin/setup'] spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] spec.required_ruby_version = '>= 2.4.0' spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0' spec.add_runtime_dependency 'dry-container', '~> 0.3' spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.4' spec.add_runtime_dependency 'dry-equalizer', '~> 0.3' spec.add_runtime_dependency 'dry-inflector', '~> 0.1', '>= 0.1.2' spec.add_runtime_dependency 'dry-logic', '~> 1.0', '>= 1.0.2' spec.add_development_dependency 'bundler' spec.add_development_dependency 'dry-monads', '~> 0.2' spec.add_development_dependency 'rake', '~> 11.0' spec.add_development_dependency 'rspec', '~> 3.3' spec.add_development_dependency 'yard', '~> 0.9.5' end dry-types-1.2.2/benchmarks/0000755000175000017500000000000013617303242015516 5ustar utkarshutkarshdry-types-1.2.2/benchmarks/profile_lax_schema_valid.rb0000644000175000017500000000042413617303242023046 0ustar utkarshutkarsh# frozen_string_literal: true require_relative 'setup' Schema = Dry::Types['params.hash'].schema( email?: 'string', age?: 'coercible.integer' ).lax ValidInput = { email: 'jane@doe.org', age: '19' }.freeze profile do 10_000.times do Schema.(ValidInput) end end dry-types-1.2.2/benchmarks/schema_valid_vs_invalid.rb0000644000175000017500000000053113617303242022677 0ustar utkarshutkarsh# frozen_string_literal: true require_relative 'setup' VALID_INPUT = { name: 'John', age: 20, email: 'john@doe.com' } INVALID_INPUT = { name: :John, age: '20', email: nil } Benchmark.ips do |x| x.report("valid input") { PersonSchema.(VALID_INPUT) } x.report("invalid input") { PersonSchema.(INVALID_INPUT) } x.compare! end dry-types-1.2.2/benchmarks/profile_invalid_input.rb0000644000175000017500000000030613617303242022427 0ustar utkarshutkarsh# frozen_string_literal: true require_relative 'setup' INVALID_INPUT = { name: :John, age: '20', email: nil }.freeze profile do 10_000.times do PersonSchema.(INVALID_INPUT) end end dry-types-1.2.2/benchmarks/profile_valid_input.rb0000644000175000017500000000031413617303242022077 0ustar utkarshutkarsh# frozen_string_literal: true require_relative 'setup' VALID_INPUT = { name: 'John', age: 20, email: 'john@doe.com' }.freeze profile do 10_000.times do PersonSchema.(VALID_INPUT) end end dry-types-1.2.2/benchmarks/lax_schema.rb0000644000175000017500000000043313617303242020147 0ustar utkarshutkarsh# frozen_string_literal: true require_relative 'setup' schema = Dry::Types['params.hash'].schema( email?: 'string', age?: 'params.integer' ).lax params = { email: 'jane@doe.org', age: '19' } Benchmark.ips do |x| x.report("valid input") { schema.(params) } x.compare! end dry-types-1.2.2/benchmarks/hash_schemas.rb0000644000175000017500000000224513617303242020474 0ustar utkarshutkarsh# frozen_string_literal: true $LOAD_PATH.unshift('lib') require 'bundler/setup' require 'dry-types' module SchemaBench def self.hash_schema(type) Dry::Types['nominal.hash'].public_send( type, email: Dry::Types['nominal.string'], age: Dry::Types['params.integer'], admin: Dry::Types['params.bool'], address: Dry::Types['nominal.hash'].public_send( type, city: Dry::Types['nominal.string'], street: Dry::Types['nominal.string'] ) ) end private_class_method(:hash_schema) SCHEMAS = Dry::Types::Hash .public_instance_methods(false) .map { |schema_type| [schema_type, hash_schema(schema_type)] } .to_h INPUT = { email: 'jane@doe.org', age: '20', admin: '1', address: { city: 'NYC', street: 'Street 1/2' } }.freeze end require 'benchmark/ips' Benchmark.ips do |x| SchemaBench::SCHEMAS.each do |schema_type, schema| x.report("#{schema_type}#call") do schema.call(SchemaBench::INPUT) end end SchemaBench::SCHEMAS.each do |schema_type, schema| x.report("#{schema_type}#try") do schema.try(SchemaBench::INPUT) end end x.compare! end dry-types-1.2.2/benchmarks/setup.rb0000644000175000017500000000043213617303242017202 0ustar utkarshutkarsh# frozen_string_literal: true require 'benchmark/ips' require 'hotch' ENV['HOTCH_VIEWER'] ||= 'open' require 'dry/types' PersonSchema = Dry::Types['hash'].schema( name: 'string', age: 'integer', email: 'string' ).lax def profile(&block) Hotch(filter: 'Dry', &block) end dry-types-1.2.2/.github/0000755000175000017500000000000013617303242014741 5ustar utkarshutkarshdry-types-1.2.2/.github/workflows/0000755000175000017500000000000013617303242016776 5ustar utkarshutkarshdry-types-1.2.2/.github/workflows/custom_ci.yml0000644000175000017500000000447513617303242021520 0ustar utkarshutkarshname: ci on: push: paths: - .github/workflows/custom_ci.yml - lib/** - spec/** jobs: tests-mri: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: ["2.6.x", "2.5.x", "2.4.x"] dry_logic_from_master: ["true", "false"] include: - ruby: "2.6.x" dry_logic_from_master: "false" coverage: "true" steps: - uses: actions/checkout@v1 - name: Set up Ruby uses: actions/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} - name: Download test reporter if: "matrix.coverage == 'true'" run: | mkdir -p tmp/ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./tmp/cc-test-reporter chmod +x ./tmp/cc-test-reporter ./tmp/cc-test-reporter before-build - name: Run all tests env: COVERAGE: ${{matrix.coverage}} DRY_LOGIC_FROM_MASTER: ${{matrix.dry_logic_from_master}} run: | gem install bundler bundle install --jobs 4 --retry 3 --without tools docs bundle exec rake - name: Send coverage results if: "matrix.coverage == 'true'" env: CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}} GIT_COMMIT_SHA: ${{github.sha}} GIT_BRANCH: ${{github.ref}} GIT_COMMITTED_AT: ${{github.event.head_commit.timestamp}} run: | GIT_BRANCH=`ruby -e "puts ENV['GITHUB_REF'].split('/', 3).last"` \ GIT_COMMITTED_AT=`ruby -r time -e "puts Time.iso8601(ENV['GIT_COMMITTED_AT']).to_i"` \ ./tmp/cc-test-reporter after-build tests-others: runs-on: ubuntu-latest strategy: fail-fast: false matrix: image: ["jruby:9.2.9", "ruby:rc"] dry_logic_from_master: ["true", "false"] container: image: ${{matrix.image}} steps: - uses: actions/checkout@v1 - name: Install git run: | apt-get update apt-get install -y --no-install-recommends git - name: Run all tests env: DRY_LOGIC_FROM_MASTER: ${{matrix.dry_logic_from_master}} run: | gem install bundler bundle install --jobs 4 --retry 3 --without tools docs bundle exec rspec dry-types-1.2.2/.github/workflows/docsite.yml0000644000175000017500000000152013617303242021151 0ustar utkarshutkarsh# 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@v1 - name: Set up Ruby uses: actions/setup-ruby@v1 with: ruby-version: "2.6.x" - name: Install dependencies run: | gem install bundler bundle install --jobs 4 --retry 3 --without benchmarks sql - name: Symlink ossy run: mkdir -p bin && ln -sf "$(bundle show ossy)/bin/ossy" bin/ossy - name: Trigger dry-rb.org deploy env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{ secrets.GH_PAT }} run: bin/ossy github workflow dry-rb/dry-rb.org ci dry-types-1.2.2/.github/workflows/sync_configs.yml0000644000175000017500000000200713617303242022204 0ustar utkarshutkarsh# this file is managed by dry-rb/devtools project name: sync_configs on: repository_dispatch: jobs: sync-configs: runs-on: ubuntu-latest if: github.event.action == 'sync_configs' steps: - uses: actions/checkout@v1 - name: Update configuration files from devtools env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{ secrets.GH_PAT }} run: | git clone https://github.com/dry-rb/devtools.git tmp/devtools if [ -f ".github/workflows/custom_ci.yml" ]; then rsync -av --exclude '.github/workflows/ci.yml' tmp/devtools/shared/ . ; else rsync -av tmp/devtools/shared/ . ; fi git config --local user.email "dry-bot@dry-rb.org" git config --local user.name "dry-bot" git add -A git commit -m "[devtools] config sync" || echo "nothing changed" - name: Push changes uses: ad-m/github-push-action@master with: github_token: ${{ secrets.GH_PAT }} dry-types-1.2.2/.github/ISSUE_TEMPLATE/0000755000175000017500000000000013617303242017124 5ustar utkarshutkarshdry-types-1.2.2/.github/ISSUE_TEMPLATE/---bug-report.md0000644000175000017500000000113113617303242021737 0ustar utkarshutkarsh--- name: "\U0001F41B Bug report" about: See CONTRIBUTING.md for more information title: '' labels: bug assignees: '' --- **Before you submit this: WE ONLY ACCEPT BUG REPORTS AND FEATURE REQUESTS** For more information see `CONTRIBUTING.md`. **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. **Your environment** - Affects my production application: **YES/NO** - Ruby version: ... - OS: ... dry-types-1.2.2/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md0000644000175000017500000000022613617303242027362 0ustar utkarshutkarsh--- name: "⚠️ Please don't ask for support via issues" about: See CONTRIBUTING.md for more information title: '' labels: '' assignees: '' --- dry-types-1.2.2/.github/ISSUE_TEMPLATE/---feature-request.md0000644000175000017500000000055613617303242023004 0ustar utkarshutkarsh--- name: "\U0001F6E0 Feature request" about: See CONTRIBUTING.md for more information title: '' labels: feature assignees: '' --- Summary of what the feature is supposed to do. ## Examples Code examples showing how the feature could be used. ## Resources Additional information, like a link to the discussion forum thread where the feature was discussed etc. dry-types-1.2.2/lib/0000755000175000017500000000000013617303242014147 5ustar utkarshutkarshdry-types-1.2.2/lib/dry-types.rb0000644000175000017500000000006313617303242016433 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types' dry-types-1.2.2/lib/dry/0000755000175000017500000000000013617303242014745 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/0000755000175000017500000000000013617303242016111 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/any.rb0000644000175000017500000000162013617303242017224 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types # Any is a nominal type that defines Object as the primitive class # # This type is useful in places where you can't be specific about the type # and anything is acceptable. # # @api public class AnyClass < Nominal def self.name 'Any' end # @api private def initialize(**options) super(::Object, **options) end # @return [String] # # @api public def name 'Any' end # @param [Hash] new_options # # @return [Type] # # @api public def with(**new_options) self.class.new(**options, meta: @meta, **new_options) end # @return [Array] # # @api public def to_ast(meta: true) [:any, meta ? self.meta : EMPTY_HASH] end end Any = AnyClass.new end end dry-types-1.2.2/lib/dry/types/version.rb0000644000175000017500000000013113617303242020116 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types VERSION = '1.2.2' end end dry-types-1.2.2/lib/dry/types/coercions/0000755000175000017500000000000013617303242020075 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/coercions/json.rb0000644000175000017500000000144213617303242021374 0ustar utkarshutkarsh# frozen_string_literal: true require 'date' require 'bigdecimal' require 'bigdecimal/util' require 'time' module Dry module Types module Coercions # JSON-specific coercions # # @api public module JSON extend Coercions # @param [#to_d, Object] input # # @return [BigDecimal,nil] # # @raise CoercionError # # @api public def self.to_decimal(input, &block) if input.is_a?(::Float) input.to_d else BigDecimal(input) end rescue ArgumentError, TypeError if block_given? yield else raise CoercionError, "#{input} cannot be coerced to decimal" end end end end end end dry-types-1.2.2/lib/dry/types/coercions/params.rb0000644000175000017500000000717513617303242021717 0ustar utkarshutkarsh# frozen_string_literal: true require 'bigdecimal' require 'bigdecimal/util' module Dry module Types module Coercions # Params-specific coercions # # @api public module Params TRUE_VALUES = %w[1 on On ON t true True TRUE T y yes Yes YES Y].freeze FALSE_VALUES = %w[0 off Off OFF f false False FALSE F n no No NO N].freeze BOOLEAN_MAP = ::Hash[ TRUE_VALUES.product([true]) + FALSE_VALUES.product([false]) ].merge(true => true, false => false).freeze extend Coercions # @param [String, Object] input # # @return [Boolean,Object] # # @see TRUE_VALUES # @see FALSE_VALUES # # @raise CoercionError # # @api public def self.to_true(input, &_block) BOOLEAN_MAP.fetch(input.to_s) do if block_given? yield else raise CoercionError, "#{input} cannot be coerced to true" end end end # @param [String, Object] input # # @return [Boolean,Object] # # @see TRUE_VALUES # @see FALSE_VALUES # # @raise CoercionError # # @api public def self.to_false(input, &_block) BOOLEAN_MAP.fetch(input.to_s) do if block_given? yield else raise CoercionError, "#{input} cannot be coerced to false" end end end # @param [#to_int, #to_i, Object] input # # @return [Integer, nil, Object] # # @raise CoercionError # # @api public def self.to_int(input, &block) if input.is_a? String Integer(input, 10) else Integer(input) end rescue ArgumentError, TypeError => e CoercionError.handle(e, &block) end # @param [#to_f, Object] input # # @return [Float, nil, Object] # # @raise CoercionError # # @api public def self.to_float(input, &block) Float(input) rescue ArgumentError, TypeError => e CoercionError.handle(e, &block) end # @param [#to_d, Object] input # # @return [BigDecimal, nil, Object] # # @raise CoercionError # # @api public def self.to_decimal(input, &block) to_float(input) do if block_given? return yield else raise CoercionError, "#{input.inspect} cannot be coerced to decimal" end end input.to_d end # @param [Array, String, Object] input # # @return [Array, Object] # # @raise CoercionError # # @api public def self.to_ary(input, &_block) if empty_str?(input) [] elsif input.is_a?(::Array) input elsif block_given? yield else raise CoercionError, "#{input.inspect} cannot be coerced to array" end end # @param [Hash, String, Object] input # # @return [Hash, Object] # # @raise CoercionError # # @api public def self.to_hash(input, &_block) if empty_str?(input) {} elsif input.is_a?(::Hash) input elsif block_given? yield else raise CoercionError, "#{input.inspect} cannot be coerced to hash" end end end end end end dry-types-1.2.2/lib/dry/types/sum.rb0000644000175000017500000000737413617303242017255 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/options' require 'dry/types/meta' module Dry module Types # Sum type # # @api public class Sum include Type include Builder include Options include Meta include Printable include Dry::Equalizer(:left, :right, :options, :meta, inspect: false, immutable: true) # @return [Type] attr_reader :left # @return [Type] attr_reader :right # @api private class Constrained < Sum # @return [Dry::Logic::Operations::Or] def rule left.rule | right.rule end # @return [true] def constrained? true end end # @param [Type] left # @param [Type] right # @param [Hash] options # # @api private def initialize(left, right, **options) super @left, @right = left, right freeze end # @return [String] # # @api public def name [left, right].map(&:name).join(' | ') end # @return [false] # # @api public def default? false end # @return [false] # # @api public def constrained? false end # @return [Boolean] # # @api public def optional? primitive?(nil) end # @param [Object] input # # @return [Object] # # @api private def call_unsafe(input) left.call_safe(input) { right.call_unsafe(input) } end # @param [Object] input # # @return [Object] # # @api private def call_safe(input, &block) left.call_safe(input) { right.call_safe(input, &block) } end # @param [Object] input # # @api public def try(input) left.try(input) do right.try(input) do |failure| if block_given? yield(failure) else failure end end end end # @api private def success(input) if left.valid?(input) left.success(input) elsif right.valid?(input) right.success(input) else raise ArgumentError, "Invalid success value '#{input}' for #{inspect}" end end # @api private def failure(input, _error = nil) if !left.valid?(input) left.failure(input, left.try(input).error) else right.failure(input, right.try(input).error) end end # @param [Object] value # # @return [Boolean] # # @api private def primitive?(value) left.primitive?(value) || right.primitive?(value) end # Manage metadata to the type. If the type is an optional, #meta delegates # to the right branch # # @see [Meta#meta] # # @api public def meta(data = nil) if data.nil? optional? ? right.meta : super elsif optional? self.class.new(left, right.meta(data), **options) else super end end # @see Nominal#to_ast # # @api public def to_ast(meta: true) [:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]] end # @param [Hash] options # # @return [Constrained,Sum] # # @see Builder#constrained # # @api public def constrained(options) if optional? right.constrained(options).optional else super end end # Wrap the type with a proc # # @return [Proc] # # @api public def to_proc proc { |value| self.(value) } end end end end dry-types-1.2.2/lib/dry/types/coercions.rb0000644000175000017500000000541213617303242020424 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types # Common coercion functions used by the built-in `Params` and `JSON` types # # @api public module Coercions include Dry::Core::Constants # @param [String, Object] input # # @return [nil] if the input is an empty string or nil # # @raise CoercionError # # @api public def to_nil(input, &_block) if input.nil? || empty_str?(input) nil elsif block_given? yield else raise CoercionError, "#{input.inspect} is not nil" end end # @param [#to_str, Object] input # # @return [Date, Object] # # @see Date.parse # # @api public def to_date(input, &block) if input.respond_to?(:to_str) begin ::Date.parse(input) rescue ArgumentError, RangeError => e CoercionError.handle(e, &block) end elsif input.is_a?(::Date) input elsif block_given? yield else raise CoercionError, "#{input.inspect} is not a string" end end # @param [#to_str, Object] input # # @return [DateTime, Object] # # @see DateTime.parse # # @api public def to_date_time(input, &block) if input.respond_to?(:to_str) begin ::DateTime.parse(input) rescue ArgumentError => e CoercionError.handle(e, &block) end elsif input.is_a?(::DateTime) input elsif block_given? yield else raise CoercionError, "#{input.inspect} is not a string" end end # @param [#to_str, Object] input # # @return [Time, Object] # # @see Time.parse # # @api public def to_time(input, &block) if input.respond_to?(:to_str) begin ::Time.parse(input) rescue ArgumentError => e CoercionError.handle(e, &block) end elsif input.is_a?(::Time) input elsif block_given? yield else raise CoercionError, "#{input.inspect} is not a string" end end # @param [#to_sym, Object] input # # @return [Symbol, Object] # # @raise CoercionError # # @api public def to_symbol(input, &block) input.to_sym rescue NoMethodError => e CoercionError.handle(e, &block) end private # Checks whether String is empty # # @param [String, Object] value # # @return [Boolean] # # @api private def empty_str?(value) EMPTY_STRING.eql?(value) end end end end dry-types-1.2.2/lib/dry/types/json.rb0000644000175000017500000000156513617303242017416 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/coercions/json' module Dry module Types register('json.nil') do self['nominal.nil'].constructor(Coercions::JSON.method(:to_nil)) end register('json.date') do self['nominal.date'].constructor(Coercions::JSON.method(:to_date)) end register('json.date_time') do self['nominal.date_time'].constructor(Coercions::JSON.method(:to_date_time)) end register('json.time') do self['nominal.time'].constructor(Coercions::JSON.method(:to_time)) end register('json.decimal') do self['nominal.decimal'].constructor(Coercions::JSON.method(:to_decimal)) end register('json.symbol') do self['nominal.symbol'].constructor(Coercions::JSON.method(:to_symbol)) end register('json.array') { self['array'] } register('json.hash') { self['hash'] } end end dry-types-1.2.2/lib/dry/types/constructor.rb0000644000175000017500000001026613617303242021030 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/fn_container' require 'dry/types/constructor/function' module Dry module Types # Constructor types apply a function to the input that is supposed to return # a new value. Coercion is a common use case for constructor types. # # @api public class Constructor < Nominal include Dry::Equalizer(:type, :options, inspect: false, immutable: true) # @return [#call] attr_reader :fn # @return [Type] attr_reader :type undef :constrained?, :meta, :optional?, :primitive, :default?, :name # @param [Builder, Object] input # @param [Hash] options # @param [#call, nil] block # # @api public def self.new(input, **options, &block) type = input.is_a?(Builder) ? input : Nominal.new(input) super(type, **options, fn: Function[options.fetch(:fn, block)]) end # Instantiate a new constructor type instance # # @param [Type] type # @param [Function] fn # @param [Hash] options # # @api private def initialize(type, fn: nil, **options) @type = type @fn = fn super(type, **options, fn: fn) end # @return [Object] # # @api private def call_safe(input) coerced = fn.(input) { |output = input| return yield(output) } type.call_safe(coerced) { |output = coerced| yield(output) } end # @return [Object] # # @api private def call_unsafe(input) type.call_unsafe(fn.(input)) end # @param [Object] input # @param [#call,nil] block # # @return [Logic::Result, Types::Result] # @return [Object] if block given and try fails # # @api public def try(input, &block) value = fn.(input) rescue CoercionError => e failure = failure(input, e) block_given? ? yield(failure) : failure else type.try(value, &block) end # Build a new constructor by appending a block to the coercion function # # @param [#call, nil] new_fn # @param [Hash] options # @param [#call, nil] block # # @return [Constructor] # # @api public def constructor(new_fn = nil, **options, &block) with(**options, fn: fn >> (new_fn || block)) end alias_method :append, :constructor alias_method :>>, :constructor # @return [Class] # # @api private def constrained_type Constrained::Coercible end # @see Nominal#to_ast # # @api public def to_ast(meta: true) [:constructor, [type.to_ast(meta: meta), fn.to_ast]] end # Build a new constructor by prepending a block to the coercion function # # @param [#call, nil] new_fn # @param [Hash] options # @param [#call, nil] block # # @return [Constructor] # # @api public def prepend(new_fn = nil, **options, &block) with(**options, fn: fn << (new_fn || block)) end alias_method :<<, :prepend # Build a lax type # # @return [Lax] # @api public def lax Lax.new(Constructor.new(type.lax, **options)) end # Wrap the type with a proc # # @return [Proc] # # @api public def to_proc proc { |value| self.(value) } end private # @param [Symbol] meth # @param [Boolean] include_private # @return [Boolean] # # @api private def respond_to_missing?(meth, include_private = false) super || type.respond_to?(meth) end # Delegates missing methods to {#type} # # @param [Symbol] method # @param [Array] args # @param [#call, nil] block # # @api private def method_missing(method, *args, &block) if type.respond_to?(method) response = type.public_send(method, *args, &block) if response.is_a?(Type) && type.class == response.class response.constructor_type.new(response, **options) else response end else super end end end end end dry-types-1.2.2/lib/dry/types/core.rb0000644000175000017500000000532113617303242017367 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/any' module Dry module Types # Primitives with {Kernel} coercion methods KERNEL_COERCIBLE = { string: String, integer: Integer, float: Float, decimal: BigDecimal, array: ::Array, hash: ::Hash }.freeze # Primitives with coercions through by convention `to_*` methods METHOD_COERCIBLE = { symbol: Symbol }.freeze # By convention methods to coerce {METHOD_COERCIBLE} primitives METHOD_COERCIBLE_METHODS = { symbol: :to_sym }.freeze # Primitives that are non-coercible NON_COERCIBLE = { nil: NilClass, class: Class, true: TrueClass, false: FalseClass, date: Date, date_time: DateTime, time: Time, range: Range }.freeze # All built-in primitives ALL_PRIMITIVES = [KERNEL_COERCIBLE, METHOD_COERCIBLE, NON_COERCIBLE].reduce(&:merge).freeze # All coercible types COERCIBLE = KERNEL_COERCIBLE.merge(METHOD_COERCIBLE).freeze # All built-in primitives except {NilClass} NON_NIL = ALL_PRIMITIVES.reject { |name, _| name == :nil }.freeze # Register generic types for {ALL_PRIMITIVES} ALL_PRIMITIVES.each do |name, primitive| type = Nominal[primitive].new(primitive) register("nominal.#{name}", type) end # Register strict types for {ALL_PRIMITIVES} ALL_PRIMITIVES.each do |name, primitive| type = self["nominal.#{name}"].constrained(type: primitive) register(name.to_s, type) register("strict.#{name}", type) end # Register {KERNEL_COERCIBLE} types KERNEL_COERCIBLE.each do |name, primitive| register("coercible.#{name}", self["nominal.#{name}"].constructor(Kernel.method(primitive.name))) end # Register {METHOD_COERCIBLE} types METHOD_COERCIBLE.each_key do |name| register( "coercible.#{name}", self["nominal.#{name}"].constructor(&METHOD_COERCIBLE_METHODS[name]) ) end # Register optional strict {NON_NIL} types NON_NIL.each_key do |name| register("optional.strict.#{name}", self["strict.#{name}"].optional) end # Register optional {COERCIBLE} types COERCIBLE.each_key do |name| register("optional.coercible.#{name}", self["coercible.#{name}"].optional) end # Register `:bool` since it's common and not a built-in Ruby type :( register('nominal.bool', self['nominal.true'] | self['nominal.false']) bool = self['strict.true'] | self['strict.false'] register('strict.bool', bool) register('bool', bool) register('any', Any) register('nominal.any', Any) register('strict.any', Any) end end require 'dry/types/coercions' require 'dry/types/params' require 'dry/types/json' dry-types-1.2.2/lib/dry/types/predicate_inferrer.rb0000644000175000017500000001026013617303242022271 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/cache' require 'dry/types/predicate_registry' module Dry module Types # PredicateInferrer returns the list of predicates used by a type. # # @api public class PredicateInferrer extend Core::Cache TYPE_TO_PREDICATE = { DateTime => :date_time?, FalseClass => :false?, Integer => :int?, NilClass => :nil?, String => :str?, TrueClass => :true?, BigDecimal => :decimal? }.freeze REDUCED_TYPES = { [[[:true?], [:false?]]] => %i[bool?] }.freeze HASH = %i[hash?].freeze ARRAY = %i[array?].freeze NIL = %i[nil?].freeze # Compiler reduces type AST into a list of predicates # # @api private class Compiler # @return [PredicateRegistry] # @api private attr_reader :registry # @api private def initialize(registry) @registry = registry end # @api private def infer_predicate(type) [TYPE_TO_PREDICATE.fetch(type) { :"#{type.name.split('::').last.downcase}?" }] end # @api private def visit(node) meth, rest = node public_send(:"visit_#{meth}", rest) end # @api private def visit_nominal(node) type = node[0] predicate = infer_predicate(type) if registry.key?(predicate[0]) predicate else [type?: type] end end # @api private def visit_hash(_) HASH end # @api private def visit_array(_) ARRAY end # @api private def visit_lax(node) visit(node) end # @api private def visit_constructor(node) other, * = node visit(other) end # @api private def visit_enum(node) other, * = node visit(other) end # @api private def visit_sum(node) left_node, right_node, = node left = visit(left_node) right = visit(right_node) if left.eql?(NIL) right else [[left, right]] end end # @api private def visit_constrained(node) other, rules = node predicates = visit(rules) if predicates.empty? visit(other) else [*visit(other), *merge_predicates(predicates)] end end # @api private def visit_any(_) EMPTY_ARRAY end # @api private def visit_and(node) left, right = node visit(left) + visit(right) end # @api private def visit_predicate(node) pred, args = node if pred.equal?(:type?) EMPTY_ARRAY elsif registry.key?(pred) *curried, _ = args values = curried.map { |_, v| v } if values.empty? [pred] else [pred => values[0]] end else EMPTY_ARRAY end end private # @api private def merge_predicates(nodes) preds, merged = nodes.each_with_object([[], {}]) do |predicate, (ps, h)| if predicate.is_a?(::Hash) h.update(predicate) else ps << predicate end end merged.empty? ? preds : [*preds, merged] end end # @return [Compiler] # @api private attr_reader :compiler # @api private def initialize(registry = PredicateRegistry.new) @compiler = Compiler.new(registry) end # Infer predicate identifier from the provided type # # @param [Type] type # @return [Symbol] # # @api private def [](type) self.class.fetch_or_store(type) do predicates = compiler.visit(type.to_ast) if predicates.is_a?(::Hash) predicates else REDUCED_TYPES[predicates] || predicates end end end end end end dry-types-1.2.2/lib/dry/types/inflector.rb0000644000175000017500000000017713617303242020430 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/inflector' module Dry module Types Inflector = Dry::Inflector.new end end dry-types-1.2.2/lib/dry/types/schema/0000755000175000017500000000000013617303242017351 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/schema/key.rb0000644000175000017500000000710013617303242020464 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/equalizer' require 'dry/core/deprecations' module Dry module Types # Schema is a hash with explicit member types defined # # @api public class Schema < Hash # Proxy type for schema keys. Contains only key name and # whether it's required or not. All other calls deletaged # to the wrapped type. # # @see Dry::Types::Schema class Key extend ::Dry::Core::Deprecations[:'dry-types'] include Type include Dry::Equalizer(:name, :type, :options, inspect: false, immutable: true) include Decorator include Builder include Printable # @return [Symbol] attr_reader :name # @api private def initialize(type, name, required: Undefined, **options) required = Undefined.default(required) do type.meta.fetch(:required) { !type.meta.fetch(:omittable, false) } end unless name.is_a?(::Symbol) raise ArgumentError, "Schemas can only contain symbol keys, #{name.inspect} given" end super(type, name, required: required, **options) @name = name end # @api private def call_safe(input, &block) type.call_safe(input, &block) end # @api private def call_unsafe(input) type.call_unsafe(input) end # @see Dry::Types::Nominal#try # # @api public def try(input, &block) type.try(input, &block) end # Whether the key is required in schema input # # @return [Boolean] # # @api public def required? options.fetch(:required) end # Control whether the key is required # # @overload required # @return [Boolean] # # @overload required(required) # Change key's "requireness" # # @param [Boolean] required New value # @return [Dry::Types::Schema::Key] # # @api public def required(required = Undefined) if Undefined.equal?(required) options.fetch(:required) else with(required: required) end end # Make key not required # # @return [Dry::Types::Schema::Key] # # @api public def omittable required(false) end # Turn key into a lax type. Lax types are not strict hence such keys are not required # # @return [Lax] # # @api public def lax __new__(type.lax).required(false) end # Dump to internal AST representation # # @return [Array] # # @api public def to_ast(meta: true) [ :key, [ name, required, type.to_ast(meta: meta) ] ] end # @see Dry::Types::Meta#meta # # @api public def meta(data = nil) if data.nil? || !data.key?(:omittable) super else self.class.warn( 'Using meta for making schema keys is deprecated, ' \ 'please use .omittable or .required(false) instead' \ "\n" + Core::Deprecations::STACK.() ) super.required(!data[:omittable]) end end private # @api private def decorate?(response) response.is_a?(Type) end end end end end dry-types-1.2.2/lib/dry/types/printable.rb0000644000175000017500000000040613617303242020416 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types # @api private module Printable # @return [String] # # @api private def to_s PRINTER.(self) { super } end alias_method :inspect, :to_s end end end dry-types-1.2.2/lib/dry/types/builder.rb0000644000175000017500000000655113617303242020073 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/deprecations' module Dry module Types # Common API for building types and composition # # @api public module Builder include Dry::Core::Constants # @return [Class] # # @api private def constrained_type Constrained end # @return [Class] # # @api private def constructor_type Constructor end # Compose two types into a Sum type # # @param [Type] other # # @return [Sum, Sum::Constrained] # # @api private def |(other) klass = constrained? && other.constrained? ? Sum::Constrained : Sum klass.new(self, other) end # Turn a type into an optional type # # @return [Sum] # # @api public def optional Types['strict.nil'] | self end # Turn a type into a constrained type # # @param [Hash] options constraining rule (see {Types.Rule}) # # @return [Constrained] # # @api public def constrained(options) constrained_type.new(self, rule: Types.Rule(options)) end # Turn a type into a type with a default value # # @param [Object] input # @param [Hash] options # @param [#call,nil] block # # @raise [ConstraintError] # # @return [Default] # # @api public def default(input = Undefined, options = EMPTY_HASH, &block) unless input.frozen? || options[:shared] where = Dry::Core::Deprecations::STACK.() Dry::Core::Deprecations.warn( "#{input.inspect} is mutable."\ ' Be careful: types will return the same instance of the default'\ ' value every time. Call `.freeze` when setting the default'\ ' or pass `shared: true` to discard this warning.'\ "\n#{where}", tag: :'dry-types' ) end value = input.equal?(Undefined) ? block : input if value.respond_to?(:call) || valid?(value) Default[value].new(self, value) else raise ConstraintError.new("default value #{value.inspect} violates constraints", value) end end # Define an enum on top of the existing type # # @param [Array] values # # @return [Enum] # # @api public def enum(*values) mapping = if values.length == 1 && values[0].is_a?(::Hash) values[0] else ::Hash[values.zip(values)] end Enum.new(constrained(included_in: mapping.keys), mapping: mapping) end # Turn a type into a lax type that will rescue from type-errors and # return the original input # # @return [Lax] # # @api public def lax Lax.new(self) end # Define a constructor for the type # # @param [#call,nil] constructor # @param [Hash] options # @param [#call,nil] block # # @return [Constructor] # # @api public def constructor(constructor = nil, **options, &block) constructor_type.new(with(**options), fn: constructor || block) end end end end require 'dry/types/default' require 'dry/types/constrained' require 'dry/types/enum' require 'dry/types/lax' require 'dry/types/sum' dry-types-1.2.2/lib/dry/types/params.rb0000644000175000017500000000336013617303242017723 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/coercions/params' module Dry module Types register('params.nil') do self['nominal.nil'].constructor(Coercions::Params.method(:to_nil)) end register('params.date') do self['nominal.date'].constructor(Coercions::Params.method(:to_date)) end register('params.date_time') do self['nominal.date_time'].constructor(Coercions::Params.method(:to_date_time)) end register('params.time') do self['nominal.time'].constructor(Coercions::Params.method(:to_time)) end register('params.true') do self['nominal.true'].constructor(Coercions::Params.method(:to_true)) end register('params.false') do self['nominal.false'].constructor(Coercions::Params.method(:to_false)) end register('params.bool') do self['params.true'] | self['params.false'] end register('params.integer') do self['nominal.integer'].constructor(Coercions::Params.method(:to_int)) end register('params.float') do self['nominal.float'].constructor(Coercions::Params.method(:to_float)) end register('params.decimal') do self['nominal.decimal'].constructor(Coercions::Params.method(:to_decimal)) end register('params.array') do self['nominal.array'].constructor(Coercions::Params.method(:to_ary)) end register('params.hash') do self['nominal.hash'].constructor(Coercions::Params.method(:to_hash)) end register('params.symbol') do self['nominal.symbol'].constructor(Coercions::Params.method(:to_symbol)) end COERCIBLE.each_key do |name| next if name.equal?(:string) register("optional.params.#{name}", self['params.nil'] | self["params.#{name}"]) end end end dry-types-1.2.2/lib/dry/types/errors.rb0000644000175000017500000000601113617303242017750 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types extend ::Dry::Core::ClassAttributes # @!attribute [r] namespace # @return [Container{String => Nominal}] defines :namespace namespace self # Base class for coercion errors raise by dry-types # class CoercionError < ::StandardError # @api private def self.handle(exception, meta: Undefined) if block_given? yield else raise new( exception.message, meta: meta, backtrace: exception.backtrace ) end end # Metadata associated with the error # # @return [Object] attr_reader :meta # @api private def initialize(message, meta: Undefined, backtrace: Undefined) unless message.is_a?(::String) raise ::ArgumentError, "message must be a string, #{message.class} given" end super(message) @meta = Undefined.default(meta, nil) set_backtrace(backtrace) unless Undefined.equal?(backtrace) end end # Collection of multiple errors # class MultipleError < CoercionError # @return [Array] attr_reader :errors # @param [Array] errors def initialize(errors) @errors = errors end # @return string def message errors.map(&:message).join(', ') end # @return [Array] def meta errors.map(&:meta) end end class SchemaError < CoercionError # @param [String,Symbol] key # @param [Object] value # @param [String, #to_s] result def initialize(key, value, result) super("#{value.inspect} (#{value.class}) has invalid type for :#{key} violates constraints (#{result} failed)") end end MapError = ::Class.new(CoercionError) SchemaKeyError = ::Class.new(CoercionError) private_constant(:SchemaKeyError) class MissingKeyError < SchemaKeyError # @return [Symbol] attr_reader :key # @param [String,Symbol] key def initialize(key) @key = key super("#{key.inspect} is missing in Hash input") end end class UnknownKeysError < SchemaKeyError # @return [Array] attr_reader :keys # @param [] keys def initialize(keys) @keys = keys super("unexpected keys #{keys.inspect} in Hash input") end end class ConstraintError < CoercionError # @return [String, #to_s] attr_reader :result # @return [Object] attr_reader :input # @param [String, #to_s] result # @param [Object] input def initialize(result, input) @result = result @input = input if result.is_a?(String) super(result) else super(to_s) end end # @return [String] def message "#{input.inspect} violates constraints (#{result} failed)" end alias_method :to_s, :message end end end dry-types-1.2.2/lib/dry/types/meta.rb0000644000175000017500000000201113617303242017356 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types # Storage for meta-data # # @api public module Meta def initialize(*args, meta: EMPTY_HASH, **options) super(*args, **options) @meta = meta.freeze end # @param [Hash] new_options # # @return [Type] # # @api public def with(**options) super(meta: @meta, **options) end # @overload meta # @return [Hash] metadata associated with type # # @overload meta(data) # @param [Hash] new metadata to merge into existing metadata # @return [Type] new type with added metadata # # @api public def meta(data = nil) if !data @meta elsif data.empty? self else with(meta: @meta.merge(data)) end end # Resets meta # # @return [Dry::Types::Type] # # @api public def pristine with(meta: EMPTY_HASH) end end end end dry-types-1.2.2/lib/dry/types/constructor/0000755000175000017500000000000013617303242020476 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/constructor/function.rb0000644000175000017500000001244013617303242022651 0ustar utkarshutkarsh# frozen_string_literal: true require 'concurrent/map' module Dry module Types class Constructor < Nominal # Function is used internally by Constructor types # # @api private class Function # Wrapper for unsafe coercion functions # # @api private class Safe < Function def call(input, &block) @fn.(input, &block) rescue NoMethodError, TypeError, ArgumentError => e CoercionError.handle(e, &block) end end # Coercion via a method call on a known object # # @api private class MethodCall < Function @cache = ::Concurrent::Map.new # Choose or build the base class # # @return [Function] def self.call_class(method, public, safe) @cache.fetch_or_store([method, public, safe].hash) do if public ::Class.new(PublicCall) do include PublicCall.call_interface(method, safe) end elsif safe PrivateCall else PrivateSafeCall end end end # Coercion with a publicly accessible method call # # @api private class PublicCall < MethodCall @interfaces = ::Concurrent::Map.new # Choose or build the interface # # @return [::Module] def self.call_interface(method, safe) @interfaces.fetch_or_store([method, safe].hash) do ::Module.new do if safe module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def call(input, &block) @target.#{method}(input, &block) end RUBY else module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def call(input, &block) @target.#{method}(input) rescue NoMethodError, TypeError, ArgumentError => error CoercionError.handle(error, &block) end RUBY end end end end end # Coercion via a private method call # # @api private class PrivateCall < MethodCall def call(input, &block) @target.send(@name, input, &block) end end # Coercion via an unsafe private method call # # @api private class PrivateSafeCall < PrivateCall def call(input, &block) @target.send(@name, input) rescue NoMethodError, TypeError, ArgumentError => e CoercionError.handle(e, &block) end end # @api private # # @return [MethodCall] def self.[](fn, safe) public = fn.receiver.respond_to?(fn.name) MethodCall.call_class(fn.name, public, safe).new(fn) end attr_reader :target, :name def initialize(fn) super @target = fn.receiver @name = fn.name end def to_ast [:method, target, name] end end # Choose or build specialized invokation code for a callable # # @param [#call] fn # @return [Function] def self.[](fn) raise ArgumentError, 'Missing constructor block' if fn.nil? if fn.is_a?(Function) fn elsif fn.is_a?(::Method) MethodCall[fn, yields_block?(fn)] elsif yields_block?(fn) new(fn) else Safe.new(fn) end end # @return [Boolean] def self.yields_block?(fn) *, (last_arg,) = if fn.respond_to?(:parameters) fn.parameters else fn.method(:call).parameters end last_arg.equal?(:block) end include Dry::Equalizer(:fn, immutable: true) attr_reader :fn def initialize(fn) @fn = fn end # @return [Object] def call(input, &block) @fn.(input, &block) end alias_method :[], :call # @return [Array] def to_ast if fn.is_a?(::Proc) [:id, Dry::Types::FnContainer.register(fn)] else [:callable, fn] end end if RUBY_VERSION >= '2.6' # @return [Function] def >>(other) proc = other.is_a?(::Proc) ? other : other.fn Function[@fn >> proc] end # @return [Function] def <<(other) proc = other.is_a?(::Proc) ? other : other.fn Function[@fn << proc] end else # @return [Function] def >>(other) proc = other.is_a?(::Proc) ? other : other.fn Function[-> x { proc[@fn[x]] }] end # @return [Function] def <<(other) proc = other.is_a?(::Proc) ? other : other.fn Function[-> x { @fn[proc[x]] }] end end end end end end dry-types-1.2.2/lib/dry/types/constrained.rb0000644000175000017500000000611113617303242020746 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/decorator' require 'dry/types/constraints' require 'dry/types/constrained/coercible' module Dry module Types # Constrained types apply rules to the input # # @api public class Constrained include Type include Decorator include Builder include Printable include Dry::Equalizer(:type, :rule, inspect: false, immutable: true) # @return [Dry::Logic::Rule] attr_reader :rule # @param [Type] type # # @param [Hash] options # # @api public def initialize(type, **options) super @rule = options.fetch(:rule) end # @api private # # @return [Object] # # @api public def call_unsafe(input) result = rule.(input) if result.success? type.call_unsafe(input) else raise ConstraintError.new(result, input) end end # @api private # # @return [Object] # # @api public def call_safe(input, &block) if rule[input] type.call_safe(input, &block) else yield end end # Safe coercion attempt. It is similar to #call with a # block given but returns a Result instance with metadata # about errors (if any). # # @overload try(input) # @param [Object] input # @return [Logic::Result] # # @overload try(input) # @param [Object] input # @yieldparam [Failure] failure # @yieldreturn [Object] # @return [Object] # # @api public def try(input, &block) result = rule.(input) if result.success? type.try(input, &block) else failure = failure(input, ConstraintError.new(result, input)) block_given? ? yield(failure) : failure end end # @param [Hash] options # The options hash provided to {Types.Rule} and combined # using {&} with previous {#rule} # # @return [Constrained] # # @see Dry::Logic::Operators#and # # @api public def constrained(options) with(rule: rule & Types.Rule(options)) end # @return [true] # # @api public def constrained? true end # @param [Object] value # # @return [Boolean] # # @api public def ===(value) valid?(value) end # Build lax type. Constraints are not applicable to lax types hence unwrapping # # @return [Lax] # @api public def lax type.lax end # @see Nominal#to_ast # @api public def to_ast(meta: true) [:constrained, [type.to_ast(meta: meta), rule.to_ast]] end # @api private def constructor_type type.constructor_type end private # @param [Object] response # # @return [Boolean] # # @api private def decorate?(response) super || response.is_a?(Constructor) end end end end dry-types-1.2.2/lib/dry/types/default.rb0000644000175000017500000000474213617303242020071 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/decorator' module Dry module Types # Default types are useful when a missing value should be replaced by a default one # # @api public class Default # @api private class Callable < Default include Dry::Equalizer(:type, inspect: false, immutable: true) # Evaluates given callable # @return [Object] def evaluate value.call(type) end end include Type include Decorator include Builder include Printable include Dry::Equalizer(:type, :value, inspect: false, immutable: true) # @return [Object] attr_reader :value alias_method :evaluate, :value # @param [Object, #call] value # # @return [Class] {Default} or {Default::Callable} # # @api private def self.[](value) if value.respond_to?(:call) Callable else self end end # @param [Type] type # @param [Object] value # # @api private def initialize(type, value, **options) super @value = value end # Build a constrained type # # @param [Array] args see {Dry::Types::Builder#constrained} # # @return [Default] # # @api public def constrained(*args) type.constrained(*args).default(value) end # @return [true] # # @api public def default? true end # @param [Object] input # # @return [Result::Success] # # @api public def try(input) success(call(input)) end # @return [Boolean] # # @api public def valid?(value = Undefined) Undefined.equal?(value) || super end # @param [Object] input # # @return [Object] value passed through {#type} or {#default} value # # @api private def call_unsafe(input = Undefined) if input.equal?(Undefined) evaluate else Undefined.default(type.call_unsafe(input)) { evaluate } end end # @param [Object] input # # @return [Object] value passed through {#type} or {#default} value # # @api private def call_safe(input = Undefined, &block) if input.equal?(Undefined) evaluate else Undefined.default(type.call_safe(input, &block)) { evaluate } end end end end end dry-types-1.2.2/lib/dry/types/lax.rb0000644000175000017500000000322213617303242017221 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/deprecations' require 'dry/types/decorator' module Dry module Types # Lax types rescue from type-related errors when constructors fail # # @api public class Lax include Type include Decorator include Builder include Printable include Dry::Equalizer(:type, inspect: false, immutable: true) undef :options, :constructor # @param [Object] input # # @return [Object] # # @api public def call(input) type.call_safe(input) { |output = input| output } end alias_method :[], :call alias_method :call_safe, :call alias_method :call_unsafe, :call # @param [Object] input # @param [#call,nil] block # # @yieldparam [Failure] failure # @yieldreturn [Result] # # @return [Result,Logic::Result] # # @api public def try(input, &block) type.try(input, &block) rescue CoercionError => e result = failure(input, e.message) block ? yield(result) : result end # @see Nominal#to_ast # # @api public def to_ast(meta: true) [:lax, type.to_ast(meta: meta)] end # @return [Lax] # # @api public def lax self end private # @param [Object, Dry::Types::Constructor] response # # @return [Boolean] # # @api private def decorate?(response) super || response.is_a?(type.constructor_type) end end extend ::Dry::Core::Deprecations[:'dry-types'] Safe = Lax deprecate_constant(:Safe) end end dry-types-1.2.2/lib/dry/types/map.rb0000644000175000017500000000612713617303242017221 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types # Homogeneous mapping. It describes a hash with unknown keys that match a certain type. # # @example # type = Dry::Types['hash'].map( # Dry::Types['integer'].constrained(gteq: 1, lteq: 10), # Dry::Types['string'] # ) # # type.(1 => 'right') # # => {1 => 'right'} # # type.('1' => 'wrong') # # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1") AND gteq?(1, "1") AND lteq?(10, "1") failed) # # type.(11 => 'wrong') # # Dry::Types::MapError: 11 violates constraints (lteq?(10, 11) failed) # # @api public class Map < Nominal def initialize(_primitive, key_type: Types['any'], value_type: Types['any'], meta: EMPTY_HASH) super(_primitive, key_type: key_type, value_type: value_type, meta: meta) end # @return [Type] # # @api public def key_type options[:key_type] end # @return [Type] # # @api public def value_type options[:value_type] end # @return [String] # # @api public def name 'Map' end # @param [Hash] hash # # @return [Hash] # # @api private def call_unsafe(hash) try(hash) { |failure| raise MapError, failure.error.message }.input end # @param [Hash] hash # # @return [Hash] # # @api private def call_safe(hash) try(hash) { return yield }.input end # @param [Hash] hash # # @return [Result] # # @api public def try(hash) result = coerce(hash) return result if result.success? || !block_given? yield(result) end # @param meta [Boolean] Whether to dump the meta to the AST # # @return [Array] An AST representation # # @api public def to_ast(meta: true) [:map, [key_type.to_ast(meta: true), value_type.to_ast(meta: true), meta ? self.meta : EMPTY_HASH]] end # @return [Boolean] # # @api public def constrained? value_type.constrained? end private # @api private def coerce(input) unless primitive?(input) return failure( input, CoercionError.new("#{input.inspect} must be an instance of #{primitive}") ) end output = {} failures = [] input.each do |k, v| res_k = key_type.try(k) res_v = value_type.try(v) if res_k.failure? failures << res_k.error elsif output.key?(res_k.input) failures << CoercionError.new("duplicate coerced hash key #{res_k.input.inspect}") elsif res_v.failure? failures << res_v.error else output[res_k.input] = res_v.input end end if failures.empty? success(output) else failure(input, MultipleError.new(failures)) end end end end end dry-types-1.2.2/lib/dry/types/builder_methods.rb0000644000175000017500000000724613617303242021620 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types # Common API for building type objects in a convenient way # # rubocop:disable Naming/MethodName # # @api public module BuilderMethods # @api private def included(base) super base.extend(BuilderMethods) end # Build an array type. # # Shortcut for Array#of. # # @example # Types::Strings = Types.Array(Types::String) # # @param [Dry::Types::Type] type # # @return [Dry::Types::Array] def Array(type) Strict(::Array).of(type) end # Build a hash schema # # @param [Hash{Symbol => Dry::Types::Type}] type_map # # @return [Dry::Types::Array] def Hash(type_map) Strict(::Hash).schema(type_map) end # Build a type which values are instances of a given class # Values are checked using `is_a?` call # # @example # Types::Error = Types.Instance(StandardError) # Types::Error = Types.Strict(StandardError) # Types.Strict(Integer) == Types::Strict::Int # => true # # @param [Class,Module] klass Class or module # # @return [Dry::Types::Type] def Instance(klass) Nominal(klass).constrained(type: klass) end alias_method :Strict, :Instance # Build a type with a single value # The equality check done with `eql?` # # @param [Object] value # # @return [Dry::Types::Type] def Value(value) Nominal(value.class).constrained(eql: value) end # Build a type with a single value # The equality check done with `equal?` # # @param [Object] object # # @return [Dry::Types::Type] def Constant(object) Nominal(object.class).constrained(is: object) end # Build a constructor type # If no constructor block given it uses .new method # # @param [Class] klass # @param [#call,nil] cons Value constructor # @param [#call,nil] block Value constructor # # @return [Dry::Types::Type] def Constructor(klass, cons = nil, &block) if klass.is_a?(Type) if cons || block klass.constructor(cons || block) else klass end else Nominal(klass).constructor(cons || block || klass.method(:new)) end end # Build a nominal type # # @param [Class] klass # # @return [Dry::Types::Type] def Nominal(klass) if klass <= ::Array Array.new(klass) elsif klass <= ::Hash Hash.new(klass) else Nominal.new(klass) end end # Build a map type # # @example # Types::IntMap = Types.Map(Types::Strict::Integer, 'any') # Types::IntStringMap = Types.Map(Types::Strict::Integer, Types::Strict::String) # # @param [Type] key_type Key type # @param [Type] value_type Value type # # @return [Dry::Types::Map] def Map(key_type, value_type) Nominal(::Hash).map(key_type, value_type) end # Builds a constrained nominal type accepting any value that # responds to given methods # # @example # Types::Callable = Types.Interface(:call) # Types::Contact = Types.Interface(:name, :address) # # @param methods [Array] Method names # # @return [Dry::Types::Contrained] def Interface(*methods) methods.reduce(Types['nominal.any']) do |type, method| type.constrained(respond_to: method) end end end end end dry-types-1.2.2/lib/dry/types/container.rb0000644000175000017500000000034513617303242020422 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/container' module Dry module Types # Internal container for the built-in types # # @api private class Container include Dry::Container::Mixin end end end dry-types-1.2.2/lib/dry/types/constraints.rb0000644000175000017500000000132213617303242021003 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/rule_compiler' require 'dry/logic/predicates' require 'dry/logic/rule/predicate' module Dry # Helper methods for constraint types # # @api public module Types # @param [Hash] options # # @return [Dry::Logic::Rule] # # @api public def self.Rule(options) rule_compiler.( options.map { |key, val| Logic::Rule::Predicate.build( Logic::Predicates[:"#{key}?"] ).curry(val).to_ast } ).reduce(:and) end # @return [Dry::Logic::RuleCompiler] # # @api private def self.rule_compiler @rule_compiler ||= Logic::RuleCompiler.new(Logic::Predicates) end end end dry-types-1.2.2/lib/dry/types/extensions/0000755000175000017500000000000013617303242020310 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/extensions/monads.rb0000644000175000017500000000111313617303242022112 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/monads/result' module Dry module Types # Monad extension for Result # # @api public class Result include Dry::Monads::Result::Mixin # Turn result into a monad # # This makes result objects work with dry-monads (or anything with a compatible interface) # # @return [Dry::Monads::Success,Dry::Monads::Failure] # # @api public def to_monad if success? Success(input) else Failure([error, input]) end end end end end dry-types-1.2.2/lib/dry/types/extensions/maybe.rb0000644000175000017500000000535613617303242021743 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/monads/maybe' require 'dry/types/decorator' module Dry module Types # Maybe extension provides Maybe types where values are wrapped using `Either` monad # # @api public class Maybe include Type include Dry::Equalizer(:type, :options, inspect: false, immutable: true) include Decorator include Builder include Printable include Dry::Monads::Maybe::Mixin # @param [Dry::Monads::Maybe, Object] input # # @return [Dry::Monads::Maybe] # # @api private def call_unsafe(input = Undefined) case input when Dry::Monads::Maybe input when Undefined None() else Maybe(type.call_unsafe(input)) end end # @param [Dry::Monads::Maybe, Object] input # # @return [Dry::Monads::Maybe] # # @api private def call_safe(input = Undefined, &block) case input when Dry::Monads::Maybe input when Undefined None() else Maybe(type.call_safe(input, &block)) end end # @param [Object] input # # @return [Result::Success] # # @api public def try(input = Undefined) res = if input.equal?(Undefined) None() else Maybe(type[input]) end Result::Success.new(res) end # @return [true] # # @api public def default? true end # @param [Object] value # # @see Dry::Types::Builder#default # # @raise [ArgumentError] if nil provided as default value # # @api public def default(value) if value.nil? raise ArgumentError, 'nil cannot be used as a default of a maybe type' else super end end end module Builder # Turn a type into a maybe type # # @return [Maybe] # # @api public def maybe Maybe.new(Types['strict.nil'] | self) end end # @api private class Schema::Key # @api private def maybe __new__(type.maybe) end end # @api private class Printer MAPPING[Maybe] = :visit_maybe # @api private def visit_maybe(maybe) visit(maybe.type) do |type| yield "Maybe<#{type}>" end end end # Register non-coercible maybe types NON_NIL.each_key do |name| register("maybe.strict.#{name}", self["strict.#{name}"].maybe) end # Register coercible maybe types COERCIBLE.each_key do |name| register("maybe.coercible.#{name}", self["coercible.#{name}"].maybe) end end end dry-types-1.2.2/lib/dry/types/hash/0000755000175000017500000000000013617303242017034 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/hash/constructor.rb0000644000175000017500000000126413617303242021751 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/constructor' module Dry module Types # Hash type exposes additional APIs for working with schema hashes # # @api public class Hash < Nominal class Constructor < ::Dry::Types::Constructor # @api private def constructor_type ::Dry::Types::Hash::Constructor end # @return [Lax] # # @api public def lax type.lax.constructor(fn, meta: meta) end # @see Dry::Types::Array#of # # @api public def schema(*args) type.schema(*args).constructor(fn, meta: meta) end end end end end dry-types-1.2.2/lib/dry/types/hash.rb0000644000175000017500000000731313617303242017365 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/hash/constructor' module Dry module Types # Hash types can be used to define maps and schemas # # @api public class Hash < Nominal NOT_REQUIRED = { required: false }.freeze # @overload schema(type_map, meta = EMPTY_HASH) # @param [{Symbol => Dry::Types::Nominal}] type_map # @param [Hash] meta # @return [Dry::Types::Schema] # # @overload schema(keys) # @param [Array] key List of schema keys # @param [Hash] meta # @return [Dry::Types::Schema] # # @api public def schema(keys_or_map, meta = EMPTY_HASH) if keys_or_map.is_a?(::Array) keys = keys_or_map else keys = build_keys(keys_or_map) end Schema.new(primitive, keys: keys, **options, meta: self.meta.merge(meta)) end # Build a map type # # @param [Type] key_type # @param [Type] value_type # # @return [Map] # # @api public def map(key_type, value_type) Map.new( primitive, key_type: resolve_type(key_type), value_type: resolve_type(value_type), meta: meta ) end # @api private def weak(*) raise 'Support for old hash schemas was removed, please refer to the CHANGELOG '\ 'on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md' end alias_method :permissive, :weak alias_method :strict, :weak alias_method :strict_with_defaults, :weak alias_method :symbolized, :weak # Injects a type transformation function for building schemas # # @param [#call,nil] proc # @param [#call,nil] block # # @return [Hash] # # @api public def with_type_transform(proc = nil, &block) fn = proc || block raise ArgumentError, 'a block or callable argument is required' if fn.nil? handle = Dry::Types::FnContainer.register(fn) with(type_transform_fn: handle) end # @api private def constructor_type ::Dry::Types::Hash::Constructor end # Whether the type transforms types of schemas created by {Dry::Types::Hash#schema} # # @return [Boolean] # # @api public def transform_types? !options[:type_transform_fn].nil? end # @param meta [Boolean] Whether to dump the meta to the AST # # @return [Array] An AST representation # # @api public def to_ast(meta: true) opts = if RUBY_VERSION >= '2.5' options.slice(:type_transform_fn) else options.select { |k, _| k == :type_transform_fn } end [:hash, [opts, meta ? self.meta : EMPTY_HASH]] end private # @api private def build_keys(type_map) type_fn = options.fetch(:type_transform_fn, Schema::NO_TRANSFORM) type_transform = Dry::Types::FnContainer[type_fn] type_map.map do |map_key, type| name, options = key_name(map_key) key = Schema::Key.new(resolve_type(type), name, **options) type_transform.(key) end end # @api private def resolve_type(type) case type when Type then type when ::Class, ::String then Types[type] else type end end # @api private def key_name(key) if key.to_s.end_with?('?') [key.to_s.chop.to_sym, NOT_REQUIRED] else [key, EMPTY_HASH] end end end end end require 'dry/types/schema/key' require 'dry/types/schema' dry-types-1.2.2/lib/dry/types/nominal.rb0000644000175000017500000001034213617303242020073 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/deprecations' require 'dry/types/builder' require 'dry/types/result' require 'dry/types/options' require 'dry/types/meta' module Dry module Types # Nominal types define a primitive class and do not apply any constructors or constraints # # Use these types for annotations and the base for building more complex types on top of them. # # @api public class Nominal include Type include Options include Meta include Builder include Printable include Dry::Equalizer(:primitive, :options, :meta, inspect: false, immutable: true) # @return [Class] attr_reader :primitive # @param [Class] primitive # # @return [Type] # # @api private def self.[](primitive) if primitive == ::Array Types::Array elsif primitive == ::Hash Types::Hash else self end end ALWAYS = proc { true } # @param [Type,Class] primitive # @param [Hash] options # # @api private def initialize(primitive, **options) super @primitive = primitive freeze end # @return [String] # # @api public def name primitive.name end # @return [false] # # @api public def default? false end # @return [false] # # @api public def constrained? false end # @return [false] # # @api public def optional? false end # @param [BasicObject] input # # @return [BasicObject] # # @api private def call_unsafe(input) input end # @param [BasicObject] input # # @return [BasicObject] # # @api private def call_safe(input) input end # @param [Object] input # @param [#call,nil] block # # @yieldparam [Failure] failure # @yieldreturn [Result] # # @return [Result,Logic::Result] when a block is not provided # @return [nil] otherwise # # @api public def try(input) success(input) end # @param (see Dry::Types::Success#initialize) # # @return [Result::Success] # # @api public def success(input) Result::Success.new(input) end # @param (see Failure#initialize) # # @return [Result::Failure] # # @api public def failure(input, error) raise ArgumentError, 'error must be a CoercionError' unless error.is_a?(CoercionError) Result::Failure.new(input, error) end # Checks whether value is of a #primitive class # # @param [Object] value # # @return [Boolean] # # @api public def primitive?(value) value.is_a?(primitive) end # @api private def coerce(input, &_block) if primitive?(input) input elsif block_given? yield else raise CoercionError, "#{input.inspect} must be an instance of #{primitive}" end end # @api private def try_coerce(input) result = success(input) coerce(input) do result = failure( input, CoercionError.new("#{input.inspect} must be an instance of #{primitive}") ) end if block_given? yield(result) else result end end # Return AST representation of a type nominal # # @return [Array] # # @api public def to_ast(meta: true) [:nominal, [primitive, meta ? self.meta : EMPTY_HASH]] end # Return self. Nominal types are lax by definition # # @return [Nominal] # # @api public def lax self end # Wrap the type with a proc # # @return [Proc] # # @api public def to_proc ALWAYS end end extend Dry::Core::Deprecations[:'dry-types'] Definition = Nominal deprecate_constant(:Definition, message: 'Nominal') end end require 'dry/types/array' require 'dry/types/hash' require 'dry/types/map' dry-types-1.2.2/lib/dry/types/result.rb0000644000175000017500000000270713617303242017762 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/equalizer' module Dry module Types # Result class used by {Type#try} # # @api public class Result include Dry::Equalizer(:input, inspect: false, immutable: true) # @return [Object] attr_reader :input # @param [Object] input # # @api private def initialize(input) @input = input end # Success result # # @api public class Success < Result # @return [true] # # @api public def success? true end # @return [false] # # @api public def failure? false end end # Failure result # # @api public class Failure < Result include Dry::Equalizer(:input, :error, inspect: false, immutable: true) # @return [#to_s] attr_reader :error # @param [Object] input # # @param [#to_s] error # # @api private def initialize(input, error) super(input) @error = error end # @return [String] # # @api private def to_s error.to_s end # @return [false] # # @api public def success? false end # @return [true] # # @api public def failure? true end end end end end dry-types-1.2.2/lib/dry/types/module.rb0000644000175000017500000000727213617303242017733 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/deprecations' require 'dry/types/builder_methods' module Dry module Types # Export types registered in a container as module constants. # @example # module Types # include Dry::Types(:strict, :coercible, :nominal, default: :strict) # end # # Types.constants # # => [:Class, :Strict, :Symbol, :Integer, :Float, :String, :Array, :Hash, # # :Decimal, :Nil, :True, :False, :Bool, :Date, :Nominal, :DateTime, :Range, # # :Coercible, :Time] # # @api public class Module < ::Module def initialize(registry, *args, **kwargs) @registry = registry check_parameters(*args, **kwargs) constants = type_constants(*args, **kwargs) define_constants(constants) extend(BuilderMethods) if constants.key?(:Nominal) singleton_class.send(:define_method, :included) do |base| super(base) base.instance_exec(const_get(:Nominal, false)) do |nominal| extend Dry::Core::Deprecations[:'dry-types'] const_set(:Definition, nominal) deprecate_constant(:Definition, message: 'Nominal') end end end end # @api private def type_constants(*namespaces, default: Undefined, **aliases) if namespaces.empty? && aliases.empty? && Undefined.equal?(default) default_ns = :Strict elsif Undefined.equal?(default) default_ns = Undefined else default_ns = Inflector.camelize(default).to_sym end tree = registry_tree if namespaces.empty? && aliases.empty? modules = tree.select { |_, v| v.is_a?(::Hash) }.map(&:first) else modules = (namespaces + aliases.keys).map { |n| Inflector.camelize(n).to_sym } end tree.each_with_object({}) do |(key, value), constants| if modules.include?(key) name = aliases.fetch(Inflector.underscore(key).to_sym, key) constants[name] = value end constants.update(value) if key == default_ns end end # @api private def registry_tree @registry_tree ||= @registry.keys.each_with_object({}) { |key, tree| type = @registry[key] *modules, const_name = key.split('.').map { |part| Inflector.camelize(part).to_sym } next if modules.empty? modules.reduce(tree) { |br, name| br[name] ||= {} }[const_name] = type }.freeze end private # @api private def check_parameters(*namespaces, default: Undefined, **aliases) referenced = namespaces.dup referenced << default unless false.equal?(default) || Undefined.equal?(default) referenced.concat(aliases.keys) known = @registry.keys.map { |k| ns, *path = k.split('.') ns.to_sym unless path.empty? }.compact.uniq (referenced.uniq - known).each do |name| raise ArgumentError, "#{name.inspect} is not a known type namespace. "\ "Supported options are #{known.map(&:inspect).join(', ')}" end end # @api private def define_constants(constants, mod = self) constants.each do |name, value| case value when ::Hash if mod.const_defined?(name, false) define_constants(value, mod.const_get(name, false)) else m = ::Module.new mod.const_set(name, m) define_constants(value, m) end else mod.const_set(name, value) end end end end end end dry-types-1.2.2/lib/dry/types/extensions.rb0000644000175000017500000000031213617303242020631 0ustar utkarshutkarsh# frozen_string_literal: true Dry::Types.register_extension(:maybe) do require 'dry/types/extensions/maybe' end Dry::Types.register_extension(:monads) do require 'dry/types/extensions/monads' end dry-types-1.2.2/lib/dry/types/compiler.rb0000644000175000017500000000631313617303242020253 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/deprecations' module Dry module Types # @api private class Compiler extend ::Dry::Core::Deprecations[:'dry-types'] attr_reader :registry def initialize(registry) @registry = registry end def call(ast) visit(ast) end def visit(node) type, body = node send(:"visit_#{type}", body) end def visit_constrained(node) nominal, rule = node type = visit(nominal) type.constrained_type.new(type, rule: visit_rule(rule)) end def visit_constructor(node) nominal, fn = node primitive = visit(nominal) primitive.constructor(compile_fn(fn)) end def visit_lax(node) Types::Lax.new(visit(node)) end deprecate(:visit_safe, :visit_lax) def visit_nominal(node) type, meta = node nominal_name = "nominal.#{Types.identifier(type)}" if registry.registered?(nominal_name) registry[nominal_name].meta(meta) else Nominal.new(type, meta: meta) end end def visit_rule(node) Dry::Types.rule_compiler.([node])[0] end def visit_sum(node) *types, meta = node types.map { |type| visit(type) }.reduce(:|).meta(meta) end def visit_array(node) member, meta = node member = member.is_a?(Class) ? member : visit(member) registry['nominal.array'].of(member).meta(meta) end def visit_hash(node) opts, meta = node registry['nominal.hash'].with(**opts, meta: meta) end def visit_schema(node) keys, options, meta = node registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(**options, meta: meta) end def visit_json_hash(node) keys, meta = node registry['json.hash'].schema(keys.map { |key| visit(key) }, meta) end def visit_json_array(node) member, meta = node registry['json.array'].of(visit(member)).meta(meta) end def visit_params_hash(node) keys, meta = node registry['params.hash'].schema(keys.map { |key| visit(key) }, meta) end def visit_params_array(node) member, meta = node registry['params.array'].of(visit(member)).meta(meta) end def visit_key(node) name, required, type = node Schema::Key.new(visit(type), name, required: required) end def visit_enum(node) type, mapping = node Enum.new(visit(type), mapping: mapping) end def visit_map(node) key_type, value_type, meta = node registry['nominal.hash'].map(visit(key_type), visit(value_type)).meta(meta) end def visit_any(meta) registry['any'].meta(meta) end def compile_fn(fn) type, *node = fn case type when :id Dry::Types::FnContainer[node.fetch(0)] when :callable node.fetch(0) when :method target, method = node target.method(method) else raise ArgumentError, "Cannot build callable from #{fn.inspect}" end end end end end dry-types-1.2.2/lib/dry/types/options.rb0000644000175000017500000000110613617303242020127 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types # Common API for types with options # # @api private module Options # @return [Hash] attr_reader :options # @see Nominal#initialize # # @api private def initialize(*args, **options) @__args__ = args.freeze @options = options.freeze end # @param [Hash] new_options # # @return [Type] # # @api private def with(**new_options) self.class.new(*@__args__, **options, **new_options) end end end end dry-types-1.2.2/lib/dry/types/constrained/0000755000175000017500000000000013617303242020422 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/constrained/coercible.rb0000644000175000017500000000246313617303242022703 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types class Constrained # Common coercion-related API for constrained types # # @api public class Coercible < Constrained # @return [Object] # # @api private def call_unsafe(input) coerced = type.call_unsafe(input) result = rule.(coerced) if result.success? coerced else raise ConstraintError.new(result, input) end end # @return [Object] # # @api private def call_safe(input) coerced = type.call_safe(input) { return yield } if rule[coerced] coerced else yield(coerced) end end # @see Dry::Types::Constrained#try # # @api public def try(input, &block) result = type.try(input) if result.success? validation = rule.(result.input) if validation.success? result else failure = failure(result.input, ConstraintError.new(validation, input)) block ? yield(failure) : failure end else block ? yield(result) : result end end end end end end dry-types-1.2.2/lib/dry/types/spec/0000755000175000017500000000000013617303242017043 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/spec/types.rb0000644000175000017500000000647213617303242020545 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.shared_examples_for 'Dry::Types::Nominal without primitive' do def be_boolean satisfy { |x| x == true || x == false } end describe '#constrained?' do it 'returns a boolean value' do expect(type.constrained?).to be_boolean end end describe '#default?' do it 'returns a boolean value' do expect(type.default?).to be_boolean end end describe '#valid?' do it 'returns a boolean value' do expect(type.valid?(1)).to be_boolean end end describe '#eql?' do it 'has #eql? defined' do expect(type).to eql(type) end end describe '#==' do it 'has #== defined' do expect(type).to eq(type) end end describe '#optional?' do it 'returns a boolean value' do expect(type.optional?).to be_boolean end end describe '#to_s' do it 'returns a custom string representation' do expect(type.to_s).to start_with('# Object}] # # @api private def call_unsafe(hash, options = EMPTY_HASH) resolve_unsafe(coerce(hash), options) end # @param [Hash] hash # # @return [Hash{Symbol => Object}] # # @api private def call_safe(hash, options = EMPTY_HASH) resolve_safe(coerce(hash) { return yield }, options) { return yield } end # @param [Hash] hash # # @option options [Boolean] :skip_missing If true don't raise error if on missing keys # @option options [Boolean] :resolve_defaults If false default value # won't be evaluated for missing key # @return [Hash{Symbol => Object}] # # @api public def apply(hash, options = EMPTY_HASH) call_unsafe(hash, options) end # @param [Hash] hash # # @yieldparam [Failure] failure # @yieldreturn [Result] # # @return [Logic::Result] # @return [Object] if coercion fails and a block is given # # @api public def try(input) if primitive?(input) success = true output = {} result = {} input.each do |key, value| k = @transform_key.(key) type = @name_key_map[k] if type key_result = type.try(value) result[k] = key_result output[k] = key_result.input success &&= key_result.success? elsif strict? success = false end end if output.size < keys.size resolve_missing_keys(output, options) do success = false end end success &&= primitive?(output) if success failure = nil else error = CoercionError.new("#{input} doesn't conform schema", meta: result) failure = failure(output, error) end else failure = failure(input, CoercionError.new("#{input} must be a hash")) end if failure.nil? success(output) elsif block_given? yield(failure) else failure end end # @param meta [Boolean] Whether to dump the meta to the AST # # @return [Array] An AST representation # # @api public def to_ast(meta: true) if RUBY_VERSION >= "2.5" opts = options.slice(:key_transform_fn, :type_transform_fn, :strict) else opts = options.select { |k, _| k == :key_transform_fn || k == :type_transform_fn || k == :strict } end [ :schema, [keys.map { |key| key.to_ast(meta: meta) }, opts, meta ? self.meta : EMPTY_HASH] ] end # Whether the schema rejects unknown keys # # @return [Boolean] # # @api public def strict? options.fetch(:strict, false) end # Make the schema intolerant to unknown keys # # @return [Schema] # # @api public def strict(strict = true) with(strict: strict) end # Injects a key transformation function # # @param [#call,nil] proc # @param [#call,nil] block # # @return [Schema] # # @api public def with_key_transform(proc = nil, &block) fn = proc || block raise ArgumentError, 'a block or callable argument is required' if fn.nil? handle = Dry::Types::FnContainer.register(fn) with(key_transform_fn: handle) end # Whether the schema transforms input keys # # @return [Boolean] # # @api public def transform_keys? !options[:key_transform_fn].nil? end # @overload schema(type_map, meta = EMPTY_HASH) # @param [{Symbol => Dry::Types::Nominal}] type_map # @param [Hash] meta # @return [Dry::Types::Schema] # # @overload schema(keys) # @param [Array] key List of schema keys # @param [Hash] meta # @return [Dry::Types::Schema] # # @api public def schema(keys_or_map) if keys_or_map.is_a?(::Array) new_keys = keys_or_map else new_keys = build_keys(keys_or_map) end keys = merge_keys(self.keys, new_keys) Schema.new(primitive, **options, keys: keys, meta: meta) end # Iterate over each key type # # @return [Array,Enumerator] # # @api public def each(&block) keys.each(&block) end # Whether the schema has the given key # # @param [Symbol] name Key name # # @return [Boolean] # # @api public def key?(name) name_key_map.key?(name) end # Fetch key type by a key name # # Behaves as ::Hash#fetch # # @overload key(name, fallback = Undefined) # @param [Symbol] name Key name # @param [Object] fallback Optional fallback, returned if key is missing # @return [Dry::Types::Schema::Key,Object] key type or fallback if key is not in schema # # @overload key(name, &block) # @param [Symbol] name Key name # @param [Proc] block Fallback block, runs if key is missing # @return [Dry::Types::Schema::Key,Object] key type or block value if key is not in schema # # @api public def key(name, fallback = Undefined, &block) if Undefined.equal?(fallback) name_key_map.fetch(name, &block) else name_key_map.fetch(name, fallback) end end # @return [Boolean] # # @api public def constrained? true end # @return [Lax] # # @api public def lax Lax.new(schema(keys.map(&:lax))) end private # @param [Array] keys # # @return [Dry::Types::Schema] # # @api private def merge_keys(*keys) keys .flatten(1) .each_with_object({}) { |key, merged| merged[key.name] = key } .values end # Validate and coerce a hash. Raise an exception on any error # # @api private # # @return [Hash] def resolve_unsafe(hash, options = EMPTY_HASH) result = {} hash.each do |key, value| k = @transform_key.(key) type = @name_key_map[k] if type begin result[k] = type.call_unsafe(value) rescue ConstraintError => e raise SchemaError.new(type.name, value, e.result) rescue CoercionError => e raise SchemaError.new(type.name, value, e.message) end elsif strict? raise unexpected_keys(hash.keys) end end resolve_missing_keys(result, options) if result.size < keys.size result end # Validate and coerce a hash. Call a block and halt on any error # # @api private # # @return [Hash] def resolve_safe(hash, options = EMPTY_HASH, &block) result = {} hash.each do |key, value| k = @transform_key.(key) type = @name_key_map[k] if type result[k] = type.call_safe(value, &block) elsif strict? yield end end resolve_missing_keys(result, options, &block) if result.size < keys.size result end # Try to add missing keys to the hash # # @api private def resolve_missing_keys(hash, options) skip_missing = options.fetch(:skip_missing, false) resolve_defaults = options.fetch(:resolve_defaults, true) keys.each do |key| next if hash.key?(key.name) if key.default? && resolve_defaults hash[key.name] = key.call_unsafe(Undefined) elsif key.required? && !skip_missing if block_given? return yield else raise missing_key(key.name) end end end end # @param hash_keys [Array] # # @return [UnknownKeysError] # # @api private def unexpected_keys(hash_keys) extra_keys = hash_keys.map(&transform_key) - name_key_map.keys UnknownKeysError.new(extra_keys) end # @return [MissingKeyError] # # @api private def missing_key(key) MissingKeyError.new(key) end end end end dry-types-1.2.2/lib/dry/types/type.rb0000644000175000017500000000254313617303242017423 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/deprecations' module Dry module Types # Common Type module denoting an object is a Type # # @api public module Type extend ::Dry::Core::Deprecations[:'dry-types'] deprecate(:safe, :lax) # Whether a value is a valid member of the type # # @return [Boolean] # # @api private def valid?(input = Undefined) call_safe(input) { return false } true end # Anything can be coerced matches alias_method :===, :valid? # Apply type to a value # # @overload call(input = Undefined) # Possibly unsafe coercion attempt. If a value doesn't # match the type, an exception will be raised. # # @param [Object] input # @return [Object] # # @overload call(input = Undefined) # When a block is passed, {#call} will never throw an exception on # failed coercion, instead it will call the block. # # @param [Object] input # @yieldparam [Object] output Partially coerced value # @return [Object] # # @api public def call(input = Undefined, &block) if block_given? call_safe(input, &block) else call_unsafe(input) end end alias_method :[], :call end end end dry-types-1.2.2/lib/dry/types/predicate_registry.rb0000644000175000017500000000126613617303242022333 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' module Dry module Types # A registry with predicate objects from `Dry::Logic::Predicates` # # @api private class PredicateRegistry # @api private attr_reader :predicates # @api private attr_reader :has_predicate # @api private def initialize(predicates = Logic::Predicates) @predicates = predicates @has_predicate = ::Kernel.instance_method(:respond_to?).bind(@predicates) end # @api private def [](name) predicates[name] end # @api private def key?(name) has_predicate.(name) end end end end dry-types-1.2.2/lib/dry/types/printer.rb0000644000175000017500000002035013617303242020121 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Types # @api private class Printer MAPPING = { Nominal => :visit_nominal, Constructor => :visit_constructor, Hash::Constructor => :visit_constructor, Array::Constructor => :visit_constructor, Constrained => :visit_constrained, Constrained::Coercible => :visit_constrained, Hash => :visit_hash, Schema => :visit_schema, Schema::Key => :visit_key, Map => :visit_map, Array => :visit_array, Array::Member => :visit_array_member, Lax => :visit_lax, Enum => :visit_enum, Default => :visit_default, Default::Callable => :visit_default, Sum => :visit_sum, Sum::Constrained => :visit_sum, Any.class => :visit_any } def call(type) output = ''.dup visit(type) { |str| output << str } "#" end def visit(type, &block) print_with = MAPPING.fetch(type.class) do if type.is_a?(Type) return yield type.inspect else raise ArgumentError, "Do not know how to print #{type.class}" end end send(print_with, type, &block) end def visit_any(_) yield 'Any' end def visit_array(type) visit_options(EMPTY_HASH, type.meta) do |opts| yield "Array#{opts}" end end def visit_array_member(array) visit(array.member) do |type| visit_options(EMPTY_HASH, array.meta) do |opts| yield "Array<#{type}#{opts}>" end end end def visit_constructor(constructor) visit(constructor.type) do |type| visit_callable(constructor.fn.fn) do |fn| options = constructor.options.dup options.delete(:fn) visit_options(options) do |opts| yield "Constructor<#{type} fn=#{fn}#{opts}>" end end end end def visit_constrained(constrained) visit(constrained.type) do |type| options = constrained.options.dup rule = options.delete(:rule) visit_options(options) do |_opts| yield "Constrained<#{type} rule=[#{rule}]>" end end end def visit_schema(schema) options = schema.options.dup size = schema.count key_fn_str = '' type_fn_str = '' strict_str = '' strict_str = 'strict ' if options.delete(:strict) if key_fn = options.delete(:key_transform_fn) visit_callable(key_fn) do |fn| key_fn_str = "key_fn=#{fn} " end end if type_fn = options.delete(:type_transform_fn) visit_callable(type_fn) do |fn| type_fn_str = "type_fn=#{fn} " end end keys = options.delete(:keys) visit_options(options, schema.meta) do |opts| opts = "#{opts[1..-1]} " unless opts.empty? schema_parameters = "#{key_fn_str}#{type_fn_str}#{strict_str}#{opts}" header = "Schema<#{schema_parameters}keys={" if size.zero? yield "#{header}}>" else yield header.dup << keys.map { |key| visit(key) { |type| type } }.join(' ') << '}>' end end end def visit_map(map) visit(map.key_type) do |key| visit(map.value_type) do |value| options = map.options.dup options.delete(:key_type) options.delete(:value_type) visit_options(options) do |_opts| yield "Map<#{key} => #{value}>" end end end end def visit_key(key) visit(key.type) do |type| if key.required? yield "#{key.name}: #{type}" else yield "#{key.name}?: #{type}" end end end def visit_sum(sum) visit_sum_constructors(sum) do |constructors| visit_options(sum.options, sum.meta) do |opts| yield "Sum<#{constructors}#{opts}>" end end end def visit_sum_constructors(sum) case sum.left when Sum visit_sum_constructors(sum.left) do |left| case sum.right when Sum visit_sum_constructors(sum.right) do |right| yield "#{left} | #{right}" end else visit(sum.right) do |right| yield "#{left} | #{right}" end end end else visit(sum.left) do |left| case sum.right when Sum visit_sum_constructors(sum.right) do |right| yield "#{left} | #{right}" end else visit(sum.right) do |right| yield "#{left} | #{right}" end end end end end def visit_enum(enum) visit(enum.type) do |type| options = enum.options.dup mapping = options.delete(:mapping) visit_options(options) do |opts| if mapping == enum.inverted_mapping values = mapping.values.map(&:inspect).join(', ') yield "Enum<#{type} values={#{values}}#{opts}>" else mapping_str = mapping.map { |key, value| "#{key.inspect}=>#{value.inspect}" }.join(', ') yield "Enum<#{type} mapping={#{mapping_str}}#{opts}>" end end end end def visit_default(default) visit(default.type) do |type| visit_options(default.options) do |opts| if default.is_a?(Default::Callable) visit_callable(default.value) do |fn| yield "Default<#{type} value_fn=#{fn}#{opts}>" end else yield "Default<#{type} value=#{default.value.inspect}#{opts}>" end end end end def visit_nominal(type) visit_options(type.options, type.meta) do |opts| yield "Nominal<#{type.primitive}#{opts}>" end end def visit_lax(lax) visit(lax.type) do |type| yield "Lax<#{type}>" end end def visit_hash(hash) options = hash.options.dup type_fn_str = '' if type_fn = options.delete(:type_transform_fn) visit_callable(type_fn) do |fn| type_fn_str = "type_fn=#{fn}" end end visit_options(options, hash.meta) do |opts| if opts.empty? && type_fn_str.empty? yield 'Hash' else yield "Hash<#{type_fn_str}#{opts}>" end end end def visit_callable(callable) fn = callable.is_a?(String) ? FnContainer[callable] : callable case fn when Method yield "#{fn.receiver}.#{fn.name}" when Proc path, line = fn.source_location if line&.zero? yield ".#{path}" elsif path yield "#{path.sub(Dir.pwd + '/', EMPTY_STRING)}:#{line}" elsif fn.lambda? yield '(lambda)' else match = fn.to_s.match(/\A#\z/) if match yield ".#{match[1]}" else yield '(proc)' end end else call = fn.method(:call) if call.owner == fn.class yield "#{fn.class}#call" else yield "#{fn}.call" end end end def visit_options(options, meta = EMPTY_HASH) if options.empty? && meta.empty? yield '' else opts = options.empty? ? '' : " options=#{options.inspect}" if meta.empty? yield opts else values = meta.map do |key, value| case key when Symbol "#{key}: #{value.inspect}" else "#{key.inspect}=>#{value.inspect}" end end yield "#{opts} meta={#{values.join(', ')}}" end end end end PRINTER = Printer.new.freeze end end dry-types-1.2.2/lib/dry/types/array/0000755000175000017500000000000013617303242017227 5ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/array/constructor.rb0000644000175000017500000000120113617303242022133 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/constructor' module Dry module Types # @api public class Array < Nominal # @api private class Constructor < ::Dry::Types::Constructor # @api private def constructor_type ::Dry::Types::Array::Constructor end # @return [Lax] # # @api public def lax Lax.new(type.lax.constructor(fn, meta: meta)) end # @see Dry::Types::Array#of # # @api public def of(member) type.of(member).constructor(fn, meta: meta) end end end end end dry-types-1.2.2/lib/dry/types/array/member.rb0000644000175000017500000000605013617303242021024 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/array/constructor' module Dry module Types class Array < Nominal # Member arrays define their member type that is applied to each element # # @api public class Member < Array # @return [Type] attr_reader :member # @param [Class] primitive # @param [Hash] options # # @option options [Type] :member # # @api private def initialize(primitive, **options) @member = options.fetch(:member) super end # @param [Object] input # # @return [Array] # # @api private def call_unsafe(input) if primitive?(input) input.each_with_object([]) do |el, output| coerced = member.call_unsafe(el) output << coerced unless Undefined.equal?(coerced) end else super end end # @param [Object] input # @return [Array] # # @api private def call_safe(input) if primitive?(input) failed = false result = input.each_with_object([]) do |el, output| coerced = member.call_safe(el) { |out = el| failed = true out } output << coerced unless Undefined.equal?(coerced) end failed ? yield(result) : result else yield end end # @param [Array, Object] input # @param [#call,nil] block # # @yieldparam [Failure] failure # @yieldreturn [Result] # # @return [Result,Logic::Result] # # @api public def try(input, &block) if primitive?(input) output = [] result = input.map { |el| member.try(el) } result.each do |r| output << r.input unless Undefined.equal?(r.input) end if result.all?(&:success?) success(output) else error = result.find(&:failure?).error failure = failure(output, error) block ? yield(failure) : failure end else failure = failure(input, CoercionError.new("#{input} is not an array")) block ? yield(failure) : failure end end # Build a lax type # # @return [Lax] # # @api public def lax Lax.new(Member.new(primitive, **options, member: member.lax, meta: meta)) end # @see Nominal#to_ast # # @api public def to_ast(meta: true) if member.respond_to?(:to_ast) [:array, [member.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]] else [:array, [member, meta ? self.meta : EMPTY_HASH]] end end # @api private def constructor_type ::Dry::Types::Array::Constructor end end end end end dry-types-1.2.2/lib/dry/types/array.rb0000644000175000017500000000133313617303242017554 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/array/member' require 'dry/types/array/constructor' module Dry module Types # Array type can be used to define an array with optional member type # # @api public class Array < Nominal # Build an array type with a member type # # @param [Type,#call] type # # @return [Array::Member] # # @api public def of(type) member = case type when String then Types[type] else type end Array::Member.new(primitive, **options, member: member) end # @api private def constructor_type ::Dry::Types::Array::Constructor end end end end dry-types-1.2.2/lib/dry/types/primitive_inferrer.rb0000644000175000017500000000355213617303242022347 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/cache' module Dry module Types # PrimitiveInferrer returns the list of classes matching a type. # # @api public class PrimitiveInferrer extend Core::Cache # Compiler reduces type AST into a list of primitives # # @api private class Compiler # @api private def visit(node) meth, rest = node public_send(:"visit_#{meth}", rest) end # @api private def visit_nominal(node) type, _ = node type end # @api private def visit_hash(_) ::Hash end alias_method :visit_schema, :visit_hash # @api private def visit_array(_) ::Array end # @api private def visit_lax(node) visit(node) end # @api private def visit_constructor(node) other, * = node visit(other) end # @api private def visit_enum(node) other, * = node visit(other) end # @api private def visit_sum(node) left, right = node [visit(left), visit(right)].flatten(1) end # @api private def visit_constrained(node) other, * = node visit(other) end # @api private def visit_any(_) ::Object end end # @return [Compiler] # @api private attr_reader :compiler # @api private def initialize @compiler = Compiler.new end # Infer primitives from the provided type # # @return [Array[Class]] # # @api private def [](type) self.class.fetch_or_store(type) do Array(compiler.visit(type.to_ast)).freeze end end end end end dry-types-1.2.2/lib/dry/types/compat.rb0000644000175000017500000000000013617303242017707 0ustar utkarshutkarshdry-types-1.2.2/lib/dry/types/decorator.rb0000644000175000017500000000431613617303242020424 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/options' module Dry module Types # Common API for types # # @api public module Decorator include Options # @return [Type] attr_reader :type # @param [Type] type def initialize(type, *, **) super @type = type end # @param [Object] input # @param [#call, nil] block # # @return [Result,Logic::Result] # @return [Object] if block given and try fails # # @api public def try(input, &block) type.try(input, &block) end # @return [Boolean] # # @api public def default? type.default? end # @return [Boolean] # # @api public def constrained? type.constrained? end # @return [Sum] # # @api public def optional Types['strict.nil'] | self end # @param [Symbol] meth # @param [Boolean] include_private # # @return [Boolean] # # @api public def respond_to_missing?(meth, include_private = false) super || type.respond_to?(meth) end # Wrap the type with a proc # # @return [Proc] # # @api public def to_proc proc { |value| self.(value) } end private # @param [Object] response # # @return [Boolean] # # @api private def decorate?(response) response.is_a?(type.class) end # Delegates missing methods to {#type} # # @param [Symbol] meth # @param [Array] args # @param [#call, nil] block # # @api private def method_missing(meth, *args, &block) if type.respond_to?(meth) response = type.public_send(meth, *args, &block) if decorate?(response) __new__(response) else response end else super end end ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) # Replace underlying type # # @api private def __new__(type) self.class.new(type, *@__args__[1..-1], **@options) end end end end dry-types-1.2.2/lib/dry/types/enum.rb0000644000175000017500000000421213617303242017401 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/types/decorator' module Dry module Types # Enum types can be used to define an enum on top of an existing type # # @api public class Enum include Type include Dry::Equalizer(:type, :mapping, inspect: false, immutable: true) include Decorator include Builder # @return [Array] attr_reader :values # @return [Hash] attr_reader :mapping # @return [Hash] attr_reader :inverted_mapping # @param [Type] type # @param [Hash] options # @option options [Array] :values # # @api private def initialize(type, **options) super @mapping = options.fetch(:mapping).freeze @values = @mapping.keys.freeze @inverted_mapping = @mapping.invert.freeze freeze end # @return [Object] # # @api private def call_unsafe(input) type.call_unsafe(map_value(input)) end # @return [Object] # # @api private def call_safe(input, &block) type.call_safe(map_value(input), &block) end # @see Dry::Types::Constrained#try # # @api public def try(input) super(map_value(input)) end # @api private def default(*) raise '.enum(*values).default(value) is not supported. Call '\ '.default(value).enum(*values) instead' end # Check whether a value is in the enum alias_method :include?, :valid? # @see Nominal#to_ast # # @api public def to_ast(meta: true) [:enum, [type.to_ast(meta: meta), mapping]] end # @return [String] # # @api public def to_s PRINTER.(self) end alias_method :inspect, :to_s private # Maps a value # # @param [Object] input # # @return [Object] # # @api private def map_value(input) if input.equal?(Undefined) type.call elsif mapping.key?(input) input else inverted_mapping.fetch(input, input) end end end end end dry-types-1.2.2/lib/dry/types.rb0000644000175000017500000001330613617303242016441 0ustar utkarshutkarsh# frozen_string_literal: true require 'bigdecimal' require 'date' require 'set' require 'concurrent' require 'dry-container' require 'dry-equalizer' require 'dry/core/extensions' require 'dry/core/constants' require 'dry/core/class_attributes' require 'dry/types/version' require 'dry/types/container' require 'dry/types/inflector' require 'dry/types/type' require 'dry/types/printable' require 'dry/types/nominal' require 'dry/types/constructor' require 'dry/types/module' require 'dry/types/errors' module Dry # Main library namespace # # @api public module Types extend Dry::Core::Extensions extend Dry::Core::ClassAttributes extend Dry::Core::Deprecations[:'dry-types'] include Dry::Core::Constants TYPE_SPEC_REGEX = /(.+)<(.+)>/.freeze # @see Dry.Types def self.module(*namespaces, default: :nominal, **aliases) Module.new(container, *namespaces, default: default, **aliases) end deprecate_class_method :module, message: <<~DEPRECATION Use Dry.Types() instead. Beware, it exports strict types by default, for old behavior use Dry.Types(default: :nominal). See more options in the changelog DEPRECATION # @api private def self.included(*) raise 'Import Dry.Types, not Dry::Types' end # Return container with registered built-in type objects # # @return [Container{String => Nominal}] # # @api private def self.container @container ||= Container.new end # Check if a give type is registered # # @return [Boolean] # # @api private def self.registered?(class_or_identifier) container.key?(identifier(class_or_identifier)) end # Register a new built-in type # # @param [String] name # @param [Type] type # @param [#call,nil] block # # @return [Container{String => Nominal}] # # @api private def self.register(name, type = nil, &block) container.register(name, type || block.call) end # Get a built-in type by its name # # @param [String,Class] name # # @return [Type,Class] # # @api public def self.[](name) type_map.fetch_or_store(name) do case name when ::String result = name.match(TYPE_SPEC_REGEX) if result type_id, member_id = result[1..2] container[type_id].of(self[member_id]) else container[name] end when ::Class warn(<<~DEPRECATION) Using Dry::Types.[] with a class is deprecated, please use string identifiers: Dry::Types[Integer] -> Dry::Types['integer']. If you're using dry-struct this means changing `attribute :counter, Integer` to `attribute :counter, Dry::Types['integer']` or to `attribute :counter, 'integer'`. DEPRECATION type_name = identifier(name) if container.key?(type_name) self[type_name] else name end end end end # Infer a type identifier from the provided class # # @param [#to_s] klass # # @return [String] def self.identifier(klass) Inflector.underscore(klass).tr('/', '.') end # Cached type map # # @return [Concurrent::Map] # # @api private def self.type_map @type_map ||= Concurrent::Map.new end # List of type keys defined in {Dry::Types.container} # # @return [String] # # @api private def self.type_keys container.keys end # @api private def self.const_missing(const) underscored = Inflector.underscore(const) if container.keys.any? { |key| key.split('.')[0] == underscored } raise NameError, 'dry-types does not define constants for default types. '\ 'You can access the predefined types with [], e.g. Dry::Types["integer"] '\ 'or generate a module with types using Dry.Types()' else super end end end # Export registered types as a module with constants # # @example no options # # module Types # # imports all types as constants, uses modules for namespaces # include Dry::Types() # end # # strict types are exported by default # Types::Integer # # => # rule=[type?(Integer)]>]> # Types::Nominal::Integer # # => #]> # # @example changing default types # # module Types # include Dry::Types(default: :nominal) # end # Types::Integer # # => #]> # # @example cherry-picking namespaces # # module Types # include Dry::Types(:strict, :coercible) # end # # cherry-picking discards default types, # # provide the :default option along with the list of # # namespaces if you want the to be exported # Types.constants # => [:Coercible, :Strict] # # @example custom names # module Types # include Dry::Types(coercible: :Kernel) # end # Types::Kernel::Integer # # => # fn=Kernel.Integer>]> # # @param [Array] namespaces List of type namespaces to export # @param [Symbol] default Default namespace to export # @param [Hash{Symbol => Symbol}] aliases Optional renamings, like strict: :Draconian # # @return [Dry::Types::Module] # # @see Dry::Types::Module # # @api public # # rubocop:disable Naming/MethodName def self.Types(*namespaces, default: Types::Undefined, **aliases) Types::Module.new(Types.container, *namespaces, default: default, **aliases) end # rubocop:enable Naming/MethodName end require 'dry/types/core' # load built-in types require 'dry/types/extensions' require 'dry/types/printer'