├── .rspec ├── Gemfile ├── spec ├── features │ ├── algebra │ │ ├── type_sum_spec.rb │ │ └── type_mult_spec.rb │ ├── object_behavior │ │ └── object_cloning_duplication_copying_spec.rb │ └── invariants │ │ ├── type_multiplication_composability_spec.rb │ │ ├── type_sum_composability_spec.rb │ │ └── definitioning_incompatabilities_spec.rb ├── types │ ├── value │ │ ├── io_spec.rb │ │ ├── comparable_spec.rb │ │ ├── enumerator_spec.rb │ │ ├── string_io_spec.rb │ │ ├── enumerator_chain_spec.rb │ │ ├── nil_spec.rb │ │ ├── array_spec.rb │ │ ├── module_spec.rb │ │ ├── string_spec.rb │ │ ├── class_spec.rb │ │ ├── boolean_spec.rb │ │ ├── text_spec.rb │ │ ├── hash_spec.rb │ │ ├── symbol_spec.rb │ │ ├── proc_spec.rb │ │ ├── time_spec.rb │ │ ├── method_spec.rb │ │ ├── set_spec.rb │ │ ├── unbound_method_spec.rb │ │ ├── big_decimal_spec.rb │ │ └── date_spec.rb │ └── variadic │ │ ├── enum_spec.rb │ │ ├── array_of_spec.rb │ │ └── tuple_spec.rb ├── spec_helper.rb └── setup_simplecov.rb ├── bin ├── console └── setup ├── lib └── smart_core │ ├── types │ ├── struct.rb │ ├── protocol.rb │ ├── primitive │ │ ├── mult_validator │ │ │ └── result.rb │ │ ├── caster.rb │ │ ├── checker.rb │ │ ├── undefined_caster.rb │ │ ├── invariant_control │ │ │ ├── factory │ │ │ │ └── chain_definition_context.rb │ │ │ ├── single │ │ │ │ └── result.rb │ │ │ ├── chain │ │ │ │ └── result.rb │ │ │ ├── single.rb │ │ │ ├── factory.rb │ │ │ ├── chain.rb │ │ │ └── result.rb │ │ ├── sum_factory │ │ │ └── definition_context.rb │ │ ├── mult_factory │ │ │ └── definition_context.rb │ │ ├── mult_validator.rb │ │ ├── nilable_validator │ │ │ └── result.rb │ │ ├── validator │ │ │ └── result.rb │ │ ├── nilable_factory.rb │ │ ├── nilable_validator.rb │ │ ├── runtime_attributes_checker.rb │ │ ├── invariant_control.rb │ │ ├── factory │ │ │ ├── runtime_type_builder.rb │ │ │ └── definition_context.rb │ │ ├── validator.rb │ │ ├── sum_validator │ │ │ └── result.rb │ │ ├── sum_validator.rb │ │ ├── sum_factory.rb │ │ └── mult_factory.rb │ ├── version.rb │ ├── value │ │ ├── any.rb │ │ ├── nil.rb │ │ ├── range.rb │ │ ├── method.rb │ │ ├── io.rb │ │ ├── class.rb │ │ ├── module.rb │ │ ├── unbound_method.rb │ │ ├── comparable.rb │ │ ├── enumerable.rb │ │ ├── enumerator.rb │ │ ├── enumerator_chain.rb │ │ ├── string_io.rb │ │ ├── boolean.rb │ │ ├── array.rb │ │ ├── symbol.rb │ │ ├── string.rb │ │ ├── set.rb │ │ ├── rational.rb │ │ ├── date.rb │ │ ├── time.rb │ │ ├── float.rb │ │ ├── proc.rb │ │ ├── date_time.rb │ │ ├── hash.rb │ │ ├── text.rb │ │ ├── big_decimal.rb │ │ ├── integer.rb │ │ ├── numeric.rb │ │ └── time_based.rb │ ├── variadic.rb │ ├── variadic │ │ ├── enum.rb │ │ ├── array_of.rb │ │ └── tuple.rb │ ├── protocol │ │ └── instance_of.rb │ ├── system.rb │ ├── errors.rb │ └── value.rb │ └── types.rb ├── .gitignore ├── Rakefile ├── .rubocop.yml ├── LICENSE.txt ├── .github └── workflows │ └── ci.yml ├── smart_types.gemspec ├── Gemfile.lock ├── CODE_OF_CONDUCT.md └── CHANGELOG.md /.rspec: -------------------------------------------------------------------------------- 1 | --format progress 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | -------------------------------------------------------------------------------- /spec/features/algebra/type_sum_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'ALGEBRA: type sum' do 4 | end 5 | -------------------------------------------------------------------------------- /spec/types/value/io_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::IO' do 4 | end 5 | -------------------------------------------------------------------------------- /spec/features/algebra/type_mult_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'ALGEBRA: type multiplication' do 4 | end 5 | -------------------------------------------------------------------------------- /spec/types/value/comparable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Comparable' do 4 | end 5 | -------------------------------------------------------------------------------- /spec/types/value/enumerator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Enumerator' do 4 | end 5 | -------------------------------------------------------------------------------- /spec/types/value/string_io_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::StringIO' do 4 | end 5 | -------------------------------------------------------------------------------- /spec/types/value/enumerator_chain_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::EnumeratorChain' do 4 | end 5 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bundler/setup' 5 | require 'smart_core/types' 6 | 7 | require 'pry' 8 | Pry.start 9 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/smart_core/types/struct.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.1.0 5 | class SmartCore::Types::Struct < SmartCore::Types::Primitive 6 | end 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | .rspec_status 10 | /.idea 11 | .ruby-version 12 | *.gem 13 | -------------------------------------------------------------------------------- /lib/smart_core/types/protocol.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.3.0 5 | class SmartCore::Types::Protocol < SmartCore::Types::Primitive 6 | require_relative 'protocol/instance_of' 7 | end 8 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/mult_validator/result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SmartCore::Types::Primitive::MultValidator 4 | # @api private 5 | # @since 0.2.0 6 | class Result < SmartCore::Types::Primitive::SumValidator::Result 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/smart_core/types/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SmartCore 4 | module Types 5 | # @return [String] 6 | # 7 | # @api public 8 | # @since 0.1.0 9 | # @version 0.8.0 10 | VERSION = '0.8.0' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/any.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.1.0 5 | SmartCore::Types::Value.define_type(:Any) do |type| 6 | type.define_checker do |value| 7 | true 8 | end 9 | 10 | type.define_caster do |value| 11 | value 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/nil.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.1.0 5 | SmartCore::Types::Value.define_type(:Nil) do |type| 6 | type.define_checker do |value| 7 | value == nil # NOTE: #nil? is not used cuz BasicObject hasn't #nil? method 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/range.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.9.0 7 | SmartCore::Types::Value.define_type(:Range) do |type| 8 | type.define_checker do |value| 9 | value.is_a?(::Range) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.8.0 7 | SmartCore::Types::Value.define_type(:Method) do |type| 8 | type.define_checker do |value| 9 | value.is_a?(::Method) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/io.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.x.0 5 | SmartCore::Types::Value.define_type(:IO) do |type| 6 | type.define_checker do |value| 7 | # TODO: realize 8 | end 9 | 10 | type.define_caster do |value| 11 | # TODO: realize 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/class.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Class) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::Class) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/smart_core/types/variadic.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.3.0 5 | # @version 0.6.0 6 | class SmartCore::Types::Variadic < SmartCore::Types::Primitive 7 | require_relative 'variadic/array_of' 8 | require_relative 'variadic/enum' 9 | require_relative 'variadic/tuple' 10 | end 11 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/module.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Module) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::Module) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/unbound_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.9.0 7 | SmartCore::Types::Value.define_type(:UnboundMethod) do |type| 8 | type.define_checker do |value| 9 | value.is_a?(::UnboundMethod) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/comparable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.1.0 5 | SmartCore::Types::Value.define_type(:Comparable) do |type| 6 | type.define_checker do |value| 7 | # TODO: realize 8 | end 9 | 10 | type.define_caster do |value| 11 | # TODO: realize 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/enumerable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.x.0 5 | SmartCore::Types::Value.define_type(:Enumerable) do |type| 6 | type.define_checker do |value| 7 | # TODO: realize 8 | end 9 | 10 | type.define_caster do |value| 11 | # TODO: realize 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/enumerator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.x.0 5 | SmartCore::Types::Value.define_type(:Enumerator) do |type| 6 | type.define_checker do |value| 7 | # TODO: realize 8 | end 9 | 10 | type.define_caster do |value| 11 | # TODO: realize 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/enumerator_chain.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.x.0 5 | SmartCore::Types::Value.define_type(:EnumeratorChain) do |type| 6 | type.define_checker do |value| 7 | # TODO: realize 8 | end 9 | 10 | type.define_caster do |value| 11 | # TODO: realize 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/smart_core/types/variadic/enum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.5.0 7 | SmartCore::Types::Variadic.define_type(:Enum) do |type| 8 | type.runtime_attributes_checker(&:any?) 9 | 10 | type.define_checker { |value, enum| enum.include?(value) } 11 | end 12 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/string_io.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'stringio' 4 | 5 | # @api public 6 | # @since 0.x.0 7 | SmartCore::Types::Value.define_type(:StringIO) do |type| 8 | type.define_checker do |value| 9 | # TODO: realize 10 | end 11 | 12 | type.define_caster do |value| 13 | # TODO: realize 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/features/object_behavior/object_cloning_duplication_copying_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'OBJECT BEHAVIOR: object cloning/copying/duplication' do 4 | specify '(smoke test [TODO]: rewrite) copying should not fail' do 5 | expect { SmartCore::Types::Value::Any.dup }.not_to raise_error 6 | expect { SmartCore::Types::Value::Any.clone }.not_to raise_error 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/boolean.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Boolean) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::TrueClass) || value.is_a?(::FalseClass) 11 | end 12 | 13 | type.define_caster do |value| 14 | !!value 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/smart_core/types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'smart_core' 4 | 5 | # @api public 6 | # @since 0.1.0 7 | module SmartCore::Types 8 | require_relative 'types/version' 9 | require_relative 'types/errors' 10 | require_relative 'types/system' 11 | require_relative 'types/primitive' 12 | require_relative 'types/value' 13 | require_relative 'types/protocol' 14 | require_relative 'types/variadic' 15 | end 16 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/array.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Array) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::Array) 11 | end 12 | 13 | type.define_caster do |value| 14 | begin 15 | ::Kernel.Array(value) 16 | rescue ::TypeError 17 | [value] 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/symbol.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Symbol) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::Symbol) 11 | end 12 | 13 | type.define_caster do |value| 14 | begin 15 | value.to_sym 16 | rescue ::NoMethodError, ::ArgumentError 17 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Symbol') 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/string.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:String) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::String) 11 | end 12 | 13 | type.define_caster do |value| 14 | begin 15 | ::Kernel.String(value) 16 | rescue ::TypeError, ::ArgumentError 17 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to String') 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative './setup_simplecov' 4 | 5 | SimpleCov.start 6 | 7 | require 'bundler/setup' 8 | require 'pry' 9 | require 'smart_core/types' 10 | 11 | RSpec.configure do |config| 12 | Kernel.srand config.seed 13 | config.disable_monkey_patching! 14 | config.filter_run_when_matching :focus 15 | config.order = :random 16 | config.shared_context_metadata_behavior = :apply_to_host_groups 17 | config.expect_with(:rspec) { |c| c.syntax = :expect } 18 | Thread.abort_on_exception = true 19 | end 20 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/set.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'set' 4 | 5 | using SmartCore::Ext::BasicObjectAsObject 6 | 7 | # @api public 8 | # @since 0.3.0 9 | SmartCore::Types::Value.define_type(:Set) do |type| 10 | type.define_checker do |value| 11 | value.is_a?(::Set) 12 | end 13 | 14 | type.define_caster do |value| 15 | begin 16 | ::Set.new(SmartCore::Types::Value::Array.cast(value)) 17 | rescue ::ArgumentError, ::NoMethodError 18 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Set') 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | require 'rubocop' 6 | require 'rubocop/rake_task' 7 | require 'rubocop-performance' 8 | require 'rubocop-rspec' 9 | require 'rubocop-rake' 10 | 11 | RuboCop::RakeTask.new(:rubocop) do |t| 12 | config_path = File.expand_path(File.join('.rubocop.yml'), __dir__) 13 | t.options = ['--config', config_path] 14 | t.requires << 'rubocop-rspec' 15 | t.requires << 'rubocop-performance' 16 | t.requires << 'rubocop-rake' 17 | end 18 | 19 | RSpec::Core::RakeTask.new(:rspec) 20 | 21 | task default: :rspec 22 | -------------------------------------------------------------------------------- /lib/smart_core/types/protocol/instance_of.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.3.0 7 | SmartCore::Types::Protocol.define_type(:InstanceOf) do |type| 8 | type.runtime_attributes_checker do |runtime_attrs| 9 | runtime_attrs.any? && runtime_attrs.all? do |runtime_attr| 10 | runtime_attr.is_a?(::Class) 11 | end 12 | end 13 | 14 | type.define_checker do |value, expected_types| 15 | expected_types.any? && expected_types.any? do |expected_type| 16 | value.is_a?(expected_type) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/rational.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.9.0 7 | SmartCore::Types::Value.define_type(:Rational) do |type| 8 | type.define_checker do |value| 9 | value.is_a?(::Rational) 10 | end 11 | 12 | type.define_caster do |value| 13 | ::Kernel.Rational(value) 14 | rescue ::TypeError, ::FloatDomainError 15 | # NOTE: FloatDomainError is raised when you try to type cast Float::INFINITY or Float::NAN 16 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Rational') 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/date.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'date' 4 | 5 | using SmartCore::Ext::BasicObjectAsObject 6 | 7 | # @api public 8 | # @since 0.1.0 9 | # @version 0.7.0 10 | SmartCore::Types::Value.define_type(:Date) do |type| 11 | type.define_checker do |value| 12 | value.is_a?(::Date) || value == ::Date::Infinity 13 | end 14 | 15 | type.define_caster do |value| 16 | next value if value.is_a?(::Date) || value == ::Date::Infinity 17 | 18 | begin 19 | ::Date.parse(value) 20 | rescue ::ArgumentError, ::TypeError 21 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Date') 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/time.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'time' 4 | 5 | using SmartCore::Ext::BasicObjectAsObject 6 | 7 | # @api public 8 | # @since 0.1.0 9 | # @version 0.3.0 10 | SmartCore::Types::Value.define_type(:Time) do |type| 11 | type.define_checker do |value| 12 | value.is_a?(::Time) 13 | end 14 | 15 | type.define_caster do |value| 16 | next value if value.is_a?(::Time) 17 | 18 | begin 19 | SmartCore::Types::Value::Integer.valid?(value) ? ::Time.at(value) : ::Time.parse(value) 20 | rescue ::TypeError, ::ArgumentError 21 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Time') 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/float.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Float) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::Float) 11 | end 12 | 13 | type.define_caster do |value| 14 | begin 15 | ::Kernel.Float(value) 16 | rescue ::TypeError, ::ArgumentError 17 | begin 18 | ::Kernel.Float(value.to_f) 19 | rescue ::NoMethodError, ::TypeError, ::ArgumentError 20 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Float') 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/proc.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Proc) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::Proc) 11 | end 12 | 13 | type.define_caster do |value| 14 | begin 15 | value.to_proc.tap do |result| 16 | unless result.is_a?(::Proc) 17 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Proc') 18 | end 19 | end 20 | rescue ::NoMethodError 21 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Proc') 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/date_time.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'date' 4 | 5 | using SmartCore::Ext::BasicObjectAsObject 6 | 7 | # @api public 8 | # @since 0.1.0 9 | # @version 0.7.0 10 | SmartCore::Types::Value.define_type(:DateTime) do |type| 11 | type.define_checker do |value| 12 | value.is_a?(::DateTime) || value == ::DateTime::Infinity 13 | end 14 | 15 | type.define_caster do |value| 16 | next value if value.is_a?(::DateTime) || value == ::DateTime::Infinity 17 | 18 | begin 19 | ::DateTime.parse(value) 20 | rescue ::ArgumentError, ::TypeError 21 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to DateTime') 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | armitage-rubocop: 3 | - lib/rubocop.general.yml 4 | - lib/rubocop.rake.yml 5 | - lib/rubocop.rspec.yml 6 | 7 | AllCops: 8 | TargetRubyVersion: 3.0.0 9 | NewCops: enable 10 | Include: 11 | - lib/**/*.rb 12 | - spec/**/*.rb 13 | - Gemfile 14 | - Rakefile 15 | - smart_types.gemspec 16 | - bin/console 17 | 18 | # NOTE: support for old ruby versions 19 | Style/RedundantBegin: 20 | Enabled: false 21 | 22 | # NOTE: used only in specs and it is ok in specs 23 | Lint/EmptyBlock: 24 | Enabled: false 25 | 26 | # NOTE: too situative and too subjective 27 | Naming/VariableNumber: 28 | Enabled: false 29 | 30 | Style/RedundantConstantBase: 31 | Enabled: false 32 | -------------------------------------------------------------------------------- /lib/smart_core/types/variadic/array_of.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.6.0 7 | SmartCore::Types::Variadic.define_type(:ArrayOf) do |type| 8 | type.define_caster { |value| SmartCore::Types::Value::Array.cast(value) } 9 | 10 | type.runtime_attributes_checker do |runtime_attrs| 11 | runtime_attrs.any? && runtime_attrs.all? do |runtime_attr| 12 | runtime_attr.is_a?(::Class) 13 | end 14 | end 15 | 16 | type.define_checker do |value, expected_types| 17 | next false unless SmartCore::Types::Value::Array.valid?(value) 18 | 19 | value.all? do |elem| 20 | expected_types.any? { |expected_type| elem.is_a?(expected_type) } 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/caster.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | class SmartCore::Types::Primitive::Caster 7 | # @param expression [Proc] 8 | # @return [void] 9 | # 10 | # @api private 11 | # @since 0.1.0 12 | def initialize(expression) 13 | @expression = expression 14 | end 15 | 16 | # @param value [Any] 17 | # @param runtime_attributes [Array] 18 | # @return [Any] 19 | # 20 | # @api private 21 | # @since 0.1.0 22 | # @version 0.3.0 23 | def call(value, runtime_attributes) 24 | expression.call(value, runtime_attributes) 25 | end 26 | 27 | private 28 | 29 | # @return [Proc] 30 | # 31 | # @api private 32 | # @since 0.1.0 33 | attr_reader :expression 34 | end 35 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/checker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | class SmartCore::Types::Primitive::Checker 7 | # @param expression [Proc] 8 | # @return [void] 9 | # 10 | # @api private 11 | # @since 0.1.0 12 | def initialize(expression) 13 | @expression = expression 14 | end 15 | 16 | # @param value [Any] 17 | # @param runtime_attributes [Array] 18 | # @return [Boolean] 19 | # 20 | # @api private 21 | # @since 0.1.0 22 | # @version 0.3.0 23 | def call(value, runtime_attributes) 24 | !!expression.call(value, runtime_attributes) 25 | end 26 | 27 | private 28 | 29 | # @return [Proc] 30 | # 31 | # @api private 32 | # @since 0.1.0 33 | attr_reader :expression 34 | end 35 | -------------------------------------------------------------------------------- /lib/smart_core/types/variadic/tuple.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.3.0 7 | SmartCore::Types::Variadic.define_type(:Tuple) do |type| 8 | type.runtime_attributes_checker do |runtime_attrs| 9 | runtime_attrs.any? && runtime_attrs.all? do |runtime_attr| 10 | runtime_attr.is_a?(::Class) 11 | end 12 | end 13 | 14 | type.define_checker do |value, tuple_signature| 15 | next false unless SmartCore::Types::Value::Array.valid?(value) 16 | next false if value.size != tuple_signature.size 17 | 18 | value.each_with_index.all? do |tuple_val, tuple_val_index| 19 | expected_tuple_type = tuple_signature.at(tuple_val_index) 20 | tuple_val.is_a?(expected_tuple_type) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/hash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Hash) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::Hash) 11 | end 12 | 13 | type.define_caster do |value| 14 | begin 15 | ::Kernel.Hash(value) 16 | rescue ::TypeError 17 | begin 18 | # NOTE: 19 | # - ::Kernel.Hash does not invoke `#to_h` under the hood (it invokes `#to_hash`) 20 | # - ::Kernel.Hash is used to validate the returned value from `#to_h` 21 | ::Kernel.Hash(value.to_h) 22 | rescue ::TypeError, ::NoMethodError, ::ArgumentError 23 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Hash') 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/undefined_caster.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | class SmartCore::Types::Primitive::UndefinedCaster < SmartCore::Types::Primitive::Caster 7 | # @param expression [NilClass, Any] 8 | # @return [void] 9 | # 10 | # @api private 11 | # @since 0.1.0 12 | def initialize(expression = nil) 13 | super 14 | end 15 | 16 | # @param value [Any] 17 | # @param runtime_attributes [Array] 18 | # @return [void] 19 | # 20 | # @raise [SmartCore::Types::TypeCastingUnsupportedError] 21 | # 22 | # @pai private 23 | # @since 0.1.0 24 | # @version 0.3.0 25 | def call(value, runtime_attributes) 26 | raise(SmartCore::Types::TypeCastingUnsupportedError, <<~ERROR_MESSAGE) 27 | 'This type has no support for type casting' 28 | ERROR_MESSAGE 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/smart_core/types/system.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.1.0 5 | module SmartCore::Types::System 6 | class << self 7 | # @param types [Array] 8 | # @param type_definition [Block] 9 | # @return [SmartCore::Types::Primitive] 10 | # 11 | # @api public 12 | # @since 0.1.0 13 | def type_sum(*types, &type_definition) 14 | SmartCore::Types::Primitive::SumFactory.create_type(types, type_definition) 15 | end 16 | 17 | # @param types [Array] 18 | # @param type_definition [Block] 19 | # @return [SmartCore::Types::Primitive] 20 | # 21 | # @api public 22 | # @since 0.1.0 23 | def type_mult(*types, &type_definition) 24 | SmartCore::Types::Primitive::MultFactory.create_type(types, type_definition) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/text.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | SmartCore::Types::Value.define_type(:Text) do |type| 7 | type.define_checker do |value| 8 | SmartCore::Types::Value::String.valid?(value) || SmartCore::Types::Value::Symbol.valid?(value) 9 | end 10 | 11 | type.define_caster do |value| 12 | next value if SmartCore::Types::Value::String.valid?(value) 13 | next value if SmartCore::Types::Value::Symbol.valid?(value) 14 | 15 | begin 16 | SmartCore::Types::Value::String.cast(value) 17 | rescue SmartCore::Types::TypeCastingError 18 | begin 19 | SmartCore::Types::Value::Symbol.cast(value) 20 | rescue SmartCore::Types::TypeCastingError 21 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to text') 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/setup_simplecov.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'simplecov' 4 | require 'simplecov-lcov' 5 | 6 | SimpleCov::Formatter::LcovFormatter.config do |config| 7 | config.report_with_single_file = true 8 | config.output_directory = 'coverage' 9 | config.lcov_file_name = 'lcov.info' 10 | end 11 | 12 | SimpleCov.configure do 13 | enable_coverage :line 14 | enable_coverage :branch 15 | 16 | minimum_coverage 95 17 | 18 | formatter SimpleCov::Formatter::MultiFormatter.new([ 19 | SimpleCov::Formatter::HTMLFormatter, 20 | SimpleCov::Formatter::LcovFormatter 21 | ]) 22 | 23 | add_group 'Engine', '/lib/smart_core/types/primitive' 24 | add_group 'Type Definitions', %w[ 25 | /lib/smart_core/types/protocol 26 | /lib/smart_core/types/struct 27 | /lib/smart_core/types/value 28 | /lib/smart_core/types/variadic 29 | ] 30 | 31 | add_filter '/spec/' 32 | end 33 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/big_decimal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bigdecimal' 4 | require 'bigdecimal/util' 5 | 6 | using SmartCore::Ext::BasicObjectAsObject 7 | 8 | # @api public 9 | # @since 0.1.0 10 | # @version 0.3.0 11 | SmartCore::Types::Value.define_type(:BigDecimal) do |type| 12 | type.define_checker do |value| 13 | value.is_a?(::BigDecimal) 14 | end 15 | 16 | type.define_caster do |value| 17 | if SmartCore::Types::Value::Numeric.valid?(value) 18 | value = SmartCore::Types::Value::String.cast(value) 19 | end 20 | 21 | begin 22 | ::Kernel.BigDecimal(value) 23 | rescue ::ArgumentError, ::TypeError 24 | begin 25 | ::Kernel.BigDecimal(value.to_d) 26 | rescue ::ArgumentError, ::TypeError, ::NoMethodError 27 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to BigDecimal') 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/integer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Integer) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::Integer) 11 | end 12 | 13 | type.define_caster do |value| 14 | begin 15 | ::Kernel.Integer(value) 16 | rescue ::TypeError, ::ArgumentError, ::FloatDomainError 17 | begin 18 | # NOTE: for cases like this: 19 | # => ::Kernel.Integer(nil) # => ::TypeError 20 | # => ::Kernel.Integer(nil.to_i) # => 0 (::Kernel.Integer used as validation layer) 21 | ::Kernel.Integer(value.to_i) 22 | rescue ::TypeError, ::NoMethodError, ::ArgumentError, ::FloatDomainError 23 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Integer') 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/numeric.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using SmartCore::Ext::BasicObjectAsObject 4 | 5 | # @api public 6 | # @since 0.1.0 7 | # @version 0.3.0 8 | SmartCore::Types::Value.define_type(:Numeric) do |type| 9 | type.define_checker do |value| 10 | value.is_a?(::Numeric) 11 | end 12 | 13 | type.define_caster do |value| 14 | next value if value.is_a?(::Numeric) 15 | 16 | begin 17 | SmartCore::Types::Value::Float.cast(value) 18 | rescue SmartCore::Types::TypeCastingError 19 | begin 20 | SmartCore::Types::Value::Integer.cast(value) 21 | rescue SmartCore::Types::TypeCastingError 22 | begin 23 | SmartCore::Types::Value::BigDecimal.cast(value) 24 | rescue SmartCore::Types::TypeCastingError 25 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to Numeric') 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2024 Rustam Ibragimov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/smart_core/types/value/time_based.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | SmartCore::Types::Value.define_type(:TimeBased) do |type| 7 | type.define_checker do |value| 8 | SmartCore::Types::Value::Time.valid?(value) || 9 | SmartCore::Types::Value::DateTime.valid?(value) || 10 | SmartCore::Types::Value::Date.valid?(value) 11 | end 12 | 13 | type.define_caster do |value| 14 | next value if SmartCore::Types::Value::Time.valid?(value) 15 | next value if SmartCore::Types::Value::DateTime.valid?(value) 16 | next value if SmartCore::Types::Value::Date.valid?(value) 17 | 18 | begin 19 | SmartCore::Types::Value::Time.cast(value) 20 | rescue SmartCore::Types::TypeCastingError 21 | begin 22 | SmartCore::Types::Value::DateTime.cast(value) 23 | rescue SmartCore::Types::TypeCastingError 24 | begin 25 | SmartCore::Types::Value::Date.cast(value) 26 | rescue SmartCore::Types::TypeCastingError 27 | raise(SmartCore::Types::TypeCastingError, 'Non-castable to time-based type') 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/smart_core/types/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SmartCore::Types 4 | # @api public 5 | # @since 0.1.0 6 | Error = Class.new(SmartCore::Error) 7 | 8 | # @api public 9 | # @since 0.1.0 10 | ArgumentError = Class.new(SmartCore::ArgumentError) 11 | 12 | # @api public 13 | # @since 0.1.0 14 | NameError = Class.new(SmartCore::NameError) 15 | 16 | # @api public 17 | # @since 0.3.0 18 | TypeDefinitionError = Class.new(ArgumentError) 19 | 20 | # @api public 21 | # @since 0.3.0 22 | IncorrectRuntimeAttributesError = Class.new(TypeDefinitionError) 23 | 24 | # @api public 25 | # @since 0.3.0 26 | RuntimeAttriburtesUnsupportedError = Class.new(TypeDefinitionError) 27 | 28 | # @api public 29 | # @since 0.1.0 30 | TypeError = Class.new(SmartCore::TypeError) 31 | 32 | # @api public 33 | # @since 0.1.0 34 | TypeCastingError = Class.new(Error) 35 | 36 | # @api public 37 | # @since 0.1.0 38 | TypeCastingUnsupportedError = Class.new(TypeCastingError) 39 | 40 | # @api public 41 | # @since 0.1.0 42 | NoCheckerDefinitionError = Class.new(Error) 43 | 44 | # @api public 45 | # @since 0.1.0 46 | IncorrectTypeNameError = Class.new(NameError) 47 | end 48 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/invariant_control/factory/chain_definition_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | class SmartCore::Types::Primitive::InvariantControl::Factory::ChainDefinitionContext 6 | # @return [SmartCore::Types::Primitive::InvariantControl::Chain] 7 | # 8 | # @api private 9 | # @since 0.2.0 10 | attr_reader :___chain___ 11 | 12 | # @param chain_name [String] 13 | # @return [void] 14 | # 15 | # @api privae 16 | # @since 0.2.0 17 | def initialize(chain_name) 18 | @___chain___ = SmartCore::Types::Primitive::InvariantControl::Chain.new(chain_name) 19 | end 20 | 21 | # @param invariant_name [String, Symbol] 22 | # @param invariant_definition [Block] 23 | # @return [void] 24 | # 25 | # @api private 26 | # @since 0.2.0 27 | def invariant(invariant_name, &invariant_definition) 28 | SmartCore::Types::Primitive::Factory::DefinitionContext.vaildate_invariant_attributes!( 29 | invariant_name, 30 | &invariant_definition 31 | ) 32 | 33 | ___chain___.add_invariant( 34 | SmartCore::Types::Primitive::InvariantControl::Single.create( 35 | invariant_name, invariant_definition 36 | ) 37 | ) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/smart_core/types/value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.1.0 5 | class SmartCore::Types::Value < SmartCore::Types::Primitive 6 | require_relative 'value/nil' 7 | require_relative 'value/string' 8 | require_relative 'value/string_io' 9 | require_relative 'value/symbol' 10 | require_relative 'value/text' 11 | require_relative 'value/integer' 12 | require_relative 'value/float' 13 | require_relative 'value/numeric' 14 | require_relative 'value/boolean' 15 | require_relative 'value/array' 16 | require_relative 'value/hash' 17 | require_relative 'value/io' 18 | require_relative 'value/proc' 19 | require_relative 'value/class' 20 | require_relative 'value/module' 21 | require_relative 'value/any' 22 | require_relative 'value/time' 23 | require_relative 'value/date' 24 | require_relative 'value/date_time' 25 | require_relative 'value/time_based' 26 | require_relative 'value/enumerable' 27 | require_relative 'value/enumerator' 28 | require_relative 'value/enumerator_chain' 29 | require_relative 'value/comparable' 30 | require_relative 'value/big_decimal' 31 | require_relative 'value/range' 32 | require_relative 'value/method' 33 | require_relative 'value/unbound_method' 34 | require_relative 'value/set' 35 | require_relative 'value/rational' 36 | end 37 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/sum_factory/definition_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | class SmartCore::Types::Primitive::SumFactory::DefinitionContext 7 | # @return [Proc, NilClass] 8 | # 9 | # @api private 10 | # @since 0.1.0 11 | attr_reader :type_caster 12 | 13 | # @return [Proc, NilClass] 14 | # 15 | # @api private 16 | # @since 0.3.0 17 | attr_reader :type_runtime_attributes_checker 18 | 19 | # @return [void] 20 | # 21 | # @api private 22 | # @since 0.1.0 23 | # @version 0.3.0 24 | def initialize 25 | @type_caster = nil 26 | @type_runtime_attributes_checker = nil 27 | end 28 | 29 | # @param caster [Block] 30 | # @return [void] 31 | # 32 | # @api public 33 | # @since 0.1.0 34 | # @version 0.3.0 35 | def define_caster(&caster) 36 | raise(SmartCore::Types::TypeDefinitionError, 'No caster definition block') unless block_given? 37 | @type_caster = caster 38 | end 39 | 40 | # @param definition [Block] 41 | # @return [void] 42 | # 43 | # @api public 44 | # @since 0.3.0 45 | def runtime_attributes_checker(&definition) 46 | unless block_given? 47 | raise(SmartCore::Types::TypeDefinitionError, 'No runtime checker definition block') 48 | end 49 | @type_runtime_attributes_checker = definition 50 | end 51 | 52 | # TODO (0.x.0): invariant API 53 | end 54 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/mult_factory/definition_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | class SmartCore::Types::Primitive::MultFactory::DefinitionContext 7 | # @return [Proc, NilClass] 8 | # 9 | # @api private 10 | # @since 0.1.0 11 | attr_reader :type_caster 12 | 13 | # @return [Proc, NilClass] 14 | # 15 | # @api private 16 | # @since 0.3.0 17 | attr_reader :type_runtime_attributes_checker 18 | 19 | # @return [void] 20 | # 21 | # @api private 22 | # @since 0.1.0 23 | # @version 0.3.0 24 | def initialize 25 | @type_caster = nil 26 | @type_runtime_attributes_checker = nil 27 | end 28 | 29 | # @param caster [Block] 30 | # @return [void] 31 | # 32 | # @api public 33 | # @since 0.1.0 34 | # @version 0.3.0 35 | def define_caster(&caster) 36 | raise(SmartCore::Types::TypeDefinitionError, 'No caster definition block') unless block_given? 37 | @type_caster = caster 38 | end 39 | 40 | # @param definition [Block] 41 | # @return [void] 42 | # 43 | # @api public 44 | # @since 0.3.0 45 | def runtime_attributes_checker(&definition) 46 | unless block_given? 47 | raise(SmartCore::Types::TypeDefinitionError, 'No runtime checker definition block') 48 | end 49 | @type_runtime_attributes_checker = definition 50 | end 51 | 52 | # TODO (0.x.0): invariant API 53 | end 54 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/mult_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | # @version 0.3.0 6 | class SmartCore::Types::Primitive::MultValidator < SmartCore::Types::Primitive::SumValidator 7 | require_relative 'mult_validator/result' 8 | 9 | # @overload validate(value) 10 | # @param value [Any] 11 | # @return [SmartCore::Types::Primitive::MultValidator::Result] 12 | # 13 | # @api private 14 | # @since 0.2.0 15 | 16 | # @overload ___copy_for___(type) 17 | # @param type [SmartCore::Types::Primitive] 18 | # @return [SmartCore::Types::Primitive::MultValidator] 19 | # 20 | # @api private 21 | # @since 0.3.0 22 | 23 | private 24 | 25 | # @param validation [Block] 26 | # @yieldparam [void] 27 | # @yieldreturn [SmartCore::Engine::Atom] 28 | # @return [SmartCore::Types::Primitive::MultValidator::Result] 29 | # 30 | # @api private 31 | # @since 0.2.0 32 | def compile_validation_result(&validation) 33 | # NOTE: at this moment type sum does not support invariant checking 34 | # TODO (0.x.0): 35 | # @yieldreturn [SmartCore::Types::Primitive::Validator::Result] 36 | # => and: 37 | # SmartCore::Types::Primitive::MultValidator::Result.new( 38 | # type, final_result.value, final_result.value.invariant_errors 39 | # ) 40 | SmartCore::Types::Primitive::MultValidator::Result.new(type, yield.value) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | ci: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | ruby: 19 | - version: 2.7 20 | continue-on-error: false 21 | - version: 3.0 22 | continue-on-error: false 23 | - version: 3.1 24 | continue-on-error: false 25 | - version: ruby-head 26 | continue-on-error: true 27 | - version: jruby-head 28 | continue-on-error: true 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | bundler-cache: true 36 | ruby-version: ${{ matrix.ruby.version }} 37 | - name: Install dependencies 38 | run: bundle install 39 | continue-on-error: ${{ matrix.ruby.continue-on-error }} 40 | - name: Run rubocop 41 | run: bundle exec rubocop 42 | continue-on-error: ${{ matrix.ruby.continue-on-error }} 43 | - name: Run specs 44 | run: bundle exec rspec 45 | continue-on-error: ${{ matrix.ruby.continue-on-error }} 46 | - name: Coveralls 47 | uses: coverallsapp/github-action@master 48 | continue-on-error: true 49 | with: 50 | github-token: ${{ secrets.GITHUB_TOKEN }} 51 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/invariant_control/single/result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | class SmartCore::Types::Primitive::InvariantControl::Single::Result 6 | # @return [SmartCore::Types::Primitive::InvariantControl::Single] 7 | # 8 | # @api private 9 | # @since 0.2.0 10 | attr_reader :invariant 11 | 12 | # @return [Any] 13 | # 14 | # @api private 15 | # @since 0.2.0 16 | attr_reader :checked_value 17 | 18 | # @param invariant [SmartCore::Types::Primitive::InvariantControl::Single] 19 | # @param checked_value [Any] 20 | # @param is_valid_check [Boolean] 21 | # @return [void] 22 | # 23 | # @api private 24 | # @since 0.2.0 25 | def initialize(invariant, checked_value, is_valid_check) 26 | @invariant = invariant 27 | @checked_value = checked_value 28 | @is_valid_check = is_valid_check 29 | end 30 | 31 | # @return [Boolean] 32 | # 33 | # @api private 34 | # @since 0.2.0 35 | def success? 36 | is_valid_check 37 | end 38 | 39 | # @return [Boolean] 40 | # 41 | # @api private 42 | # @since 0.2.0 43 | def failure? 44 | !success? 45 | end 46 | 47 | # @return [Array] 48 | # 49 | # @api private 50 | # @since 0.2.0 51 | def error_codes 52 | success? ? [] : [invariant.name] 53 | end 54 | 55 | private 56 | 57 | # @return [Boolean] 58 | # 59 | # @api private 60 | # @since 0.2.0 61 | attr_reader :is_valid_check 62 | alias_method :valid_check?, :is_valid_check 63 | end 64 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/nilable_validator/result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | class SmartCore::Types::Primitive::NilableValidator::Result 6 | # @return [Array] 7 | # 8 | # @api private 9 | # @since 0.2.0 10 | NO_INVARIANT_ERRORS = [].freeze 11 | 12 | # @return [SmartCore::Types::Primitive] 13 | # 14 | # @api public 15 | # @since 0.2.0 16 | attr_reader :type 17 | 18 | # @return [Any] 19 | # 20 | # @api public 21 | # @since 0.2.0 22 | attr_reader :checked_value 23 | alias_method :value, :checked_value 24 | 25 | # @param type [SmartCore::Types::Primitive] 26 | # @param checked_value [Any] 27 | # @return [void] 28 | # 29 | # @api private 30 | # @since 0.2.0 31 | def initialize(type, checked_value) 32 | @type = type 33 | @checked_value = checked_value 34 | end 35 | 36 | # @return [Boolean] 37 | # 38 | # @api public 39 | # @since 0.2.0 40 | def is_valid_check 41 | true 42 | end 43 | alias_method :valid_check?, :is_valid_check 44 | 45 | # @return [Array] 46 | # 47 | # @api public 48 | # @since 0.2.0 49 | def invariant_errors 50 | NO_INVARIANT_ERRORS 51 | end 52 | alias_method :errors, :invariant_errors 53 | alias_method :error_codes, :invariant_errors 54 | 55 | # @return [Boolean] 56 | # 57 | # @api public 58 | # @since 0.2.0 59 | def valid_invariants? 60 | true 61 | end 62 | 63 | # @return [Boolean] 64 | # 65 | # @api public 66 | # @since 0.2.0 67 | def success? 68 | true 69 | end 70 | 71 | # @return [Boolean] 72 | # 73 | # @api public 74 | # @since 0.2.0 75 | def failure? 76 | false 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/invariant_control/chain/result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | class SmartCore::Types::Primitive::InvariantControl::Chain::Result 6 | # @return [SmartCore::Types::Primitive::invariantControl::Chain] 7 | # 8 | # @api private 9 | # @since 0.2.0 10 | attr_reader :invariant_chain 11 | 12 | # @return [Any] 13 | # 14 | # @api private 15 | # @since 0.2.0 16 | attr_reader :checked_value 17 | 18 | # @param invariant_chain [SmartCore::Types::Primitive::invariantControl::Chain] 19 | # @param checked_value [Any] 20 | # @param invariant_results [Array] 21 | # @return [void] 22 | # 23 | # @api private 24 | # @since 0.2.0 25 | def initialize(invariant_chain, checked_value, invariant_results) 26 | @invariant_chain = invariant_chain 27 | @checked_value = checked_value 28 | @invariant_results = invariant_results 29 | end 30 | 31 | # @return [Boolean] 32 | # 33 | # @api private 34 | # @since 0.2.0 35 | def success? 36 | invariant_results.all?(&:success?) 37 | end 38 | 39 | # @return [Boolean] 40 | # 41 | # @api private 42 | # @since 0.2.0 43 | def failure? 44 | invariant_results.any?(&:failure?) 45 | end 46 | 47 | # @return [Array] 48 | # 49 | # @api private 50 | # @since 0.1.0 51 | def error_codes 52 | invariant_results.select(&:failure?).map! do |invariant_result| 53 | "#{invariant_chain.name}.#{invariant_result.invariant.name}".tap(&:freeze) 54 | end 55 | end 56 | 57 | private 58 | 59 | # @return [Array] 60 | # 61 | # @api private 62 | # @since 0.2.0 63 | attr_reader :invariant_results 64 | end 65 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/invariant_control/single.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | # @version 0.8.0 6 | class SmartCore::Types::Primitive::InvariantControl::Single 7 | require_relative 'single/result' 8 | 9 | class << self 10 | # @param name [String, Symbol] 11 | # @param invariant_checker [Proc] 12 | # @return [SmartCore::Types::Primitive::InvariantControl::Single] 13 | # 14 | # @api private 15 | # @since 0.2.0 16 | def create(name, invariant_checker) 17 | new(name.to_s, invariant_checker) 18 | end 19 | end 20 | 21 | # @return [String] 22 | # 23 | # @api private 24 | # @since 0.2.0 25 | attr_reader :name 26 | 27 | # @param name [String] 28 | # @param invariant_checker [Proc] 29 | # @return [void] 30 | # 31 | # @api private 32 | # @since 0.2.0 33 | def initialize(name, invariant_checker) 34 | @name = name.dup.tap(&:freeze) 35 | @invariant_checker = invariant_checker 36 | end 37 | 38 | # @param value [Any] 39 | # @param runtime_attributes [Array] 40 | # @return [SmartCore::Types::Primitive::InvariantControl::Single::Result] 41 | # 42 | # @api private 43 | # @since 0.2.0 44 | # @version 0.3.0 45 | def check(value, runtime_attributes) 46 | validation_result = !!invariant_checker.call(value, runtime_attributes) 47 | Result.new(self, value, validation_result) 48 | end 49 | 50 | # @param value [Any] 51 | # @param runtime_attributes [Array] 52 | # @return [Boolean] 53 | # 54 | # @api private 55 | # @since 0.8.0 56 | def simply_check(value, runtime_attributes) 57 | !!invariant_checker.call(value, runtime_attributes) 58 | end 59 | 60 | private 61 | 62 | # @return [Proc] 63 | # 64 | # @api private 65 | # @since 0.2.0 66 | attr_reader :invariant_checker 67 | end 68 | -------------------------------------------------------------------------------- /spec/types/value/nil_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Nil' do 4 | shared_examples 'type operations' do 5 | specify 'type-casting' do 6 | expect { type.cast(Object.new) }.to raise_error( 7 | SmartCore::Types::TypeCastingUnsupportedError 8 | ) 9 | 10 | expect { type.cast(BasicObject.new) }.to raise_error( 11 | SmartCore::Types::TypeCastingUnsupportedError 12 | ) 13 | end 14 | 15 | specify 'type-checking' do 16 | expect(type.valid?(nil)).to eq(true) 17 | expect(type.valid?(Object.new)).to eq(false) 18 | expect(type.valid?(BasicObject.new)).to eq(false) 19 | end 20 | 21 | specify 'type-validation' do 22 | expect { type.validate!(nil) }.not_to raise_error 23 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 24 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 25 | end 26 | end 27 | 28 | context 'non-nilable type' do 29 | let(:type) { SmartCore::Types::Value::Nil } 30 | 31 | include_examples 'type operations' 32 | end 33 | 34 | context 'runtime-based non-nilable type' do 35 | let(:type) { SmartCore::Types::Value::Nil() } 36 | 37 | include_examples 'type operations' 38 | end 39 | 40 | context 'nilable type' do 41 | let(:type) { SmartCore::Types::Value::Nil.nilable } 42 | 43 | include_examples 'type operations' 44 | end 45 | 46 | context 'runtime-based nilable type' do 47 | let(:type) { SmartCore::Types::Value::Nil().nilable } 48 | 49 | include_examples 'type operations' 50 | end 51 | 52 | specify 'has no support for runtime attributes' do 53 | expect { SmartCore::Types::Value::Nil(nil) }.to raise_error( 54 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 55 | ) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/invariant_control/factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | module SmartCore::Types::Primitive::InvariantControl::Factory 6 | require_relative 'factory/chain_definition_context' 7 | 8 | class << self 9 | # @param invariant_chains [Hash] 10 | # @param invariants [Hash] 11 | # @return [SmartCore::Types::Primitive::InvariantControl] 12 | # 13 | # @api private 14 | # @since 0.2.0 15 | def create(invariant_chains, invariants) 16 | completed_invariant_chains = build_invariant_chains(invariant_chains) 17 | completed_invariants = build_invariants(invariants) 18 | 19 | SmartCore::Types::Primitive::InvariantControl.new( 20 | completed_invariant_chains, 21 | completed_invariants 22 | ) 23 | end 24 | 25 | private 26 | 27 | # @param invariant_chains [Hash] 28 | # @return [Array] 29 | # 30 | # @api private 31 | # @since 0.2.0 32 | def build_invariant_chains(invariant_chains) 33 | invariant_chains.map do |chain_name, chain_invariants| 34 | context = ChainDefinitionContext.new(chain_name) 35 | chain_invariants.each { |invariant_logic| context.instance_eval(&invariant_logic) } 36 | context.___chain___ 37 | end 38 | end 39 | 40 | # @param invariants [Hash] 41 | # @return [Array] 42 | # 43 | # @api private 44 | # @since 0.2.0 45 | def build_invariants(invariants) 46 | invariants.map do |invariant_name, invariant_logics| 47 | SmartCore::Types::Primitive::InvariantControl::Single.create( 48 | invariant_name, 49 | invariant_logics 50 | ) 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/validator/result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | class SmartCore::Types::Primitive::Validator::Result 6 | # @return [Array] 7 | # 8 | # @api private 9 | # @since 0.2.0 10 | NO_INVARIANT_ERRORS = [].freeze 11 | 12 | # @return [SmartCore::Types::Primitive] 13 | # 14 | # @api public 15 | # @since 0.2.0 16 | attr_reader :type 17 | 18 | # @return [Boolean] 19 | # 20 | # @api public 21 | # @since 0.2.0 22 | attr_reader :is_valid_check 23 | alias_method :valid_check?, :is_valid_check 24 | 25 | # @return [Any] 26 | # 27 | # @api public 28 | # @since 0.2.0 29 | attr_reader :checked_value 30 | alias_method :value, :checked_value 31 | 32 | # @return [Array] 33 | # 34 | # @api public 35 | # @since 0.2.0 36 | attr_reader :invariant_errors 37 | alias_method :errors, :invariant_errors 38 | alias_method :error_codes, :invariant_errors 39 | 40 | # @param type [SmartCore::Types::Primitive] 41 | # @param checked_value [Any] 42 | # @param is_valid_check [Boolean] 43 | # @param invariant_errors [Array] 44 | # @return [void] 45 | # 46 | # @api private 47 | # @since 0.2.0 48 | def initialize(type, checked_value, is_valid_check, invariant_errors = NO_INVARIANT_ERRORS.dup) 49 | @type = type 50 | @checked_value = checked_value 51 | @is_valid_check = is_valid_check 52 | @invariant_errors = invariant_errors.tap(&:freeze) 53 | end 54 | 55 | # @return [Boolean] 56 | # 57 | # @api public 58 | # @since 0.2.0 59 | def valid_invariants? 60 | invariant_errors.empty? 61 | end 62 | 63 | # @return [Boolean] 64 | # 65 | # @api public 66 | # @since 0.2.0 67 | def success? 68 | valid_check? && invariant_errors.empty? 69 | end 70 | 71 | # @return [Boolean] 72 | # 73 | # @api public 74 | # @since 0.2.0 75 | def failure? 76 | !success? 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /smart_types.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/smart_core/types/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.required_ruby_version = Gem::Requirement.new('>= 3.0') 7 | 8 | spec.name = 'smart_types' 9 | spec.version = SmartCore::Types::VERSION 10 | spec.authors = ['Rustam Ibragimov'] 11 | spec.email = ['iamdaiver@gmail.com'] 12 | 13 | spec.summary = 'Full-featured type system for any ruby project.' 14 | spec.description = <<~DESCRIPTION 15 | Full-featured type system for any ruby project. Supports custom type definition, 16 | type validation, type casting and type categorizing. Provides a set of commonly used type 17 | categories and general purpose types. Has a flexible and simplest type definition toolchain. 18 | DESCRIPTION 19 | 20 | spec.homepage = 'https://github.com/smart-rb/smart_types' 21 | spec.license = 'MIT' 22 | 23 | spec.metadata['homepage_uri'] = spec.homepage 24 | spec.metadata['source_code_uri'] = 'https://github.com/smart-rb/smart_types' 25 | spec.metadata['changelog_uri'] = 'https://github.com/smart-rb/smart_types/blob/master/CHANGELOG.md' 26 | 27 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 28 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 29 | end 30 | 31 | spec.bindir = 'exe' 32 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 33 | spec.require_paths = ['lib'] 34 | 35 | spec.add_dependency 'smart_engine', '~> 0.17' 36 | 37 | spec.add_development_dependency 'bundler', '~> 2.4' 38 | spec.add_development_dependency 'rake', '~> 13.0' 39 | spec.add_development_dependency 'rspec', '~> 3.12' 40 | spec.add_development_dependency 'armitage-rubocop', '~> 1.51' 41 | spec.add_development_dependency 'simplecov', '~> 0.22' 42 | spec.add_development_dependency 'simplecov-lcov', '~> 0.8' 43 | spec.add_development_dependency 'pry', '~> 0.14' 44 | end 45 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/invariant_control/chain.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | # @version 0.8.0 6 | class SmartCore::Types::Primitive::InvariantControl::Chain 7 | require_relative 'chain/result' 8 | 9 | # @return [String] 10 | # 11 | # @api private 12 | # @since 0.2.0 13 | attr_reader :name 14 | 15 | # @param name [String] 16 | # @return [void] 17 | # 18 | # @api private 19 | # @since 0.1.0 20 | def initialize(name) 21 | @name = name.dup.tap(&:freeze) 22 | @invariants = [] 23 | end 24 | 25 | # @param invariant [SmartCore::Types::Primitive::InvariantControl::Single] 26 | # @return [void] 27 | # 28 | # @api private 29 | # @since 0.2.0 30 | def add_invariant(invariant) 31 | invariants << invariant 32 | end 33 | 34 | # @param value [Any] 35 | # @param runtime_attributes [Array] 36 | # @return [SmartCore::Types::Primitive::InvariantControl::Chain::Result] 37 | # 38 | # @api private 39 | # @since 0.2.0 40 | # @version 0.3.0 41 | def check(value, runtime_attributes) 42 | invariant_results = [].tap do |results| 43 | invariants.each do |invariant| 44 | result = invariant.check(value, runtime_attributes).tap { |res| results << res } 45 | break if result.failure? 46 | end 47 | end 48 | 49 | SmartCore::Types::Primitive::InvariantControl::Chain::Result.new( 50 | self, value, invariant_results 51 | ) 52 | end 53 | 54 | # @param value [Any] 55 | # @param runtime_attributes [Array] 56 | # @return [Boolean] 57 | # 58 | # @api private 59 | # @since 0.8.0 60 | def simply_check(value, runtime_attributes) 61 | (invariants.any? do |invariant| 62 | invariant.simply_check(value, runtime_attributes) == false 63 | end) ? false : true 64 | end 65 | 66 | private 67 | 68 | # @return [Array] 69 | # 70 | # @api private 71 | # @since 0.2.0 72 | attr_reader :invariants 73 | end 74 | -------------------------------------------------------------------------------- /spec/features/invariants/type_multiplication_composability_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'INVARIANTS: Type multiplication composability' do 4 | SmartCore::Types::Value.define_type(:InvFirstMultTypeSpec) do |type| 5 | type.define_checker { |value| value.is_a?(::Integer) } 6 | 7 | type.define_caster { |value| ::Kernel::Integer(value) } 8 | 9 | type.invariant_chain(:range) do 10 | invariant(:non_zero) { |value| value != 0 } 11 | invariant(:not_59) { |value| value != 59 } 12 | end 13 | 14 | type.invariant(:danger_value) { |value| value != 25 } 15 | end 16 | 17 | SmartCore::Types::Value.define_type(:InvSecMultTypeSpec) do |type| 18 | type.define_checker { |value| value.is_a?(::Float) } 19 | 20 | type.define_caster { |value| ::Kernel::Float(value) } 21 | 22 | type.invariant_chain(:range) do 23 | invariant(:less_than_100) { |value| value < 100 } 24 | invariant(:more_than_10) { |value| value > 10 } 25 | end 26 | 27 | type.invariant(:danger_value) { |value| value != 70.0 } 28 | end 29 | 30 | SmartCore::Types::Value::InvMultTypeSpec = SmartCore::Types::System.type_mult( 31 | SmartCore::Types::Value::InvFirstMultTypeSpec, 32 | SmartCore::Types::Value::InvSecMultTypeSpec 33 | ) 34 | 35 | let!(:mult_type) { SmartCore::Types::Value::InvMultTypeSpec } 36 | let!(:nilable_mult_type) { SmartCore::Types::Value::InvMultTypeSpec.nilable } 37 | 38 | specify('TODO: support for invariant checking in type mult') {} 39 | 40 | # rubocop:disable Layout/LineLength 41 | specify 'result type' do 42 | expect(mult_type.validate('123')).to be_a(::SmartCore::Types::Primitive::MultValidator::Result) 43 | expect(mult_type.validate(123)).to be_a(::SmartCore::Types::Primitive::MultValidator::Result) 44 | expect(mult_type.validate(123.0)).to be_a(::SmartCore::Types::Primitive::MultValidator::Result) 45 | expect(mult_type.validate(nil)).to be_a(::SmartCore::Types::Primitive::MultValidator::Result) 46 | expect(nilable_mult_type.validate(nil)).to be_a(::SmartCore::Types::Primitive::NilableValidator::Result) 47 | end 48 | # rubocop:enable Layout/LineLength 49 | end 50 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/nilable_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | module SmartCore::Types::Primitive::NilableFactory 7 | class << self 8 | # @param type [SmartCore::Types::Primitive] 9 | # @return [SmartCore::Type::Primitive] 10 | # 11 | # @api private 12 | # @since 0.1.0 13 | # @version 0.2.0 14 | def create_type(type) 15 | type_validator = build_type_validator(type) 16 | type_caster = build_type_caster(type) 17 | build_type(type, type_validator, type_caster).tap do |new_type| 18 | assign_type_validator(new_type, type_validator) 19 | end 20 | end 21 | 22 | private 23 | 24 | # @param type [SmartCore::Types::Primitive] 25 | # @return [SmartCore::Types::Primitive::NilableValidator] 26 | # 27 | # @api private 28 | # @since 0.2.0 29 | def build_type_validator(type) 30 | SmartCore::Types::Primitive::NilableValidator.new(type.validator) 31 | end 32 | 33 | # @param type [SmartCore::Types::Primitive] 34 | # @return [SmartCore::Types::Primitive::Caster] 35 | # 36 | # @api private 37 | # @since 0.1.0 38 | def build_type_caster(type) 39 | type.caster 40 | end 41 | 42 | # @param type [SmartCore::Types::Primitive] 43 | # @param type_validator [SmartCore::Types::Primitive::NilableValidator] 44 | # @return [void] 45 | # 46 | # @api private 47 | # @since 0.2.0 48 | def assign_type_validator(type, type_validator) 49 | type_validator.___assign_type___(type) 50 | end 51 | 52 | # @param type [SmartCore::Types::Primitive] 53 | # @param type_validator [SmartCore::Types::Primitive::NilableValidator] 54 | # @param type_caster [SmartCore::Types::Caster] 55 | # @return [SmartCore::Type::Primitive] 56 | # 57 | # @api private 58 | # @since 0.1.0 59 | # @version 0.3.0 60 | def build_type(type, type_validator, type_caster) 61 | Class.new(type.class).new( 62 | type.name, 63 | type.category, 64 | type_validator, 65 | type_caster, 66 | type.runtime_attributes_checker, 67 | *type.runtime_attributes 68 | ) 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/nilable_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | class SmartCore::Types::Primitive::NilableValidator 6 | require_relative 'nilable_validator/result' 7 | 8 | # @param validator [ 9 | # SmartCore::Types::Primitive::Validator, 10 | # SmartCore::Types::Primitive::SumValidator, 11 | # SmartCore::Types::Primitive::MultValidator, 12 | # SmartCore::Types::Primitive::NilableValidator 13 | # ] 14 | # @return [void] 15 | # 16 | # @api private 17 | # @since 0.2.0 18 | def initialize(validator) 19 | @type = nil 20 | @validator = validator 21 | end 22 | 23 | # @param type [SmartCore::Types::Primitive] 24 | # @return [void] 25 | # 26 | # @api private 27 | # @since 0.2.0 28 | def ___assign_type___(type) 29 | @type = type 30 | end 31 | 32 | # @return [Boolean] 33 | # 34 | # @api private 35 | # @since 0.2.0 36 | def valid?(value) 37 | (value == nil) ? true : validator.valid?(value) 38 | end 39 | 40 | # @param value [Any] 41 | # @return [SmartCore::Types::Primitive::Validator::Result] 42 | # @return [SmartCore::Types::Primitive::SumValidator::Result] 43 | # @return [SmartCore::Types::Primitive::MultValidator::Result] 44 | # @return [SmartCore::Types::Primitive::NilableValidator::Result] 45 | # 46 | # @api private 47 | # @since 0.2.0 48 | def validate(value) 49 | return Result.new(type, value) if value == nil 50 | validator.validate(value) 51 | end 52 | 53 | # @param value [Any] 54 | # @return [void] 55 | # 56 | # @raise [SmartCore::Types::TypeError] 57 | # 58 | # @api private 59 | # @since 0.2.0 60 | def validate!(value) 61 | return if valid?(value) 62 | raise(SmartCore::Types::TypeError, 'Invalid type') 63 | end 64 | 65 | private 66 | 67 | # @return [SmartCore::Types::Primitive] 68 | # 69 | # @api private 70 | # @since 0.2.0 71 | attr_reader :type 72 | 73 | # @return [ 74 | # SmartCore::Types::Primitive::Validator, 75 | # SmartCore::Types::Primitive::SumValidator, 76 | # SmartCore::Types::Primitive::MultValidator, 77 | # SmartCore::Types::Primitive::NilableValidator 78 | # ] 79 | # 80 | # @api private 81 | # @since 0.2.0 82 | attr_reader :validator 83 | end 84 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/runtime_attributes_checker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.3.0 5 | class SmartCore::Types::Primitive::RuntimeAttributesChecker 6 | # @return [Proc] 7 | # 8 | # @api private 9 | # @since 0.3.0 10 | ATTRIBUTES_IS_NOT_ALLOWED_CHECKER = proc do |attrs| 11 | attrs.empty? || raise(SmartCore::Types::RuntimeAttriburtesUnsupportedError, <<~ERROR_MESSAGE) 12 | This type has no support for runtime attributes. 13 | ERROR_MESSAGE 14 | end.freeze 15 | 16 | # @param attributes_checker [Proc] 17 | # @return [void] 18 | # 19 | # @api private 20 | # @since 0.3.0 21 | def initialize(attributes_checker) 22 | @type = nil 23 | @attributes_checker = attributes_checker || ATTRIBUTES_IS_NOT_ALLOWED_CHECKER 24 | end 25 | 26 | # @param type [SmartCore::Types::Primitive] 27 | # @return [SmartCore::Types::Primitive::RuntimeAttributesChecker] 28 | # 29 | # @api private 30 | # @since 0.3.0 31 | def ___copy_for___(type) 32 | self.class.new(attributes_checker).tap do |instance_copy| 33 | instance_copy.___assign_type___(type) 34 | end 35 | end 36 | 37 | # @param type [SmartCore::Types::Primitive] 38 | # @return [void] 39 | # 40 | # @api private 41 | # @since 0.3.0 42 | def ___assign_type___(type) 43 | @type = type 44 | end 45 | 46 | # @param runtime_attributes [Array] 47 | # @return [void] 48 | # 49 | # @raise [SmartCore::Types::IncorrectRuntimeAttributesError] 50 | # 51 | # @api private 52 | # @since 0.3.0 53 | def check!(runtime_attributes) 54 | unless !!attributes_checker.call(runtime_attributes) 55 | # TODO (0.x.0): 56 | # Full type name (with type category; and delegated to the type object). 57 | # (main problem: sum and mult types has no type name and type category) 58 | raise(SmartCore::Types::IncorrectRuntimeAttributesError, <<~ERROR_MESSAGE) 59 | Incorrect runtime attributes for #{type.name}. 60 | ERROR_MESSAGE 61 | end 62 | end 63 | 64 | private 65 | 66 | # @return [SmartCore::Types::Primitive] 67 | # 68 | # @api private 69 | # @since 0.3.0 70 | attr_reader :type 71 | 72 | # @return [Proc] 73 | # 74 | # @api private 75 | # @since 0.3.0 76 | attr_reader :attributes_checker 77 | end 78 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/invariant_control.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | # @version 0.8.0 6 | class SmartCore::Types::Primitive::InvariantControl 7 | require_relative 'invariant_control/result' 8 | require_relative 'invariant_control/single' 9 | require_relative 'invariant_control/chain' 10 | require_relative 'invariant_control/factory' 11 | 12 | class << self 13 | # @param invariant_chains [Hash>] 14 | # @param invariants [Hash] 15 | # @return [SmartCore::Types::Primitive::InvariantControl] 16 | # 17 | # @api private 18 | # @since 0.2.0 19 | def create(invariant_chains, invariants) 20 | Factory.create(invariant_chains, invariants) 21 | end 22 | end 23 | 24 | # @param invariant_chains [Array] 25 | # @param invariants [Array] 26 | # @return [void] 27 | # 28 | # @api private 29 | # @since 0.2.0 30 | def initialize(invariant_chains, invariants) 31 | @invariant_chains = invariant_chains 32 | @invariants = invariants 33 | end 34 | 35 | # @param value [Any] 36 | # @param runtime_attributes [Array] 37 | # @return [SmartCore::Types::Primitive::InvariantControl::Result] 38 | # 39 | # @api private 40 | # @since 0.2.0 41 | # @version 0.3.0 42 | def check(value, runtime_attributes) 43 | Result.new(self, value).tap do |result| 44 | invariant_chains.each do |chain| 45 | result.add_chain_result(chain.check(value, runtime_attributes)) 46 | end 47 | 48 | invariants.each do |invariant| 49 | result.add_single_result(invariant.check(value, runtime_attributes)) 50 | end 51 | end 52 | end 53 | 54 | # @param value [Any] 55 | # @param runtime_attributes [Array] 56 | # @return [Boolean] 57 | # 58 | # @api private 59 | # @since 0.8.0 60 | def simply_check(value, runtime_attributes) 61 | return false if invariant_chains.any? do |chain| 62 | chain.simply_check(value, runtime_attributes) == false 63 | end 64 | 65 | return false if invariants.any? do |invariant| 66 | invariant.simply_check(value, runtime_attributes) == false 67 | end 68 | 69 | true 70 | end 71 | 72 | private 73 | 74 | # @return [Array] 75 | # 76 | # @api private 77 | # @since 0.2.0 78 | attr_reader :invariant_chains 79 | 80 | # @return [Array] 81 | # 82 | # @api private 83 | # @since 0.2.0 84 | attr_reader :invariants 85 | end 86 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/factory/runtime_type_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.3.0 5 | module SmartCore::Types::Primitive::Factory::RuntimeTypeBuilder 6 | class << self 7 | # @param type_name [String, Symbol] 8 | # @param type_category [Class] 9 | # @param runtime_attributes [Array] 10 | # @return [SmartCore::Types::Primitive] 11 | # 12 | # @api private 13 | # @since 0.3.0 14 | def build_with_runtime(type_name, type_category, runtime_attributes) 15 | type = type_category.const_get(type_name) 16 | type.runtime_attributes_checker.check!(runtime_attributes) 17 | 18 | type.clone.tap do |type_with_custom_runtime| 19 | type_with_custom_runtime.instance_variable_set( 20 | :@runtime_attributes, runtime_attributes.freeze 21 | ) 22 | end 23 | end 24 | 25 | # @param new_instance [SmartCore::Types::Primitive] 26 | # @param cloneable_instance [SmartCore::Types::Primitive] 27 | # @return [void] 28 | # 29 | # @api private 30 | # @since 0.3.0 31 | # @version 0.7.1 32 | # rubocop:disable Style/AccessModifierDeclarations, Metrics/AbcSize, Layout/LineLength 33 | public def initialize_clone(new_instance, cloneable_instance) 34 | name_clone = cloneable_instance.instance_variable_get(:@name) 35 | category_clone = cloneable_instance.instance_variable_get(:@category) 36 | validator_clone = cloneable_instance.instance_variable_get(:@validator).___copy_for___(new_instance) 37 | caster_clone = cloneable_instance.instance_variable_get(:@caster) 38 | runtime_attributes_clone = cloneable_instance.instance_variable_get(:@runtime_attributes).clone 39 | runtime_attributes_checker_clone = cloneable_instance.instance_variable_get(:@runtime_attributes_checker).___copy_for___(new_instance) 40 | lock_clone = SmartCore::Engine::Lock.new 41 | nilable_clone = nil 42 | 43 | new_instance.instance_variable_set(:@name, name_clone) 44 | new_instance.instance_variable_set(:@category, category_clone) 45 | new_instance.instance_variable_set(:@validator, validator_clone) 46 | new_instance.instance_variable_set(:@caster, caster_clone) 47 | new_instance.instance_variable_set(:@runtime_attributes, runtime_attributes_clone) 48 | new_instance.instance_variable_set(:@runtime_attributes_checker, runtime_attributes_checker_clone) 49 | new_instance.instance_variable_set(:@lock_clone, lock_clone) 50 | new_instance.instance_variable_set(:@nilable, nilable_clone) 51 | end 52 | # rubocop:enable Style/AccessModifierDeclarations, Metrics/AbcSize, Layout/LineLength 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | # @version 0.8.0 6 | class SmartCore::Types::Primitive::Validator 7 | require_relative 'validator/result' 8 | 9 | # @return [SmartCore::Type::Primitive] 10 | # 11 | # @api private 12 | # @since 0.2.0 13 | attr_reader :type 14 | 15 | # @return [SmartCore::Types::Primitive::Checker] 16 | # 17 | # @api private 18 | # @since 0.2.0 19 | attr_reader :type_checker 20 | 21 | # @return [SmartCore::Types::Primitive::InvariantControl] 22 | # 23 | # @api private 24 | # @since 0.2.0 25 | attr_reader :invariant_control 26 | 27 | # @param type_checker [SmartCore::Types::Primitive::Checker] 28 | # @param invariant_control [SmartCore::Types::Primitive::InvariantControl] 29 | # @return [void] 30 | # 31 | # @api private 32 | # @since 0.2.0 33 | def initialize(type_checker, invariant_control) 34 | @type = nil 35 | @type_checker = type_checker 36 | @invariant_control = invariant_control 37 | end 38 | 39 | # @param type [SmartCore::Types::Primitive] 40 | # @return [SmartCore::Types::Primitive::Validator] 41 | # 42 | # @api private 43 | # @since 0.3.0 44 | def ___copy_for___(type) 45 | self.class.new(type_checker, invariant_control).tap do |instance_copy| 46 | instance_copy.___assign_type___(type) 47 | end 48 | end 49 | 50 | # @param type [SmartCore::Types::Primitive] 51 | # @return [void] 52 | # 53 | # @api private 54 | # @since 0.2.0 55 | def ___assign_type___(type) 56 | @type = type 57 | end 58 | 59 | # @param value [Any] 60 | # @return [Boolean] 61 | # 62 | # @api private 63 | # @since 0.2.0 64 | # @version 0.8.0 65 | def valid?(value) 66 | return false unless type_checker.call(value, type.runtime_attributes) # => Boolean 67 | return false unless invariant_control.simply_check(value, type.runtime_attributes) # => Boolean 68 | true 69 | end 70 | 71 | # @param value [Any] 72 | # @return [SmartCore::Types::Primitive::Validator::Result] 73 | # 74 | # @api private 75 | # @since 0.2.0 76 | # @version 0.3.0 77 | def validate(value) 78 | checker_result = type_checker.call(value, type.runtime_attributes) # => Boolean 79 | return Result.new(type, value, checker_result) unless checker_result 80 | invariant_result = invariant_control.check(value, type.runtime_attributes) 81 | invariant_errors = invariant_result.invariant_errors.map { |error| "#{type.name}.#{error}" } 82 | Result.new(type, value, checker_result, invariant_errors) 83 | end 84 | 85 | # @param value [Any] 86 | # @return [void] 87 | # 88 | # @raise [SmartCore::Types::TypeError] 89 | # 90 | # @api private 91 | # @since 0.2.0 92 | def validate!(value) 93 | return if validate(value).success? 94 | raise(SmartCore::Types::TypeError, 'Invalid type') 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/invariant_control/result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | class SmartCore::Types::Primitive::InvariantControl::Result 6 | # @return [Array] 7 | # 8 | # @api private 9 | # @since 0.2.0 10 | attr_reader :chain_results 11 | 12 | # @return [Array] 13 | # 14 | # @api private 15 | # @since 0.2.0 16 | attr_reader :single_results 17 | 18 | # @param invariant_control [SmartCore::Types::Primitive::InvariantControl] 19 | # @param checked_value [Any] 20 | # @return [void] 21 | # 22 | # @api private 23 | # @since 0.2.0 24 | def initialize(invariant_control, checked_value) 25 | @invariant_control = invariant_control 26 | @checked_value = checked_value 27 | @chain_results = [] 28 | @single_results = [] 29 | end 30 | 31 | # @return [Array] 32 | # 33 | # @api private 34 | # @since 0.2.0 35 | def invariant_errors 36 | collect_invariant_errors 37 | end 38 | 39 | # @return [Boolean] 40 | # 41 | # @api private 42 | # @since 0.2.0 43 | def success? 44 | chain_results.all(&:success?) && single_results.all?(&:success?) 45 | end 46 | 47 | # @return [Boolean] 48 | # 49 | # @api private 50 | # @since 0.2.0 51 | def failure? 52 | !success? 53 | end 54 | 55 | # @param result [SmartCore::Types::Primitive::InvariantControl::Chain::Result] 56 | # @return [void] 57 | # 58 | # @api private 59 | # @since 0.2.0 60 | def add_chain_result(result) 61 | chain_results << result 62 | end 63 | 64 | # @param result [SmartCore::Types::Primitive::InvariantControl::Single::Result] 65 | # @return [void] 66 | # 67 | # @api private 68 | # @since 0.2.0 69 | def add_single_result(result) 70 | single_results << result 71 | end 72 | 73 | private 74 | 75 | # @return [SmartCore::Types::Primitive::InvariantControl] 76 | # 77 | # @api private 78 | # @since 0.2.0 79 | attr_reader :invariant_control 80 | 81 | # @return [Any] 82 | # 83 | # @api private 84 | # @since 0.2.0 85 | attr_reader :checked_value 86 | 87 | # @return [Array] 88 | # 89 | # @api private 90 | # @since 0.2.0 91 | def collect_invariant_errors 92 | [].tap do |invariant_errors| 93 | # collect invariant errors from invariant chains 94 | chain_results.select(&:failure?).each do |chain_result| 95 | invariant_errors.concat(chain_result.error_codes) 96 | end 97 | 98 | # collect invariant errors from single invariants 99 | single_results.select(&:failure?).each do |single_result| 100 | invariant_errors.concat(single_result.error_codes) 101 | end 102 | end.tap(&:freeze) 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/sum_validator/result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | class SmartCore::Types::Primitive::SumValidator::Result 6 | # @return [Array] 7 | # 8 | # @api private 9 | # @since 0.2.0 10 | NO_INVARIANT_ERRORS = [].freeze 11 | 12 | # @return [SmartCore::Types::Primitive] 13 | # 14 | # @api public 15 | # @since 0.2.0 16 | attr_reader :type 17 | 18 | # @return concrete_validation_result [ 19 | # SmartCore::Types::Primitive::Validator::Result, 20 | # SmartCore::Types::Primitive::SumValidator::Result, 21 | # SmartCore::Types::Primitive::MultValidator::Result, 22 | # SmartCore::Types::Primitive::NilableValidator::Result 23 | # ] 24 | # 25 | # @api private 26 | # @since 0.2.0 27 | attr_reader :concrete_validation_result 28 | 29 | # @return [Array] 30 | # 31 | # @api public 32 | # @since 0.2.0 33 | attr_reader :invariant_errors 34 | alias_method :errors, :invariant_errors 35 | alias_method :error_codes, :invariant_errors 36 | 37 | # @param type [SmartCore::Types::Primitive] 38 | # @param concrete_validation_result [ 39 | # Boolean, (TEMPORARY) 40 | # SmartCore::Types::Primitive::Validator::Result, 41 | # SmartCore::Types::Primitive::SumValidator::Result, 42 | # SmartCore::Types::Primitive::MultValidator::Result, 43 | # SmartCore::Types::Primitive::NilableValidator::Result 44 | # ] 45 | # @param invariant_errors [Array] 46 | # @return [void] 47 | # 48 | # @api private 49 | # @since 0.2.0 50 | def initialize(type, concrete_validation_result, invariant_errors = NO_INVARIANT_ERRORS.dup) 51 | @type = type 52 | @concrete_validation_result = concrete_validation_result 53 | @invariant_errors = invariant_errors.dup.tap(&:freeze) 54 | end 55 | 56 | # @return [Boolean] 57 | # 58 | # @api public 59 | # @since 0.2.0 60 | def valid_invariants? 61 | # NOTE: at this moment type sum does not support invariant checking 62 | # TODO (0.x.0): 63 | # concrete_validation_result.valid_invariants? 64 | true 65 | end 66 | 67 | # @return [Boolean] 68 | # 69 | # @api public 70 | # @since 0.2.0 71 | def is_valid_check 72 | # NOTE: at this moment type sum does not support invariant checking 73 | # TODO (0.x.0): 74 | # concrete_validation_result.is_valid_check 75 | concrete_validation_result 76 | end 77 | alias_method :valid_check?, :is_valid_check 78 | 79 | # @return [Boolean] 80 | # 81 | # @api public 82 | # @since 0.2.0 83 | def success? 84 | # NOTE: at this moment type sum does not support invariant checking 85 | # TODO (0.x.0): 86 | # concrete_validation_result.success? 87 | concrete_validation_result 88 | end 89 | 90 | # @return [Boolean] 91 | # 92 | # @api public 93 | # @since 0.2.0 94 | def failure? 95 | # NOTE: at this moment type sum does not support invariant checking 96 | # TODO (0.x.0): 97 | # concrete_validation_result.failure? 98 | !success? 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | smart_types (0.8.0) 5 | smart_engine (~> 0.17) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | activesupport (7.0.5) 11 | concurrent-ruby (~> 1.0, >= 1.0.2) 12 | i18n (>= 1.6, < 2) 13 | minitest (>= 5.1) 14 | tzinfo (~> 2.0) 15 | armitage-rubocop (1.51.0.4) 16 | rubocop (= 1.51.0) 17 | rubocop-capybara (= 2.18.0) 18 | rubocop-factory_bot (= 2.23.1) 19 | rubocop-performance (= 1.18.0) 20 | rubocop-rails (= 2.19.1) 21 | rubocop-rake (= 0.6.0) 22 | rubocop-rspec (= 2.22.0) 23 | ast (2.4.2) 24 | coderay (1.1.3) 25 | concurrent-ruby (1.2.2) 26 | diff-lcs (1.5.0) 27 | docile (1.4.0) 28 | i18n (1.13.0) 29 | concurrent-ruby (~> 1.0) 30 | json (2.6.3) 31 | method_source (1.0.0) 32 | minitest (5.18.0) 33 | parallel (1.23.0) 34 | parser (3.2.2.1) 35 | ast (~> 2.4.1) 36 | pry (0.14.2) 37 | coderay (~> 1.1) 38 | method_source (~> 1.0) 39 | rack (3.0.7) 40 | rainbow (3.1.1) 41 | rake (13.0.6) 42 | regexp_parser (2.8.0) 43 | rexml (3.2.5) 44 | rspec (3.12.0) 45 | rspec-core (~> 3.12.0) 46 | rspec-expectations (~> 3.12.0) 47 | rspec-mocks (~> 3.12.0) 48 | rspec-core (3.12.2) 49 | rspec-support (~> 3.12.0) 50 | rspec-expectations (3.12.3) 51 | diff-lcs (>= 1.2.0, < 2.0) 52 | rspec-support (~> 3.12.0) 53 | rspec-mocks (3.12.5) 54 | diff-lcs (>= 1.2.0, < 2.0) 55 | rspec-support (~> 3.12.0) 56 | rspec-support (3.12.0) 57 | rubocop (1.51.0) 58 | json (~> 2.3) 59 | parallel (~> 1.10) 60 | parser (>= 3.2.0.0) 61 | rainbow (>= 2.2.2, < 4.0) 62 | regexp_parser (>= 1.8, < 3.0) 63 | rexml (>= 3.2.5, < 4.0) 64 | rubocop-ast (>= 1.28.0, < 2.0) 65 | ruby-progressbar (~> 1.7) 66 | unicode-display_width (>= 2.4.0, < 3.0) 67 | rubocop-ast (1.28.1) 68 | parser (>= 3.2.1.0) 69 | rubocop-capybara (2.18.0) 70 | rubocop (~> 1.41) 71 | rubocop-factory_bot (2.23.1) 72 | rubocop (~> 1.33) 73 | rubocop-performance (1.18.0) 74 | rubocop (>= 1.7.0, < 2.0) 75 | rubocop-ast (>= 0.4.0) 76 | rubocop-rails (2.19.1) 77 | activesupport (>= 4.2.0) 78 | rack (>= 1.1) 79 | rubocop (>= 1.33.0, < 2.0) 80 | rubocop-rake (0.6.0) 81 | rubocop (~> 1.0) 82 | rubocop-rspec (2.22.0) 83 | rubocop (~> 1.33) 84 | rubocop-capybara (~> 2.17) 85 | rubocop-factory_bot (~> 2.22) 86 | ruby-progressbar (1.13.0) 87 | simplecov (0.22.0) 88 | docile (~> 1.1) 89 | simplecov-html (~> 0.11) 90 | simplecov_json_formatter (~> 0.1) 91 | simplecov-html (0.12.3) 92 | simplecov-lcov (0.8.0) 93 | simplecov_json_formatter (0.1.4) 94 | smart_engine (0.17.0) 95 | tzinfo (2.0.6) 96 | concurrent-ruby (~> 1.0) 97 | unicode-display_width (2.4.2) 98 | 99 | PLATFORMS 100 | arm64-darwin-22 101 | 102 | DEPENDENCIES 103 | armitage-rubocop (~> 1.51) 104 | bundler (~> 2.4) 105 | pry (~> 0.14) 106 | rake (~> 13.0) 107 | rspec (~> 3.12) 108 | simplecov (~> 0.22) 109 | simplecov-lcov (~> 0.8) 110 | smart_types! 111 | 112 | BUNDLED WITH 113 | 2.4.13 114 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at iamdaiver@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [https://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: https://contributor-covenant.org 74 | [version]: https://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/sum_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.2.0 5 | # @version 0.3.0 6 | class SmartCore::Types::Primitive::SumValidator 7 | require_relative 'sum_validator/result' 8 | 9 | # @param validators [Array] 10 | # @return [void] 11 | # 12 | # @api private 13 | # @since 0.2.0 14 | def initialize(*validators) 15 | @type = nil 16 | @validators = validators 17 | end 18 | 19 | # @param type [SmartCore::Types::Primitive] 20 | # @return [SmartCore::Types::Primitive::SumValidator] 21 | # 22 | # @api private 23 | # @since 0.3.0 24 | def ___copy_for___(type) 25 | self.class.new(type_checker, invariant_control).tap do |instance_copy| 26 | instance_copy.___assign_type___(type) 27 | end 28 | end 29 | 30 | # @param type [SmartCore::Types::Primitive] 31 | # @return [void] 32 | # 33 | # @api private 34 | # @since 0.2.0 35 | def ___assign_type___(type) 36 | @type = type 37 | end 38 | 39 | # @return [Boolean] 40 | # 41 | # @api private 42 | # @since 0.2.0 43 | # @version 0.3.0 44 | def valid?(value) 45 | # NOTE: at this moment type sum does not support invariant checking 46 | # TODO (0.x.0): 47 | # validators.any? { |validator| validator.valid?(value) } 48 | validators.any? do |validator| 49 | validator.type_checker.call(value, type.runtime_attributes) 50 | end 51 | end 52 | 53 | # @param value [Any] 54 | # @return [SmartCore::Types::Primitive::SumValidator::Result] 55 | # 56 | # @api private 57 | # @since 0.2.0 58 | # @version 0.3.0 59 | def validate(value) 60 | compile_validation_result do 61 | SmartCore::Engine::Atom.new.tap do |result| 62 | validators.each do |validator| 63 | # NOTE: at this moment type sum does not support invariant checking 64 | # TODO (0.x.0): 65 | # result.swap { validator.validate(value) } 66 | # break if result.value.success? 67 | result.swap { validator.type_checker.call(value, type.runtime_attributes) } 68 | break if result.value # => boolean 69 | end 70 | end 71 | end 72 | end 73 | 74 | # @param value [Any] 75 | # @return [void] 76 | # 77 | # @raise [SmartCore::Types::TypeError] 78 | # 79 | # @api private 80 | # @since 0.2.0 81 | def validate!(value) 82 | return if valid?(value) 83 | raise(SmartCore::Types::TypeError, 'Invalid type') 84 | end 85 | 86 | private 87 | 88 | # @return [SmartCore::Types::Primitive] 89 | # 90 | # @api private 91 | # @since 0.2.0 92 | attr_reader :type 93 | 94 | # @return [Array] 95 | # 96 | # @api private 97 | # @since 0.2.0 98 | attr_reader :validators 99 | 100 | # @param validation [Block] 101 | # @yieldparam [void] 102 | # @yieldreturn [SmartCore::Engine::Atom] 103 | # @return [SmartCore::Types::Primitive::SumValidator::Result] 104 | # 105 | # @api private 106 | # @since 0.2.0 107 | def compile_validation_result(&validation) 108 | # NOTE: at this moment type sum does not support invariant checking 109 | # TODO (0.x.0): 110 | # @yieldreturn [SmartCore::Types::Primitive::Validator::Result] 111 | # => and: 112 | # SmartCore::Types::Primitive::SumValidator::Result.new( 113 | # type, final_result.value, final_result.value.invariant_errors 114 | # ) 115 | SmartCore::Types::Primitive::SumValidator::Result.new(type, yield.value) 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /spec/types/variadic/enum_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Variadic::Enum' do 4 | describe 'runtime-based behavior' do 5 | specify 'requires enum values (runtime attributes)' do 6 | expect do 7 | SmartCore::Types::Variadic::Enum() 8 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 9 | end 10 | end 11 | 12 | describe 'logic' do 13 | let(:type) { SmartCore::Types::Variadic::Enum(10, '20', false) } 14 | 15 | specify 'type-checking' do 16 | expect(type.valid?(10)).to eq(true) 17 | expect(type.valid?('20')).to eq(true) 18 | expect(type.valid?(false)).to eq(true) 19 | 20 | expect(type.valid?(nil)).to eq(false) 21 | expect(type.valid?('123')).to eq(false) 22 | expect(type.valid?(true)).to eq(false) 23 | expect(type.valid?(:test)).to eq(false) 24 | expect(type.valid?(18_483.991238)).to eq(false) 25 | expect(type.valid?(99_999_999_999)).to eq(false) 26 | expect(type.valid?({})).to eq(false) 27 | expect(type.valid?([])).to eq(false) 28 | expect(type.valid?(Object.new)).to eq(false) 29 | expect(type.valid?(BasicObject.new)).to eq(false) 30 | end 31 | 32 | specify 'type-validation' do 33 | expect { type.validate!(10) }.not_to raise_error 34 | expect { type.validate!('20') }.not_to raise_error 35 | expect { type.validate!(false) }.not_to raise_error 36 | 37 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 38 | expect { type.validate!('123') }.to raise_error(SmartCore::Types::TypeError) 39 | expect { type.validate!(true) }.to raise_error(SmartCore::Types::TypeError) 40 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 41 | expect { type.validate!(18_483.991238) }.to raise_error(SmartCore::Types::TypeError) 42 | expect { type.validate!(99_999_999_999) }.to raise_error(SmartCore::Types::TypeError) 43 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 44 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 45 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 46 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 47 | end 48 | 49 | context 'nilable type' do 50 | let(:type) { SmartCore::Types::Variadic::Enum(10, '20', false).nilable } 51 | 52 | specify 'type-checking' do 53 | expect(type.valid?(10)).to eq(true) 54 | expect(type.valid?('20')).to eq(true) 55 | expect(type.valid?(false)).to eq(true) 56 | expect(type.valid?(nil)).to eq(true) 57 | 58 | expect(type.valid?('123')).to eq(false) 59 | expect(type.valid?(true)).to eq(false) 60 | expect(type.valid?(:test)).to eq(false) 61 | expect(type.valid?(18_483.991238)).to eq(false) 62 | expect(type.valid?(99_999_999_999)).to eq(false) 63 | expect(type.valid?({})).to eq(false) 64 | expect(type.valid?([])).to eq(false) 65 | expect(type.valid?(Object.new)).to eq(false) 66 | expect(type.valid?(BasicObject.new)).to eq(false) 67 | end 68 | 69 | specify 'type-validation' do 70 | expect { type.validate!(10) }.not_to raise_error 71 | expect { type.validate!('20') }.not_to raise_error 72 | expect { type.validate!(false) }.not_to raise_error 73 | expect { type.validate!(nil) }.not_to raise_error 74 | 75 | expect { type.validate!('123') }.to raise_error(SmartCore::Types::TypeError) 76 | expect { type.validate!(true) }.to raise_error(SmartCore::Types::TypeError) 77 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 78 | expect { type.validate!(18_483.991238) }.to raise_error(SmartCore::Types::TypeError) 79 | expect { type.validate!(99_999_999_999) }.to raise_error(SmartCore::Types::TypeError) 80 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 81 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 82 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 83 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/types/value/array_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Array' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect(type.cast(123)).to eq([123]) 7 | expect(type.cast('test')).to eq(['test']) 8 | expect(type.cast(:test)).to eq([:test]) 9 | expect(type.cast([])).to eq([]) 10 | expect(type.cast([123, '456', :test])).to eq([123, '456', :test]) 11 | expect(type.cast({})).to eq([]) 12 | expect(type.cast({ a: 1, b: '2', 'c' => :test })).to eq([[:a, 1], [:b, '2'], ['c', :test]]) 13 | expect(type.cast(nil)).to eq([]) 14 | 15 | as_array_1 = Class.new { def to_a; [123]; end }.new 16 | as_array_2 = Class.new { def to_ary; ['456']; end }.new 17 | non_array_1 = Class.new { def to_a; :test; end }.new 18 | non_array_2 = Class.new { def to_ary; 'test'; end }.new 19 | basic_object = BasicObject.new 20 | 21 | expect(type.cast(as_array_1)).to eq([123]) 22 | expect(type.cast(as_array_2)).to eq(['456']) 23 | expect(type.cast(non_array_1)).to eq([non_array_1]) 24 | expect(type.cast(non_array_2)).to eq([non_array_2]) 25 | expect(type.cast(basic_object)).to eq([basic_object]) 26 | end 27 | end 28 | 29 | shared_examples 'type-checking / type-validation (non-nilable)' do 30 | specify 'type-checking' do 31 | expect(type.valid?([])).to eq(true) 32 | expect(type.valid?([123, '456', :test])).to eq(true) 33 | expect(type.valid?(123)).to eq(false) 34 | expect(type.valid?(Object.new)).to eq(false) 35 | expect(type.valid?(BasicObject.new)).to eq(false) 36 | expect(type.valid?(nil)).to eq(false) 37 | end 38 | 39 | specify 'type-validation' do 40 | expect { type.validate!([]) }.not_to raise_error 41 | expect { type.validate!([123, '456', :test]) }.not_to raise_error 42 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 43 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 44 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 45 | end 46 | end 47 | 48 | shared_examples 'type-checking / type-validation (nilable)' do 49 | specify 'type-checking' do 50 | expect(type.valid?([])).to eq(true) 51 | expect(type.valid?([123, '456', :test])).to eq(true) 52 | expect(type.valid?(123)).to eq(false) 53 | expect(type.valid?(Object.new)).to eq(false) 54 | expect(type.valid?(BasicObject.new)).to eq(false) 55 | expect(type.valid?(nil)).to eq(true) # NOTE: nil 56 | end 57 | 58 | specify 'type-validation' do 59 | expect { type.validate!([]) }.not_to raise_error 60 | expect { type.validate!([123, '456', :test]) }.not_to raise_error 61 | expect { type.validate!(nil) }.not_to raise_error 62 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 63 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 64 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 65 | end 66 | end 67 | 68 | context 'runtime-based non-nilable type' do 69 | let(:type) { SmartCore::Types::Value::Array() } 70 | 71 | include_examples 'type casting' 72 | include_examples 'type-checking / type-validation (non-nilable)' 73 | end 74 | 75 | context 'non-nilable type' do 76 | let(:type) { SmartCore::Types::Value::Array } 77 | 78 | include_examples 'type casting' 79 | include_examples 'type-checking / type-validation (non-nilable)' 80 | end 81 | 82 | context 'runtime-based nilable type' do 83 | let(:type) { SmartCore::Types::Value::Array().nilable } 84 | 85 | include_examples 'type casting' 86 | include_examples 'type-checking / type-validation (nilable)' 87 | end 88 | 89 | context 'nilable type' do 90 | let(:type) { SmartCore::Types::Value::Array.nilable } 91 | 92 | include_examples 'type casting' 93 | include_examples 'type-checking / type-validation (nilable)' 94 | end 95 | 96 | specify 'has no support for runtime attributes' do 97 | expect { SmartCore::Types::Value::Array(1) }.to raise_error( 98 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 99 | ) 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/sum_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | module SmartCore::Types::Primitive::SumFactory 7 | require_relative 'sum_factory/definition_context' 8 | 9 | class << self 10 | # @param types [Array] 11 | # @param type_definition [NilClass, Proc] 12 | # @return [SmartCore::Types::Primitive] 13 | # 14 | # @api private 15 | # @since 0.1.0 16 | # @version 0.3.0 17 | def create_type(types, type_definition) 18 | type_definitions = build_type_definitions(type_definition) 19 | type_runtime_attributes_checker = build_type_runtime_attributes_checker(type_definitions) 20 | type_validator = build_type_validator(types, type_definitions) 21 | type_caster = build_type_caster(types, type_definitions) 22 | build_type( 23 | type_validator, 24 | type_caster, 25 | type_runtime_attributes_checker 26 | ).tap do |type| 27 | assign_type_validator(type, type_validator) 28 | assign_type_runtime_attributes_checker(type, type_runtime_attributes_checker) 29 | end 30 | end 31 | 32 | private 33 | 34 | # @param type_definition [NilClass, Proc] 35 | # @return [SmartCore::Types::Primitive::SumFactory::DefinitionContext] 36 | # 37 | # @pai private 38 | # @since 0.1.0 39 | def build_type_definitions(type_definition) 40 | SmartCore::Types::Primitive::SumFactory::DefinitionContext.new.tap do |context| 41 | context.instance_eval(&type_definition) if type_definition 42 | end 43 | end 44 | 45 | # @param type_definitions [SmartCore::Types::Primitive::SumFactory::DefinitionContext] 46 | # @return [SmartCore::Types::Primitive::RuntimeAttributesChecker] 47 | # 48 | # @api private 49 | # @since 0.3.0 50 | def build_type_runtime_attributes_checker(type_definitions) 51 | SmartCore::Types::Primitive::RuntimeAttributesChecker.new( 52 | type_definitions.type_runtime_attributes_checker 53 | ) 54 | end 55 | 56 | # @param types [Array] 57 | # @param type_definitions [SmartCore::Types::Primitive::SumFactory::DefinitionContext] 58 | # @return [SmartCore::Types::Primitive::SumValidator] 59 | # 60 | # @api private 61 | # @since 0.2.0 62 | def build_type_validator(types, type_definitions) 63 | SmartCore::Types::Primitive::SumValidator.new(*types.map(&:validator)) 64 | end 65 | 66 | # @param types [Array] 67 | # @param type_definition [SmartCore::Types::Primitive::SumFactory::DefinitionContext] 68 | # @return [SmartCore::Types::Primitive::Caster] 69 | # 70 | # @api private 71 | # @since 0.1.0 72 | def build_type_caster(types, type_definitions) 73 | if type_definitions.type_caster == nil 74 | SmartCore::Types::Primitive::UndefinedCaster.new 75 | else 76 | SmartCore::Types::Primitive::Caster.new(type_definitions.type_caster) 77 | end 78 | end 79 | 80 | # @param type [SmartCore::Types::Primitive] 81 | # @param type_validator [SmartCore::Types::Primitive::SumValidator] 82 | # @return [void] 83 | # 84 | # @api private 85 | # @since 0.2.0 86 | def assign_type_validator(type, type_validator) 87 | type_validator.___assign_type___(type) 88 | end 89 | 90 | # @param type [SmartCore::Types::Primitive] 91 | # @param type_runtime_attributes_checker [SmartCore::Types::Primitive::RuntimeAttributesChecker] 92 | # @return [void] 93 | # 94 | # @api private 95 | # @since 0.3.0 96 | def assign_type_runtime_attributes_checker(type, type_runtime_attributes_checker) 97 | type_runtime_attributes_checker.___assign_type___(type) 98 | end 99 | 100 | # @param type_validator [SmartCore::Types::Primitive::SumValidator)] 101 | # @param type_caster [SmartCore::Types::Primitive::Caster] 102 | # @param type_runtime_attributes_checker [SmartCore::Types::Primitive::RuntimeAttributesChecker] 103 | # @return [SmartCore::Types::Primitive] 104 | # 105 | # @api private 106 | # @since 0.1.0 107 | # @version 0.3.0 108 | def build_type(type_validator, type_caster, type_runtime_attributes_checker) 109 | SmartCore::Types::Primitive.new( 110 | nil, 111 | nil, 112 | type_validator, 113 | type_caster, 114 | type_runtime_attributes_checker 115 | ) 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/mult_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | module SmartCore::Types::Primitive::MultFactory 7 | require_relative 'mult_factory/definition_context' 8 | 9 | class << self 10 | # @param types [Array] 11 | # @param type_definition [NilClass, Proc] 12 | # @return [SmartCore::Types::Primitive] 13 | # 14 | # @api private 15 | # @since 0.1.0 16 | # @version 0.3.0 17 | def create_type(types, type_definition) 18 | type_definitions = build_type_definitions(type_definition) 19 | type_runtime_attributes_checker = build_type_runtime_attributes_checker(type_definitions) 20 | type_validator = build_type_validator(types, type_definitions) 21 | type_caster = build_type_caster(types, type_definitions) 22 | build_type( 23 | type_validator, 24 | type_caster, 25 | type_runtime_attributes_checker 26 | ).tap do |type| 27 | assign_type_validator(type, type_validator) 28 | assign_type_runtime_attributes_checker(type, type_runtime_attributes_checker) 29 | end 30 | end 31 | 32 | private 33 | 34 | # @param type_definition [NilClass, Proc] 35 | # @return [SmartCore::Types::Primitive::MultFactory::DefinitionContext] 36 | # 37 | # @pai private 38 | # @since 0.1.0 39 | def build_type_definitions(type_definition) 40 | SmartCore::Types::Primitive::MultFactory::DefinitionContext.new.tap do |context| 41 | context.instance_eval(&type_definition) if type_definition 42 | end 43 | end 44 | 45 | # @param type_definitions [SmartCore::Types::Primitive::MultFactory::DefinitionContext] 46 | # @return [SmartCore::Types::Primitive::RuntimeAttributesChecker] 47 | # 48 | # @api private 49 | # @since 0.3.0 50 | def build_type_runtime_attributes_checker(type_definitions) 51 | SmartCore::Types::Primitive::RuntimeAttributesChecker.new( 52 | type_definitions.type_runtime_attributes_checker 53 | ) 54 | end 55 | 56 | # @param types [Array] 57 | # @param type_definitions [SmartCore::Types::Primitive::MultFactory::DefinitionContext] 58 | # @return [SmartCore::Types::Primitive::MultValidator] 59 | # 60 | # @api private 61 | # @since 0.2.0 62 | def build_type_validator(types, type_definitions) 63 | SmartCore::Types::Primitive::MultValidator.new(*types.map(&:validator)) 64 | end 65 | 66 | # @param types [Array] 67 | # @param type_definition [SmartCore::Types::Primitive::MultFactory::DefinitionContext] 68 | # @return [SmartCore::Types::Primitive::Caster] 69 | # 70 | # @api private 71 | # @since 0.1.0 72 | def build_type_caster(types, type_definitions) 73 | if type_definitions.type_caster == nil 74 | SmartCore::Types::Primitive::UndefinedCaster.new 75 | else 76 | SmartCore::Types::Primitive::Caster.new(type_definitions.type_caster) 77 | end 78 | end 79 | 80 | # @param type [SmartCore::Types::Primitive] 81 | # @param type_validator [SmartCore::Types::Primitive::MultValidator] 82 | # @return [void] 83 | # 84 | # @api private 85 | # @since 0.2.0 86 | def assign_type_validator(type, type_validator) 87 | type_validator.___assign_type___(type) 88 | end 89 | 90 | # @param type [SmartCore::Types::Primitive] 91 | # @param type_runtime_attributes_checker [SmartCore::Types::Primitive::RuntimeAttributesChecker] 92 | # @return [void] 93 | # 94 | # @api private 95 | # @since 0.3.0 96 | def assign_type_runtime_attributes_checker(type, type_runtime_attributes_checker) 97 | type_runtime_attributes_checker.___assign_type___(type) 98 | end 99 | 100 | # @param type_validator [SmartCore::Types::Primitive::MultValidator] 101 | # @param type_caster [SmartCore::Types::Primitive::Caster] 102 | # @param type_runtime_attributes_checker [SmartCore::Types::Primitive::RuntimeAttributesChecker] 103 | # @return [SmartCore::Types::Primitive] 104 | # 105 | # @api private 106 | # @since 0.1.0 107 | # @version 0.3.0 108 | def build_type(type_validator, type_caster, type_runtime_attributes_checker) 109 | SmartCore::Types::Primitive.new( 110 | nil, 111 | nil, 112 | type_validator, 113 | type_caster, 114 | type_runtime_attributes_checker 115 | ) 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | # [Unreleased] 5 | - New types of `SmartCore::Types::Value` category: 6 | - `SmartCore::Types::Value::UnboundMethod`; 7 | - `SmartCore::Types::Value::Range`; 8 | - `SmartCore::Types::Value::Rational`; 9 | 10 | # [0.8.0] - 2022-11-25 11 | ### Added 12 | - New types of `SmartCore::Types::Value` category: 13 | - `SmartCore::Types::Value::Method` 14 | - Support for *Ruby@3.1*; 15 | ### Changed 16 | - `SmartCore::Types::Primitive#valid?` now has no dependency on invariant control result object interface 17 | (reduced object allocation count during validation: move from OOP-style to Procedure-style inside boolean methods); 18 | - Updated development dependencies (see `Gemfile.lock` diffs); 19 | - Support for *Ruby@2.4*, *Ruby@2.5* and *Ruby@2.6* has ended; 20 | 21 | # [0.7.1] - 2022-08-31 22 | ### Fixed 23 | - **TruffelRuby**: fixed `NoMethodError: private method 'initialize_clone'` failing on type object duplication and cloning (`#dup` and `#clone`). 24 | `TruffleRuby` implementation makes `initialize_clone` method private by default even if your manually defined method is implicitly public 25 | (see `SmartCore::Types::Primitive::Factory::RuntimeTypeBuilder.initialize_clone` and `SmartCore::Types::Primitive#initialize_copy`). 26 | To fix this we should explicitly define our method as a public method (`public def initialize_clone`). 27 | 28 | # [0.7.0] - 2021-11-22 29 | ### Added 30 | - Added Github Actions CI; 31 | - Use `ArgumentError` instead of `DateError` in related type-casters; 32 | - Fix typos in documentation; 33 | 34 | # [0.6.0] - 2021-04-29 35 | ### Added 36 | - New type of `SmartCode::Types::Variadic` category: 37 | - `SmartCore::Types::Variadic::ArrayOf` (`Array` with element types validation) 38 | 39 | # [0.5.0] - 2021-01-28 40 | ### Added 41 | - New types of `SmartCore::Types::Variadic` category: 42 | - `SmartCore::Types::Variadic::Enum` (a simple enumeration on plain values); 43 | 44 | # [0.4.0] - 2021-01-18 45 | ### Added 46 | - Support for *Ruby 3*; 47 | 48 | ## [0.3.0] - 2020-12-22 49 | ### Added 50 | - Extended **Type Definition API**: support for **runtime attributes**: 51 | - Type checkers, type casters and type invariants now receives runtime attributes (you can omit these); 52 | - Type definition extended with `runtime_attribute_checker`-checker definition (runtime attributes validator); 53 | - Types with incorrect runtime attributes will raise `SmartCore::Types::IncorrectRuntimeAttributesError` exception; 54 | - Types which has no support for runtime attributes will raise `SmartCore::Types::RuntimeAttributesUnsupportedError` excpetion; 55 | - All types by default has a method alias (`()`) which does not allow runtime attributes (for example: `SmartCore::Types::Value::String` has 56 | a runtime-based alias `SmartCore::Types::Value::String()` which does not accept any attribute 57 | (`SmartCore::Types::Value::String('test')` will raise `SmartCore::Types::RuntimeAttributesUnsupportedError` respectively)); 58 | - Extended Internal **Type Development API**: 59 | - all types has a reference to it's type category; 60 | - Brand new `SmartCore::Types::Protocol` type category and new types: 61 | - `SmartCore::Types::Protocol::InstanceOf` (runtime-based type); 62 | - Brand new `SmartCore::Types::Variadic` type category and new types: 63 | - `SmartCore::Types::Variadic::Tuple` (runtime-based type); 64 | - New types of `SmartCore::Types::Value` category: 65 | - `SmartCore::Types::Value::Set` (based on `Set` type with a support for type casting); 66 | - Support for BasicObject values inside type checkers that can not be checked correctly via `#is_a?/#kind_of?` before; 67 | 68 | ### Changed 69 | - Updated development dependencies; 70 | - Drop `Travis CI` (TODO: migrate to `Github Actions`); 71 | - **Ruby@2.4** is no longer supported; 72 | 73 | ## [0.2.0] - 2020-11-21 74 | ### Added 75 | - Brand new **Type invariant API**: 76 | - globally refactored validation logic (with backward compatibility for `#valid?(value)` method); 77 | - new type definition DSL: `.invariant(name)` and `.invariant_chain(name)`; 78 | - chained invariants will be invoked according to the definition order (second invocation 79 | depends on previous successful invariant check); 80 | - new validation API: `validate(value)` (with `#errors` support based on invariant names); 81 | - at this moment Invariant API is supported only by primitive types (type sum and type multiplication support coming soon); 82 | 83 | ### Changed 84 | 85 | - Updated `smart_engine` dependency (to `~> 0.7`) (need `SmartCore::Engine::Atom`); 86 | 87 | ## [0.1.0] - 2020-05-05 88 | - Release :) 89 | -------------------------------------------------------------------------------- /spec/types/value/module_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Module' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect { type.cast(nil) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 7 | expect { type.cast(Class) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 8 | expect { type.cast(Module) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 9 | end 10 | end 11 | 12 | shared_examples 'type-checking / type-validation (non-nilable)' do 13 | specify 'type-checking' do 14 | expect(type.valid?(Module)).to eq(true) 15 | expect(type.valid?(Module.new)).to eq(true) 16 | expect(type.valid?(Class)).to eq(true) 17 | expect(type.valid?(Class.new)).to eq(true) 18 | 19 | expect(type.valid?(nil)).to eq(false) 20 | expect(type.valid?(Object.new)).to eq(false) 21 | expect(type.valid?(BasicObject.new)).to eq(false) 22 | expect(type.valid?(123)).to eq(false) 23 | expect(type.valid?('test')).to eq(false) 24 | expect(type.valid?(:test)).to eq(false) 25 | end 26 | 27 | specify 'type-validation' do 28 | expect { type.validate!(Module) }.not_to raise_error 29 | expect { type.validate!(Module.new) }.not_to raise_error 30 | expect { type.validate!(Class) }.not_to raise_error 31 | expect { type.validate!(Class.new) }.not_to raise_error 32 | 33 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 34 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 35 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 36 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 37 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 38 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 39 | end 40 | end 41 | 42 | shared_examples 'type-checking / type-validation (nilable)' do 43 | specify 'type-checking' do 44 | expect(type.valid?(Module)).to eq(true) 45 | expect(type.valid?(Module.new)).to eq(true) 46 | expect(type.valid?(Class)).to eq(true) 47 | expect(type.valid?(Class.new)).to eq(true) 48 | expect(type.valid?(nil)).to eq(true) 49 | 50 | expect(type.valid?(Object.new)).to eq(false) 51 | expect(type.valid?(BasicObject.new)).to eq(false) 52 | expect(type.valid?(123)).to eq(false) 53 | expect(type.valid?('test')).to eq(false) 54 | expect(type.valid?(:test)).to eq(false) 55 | end 56 | 57 | specify 'type-validation' do 58 | expect { type.validate!(Module) }.not_to raise_error 59 | expect { type.validate!(Module.new) }.not_to raise_error 60 | expect { type.validate!(Class) }.not_to raise_error 61 | expect { type.validate!(Class.new) }.not_to raise_error 62 | expect { type.validate!(nil) }.not_to raise_error 63 | 64 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 65 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 66 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 67 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 68 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 69 | end 70 | end 71 | 72 | context 'non-nilable type' do 73 | let(:type) { SmartCore::Types::Value::Module } 74 | 75 | include_examples 'type casting' 76 | include_examples 'type-checking / type-validation (non-nilable)' 77 | end 78 | 79 | context 'runtime-based non-nilable type' do 80 | let(:type) { SmartCore::Types::Value::Module() } 81 | 82 | include_examples 'type casting' 83 | include_examples 'type-checking / type-validation (non-nilable)' 84 | end 85 | 86 | context 'nilable type' do 87 | let(:type) { SmartCore::Types::Value::Module.nilable } 88 | 89 | include_examples 'type casting' 90 | include_examples 'type-checking / type-validation (nilable)' 91 | end 92 | 93 | context 'runtime-based nilable type' do 94 | let(:type) { SmartCore::Types::Value::Module().nilable } 95 | 96 | include_examples 'type casting' 97 | include_examples 'type-checking / type-validation (nilable)' 98 | end 99 | 100 | specify 'has no support for runtime attributes' do 101 | expect { SmartCore::Types::Value::Module(::Module.new) }.to raise_error( 102 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 103 | ) 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/types/value/string_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::String' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect(type.cast(:test)).to eq('test') 7 | expect(type.cast('test')).to eq('test') 8 | expect(type.cast(nil)).to eq('') 9 | expect(type.cast({})).to eq('{}') 10 | expect(type.cast({ a: 1, 'b' => :test })).to eq('{:a=>1, "b"=>:test}') 11 | expect(type.cast([])).to eq('[]') 12 | expect(type.cast([1, 'test', :test])).to eq('[1, "test", :test]') 13 | expect(type.cast(Object.new)).to match(/\A\#\z/) 14 | 15 | castable = Class.new { def to_s; 'test'; end; }.new 16 | expect(type.cast(castable)).to eq('test') 17 | 18 | non_castable_1 = Class.new { undef_method :to_s; }.new 19 | non_castable_2 = Class.new { def to_s; 2; end }.new 20 | non_castable_3 = BasicObject.new 21 | expect { type.cast(non_castable_1) }.to raise_error(SmartCore::Types::TypeCastingError) 22 | expect { type.cast(non_castable_2) }.to raise_error(SmartCore::Types::TypeCastingError) 23 | expect { type.cast(non_castable_3) }.to raise_error(SmartCore::Types::TypeCastingError) 24 | end 25 | end 26 | 27 | shared_examples 'type-checking / type-validation (non-nilable)' do 28 | specify 'type-checking' do 29 | expect(type.valid?('test')).to eq(true) 30 | 31 | expect(type.valid?(nil)).to eq(false) 32 | expect(type.valid?(Object.new)).to eq(false) 33 | expect(type.valid?(BasicObject.new)).to eq(false) 34 | expect(type.valid?({})).to eq(false) 35 | expect(type.valid?([])).to eq(false) 36 | expect(type.valid?(:test)).to eq(false) 37 | end 38 | 39 | specify 'type-validation' do 40 | expect { type.valid?('test') }.not_to raise_error 41 | 42 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 43 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 44 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 45 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 46 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 47 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 48 | end 49 | end 50 | 51 | shared_examples 'type-checking / type-validation (nilable)' do 52 | specify 'type-checking' do 53 | expect(type.valid?('test')).to eq(true) 54 | expect(type.valid?(nil)).to eq(true) 55 | 56 | expect(type.valid?(Object.new)).to eq(false) 57 | expect(type.valid?(BasicObject.new)).to eq(false) 58 | expect(type.valid?({})).to eq(false) 59 | expect(type.valid?([])).to eq(false) 60 | expect(type.valid?(:test)).to eq(false) 61 | end 62 | 63 | specify 'type-validation' do 64 | expect { type.valid?('test') }.not_to raise_error 65 | expect { type.valid?(nil) }.not_to raise_error 66 | 67 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 68 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 69 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 70 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 71 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 72 | end 73 | end 74 | 75 | context 'non-nilable type' do 76 | let(:type) { SmartCore::Types::Value::String } 77 | 78 | include_examples 'type casting' 79 | include_examples 'type-checking / type-validation (non-nilable)' 80 | end 81 | 82 | context 'runtime-based non-nilable type' do 83 | let(:type) { SmartCore::Types::Value::String() } 84 | 85 | include_examples 'type casting' 86 | include_examples 'type-checking / type-validation (non-nilable)' 87 | end 88 | 89 | context 'nilable type' do 90 | let(:type) { SmartCore::Types::Value::String.nilable } 91 | 92 | include_examples 'type casting' 93 | include_examples 'type-checking / type-validation (nilable)' 94 | end 95 | 96 | context 'runtime-based nilable type' do 97 | let(:type) { SmartCore::Types::Value::String().nilable } 98 | 99 | include_examples 'type casting' 100 | include_examples 'type-checking / type-validation (nilable)' 101 | end 102 | 103 | specify 'has no support for runtime attributes' do 104 | expect { SmartCore::Types::Value::String('test') }.to raise_error( 105 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 106 | ) 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /spec/types/value/class_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Class' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect { type.cast(nil) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 7 | expect { type.cast(Class) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 8 | expect { type.cast(Module) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 9 | end 10 | end 11 | 12 | shared_examples 'type-checking / type-validation (non-nilable)' do 13 | specify 'type-checking' do 14 | expect(type.valid?(Class)).to eq(true) 15 | expect(type.valid?(Class.new)).to eq(true) 16 | expect(type.valid?(Module)).to eq(true) 17 | 18 | expect(type.valid?(nil)).to eq(false) 19 | expect(type.valid?(Module.new)).to eq(false) 20 | expect(type.valid?(Object.new)).to eq(false) 21 | expect(type.valid?(BasicObject.new)).to eq(false) 22 | expect(type.valid?(123)).to eq(false) 23 | expect(type.valid?('test')).to eq(false) 24 | expect(type.valid?(:test)).to eq(false) 25 | end 26 | 27 | specify 'type-validation' do 28 | expect { type.validate!(Class) }.not_to raise_error 29 | expect { type.validate!(Class.new) }.not_to raise_error 30 | expect { type.validate!(Module) }.not_to raise_error 31 | 32 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 33 | expect { type.validate!(Module.new) }.to raise_error(SmartCore::Types::TypeError) 34 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 35 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 36 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 37 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 38 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 39 | end 40 | end 41 | 42 | shared_examples 'type-checking / type-validation (nilable)' do 43 | specify 'type-checking' do 44 | expect(type.valid?(Class)).to eq(true) 45 | expect(type.valid?(Class.new)).to eq(true) 46 | expect(type.valid?(Module)).to eq(true) 47 | expect(type.valid?(nil)).to eq(true) 48 | 49 | expect(type.valid?(Module.new)).to eq(false) 50 | expect(type.valid?(Object.new)).to eq(false) 51 | expect(type.valid?(BasicObject.new)).to eq(false) 52 | expect(type.valid?(123)).to eq(false) 53 | expect(type.valid?('test')).to eq(false) 54 | expect(type.valid?(:test)).to eq(false) 55 | end 56 | 57 | specify 'type-validation' do 58 | expect { type.validate!(Class) }.not_to raise_error 59 | expect { type.validate!(Class.new) }.not_to raise_error 60 | expect { type.validate!(Module) }.not_to raise_error 61 | expect { type.validate!(nil) }.not_to raise_error 62 | 63 | expect { type.validate!(Module.new) }.to raise_error(SmartCore::Types::TypeError) 64 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 65 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 66 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 67 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 68 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 69 | end 70 | end 71 | 72 | context 'non-nilable type' do 73 | let(:type) { SmartCore::Types::Value::Class } 74 | 75 | include_examples 'type casting' 76 | include_examples 'type-checking / type-validation (non-nilable)' 77 | end 78 | 79 | context 'runtime-based non-nilable type' do 80 | let(:type) { SmartCore::Types::Value::Class() } 81 | 82 | include_examples 'type casting' 83 | include_examples 'type-checking / type-validation (non-nilable)' 84 | end 85 | 86 | context 'nilable type' do 87 | let(:type) { SmartCore::Types::Value::Class.nilable } 88 | 89 | include_examples 'type casting' 90 | include_examples 'type-checking / type-validation (nilable)' 91 | end 92 | 93 | context 'runtime-based nilable type' do 94 | let(:type) { SmartCore::Types::Value::Class().nilable } 95 | 96 | include_examples 'type casting' 97 | include_examples 'type-checking / type-validation (nilable)' 98 | end 99 | 100 | specify 'has no support for runtime attributes' do 101 | expect { SmartCore::Types::Value::Class(::Class) }.to raise_error( 102 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 103 | ) 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/types/value/boolean_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Boolean' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect(type.cast('test')).to eq(true) 7 | expect(type.cast(:test)).to eq(true) 8 | expect(type.cast([])).to eq(true) 9 | expect(type.cast({})).to eq(true) 10 | expect(type.cast(123.456)).to eq(true) 11 | expect(type.cast(Object.new)).to eq(true) 12 | expect(type.cast(BasicObject.new)).to eq(true) 13 | expect(type.cast(Class.new)).to eq(true) 14 | expect(type.cast(Module.new)).to eq(true) 15 | 16 | expect(type.cast(nil)).to eq(false) 17 | expect(type.cast(false)).to eq(false) 18 | end 19 | end 20 | 21 | shared_examples 'type-checking / type-validation (non-nilable)' do 22 | specify 'type-checking' do 23 | expect(type.valid?(true)).to eq(true) 24 | expect(type.valid?(false)).to eq(true) 25 | 26 | expect(type.valid?(Object.new)).to eq(false) 27 | expect(type.valid?(BasicObject.new)).to eq(false) 28 | expect(type.valid?(123)).to eq(false) 29 | expect(type.valid?(:true)).to eq(false) 30 | expect(type.valid?(:false)).to eq(false) 31 | expect(type.valid?([])).to eq(false) 32 | expect(type.valid?({})).to eq(false) 33 | end 34 | 35 | specify 'type-validation' do 36 | expect { type.validate!(true) }.not_to raise_error 37 | expect { type.validate!(false) }.not_to raise_error 38 | 39 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 40 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 41 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 42 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 43 | expect { type.validate!(:true) }.to raise_error(SmartCore::Types::TypeError) 44 | expect { type.validate!(:false) }.to raise_error(SmartCore::Types::TypeError) 45 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 46 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 47 | end 48 | end 49 | 50 | shared_examples 'type-checking / type-validation (nilable)' do 51 | specify 'type-checking' do 52 | expect(type.valid?(true)).to eq(true) 53 | expect(type.valid?(false)).to eq(true) 54 | 55 | expect(type.valid?(Object.new)).to eq(false) 56 | expect(type.valid?(BasicObject.new)).to eq(false) 57 | expect(type.valid?(123)).to eq(false) 58 | expect(type.valid?(:true)).to eq(false) 59 | expect(type.valid?(:false)).to eq(false) 60 | expect(type.valid?([])).to eq(false) 61 | expect(type.valid?({})).to eq(false) 62 | end 63 | 64 | specify 'type-validation' do 65 | expect { type.validate!(true) }.not_to raise_error 66 | expect { type.validate!(false) }.not_to raise_error 67 | expect { type.validate!(nil) }.not_to raise_error 68 | 69 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 70 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 71 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 72 | expect { type.validate!(:true) }.to raise_error(SmartCore::Types::TypeError) 73 | expect { type.validate!(:false) }.to raise_error(SmartCore::Types::TypeError) 74 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 75 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 76 | end 77 | end 78 | 79 | context 'non-nilable type' do 80 | let(:type) { SmartCore::Types::Value::Boolean } 81 | 82 | include_examples 'type casting' 83 | include_examples 'type-checking / type-validation (non-nilable)' 84 | end 85 | 86 | context 'runtime-based non-nilable type' do 87 | let(:type) { SmartCore::Types::Value::Boolean() } 88 | 89 | include_examples 'type casting' 90 | include_examples 'type-checking / type-validation (non-nilable)' 91 | end 92 | 93 | context 'nilable type' do 94 | let(:type) { SmartCore::Types::Value::Boolean.nilable } 95 | 96 | include_examples 'type casting' 97 | include_examples 'type-checking / type-validation (nilable)' 98 | end 99 | 100 | context 'runtime-based nilable type' do 101 | let(:type) { SmartCore::Types::Value::Boolean().nilable } 102 | 103 | include_examples 'type casting' 104 | include_examples 'type-checking / type-validation (nilable)' 105 | end 106 | 107 | specify 'has no support for runtime attributes' do 108 | expect { SmartCore::Types::Value::Boolean(true) }.to raise_error( 109 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 110 | ) 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /spec/types/value/text_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Text' do 4 | shared_examples 'type-casting' do 5 | specify 'string/stringable => string' do 6 | expect(type.cast('test')).to eq('test') 7 | expect(type.cast([])).to eq('[]') 8 | expect(type.cast({})).to eq('{}') 9 | expect(type.cast(Object.new)).to match(/\A\#\z/) 10 | end 11 | 12 | specify 'symbol => symbol' do 13 | expect(type.cast(:test)).to eq(:test) 14 | expect(type.cast(:notest)).to eq(:notest) 15 | end 16 | 17 | specify 'castable => string/symbol' do 18 | castable = Class.new { def to_s; 'kekpek'; end }.new 19 | expect(type.cast(castable)).to eq('kekpek') 20 | 21 | castable = Class.new do 22 | undef_method :to_s 23 | def to_sym; :middle; end 24 | end.new 25 | expect(type.cast(castable)).to eq(:middle) 26 | end 27 | 28 | specify 'invalid casting' do 29 | non_castable = Class.new { undef_method :to_s; }.new 30 | expect { type.cast(non_castable) }.to raise_error(SmartCore::Types::TypeCastingError) 31 | 32 | non_castable = Class.new { def to_s; 123; end; }.new 33 | expect { type.cast(non_castable) }.to raise_error(SmartCore::Types::TypeCastingError) 34 | 35 | non_castable = BasicObject.new 36 | expect { type.cast(non_castable) }.to raise_error(SmartCore::Types::TypeCastingError) 37 | end 38 | 39 | specify 'cast priority (string > symbol)' do 40 | castable = Class.new do 41 | def to_s; 'as_string'; end 42 | def to_sym; :as_sym; end 43 | end.new 44 | expect(type.cast(castable)).to eq('as_string') 45 | end 46 | end 47 | 48 | shared_examples 'type-checking / type-validation (non-nilable)' do 49 | specify 'type-checking' do 50 | expect(type.valid?('test')).to eq(true) 51 | expect(type.valid?(:test)).to eq(true) 52 | 53 | expect(type.valid?(123)).to eq(false) 54 | expect(type.valid?(Object.new)).to eq(false) 55 | expect(type.valid?(BasicObject.new)).to eq(false) 56 | expect(type.valid?(nil)).to eq(false) 57 | end 58 | 59 | specify 'type-validation' do 60 | expect { type.validate!('test') }.not_to raise_error 61 | expect { type.validate!(:test) }.not_to raise_error 62 | 63 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 64 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 65 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 66 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 67 | end 68 | end 69 | 70 | shared_examples 'type-checking / type-validation (nilable)' do 71 | specify 'type-checking' do 72 | expect(type.valid?('test')).to eq(true) 73 | expect(type.valid?(:test)).to eq(true) 74 | expect(type.valid?(nil)).to eq(true) 75 | 76 | expect(type.valid?(123)).to eq(false) 77 | expect(type.valid?(Object.new)).to eq(false) 78 | expect(type.valid?(BasicObject.new)).to eq(false) 79 | end 80 | 81 | specify 'type-validation' do 82 | expect { type.validate!('test') }.not_to raise_error 83 | expect { type.validate!(:test) }.not_to raise_error 84 | expect { type.validate!(nil) }.not_to raise_error 85 | 86 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 87 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 88 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 89 | end 90 | end 91 | 92 | context 'non-nilable type' do 93 | let(:type) { SmartCore::Types::Value::Text } 94 | 95 | it_behaves_like 'type-casting' 96 | it_behaves_like 'type-checking / type-validation (non-nilable)' 97 | end 98 | 99 | context 'runtime-based non-nilable type' do 100 | let(:type) { SmartCore::Types::Value::Text() } 101 | 102 | it_behaves_like 'type-casting' 103 | it_behaves_like 'type-checking / type-validation (non-nilable)' 104 | end 105 | 106 | context 'nilable type' do 107 | let(:type) { SmartCore::Types::Value::Text.nilable } 108 | 109 | it_behaves_like 'type-casting' 110 | it_behaves_like 'type-checking / type-validation (nilable)' 111 | end 112 | 113 | context 'runtime-based nilable type' do 114 | let(:type) { SmartCore::Types::Value::Text().nilable } 115 | 116 | it_behaves_like 'type-casting' 117 | it_behaves_like 'type-checking / type-validation (nilable)' 118 | end 119 | 120 | specify 'has no support for runtime attributes' do 121 | expect { SmartCore::Types::Value::Text(:test) }.to raise_error( 122 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 123 | ) 124 | 125 | expect { SmartCore::Types::Value::Text('test') }.to raise_error( 126 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 127 | ) 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /spec/types/value/hash_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Hash' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect(type.cast(nil)).to eq({}) 7 | expect(type.cast({})).to eq({}) 8 | expect(type.cast([])).to eq({}) 9 | expect(type.cast([[:a, 1], ['b', 2], ['c', :test]])).to eq({ a: 1, 'b' => 2, 'c' => :test }) 10 | expect(type.cast({ 'fiz' => :baz, kek: 'pek' })).to eq({ 'fiz' => :baz, kek: 'pek' }) 11 | 12 | expect { type.cast(1) }.to raise_error(SmartCore::Types::TypeCastingError) 13 | expect { type.cast(Object.new) }.to raise_error(SmartCore::Types::TypeCastingError) 14 | expect { type.cast(BasicObject.new) }.to raise_error(SmartCore::Types::TypeCastingError) 15 | 16 | as_hash_1 = Class.new { def to_h; { a: 1, 'b' => :baz }; end; }.new 17 | as_hash_2 = Class.new { def to_hash; { c: 3, 'd' => :fiz }; end; }.new 18 | non_hashable_1 = Class.new { def to_h; 1; end; }.new 19 | non_hashable_2 = Class.new { def to_hash; :test; end; }.new 20 | 21 | expect(type.cast(as_hash_1)).to eq({ a: 1, 'b' => :baz }) 22 | expect(type.cast(as_hash_2)).to eq({ c: 3, 'd' => :fiz }) 23 | 24 | expect { type.cast(non_hashable_1) }.to raise_error(SmartCore::Types::TypeCastingError) 25 | expect { type.cast(non_hashable_2) }.to raise_error(SmartCore::Types::TypeCastingError) 26 | end 27 | end 28 | 29 | shared_examples 'type-checking / type-validation (non-nilable)' do 30 | specify 'type-checking' do 31 | expect(type.valid?({})).to eq(true) 32 | expect(type.valid?({ a: 1, 'b' => 2 })).to eq(true) 33 | 34 | expect(type.valid?(nil)).to eq(false) 35 | expect(type.valid?(123)).to eq(false) 36 | expect(type.valid?([])).to eq(false) 37 | expect(type.valid?([[:a, 1], ['b', Object.new]])).to eq(false) 38 | expect(type.valid?(Object.new)).to eq(false) 39 | expect(type.valid?(BasicObject.new)).to eq(false) 40 | end 41 | 42 | specify 'type-validation' do 43 | expect { type.validate!({}) }.not_to raise_error 44 | expect { type.validate!({ a: 1, 'b' => 2 }) }.not_to raise_error 45 | 46 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 47 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 48 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 49 | expect { type.validate!([[:a, 1], ['b', :test]]) }.to raise_error(SmartCore::Types::TypeError) 50 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 51 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 52 | end 53 | end 54 | 55 | shared_examples 'type-checking / type-validation (nilable)' do 56 | specify 'type-checking' do 57 | expect(type.valid?({})).to eq(true) 58 | expect(type.valid?({ a: 1, 'b' => 2 })).to eq(true) 59 | expect(type.valid?(nil)).to eq(true) 60 | 61 | expect(type.valid?(123)).to eq(false) 62 | expect(type.valid?([])).to eq(false) 63 | expect(type.valid?([[:a, 1], ['b', Object.new]])).to eq(false) 64 | expect(type.valid?(Object.new)).to eq(false) 65 | expect(type.valid?(BasicObject.new)).to eq(false) 66 | end 67 | 68 | specify 'type-validation' do 69 | expect { type.validate!({}) }.not_to raise_error 70 | expect { type.validate!({ a: 1, 'b' => 2 }) }.not_to raise_error 71 | expect { type.validate!(nil) }.not_to raise_error 72 | 73 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 74 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 75 | expect { type.validate!([[:a, 1], ['b', :test]]) }.to raise_error(SmartCore::Types::TypeError) 76 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 77 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 78 | end 79 | end 80 | 81 | context 'non-nilable type' do 82 | let(:type) { SmartCore::Types::Value::Hash } 83 | 84 | include_examples 'type casting' 85 | include_examples 'type-checking / type-validation (non-nilable)' 86 | end 87 | 88 | context 'runtime-based non-nilable type' do 89 | let(:type) { SmartCore::Types::Value::Hash() } 90 | 91 | include_examples 'type casting' 92 | include_examples 'type-checking / type-validation (non-nilable)' 93 | end 94 | 95 | context 'nilable type' do 96 | let(:type) { SmartCore::Types::Value::Hash.nilable } 97 | 98 | include_examples 'type casting' 99 | include_examples 'type-checking / type-validation (nilable)' 100 | end 101 | 102 | context 'runtime-based nilable type' do 103 | let(:type) { SmartCore::Types::Value::Hash().nilable } 104 | 105 | include_examples 'type casting' 106 | include_examples 'type-checking / type-validation (nilable)' 107 | end 108 | 109 | specify 'has no support for runtime attributes' do 110 | expect { SmartCore::Types::Value::Hash({}) }.to raise_error( 111 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 112 | ) 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /spec/features/invariants/type_sum_composability_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'INVARIANTS: Type sum composability' do 4 | SmartCore::Types::Value.define_type(:InvFirstSumTypeSpec) do |type| 5 | type.define_checker { |value| value.is_a?(::Integer) } 6 | 7 | type.invariant_chain(:range) do 8 | invariant(:non_zero) { |value| value != 0 } 9 | invariant(:not_59) { |value| value != 59 } 10 | end 11 | 12 | type.invariant(:danger_value) { |value| value != 25 } 13 | end 14 | 15 | SmartCore::Types::Value.define_type(:InvSecSumTypeSpec) do |type| 16 | type.define_checker { |value| value.is_a?(::Float) } 17 | 18 | type.invariant_chain(:range) do 19 | invariant(:less_than_100) { |value| value < 100 } 20 | invariant(:more_than_10) { |value| value > 10 } 21 | end 22 | 23 | type.invariant(:danger_value) { |value| value != 70.0 } 24 | end 25 | 26 | SmartCore::Types::Value::InvSumTypeSpec = SmartCore::Types::System.type_sum( 27 | SmartCore::Types::Value::InvFirstSumTypeSpec, 28 | SmartCore::Types::Value::InvSecSumTypeSpec 29 | ) 30 | 31 | let!(:sum_type) { SmartCore::Types::Value::InvSumTypeSpec } 32 | let!(:nilable_sum_type) { SmartCore::Types::Value::InvSumTypeSpec.nilable } 33 | 34 | specify 'TODO: support for invariant checking in type sum' do 35 | aggregate_failures 'true-checks' do 36 | # (integer) common type 37 | result = sum_type.validate(59) 38 | expect(result.success?).to eq(true) 39 | expect(result.failure?).to eq(false) 40 | expect(result.errors).to eq([]) # invariant checks does not supported at this moment 41 | expect(result.valid_check?).to eq(true) 42 | expect(result.valid_invariants?).to eq(true) 43 | 44 | # (integer) nilable type (value check) 45 | result = nilable_sum_type.validate(0) 46 | expect(result.success?).to eq(true) 47 | expect(result.failure?).to eq(false) 48 | expect(result.errors).to eq([]) # invariant checks does not supported at this moment 49 | expect(result.valid_check?).to eq(true) 50 | expect(result.valid_invariants?).to eq(true) 51 | 52 | # (integer) nilable type (nil check) 53 | result = nilable_sum_type.validate(nil) 54 | expect(result.success?).to eq(true) 55 | expect(result.failure?).to eq(false) 56 | expect(result.errors).to eq([]) # invariant checks does not supported at this moment 57 | expect(result.valid_check?).to eq(true) 58 | expect(result.valid_invariants?).to eq(true) 59 | 60 | # (float) common type 61 | result = sum_type.validate(70.0) 62 | expect(result.success?).to eq(true) 63 | expect(result.failure?).to eq(false) 64 | expect(result.errors).to eq([]) # invariant checks does not supported at this moment 65 | expect(result.valid_check?).to eq(true) 66 | expect(result.valid_invariants?).to eq(true) 67 | 68 | # (float) nilable type (value check) 69 | result = nilable_sum_type.validate(70.0) 70 | expect(result.success?).to eq(true) 71 | expect(result.failure?).to eq(false) 72 | expect(result.errors).to eq([]) # invariant checks does not supported at this moment 73 | expect(result.valid_check?).to eq(true) 74 | expect(result.valid_invariants?).to eq(true) 75 | 76 | # (float) nilable type (nil check) 77 | result = nilable_sum_type.validate(nil) 78 | expect(result.success?).to eq(true) 79 | expect(result.failure?).to eq(false) 80 | expect(result.errors).to eq([]) # invariant checks does not supported at this moment 81 | expect(result.valid_check?).to eq(true) 82 | expect(result.valid_invariants?).to eq(true) 83 | end 84 | 85 | aggregate_failures 'false-checks' do 86 | # (integer) common type 87 | result = sum_type.validate('123') 88 | expect(result.success?).to eq(false) 89 | expect(result.failure?).to eq(true) 90 | expect(result.errors).to eq([]) # invariant checks does not supported at this moment 91 | expect(result.valid_check?).to eq(false) 92 | expect(result.valid_invariants?).to eq(true) 93 | 94 | # (integer) nilable type (value check) 95 | result = nilable_sum_type.validate('123') 96 | expect(result.success?).to eq(false) 97 | expect(result.failure?).to eq(true) 98 | expect(result.errors).to eq([]) # invariant checks does not supported at this moment 99 | expect(result.valid_check?).to eq(false) 100 | expect(result.valid_invariants?).to eq(true) 101 | end 102 | end 103 | 104 | # rubocop:disable Layout/LineLength 105 | specify 'result type' do 106 | expect(sum_type.validate('123')).to be_a(::SmartCore::Types::Primitive::SumValidator::Result) 107 | expect(sum_type.validate(123)).to be_a(::SmartCore::Types::Primitive::SumValidator::Result) 108 | expect(sum_type.validate(123.0)).to be_a(::SmartCore::Types::Primitive::SumValidator::Result) 109 | expect(sum_type.validate(nil)).to be_a(::SmartCore::Types::Primitive::SumValidator::Result) 110 | expect(nilable_sum_type.validate(nil)).to be_a(::SmartCore::Types::Primitive::NilableValidator::Result) 111 | end 112 | # rubocop:enable Layout/LineLength 113 | end 114 | -------------------------------------------------------------------------------- /lib/smart_core/types/primitive/factory/definition_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.1.0 5 | # @version 0.3.0 6 | class SmartCore::Types::Primitive::Factory::DefinitionContext 7 | class << self 8 | # @param name [String, Symbol] 9 | # @param definition [Block] 10 | # @return [void] 11 | # 12 | # @api private 13 | # @since 0.2.0 14 | # @version 0.3.0 15 | def vaildate_invariant_attributes!(name, &definition) 16 | unless block_given? 17 | raise(SmartCore::Types::TypeDefinitionError, 'No invariant block') 18 | end 19 | 20 | unless name.is_a?(::String) || name.is_a?(::Symbol) 21 | raise(SmartCore::Types::TypeDefinitionError, <<~ERROR_MESSAGE) 22 | Invariant name should be a type of string or symbol. 23 | ERROR_MESSAGE 24 | end 25 | 26 | if name == '' || name == :"" 27 | raise(SmartCore::Types::TypeDefinitionError, <<~ERROR_MESSAGE) 28 | Invariant name can not be empty. 29 | ERROR_MESSAGE 30 | end 31 | end 32 | 33 | # @param chain_name [String, Symbol] 34 | # @param definition [Block] 35 | # @return [void] 36 | # 37 | # @api private 38 | # @since 0.3.0 39 | def vaildate_invariant_chain_attributes!(chain_name, &definition) 40 | unless block_given? 41 | raise(SmartCore::Types::TypeDefinitionError, 'No invariant chain block') 42 | end 43 | 44 | unless chain_name.is_a?(::String) || chain_name.is_a?(::Symbol) 45 | raise(SmartCore::Types::TypeDefinitionError, <<~ERROR_MESSAGE) 46 | Invariant chain name should be a type of string or symbol. 47 | ERROR_MESSAGE 48 | end 49 | 50 | if chain_name == '' || chain_name == :"" 51 | raise(SmartCore::Types::TypeDefinitionError, <<~ERROR_MESSAGE) 52 | Invariant chain name can not be empty. 53 | ERROR_MESSAGE 54 | end 55 | end 56 | end 57 | 58 | # @return [Proc, NilClass] 59 | # 60 | # @api private 61 | # @since 0.1.0 62 | attr_reader :type_checker 63 | 64 | # @return [Proc, NilClass] 65 | # 66 | # @api private 67 | # @since 0.1.0 68 | attr_reader :type_caster 69 | 70 | # @return [Hash>] 71 | # 72 | # @api private 73 | # @since 0.2.0 74 | attr_reader :type_invariant_chains 75 | 76 | # @return [Hash] 77 | # 78 | # @api private 79 | # @since 0.2.0 80 | attr_reader :type_invariants 81 | 82 | # @return [Proc, NilClass] 83 | # 84 | # @api private 85 | # @since 0.3.0 86 | attr_reader :type_runtime_attributes_checker 87 | 88 | # @return [void] 89 | # 90 | # @api private 91 | # @since 0.1.0 92 | # @version 0.2.0 93 | def initialize 94 | @type_invariant_chains = Hash.new { |h, k| h[k] = [] } 95 | @type_invariants = {} 96 | @type_checker = nil 97 | @type_caster = nil 98 | @type_runtime_attributes_checker = nil 99 | @definition_lock = SmartCore::Engine::Lock.new 100 | end 101 | 102 | # @param checker [Block] 103 | # @return [void] 104 | # 105 | # @api public 106 | # @since 0.1.0 107 | # @version 0.3.0 108 | def define_checker(&checker) 109 | thread_safe do 110 | unless block_given? 111 | raise(SmartCore::Types::TypeDefinitionError, 'No checker definition block') 112 | end 113 | @type_checker = checker 114 | end 115 | end 116 | 117 | # @param caster [Block] 118 | # @return [void] 119 | # 120 | # @api public 121 | # @since 0.1.0 122 | # @version 0.3.0 123 | def define_caster(&caster) 124 | thread_safe do 125 | unless block_given? 126 | raise(SmartCore::Types::TypeDefinitionError, 'No caster definition block') 127 | end 128 | @type_caster = caster 129 | end 130 | end 131 | 132 | # @param chain_name [String, Symbol] 133 | # @param definitions [Block] 134 | # @return [void] 135 | # 136 | # @api public 137 | # @since 0.2.0 138 | def invariant_chain(chain_name, &definitions) 139 | thread_safe do 140 | self.class.vaildate_invariant_chain_attributes!(chain_name, &definitions) 141 | @type_invariant_chains[chain_name.to_s] << definitions 142 | end 143 | end 144 | 145 | # @param name [String, Symbol] 146 | # @param definition [Block] 147 | # @return [void] 148 | # 149 | # @api public 150 | # @since 0.2.0 151 | def invariant(name, &definition) 152 | thread_safe do 153 | self.class.vaildate_invariant_attributes!(name, &definition) 154 | @type_invariants[name.to_s] = definition 155 | end 156 | end 157 | 158 | # @param definition [Block] 159 | # @return [void] 160 | # 161 | # @api public 162 | # @since 0.3.0 163 | def runtime_attributes_checker(&definition) 164 | thread_safe do 165 | unless block_given? 166 | raise(SmartCore::Types::TypeDefinitionError, 'No runtime checker definition block') 167 | end 168 | @type_runtime_attributes_checker = definition 169 | end 170 | end 171 | 172 | private 173 | 174 | # @param block [Block] 175 | # @return [Any] 176 | # 177 | # @api private 178 | # @since 0.2.0 179 | def thread_safe(&block) 180 | @definition_lock.synchronize(&block) 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /spec/types/value/symbol_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Symbol' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect(type.cast('cast')).to eq(:cast) 7 | expect(type.cast(:kek)).to eq(:kek) 8 | 9 | expect { type.cast(Class.new) }.to raise_error(SmartCore::Types::TypeCastingError) 10 | expect { type.cast(123) }.to raise_error(SmartCore::Types::TypeCastingError) 11 | expect { type.cast({}) }.to raise_error(SmartCore::Types::TypeCastingError) 12 | expect { type.cast([]) }.to raise_error(SmartCore::Types::TypeCastingError) 13 | expect { type.cast(Object.new) }.to raise_error(SmartCore::Types::TypeCastingError) 14 | expect { type.cast(BasicObject.new) }.to raise_error(SmartCore::Types::TypeCastingError) 15 | end 16 | end 17 | 18 | shared_examples 'type-checking / type-validation (non-nilable)' do 19 | specify 'type-checking' do 20 | expect(type.valid?(:meta)).to eq(true) 21 | 22 | expect(type.valid?(nil)).to eq(false) 23 | expect(type.valid?('test')).to eq(false) 24 | expect(type.valid?(true)).to eq(false) 25 | expect(type.valid?(false)).to eq(false) 26 | expect(type.valid?({})).to eq(false) 27 | expect(type.valid?([])).to eq(false) 28 | expect(type.valid?(Object.new)).to eq(false) 29 | expect(type.valid?(BasicObject.new)).to eq(false) 30 | expect(type.valid?(123)).to eq(false) 31 | expect(type.valid?(123.456)).to eq(false) 32 | end 33 | 34 | specify 'type-validation' do 35 | expect { type.validate!(:meta) }.not_to raise_error 36 | 37 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 38 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 39 | expect { type.validate!(true) }.to raise_error(SmartCore::Types::TypeError) 40 | expect { type.validate!(false) }.to raise_error(SmartCore::Types::TypeError) 41 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 42 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 43 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 44 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 45 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 46 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 47 | end 48 | end 49 | 50 | shared_examples 'type-checking / type-validation (nilable)' do 51 | specify 'type-checking' do 52 | expect(type.valid?(:meta)).to eq(true) 53 | expect(type.valid?(nil)).to eq(true) 54 | 55 | expect(type.valid?('test')).to eq(false) 56 | expect(type.valid?(true)).to eq(false) 57 | expect(type.valid?(false)).to eq(false) 58 | expect(type.valid?({})).to eq(false) 59 | expect(type.valid?([])).to eq(false) 60 | expect(type.valid?(Object.new)).to eq(false) 61 | expect(type.valid?(BasicObject.new)).to eq(false) 62 | expect(type.valid?(123)).to eq(false) 63 | expect(type.valid?(123.456)).to eq(false) 64 | end 65 | 66 | specify 'type-validation' do 67 | expect { type.validate!(:meta) }.not_to raise_error 68 | expect { type.validate!(nil) }.not_to raise_error 69 | 70 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 71 | expect { type.validate!(true) }.to raise_error(SmartCore::Types::TypeError) 72 | expect { type.validate!(false) }.to raise_error(SmartCore::Types::TypeError) 73 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 74 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 75 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 76 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 77 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 78 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 79 | end 80 | end 81 | 82 | context 'non-nilable type' do 83 | let(:type) { SmartCore::Types::Value::Symbol } 84 | 85 | include_examples 'type casting' 86 | include_examples 'type-checking / type-validation (non-nilable)' 87 | end 88 | 89 | context 'runtime-based non-nilable type' do 90 | let(:type) { SmartCore::Types::Value::Symbol() } 91 | 92 | include_examples 'type casting' 93 | include_examples 'type-checking / type-validation (non-nilable)' 94 | end 95 | 96 | context 'nilable type' do 97 | let(:type) { SmartCore::Types::Value::Symbol.nilable } 98 | 99 | include_examples 'type casting' 100 | include_examples 'type-checking / type-validation (nilable)' 101 | end 102 | 103 | context 'runtime-based nilable type' do 104 | let(:type) { SmartCore::Types::Value::Symbol().nilable } 105 | 106 | include_examples 'type casting' 107 | include_examples 'type-checking / type-validation (nilable)' 108 | end 109 | 110 | specify 'has no support for runtime attributes' do 111 | expect { SmartCore::Types::Value::Symbol(:test) }.to raise_error( 112 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 113 | ) 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/types/variadic/array_of_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Variadic::ArrayOf' do 4 | describe 'runtime-based behavior' do 5 | specify 'fails on non-class objects (should work only with classes)' do 6 | expect do 7 | SmartCore::Types::Variadic::ArrayOf(123) 8 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 9 | 10 | expect do 11 | SmartCore::Types::Variadic::ArrayOf('test', 123) 12 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 13 | 14 | expect do 15 | SmartCore::Types::Variadic::ArrayOf(::Symbol, 123) 16 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 17 | 18 | expect do 19 | SmartCore::Types::Variadic::ArrayOf(Module.new, ::String) 20 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 21 | end 22 | 23 | specify 'requires type list (runtime attributes)' do 24 | expect do 25 | SmartCore::Types::Variadic::ArrayOf() 26 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 27 | 28 | expect { SmartCore::Types::Variadic::ArrayOf(::String, ::Integer) }.not_to raise_error 29 | end 30 | end 31 | 32 | describe 'logic' do 33 | specify 'type-casting (as Array)' do 34 | type = SmartCore::Types::Variadic::ArrayOf(::Object) 35 | nilable_type = type.nilable 36 | object = Object.new 37 | array = [Object.new, Object.new] 38 | expect(type.cast(object)).to eq([object]) 39 | expect(type.cast(array)).to eq(array) 40 | expect(type.cast(nil)).to eq([]) 41 | 42 | expect(nilable_type.cast(object)).to eq([object]) 43 | expect(nilable_type.cast(array)).to eq(array) 44 | expect(nilable_type.cast(nil)).to eq([]) 45 | end 46 | 47 | # rubocop:disable Layout/LineLength 48 | specify 'type-checking / type-validation' do 49 | type = SmartCore::Types::Variadic::ArrayOf(::String, ::Symbol, ::Array, ::Hash) 50 | nilable_type = type.nilable 51 | 52 | aggregate_failures 'non-nilable array_of (String, Symbol, Array, Hash)' do 53 | expect(type.valid?(['test', :test, [], { test: :test }])).to eq(true) 54 | expect(type.valid?(%w[test test2])).to eq(true) 55 | expect(type.valid?([%i[key value], ['test', :test], [123]])).to eq(true) 56 | expect(type.valid?(nil)).to eq(false) 57 | expect(type.valid?([nil, 'test'])).to eq(false) 58 | expect(type.valid?(Object.new)).to eq(false) 59 | expect(type.valid?(['test', BasicObject.new])).to eq(false) 60 | expect { type.validate!(['test', :test, [], { test: :test }]) }.not_to raise_error 61 | expect { type.validate!(%w[test test2]) }.not_to raise_error 62 | expect { type.validate!([%i[key value], ['test', :test], [123]]) }.not_to raise_error 63 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 64 | expect { type.validate!([nil, 'test']) }.to raise_error(SmartCore::Types::TypeError) 65 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 66 | expect { type.validate!(['test', BasicObject.new]) }.to raise_error(SmartCore::Types::TypeError) 67 | end 68 | 69 | aggregate_failures 'nilable array_of (String, Symbol, Array, Hash)' do 70 | expect(nilable_type.valid?(['test', :test, [], { test: :test }])).to eq(true) 71 | expect(nilable_type.valid?(%w[test test2])).to eq(true) 72 | expect(nilable_type.valid?([%i[key value], ['test', :test], [123]])).to eq(true) 73 | expect(nilable_type.valid?(nil)).to eq(true) 74 | expect(nilable_type.valid?([nil, 'test'])).to eq(false) 75 | expect(nilable_type.valid?(Object.new)).to eq(false) 76 | expect(nilable_type.valid?(['test', BasicObject.new])).to eq(false) 77 | expect { nilable_type.validate!(['test', :test, [], { test: :test }]) }.not_to raise_error 78 | expect { nilable_type.validate!(%w[test test2]) }.not_to raise_error 79 | expect { nilable_type.validate!([%i[key value], ['test', :test], [123]]) }.not_to raise_error 80 | expect { nilable_type.validate!(nil) }.not_to raise_error 81 | expect { nilable_type.validate!([nil, 'test']) }.to raise_error(SmartCore::Types::TypeError) 82 | expect { nilable_type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 83 | expect { nilable_type.validate!(['test', BasicObject.new]) }.to raise_error(SmartCore::Types::TypeError) 84 | end 85 | 86 | no_any_type = SmartCore::Types::Variadic::ArrayOf 87 | nillable_no_any_type = no_any_type.nilable 88 | 89 | aggregate_failures 'example of no-one-type-is-supported collection (non-nilable)' do 90 | expect(no_any_type.valid?('test')).to eq(false) 91 | expect(no_any_type.valid?(Object.new)).to eq(false) 92 | expect(no_any_type.valid?([Object.new, Object.new])).to eq(false) 93 | expect(no_any_type.valid?(BasicObject.new)).to eq(false) 94 | expect(no_any_type.valid?(nil)).to eq(false) 95 | end 96 | 97 | aggregate_failures 'example of no-one-type-is-supported collection (nilable)' do 98 | expect(nillable_no_any_type.valid?('test')).to eq(false) 99 | expect(nillable_no_any_type.valid?(Object.new)).to eq(false) 100 | expect(nillable_no_any_type.valid?([Object.new, Object.new])).to eq(false) 101 | expect(nillable_no_any_type.valid?(BasicObject.new)).to eq(false) 102 | expect(nillable_no_any_type.valid?(nil)).to eq(true) 103 | end 104 | end 105 | # rubocop:enable Layout/LineLength 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /spec/types/value/proc_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Proc' do 4 | shared_examples 'type-casting' do 5 | specify 'proc => proc' do 6 | proc_object = proc { |a, b| } 7 | expect(type.cast(proc_object)).to eq(proc_object) 8 | end 9 | 10 | specify 'lambda => lambda' do 11 | lambda_object = -> (a, b) {} 12 | expect(type.cast(lambda_object)).to eq(lambda_object).and be_a(::Proc) 13 | end 14 | 15 | specify 'castable => proc' do 16 | expect(type.cast(:to_s)).to be_a(::Proc) 17 | expect(type.cast(Class.new { def to_proc; proc {}; end; }.new)).to be_a(::Proc) 18 | end 19 | 20 | specify 'invalid casting' do 21 | expect { type.cast(Class.new.new) }.to raise_error(SmartCore::Types::TypeCastingError) 22 | expect { type.cast(Object.new) }.to raise_error(SmartCore::Types::TypeCastingError) 23 | expect { type.cast(BasicObject.new) }.to raise_error(SmartCore::Types::TypeCastingError) 24 | 25 | non_castable = Class.new { def to_proc; nil; end; }.new 26 | expect { type.cast(non_castable) }.to raise_error(SmartCore::Types::TypeCastingError) 27 | end 28 | end 29 | 30 | shared_examples 'type-checking / type-validation (non-nilable)' do 31 | specify 'type-checking' do 32 | expect(type.valid?(-> {})).to eq(true) 33 | expect(type.valid?(proc {})).to eq(true) 34 | 35 | expect(type.valid?(nil)).to eq(false) 36 | expect(type.valid?(Object.new)).to eq(false) 37 | expect(type.valid?(BasicObject.new)).to eq(false) 38 | expect(type.valid?({})).to eq(false) 39 | expect(type.valid?([])).to eq(false) 40 | expect(type.valid?('test')).to eq(false) 41 | expect(type.valid?(:test)).to eq(false) 42 | expect(type.valid?(true)).to eq(false) 43 | expect(type.valid?(false)).to eq(false) 44 | end 45 | 46 | specify 'type-validation' do 47 | expect { type.validate!(-> {}) }.not_to raise_error 48 | expect { type.validate!(proc {}) }.not_to raise_error 49 | 50 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 51 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 52 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 53 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 54 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 55 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 56 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 57 | expect { type.validate!(true) }.to raise_error(SmartCore::Types::TypeError) 58 | expect { type.validate!(false) }.to raise_error(SmartCore::Types::TypeError) 59 | end 60 | end 61 | 62 | shared_examples 'type-checking / type-validation (nilable)' do 63 | specify 'type-checking' do 64 | expect(type.valid?(-> {})).to eq(true) 65 | expect(type.valid?(proc {})).to eq(true) 66 | expect(type.valid?(nil)).to eq(true) 67 | 68 | expect(type.valid?(Object.new)).to eq(false) 69 | expect(type.valid?(BasicObject.new)).to eq(false) 70 | expect(type.valid?({})).to eq(false) 71 | expect(type.valid?([])).to eq(false) 72 | expect(type.valid?('test')).to eq(false) 73 | expect(type.valid?(:test)).to eq(false) 74 | expect(type.valid?(true)).to eq(false) 75 | expect(type.valid?(false)).to eq(false) 76 | end 77 | 78 | specify 'type-validation' do 79 | expect { type.validate!(-> {}) }.not_to raise_error 80 | expect { type.validate!(proc {}) }.not_to raise_error 81 | expect { type.validate!(nil) }.not_to raise_error 82 | 83 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 84 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 85 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 86 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 87 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 88 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 89 | expect { type.validate!(true) }.to raise_error(SmartCore::Types::TypeError) 90 | expect { type.validate!(false) }.to raise_error(SmartCore::Types::TypeError) 91 | end 92 | end 93 | 94 | context 'non-nilable type' do 95 | let(:type) { SmartCore::Types::Value::Proc } 96 | 97 | it_behaves_like 'type-casting' 98 | it_behaves_like 'type-checking / type-validation (non-nilable)' 99 | end 100 | 101 | context 'runtime-based non-nilable type' do 102 | let(:type) { SmartCore::Types::Value::Proc() } 103 | 104 | it_behaves_like 'type-casting' 105 | it_behaves_like 'type-checking / type-validation (non-nilable)' 106 | end 107 | 108 | context 'nilable type' do 109 | let(:type) { SmartCore::Types::Value::Proc.nilable } 110 | 111 | it_behaves_like 'type-casting' 112 | it_behaves_like 'type-checking / type-validation (nilable)' 113 | end 114 | 115 | context 'runtime-based nilable type' do 116 | let(:type) { SmartCore::Types::Value::Proc().nilable } 117 | 118 | it_behaves_like 'type-casting' 119 | it_behaves_like 'type-checking / type-validation (nilable)' 120 | end 121 | 122 | specify 'has no support for runtime attributes' do 123 | expect { SmartCore::Types::Value::Proc((proc {})) }.to raise_error( 124 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 125 | ) 126 | 127 | expect { SmartCore::Types::Value::Proc((-> {})) }.to raise_error( 128 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 129 | ) 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /spec/types/value/time_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Time' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect(type.cast('2010-10-31')).to eq(Time.new(2010, 10, 31)) 7 | expect(type.cast(Time.new(2010, 10, 31))).to eq(Time.new(2010, 10, 31)) 8 | expect(type.cast(946_702_800)).to eq(Time.utc(2000, 1, 1, 5)) 9 | 10 | current_time = Time.now 11 | current_year = current_time.year 12 | current_month = current_time.month 13 | current_day = current_time.day 14 | expect(type.cast('12:00')).to eq(Time.new(current_year, current_month, current_day, 12)) 15 | 16 | expect { type.cast('2001') }.to raise_error(SmartCore::Types::TypeCastingError) 17 | expect { type.cast(nil) }.to raise_error(SmartCore::Types::TypeCastingError) 18 | expect { type.cast(Object.new) }.to raise_error(SmartCore::Types::TypeCastingError) 19 | expect { type.cast(BasicObject.new) }.to raise_error(SmartCore::Types::TypeCastingError) 20 | expect { type.cast({}) }.to raise_error(SmartCore::Types::TypeCastingError) 21 | expect { type.cast([]) }.to raise_error(SmartCore::Types::TypeCastingError) 22 | end 23 | end 24 | 25 | shared_examples 'type-checking / type-validation (non-nilable)' do 26 | specify 'type-checking' do 27 | expect(type.valid?(Time.now)).to eq(true) 28 | 29 | expect(type.valid?(nil)).to eq(false) 30 | expect(type.valid?(Date.new)).to eq(false) 31 | expect(type.valid?(DateTime.new)).to eq(false) 32 | expect(type.valid?({})).to eq(false) 33 | expect(type.valid?([])).to eq(false) 34 | expect(type.valid?(Object.new)).to eq(false) 35 | expect(type.valid?(BasicObject.new)).to eq(false) 36 | expect(type.valid?(123.456)).to eq(false) 37 | expect(type.valid?('2010-10-31')).to eq(false) 38 | expect(type.valid?(:time)).to eq(false) 39 | end 40 | 41 | specify 'type-casting' do 42 | expect { type.validate!(Time.now) }.not_to raise_error 43 | 44 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 45 | expect { type.validate!(Date.new) }.to raise_error(SmartCore::Types::TypeError) 46 | expect { type.validate!(DateTime.new) }.to raise_error(SmartCore::Types::TypeError) 47 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 48 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 49 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 50 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 51 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 52 | expect { type.validate!('2010-10-31') }.to raise_error(SmartCore::Types::TypeError) 53 | expect { type.validate!(:time) }.to raise_error(SmartCore::Types::TypeError) 54 | end 55 | end 56 | 57 | shared_examples 'type-checking / type-validation (nilable)' do 58 | specify 'type-checking' do 59 | expect(type.valid?(Time.now)).to eq(true) 60 | expect(type.valid?(nil)).to eq(true) 61 | 62 | expect(type.valid?(Date.new)).to eq(false) 63 | expect(type.valid?(DateTime.new)).to eq(false) 64 | expect(type.valid?({})).to eq(false) 65 | expect(type.valid?([])).to eq(false) 66 | expect(type.valid?(Object.new)).to eq(false) 67 | expect(type.valid?(BasicObject.new)).to eq(false) 68 | expect(type.valid?(123.456)).to eq(false) 69 | expect(type.valid?('2010-10-31')).to eq(false) 70 | expect(type.valid?(:time)).to eq(false) 71 | end 72 | 73 | specify 'type-casting' do 74 | expect { type.validate!(Time.now) }.not_to raise_error 75 | expect { type.validate!(nil) }.not_to raise_error 76 | 77 | expect { type.validate!(Date.new) }.to raise_error(SmartCore::Types::TypeError) 78 | expect { type.validate!(DateTime.new) }.to raise_error(SmartCore::Types::TypeError) 79 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 80 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 81 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 82 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 83 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 84 | expect { type.validate!('2010-10-31') }.to raise_error(SmartCore::Types::TypeError) 85 | expect { type.validate!(:time) }.to raise_error(SmartCore::Types::TypeError) 86 | end 87 | end 88 | 89 | context 'non-nilable type' do 90 | let(:type) { SmartCore::Types::Value::Time } 91 | 92 | include_examples 'type casting' 93 | include_examples 'type-checking / type-validation (non-nilable)' 94 | end 95 | 96 | context 'runtime-based non-nilable type' do 97 | let(:type) { SmartCore::Types::Value::Time() } 98 | 99 | include_examples 'type casting' 100 | include_examples 'type-checking / type-validation (non-nilable)' 101 | end 102 | 103 | context 'nilable type' do 104 | let(:type) { SmartCore::Types::Value::Time.nilable } 105 | 106 | include_examples 'type casting' 107 | include_examples 'type-checking / type-validation (nilable)' 108 | end 109 | 110 | context 'runtime-based nilable type' do 111 | let(:type) { SmartCore::Types::Value::Time().nilable } 112 | 113 | include_examples 'type casting' 114 | include_examples 'type-checking / type-validation (nilable)' 115 | end 116 | 117 | specify 'has no support for runtime attributes' do 118 | expect { SmartCore::Types::Value::Time(Time.new) }.to raise_error( 119 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 120 | ) 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /spec/types/value/method_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Method' do 4 | shared_examples 'type-casting' do 5 | specify 'type-casting' do 6 | expect { type.cast(nil) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 7 | expect { type.cast(Class) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 8 | expect { type.cast(Module) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 9 | end 10 | end 11 | 12 | shared_examples 'type-checking / type-validation (non-nilable)' do 13 | specify 'type-checking' do 14 | method_object = Object.new.method(:nil?) 15 | unbound_method_object = Object.new.method(:nil?).unbind 16 | 17 | expect(type.valid?(method_object)).to eq(true) 18 | expect(type.valid?(unbound_method_object)).to eq(false) 19 | expect(type.valid?(nil)).to eq(false) 20 | 21 | expect(type.valid?(-> {})).to eq(false) 22 | expect(type.valid?(proc {})).to eq(false) 23 | 24 | expect(type.valid?(Object.new)).to eq(false) 25 | expect(type.valid?(BasicObject.new)).to eq(false) 26 | expect(type.valid?(123)).to eq(false) 27 | expect(type.valid?(123.456)).to eq(false) 28 | expect(type.valid?('test')).to eq(false) 29 | expect(type.valid?(:test)).to eq(false) 30 | expect(type.valid?({})).to eq(false) 31 | expect(type.valid?([])).to eq(false) 32 | end 33 | 34 | specify 'type-valdation' do 35 | method_object = Object.new.method(:nil?) 36 | unbound_method_object = Object.new.method(:nil?).unbind 37 | 38 | expect { type.validate!(method_object) }.not_to raise_error 39 | expect { type.validate!(unbound_method_object) }.to raise_error(SmartCore::Types::TypeError) 40 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 41 | 42 | expect { type.validate!(-> {}) }.to raise_error(SmartCore::Types::TypeError) 43 | expect { type.validate!(proc {}) }.to raise_error(SmartCore::Types::TypeError) 44 | 45 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 46 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 47 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 48 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 49 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 50 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 51 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 52 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 53 | end 54 | end 55 | 56 | shared_examples 'type-checking / type-validation (nilable)' do 57 | specify 'type-checking' do 58 | method_object = Object.new.method(:nil?) 59 | unbound_method_object = Object.new.method(:nil?).unbind 60 | 61 | expect(type.valid?(method_object)).to eq(true) 62 | expect(type.valid?(unbound_method_object)).to eq(false) 63 | expect(type.valid?(nil)).to eq(true) 64 | 65 | expect(type.valid?(-> {})).to eq(false) 66 | expect(type.valid?(proc {})).to eq(false) 67 | 68 | expect(type.valid?(Object.new)).to eq(false) 69 | expect(type.valid?(BasicObject.new)).to eq(false) 70 | expect(type.valid?(123)).to eq(false) 71 | expect(type.valid?(123.456)).to eq(false) 72 | expect(type.valid?('test')).to eq(false) 73 | expect(type.valid?(:test)).to eq(false) 74 | expect(type.valid?({})).to eq(false) 75 | expect(type.valid?([])).to eq(false) 76 | end 77 | 78 | specify 'type-valdation' do 79 | method_object = Object.new.method(:nil?) 80 | unbound_method_object = Object.new.method(:nil?).unbind 81 | 82 | expect { type.validate!(method_object) }.not_to raise_error 83 | expect { type.validate!(unbound_method_object) }.to raise_error(SmartCore::Types::TypeError) 84 | expect { type.validate!(nil) }.not_to raise_error 85 | 86 | expect { type.validate!(-> {}) }.to raise_error(SmartCore::Types::TypeError) 87 | expect { type.validate!(proc {}) }.to raise_error(SmartCore::Types::TypeError) 88 | 89 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 90 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 91 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 92 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 93 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 94 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 95 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 96 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 97 | end 98 | end 99 | 100 | context 'non-nilable type' do 101 | let(:type) { SmartCore::Types::Value::Method } 102 | 103 | it_behaves_like 'type-casting' 104 | it_behaves_like 'type-checking / type-validation (non-nilable)' 105 | end 106 | 107 | context 'runtime-based non-nilable type' do 108 | let(:type) { SmartCore::Types::Value::Method() } 109 | 110 | it_behaves_like 'type-casting' 111 | it_behaves_like 'type-checking / type-validation (non-nilable)' 112 | end 113 | 114 | context 'nilable type' do 115 | let(:type) { SmartCore::Types::Value::Method.nilable } 116 | 117 | it_behaves_like 'type-casting' 118 | it_behaves_like 'type-checking / type-validation (nilable)' 119 | end 120 | 121 | context 'runtime-based nilable type' do 122 | let(:type) { SmartCore::Types::Value::Method().nilable } 123 | 124 | it_behaves_like 'type-casting' 125 | it_behaves_like 'type-checking / type-validation (nilable)' 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /spec/types/value/set_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Set' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect(type.cast(123)).to eq(Set.new([123])) 7 | expect(type.cast([:test, '123'])).to eq(Set.new([:test, '123'])) 8 | expect(type.cast(123.456)).to eq(Set.new([123.456])) 9 | expect(type.cast({ a: 1 })).to eq(Set.new({ a: 1 })) 10 | expect(type.cast(nil)).to eq(Set.new) 11 | expect(type.cast([])).to eq(Set.new) 12 | 13 | as_array_1 = Class.new { def to_a; [123]; end }.new 14 | as_array_2 = Class.new { def to_ary; ['456']; end }.new 15 | non_array_1 = Class.new { def to_a; :test; end }.new 16 | non_array_2 = Class.new { def to_ary; 'test'; end }.new 17 | object = Object.new 18 | 19 | expect(type.cast(as_array_1)).to eq(Set.new([123])) 20 | expect(type.cast(as_array_2)).to eq(Set.new(['456'])) 21 | expect(type.cast(non_array_1)).to eq(Set.new([non_array_1])) 22 | expect(type.cast(non_array_2)).to eq(Set.new([non_array_2])) 23 | expect(type.cast(object)).to eq(Set.new([object])) 24 | 25 | basic_object = BasicObject.new 26 | expect { type.cast(basic_object) }.to raise_error(SmartCore::Types::TypeCastingError) 27 | end 28 | end 29 | 30 | shared_examples 'type-checking / type-validation (non-nilable)' do 31 | specify 'type-checking' do 32 | expect(type.valid?(Set.new)).to eq(true) 33 | expect(type.valid?(Set.new([123, 45.6, 'test', :test, Object.new]))).to eq(true) 34 | 35 | expect(type.valid?([])).to eq(false) 36 | expect(type.valid?({})).to eq(false) 37 | expect(type.valid?(nil)).to eq(false) 38 | expect(type.valid?(Object.new)).to eq(false) 39 | expect(type.valid?(BasicObject.new)).to eq(false) 40 | expect(type.valid?(123.456)).to eq(false) 41 | expect(type.valid?(123)).to eq(false) 42 | expect(type.valid?(:test)).to eq(false) 43 | expect(type.valid?('test')).to eq(false) 44 | end 45 | 46 | specify 'type-validation' do 47 | expect { type.validate!(Set.new) }.not_to raise_error 48 | expect { type.validate!(Set.new([123, 45.6, 'test', :test, Object.new])) }.not_to raise_error 49 | 50 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 51 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 52 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 53 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 54 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 55 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 56 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 57 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 58 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 59 | end 60 | end 61 | 62 | shared_examples 'type-checking / type-validation (nilable)' do 63 | specify 'type-checking' do 64 | expect(type.valid?(Set.new)).to eq(true) 65 | expect(type.valid?(Set.new([123, 45.6, 'test', :test, Object.new]))).to eq(true) 66 | expect(type.valid?(nil)).to eq(true) 67 | 68 | expect(type.valid?([])).to eq(false) 69 | expect(type.valid?({})).to eq(false) 70 | expect(type.valid?(Object.new)).to eq(false) 71 | expect(type.valid?(BasicObject.new)).to eq(false) 72 | expect(type.valid?(123.456)).to eq(false) 73 | expect(type.valid?(123)).to eq(false) 74 | expect(type.valid?(:test)).to eq(false) 75 | expect(type.valid?('test')).to eq(false) 76 | end 77 | 78 | specify 'type-validation' do 79 | expect { type.validate!(Set.new) }.not_to raise_error 80 | expect { type.validate!(Set.new([123, 45.6, 'test', :test, Object.new])) }.not_to raise_error 81 | expect { type.validate!(nil) }.not_to raise_error 82 | 83 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 84 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 85 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 86 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 87 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 88 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 89 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 90 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 91 | end 92 | end 93 | 94 | context 'non-nilable type' do 95 | let(:type) { SmartCore::Types::Value::Set } 96 | 97 | include_examples 'type casting' 98 | include_examples 'type-checking / type-validation (non-nilable)' 99 | end 100 | 101 | context 'runtime-based non-nilable type' do 102 | let(:type) { SmartCore::Types::Value::Set() } 103 | 104 | include_examples 'type casting' 105 | include_examples 'type-checking / type-validation (non-nilable)' 106 | end 107 | 108 | context 'nilable type' do 109 | let(:type) { SmartCore::Types::Value::Set.nilable } 110 | 111 | include_examples 'type casting' 112 | include_examples 'type-checking / type-validation (nilable)' 113 | end 114 | 115 | context 'runtime-based nilable type' do 116 | let(:type) { SmartCore::Types::Value::Set().nilable } 117 | 118 | include_examples 'type casting' 119 | include_examples 'type-checking / type-validation (nilable)' 120 | end 121 | 122 | specify 'has no support for runtime attributes' do 123 | expect { SmartCore::Types::Value::Set(Set.new([1, 2, 3])) }.to raise_error( 124 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 125 | ) 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /spec/types/value/unbound_method_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::UnboundMethod' do 4 | shared_examples 'type-casting' do 5 | specify 'type-casting' do 6 | expect { type.cast(nil) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 7 | expect { type.cast(Class) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 8 | expect { type.cast(Module) }.to raise_error(SmartCore::Types::TypeCastingUnsupportedError) 9 | end 10 | end 11 | 12 | shared_examples 'type-checking / type-validation (non-nilable)' do 13 | specify 'type-checking' do 14 | unbound_method_object = Object.new.method(:nil?).unbind 15 | method_object = Object.new.method(:nil?) 16 | 17 | expect(type.valid?(unbound_method_object)).to eq(true) 18 | expect(type.valid?(method_object)).to eq(false) 19 | expect(type.valid?(nil)).to eq(false) 20 | 21 | expect(type.valid?(-> {})).to eq(false) 22 | expect(type.valid?(proc {})).to eq(false) 23 | 24 | expect(type.valid?(Object.new)).to eq(false) 25 | expect(type.valid?(BasicObject.new)).to eq(false) 26 | expect(type.valid?(123)).to eq(false) 27 | expect(type.valid?(123.456)).to eq(false) 28 | expect(type.valid?('test')).to eq(false) 29 | expect(type.valid?(:test)).to eq(false) 30 | expect(type.valid?({})).to eq(false) 31 | expect(type.valid?([])).to eq(false) 32 | end 33 | 34 | specify 'type-valdation' do 35 | unbound_method_object = Object.new.method(:nil?).unbind 36 | method_object = Object.new.method(:nil?) 37 | 38 | expect { type.validate!(unbound_method_object) }.not_to raise_error 39 | expect { type.validate!(method_object) }.to raise_error(SmartCore::Types::TypeError) 40 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 41 | 42 | expect { type.validate!(-> {}) }.to raise_error(SmartCore::Types::TypeError) 43 | expect { type.validate!(proc {}) }.to raise_error(SmartCore::Types::TypeError) 44 | 45 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 46 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 47 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 48 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 49 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 50 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 51 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 52 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 53 | end 54 | end 55 | 56 | shared_examples 'type-checking / type-validation (nilable)' do 57 | specify 'type-checking' do 58 | unbound_method_object = Object.new.method(:nil?).unbind 59 | method_object = Object.new.method(:nil?) 60 | 61 | expect(type.valid?(unbound_method_object)).to eq(true) 62 | expect(type.valid?(method_object)).to eq(false) 63 | expect(type.valid?(nil)).to eq(true) 64 | 65 | expect(type.valid?(-> {})).to eq(false) 66 | expect(type.valid?(proc {})).to eq(false) 67 | 68 | expect(type.valid?(Object.new)).to eq(false) 69 | expect(type.valid?(BasicObject.new)).to eq(false) 70 | expect(type.valid?(123)).to eq(false) 71 | expect(type.valid?(123.456)).to eq(false) 72 | expect(type.valid?('test')).to eq(false) 73 | expect(type.valid?(:test)).to eq(false) 74 | expect(type.valid?({})).to eq(false) 75 | expect(type.valid?([])).to eq(false) 76 | end 77 | 78 | specify 'type-valdation' do 79 | unbound_method_object = Object.new.method(:nil?).unbind 80 | method_object = Object.new.method(:nil?) 81 | 82 | expect { type.validate!(unbound_method_object) }.not_to raise_error 83 | expect { type.validate!(method_object) }.to raise_error(SmartCore::Types::TypeError) 84 | expect { type.validate!(nil) }.not_to raise_error 85 | 86 | expect { type.validate!(-> {}) }.to raise_error(SmartCore::Types::TypeError) 87 | expect { type.validate!(proc {}) }.to raise_error(SmartCore::Types::TypeError) 88 | 89 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 90 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 91 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 92 | expect { type.validate!(123.456) }.to raise_error(SmartCore::Types::TypeError) 93 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 94 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 95 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 96 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 97 | end 98 | end 99 | 100 | context 'non-nilable type' do 101 | let(:type) { SmartCore::Types::Value::UnboundMethod } 102 | 103 | it_behaves_like 'type-casting' 104 | it_behaves_like 'type-checking / type-validation (non-nilable)' 105 | end 106 | 107 | context 'runtime-based non-nilable type' do 108 | let(:type) { SmartCore::Types::Value::UnboundMethod() } 109 | 110 | it_behaves_like 'type-casting' 111 | it_behaves_like 'type-checking / type-validation (non-nilable)' 112 | end 113 | 114 | context 'nilable type' do 115 | let(:type) { SmartCore::Types::Value::UnboundMethod.nilable } 116 | 117 | it_behaves_like 'type-casting' 118 | it_behaves_like 'type-checking / type-validation (nilable)' 119 | end 120 | 121 | context 'runtime-based nilable type' do 122 | let(:type) { SmartCore::Types::Value::UnboundMethod().nilable } 123 | 124 | it_behaves_like 'type-casting' 125 | it_behaves_like 'type-checking / type-validation (nilable)' 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /spec/types/value/big_decimal_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::BigDecimal' do 4 | shared_examples 'type casting' do 5 | specify 'type-casting' do 6 | expect(type.cast('18491.1823')).to eq(BigDecimal('18491.1823')).and be_a(::BigDecimal) 7 | expect(type.cast('71')).to eq(BigDecimal('71')).and be_a(::BigDecimal) 8 | expect(type.cast(9301)).to eq(BigDecimal('9301')).and be_a(::BigDecimal) 9 | expect(type.cast(81.29)).to eq(BigDecimal('81.29')).and be_a(::BigDecimal) 10 | expect(type.cast('test')).to eq(BigDecimal('0.0')).and be_a(::BigDecimal) 11 | 12 | as_decimal = Class.new { def to_d; BigDecimal('77.11'); end; }.new 13 | expect(type.cast(as_decimal)).to eq(BigDecimal('77.11')).and be_a(::BigDecimal) 14 | 15 | expect { type.cast(Object.new) }.to raise_error(SmartCore::Types::TypeCastingError) 16 | expect { type.cast(BasicObject.new) }.to raise_error(SmartCore::Types::TypeCastingError) 17 | expect { type.cast([]) }.to raise_error(SmartCore::Types::TypeCastingError) 18 | expect { type.cast({}) }.to raise_error(SmartCore::Types::TypeCastingError) 19 | expect { type.cast(:test) }.to raise_error(SmartCore::Types::TypeCastingError) 20 | end 21 | end 22 | 23 | shared_examples 'type-checking / type-validation (non-nilable)' do 24 | specify 'type-checking' do 25 | expect(type.valid?(BigDecimal('123.456'))).to eq(true) 26 | expect(type.valid?(BigDecimal('99999999999999999999999999'))).to eq(true) 27 | 28 | expect(type.valid?(nil)).to eq(false) 29 | expect(type.valid?('123')).to eq(false) 30 | expect(type.valid?(:test)).to eq(false) 31 | expect(type.valid?(18_483.991238)).to eq(false) 32 | expect(type.valid?(99_999_999_999)).to eq(false) 33 | expect(type.valid?({})).to eq(false) 34 | expect(type.valid?([])).to eq(false) 35 | expect(type.valid?(Object.new)).to eq(false) 36 | expect(type.valid?(BasicObject.new)).to eq(false) 37 | end 38 | 39 | specify 'type-validation' do 40 | expect { type.validate!(BigDecimal('123.456')) }.not_to raise_error 41 | expect { type.validate!(BigDecimal('99999999999999999999999999')) }.not_to raise_error 42 | 43 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 44 | expect { type.validate!('123') }.to raise_error(SmartCore::Types::TypeError) 45 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 46 | expect { type.validate!(18_483.991238) }.to raise_error(SmartCore::Types::TypeError) 47 | expect { type.validate!(99_999_999_999) }.to raise_error(SmartCore::Types::TypeError) 48 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 49 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 50 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 51 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 52 | end 53 | end 54 | 55 | shared_examples 'type-checking / type-validation (nilable)' do 56 | specify 'type-checking' do 57 | expect(type.valid?(BigDecimal('123.456'))).to eq(true) 58 | expect(type.valid?(BigDecimal('99999999999999999999999999'))).to eq(true) 59 | expect(type.valid?(nil)).to eq(true) 60 | 61 | expect(type.valid?('123')).to eq(false) 62 | expect(type.valid?(:test)).to eq(false) 63 | expect(type.valid?(18_483.991238)).to eq(false) 64 | expect(type.valid?(99_999_999_999)).to eq(false) 65 | expect(type.valid?({})).to eq(false) 66 | expect(type.valid?([])).to eq(false) 67 | expect(type.valid?(Object.new)).to eq(false) 68 | expect(type.valid?(BasicObject.new)).to eq(false) 69 | end 70 | 71 | specify 'type-validation' do 72 | expect { type.validate!(BigDecimal('123.456')) }.not_to raise_error 73 | expect { type.validate!(BigDecimal('99999999999999999999999999')) }.not_to raise_error 74 | expect { type.validate!(nil) }.not_to raise_error 75 | 76 | expect { type.validate!('123') }.to raise_error(SmartCore::Types::TypeError) 77 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 78 | expect { type.validate!(18_483.991238) }.to raise_error(SmartCore::Types::TypeError) 79 | expect { type.validate!(99_999_999_999) }.to raise_error(SmartCore::Types::TypeError) 80 | expect { type.validate!({}) }.to raise_error(SmartCore::Types::TypeError) 81 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 82 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 83 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 84 | end 85 | end 86 | 87 | context 'non-nilable type' do 88 | let(:type) { SmartCore::Types::Value::BigDecimal } 89 | 90 | include_examples 'type casting' 91 | include_examples 'type-checking / type-validation (non-nilable)' 92 | end 93 | 94 | context 'runtime-based non-nilable type' do 95 | let(:type) { SmartCore::Types::Value::BigDecimal() } 96 | 97 | include_examples 'type casting' 98 | include_examples 'type-checking / type-validation (non-nilable)' 99 | end 100 | 101 | context 'nilable type' do 102 | let(:type) { SmartCore::Types::Value::BigDecimal.nilable } 103 | 104 | include_examples 'type casting' 105 | include_examples 'type-checking / type-validation (nilable)' 106 | end 107 | 108 | context 'runtime-based nilable type' do 109 | let(:type) { SmartCore::Types::Value::BigDecimal().nilable } 110 | 111 | include_examples 'type casting' 112 | include_examples 'type-checking / type-validation (nilable)' 113 | end 114 | 115 | specify 'has no support for runtime attributes' do 116 | expect { SmartCore::Types::Value::BigDecimal('1.0') }.to raise_error( 117 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 118 | ) 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /spec/features/invariants/definitioning_incompatabilities_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'INVARIANTS: Type invariant definition incompatabilities' do 4 | specify 'definition incompatabilities' do 5 | aggregate_failures 'definition_incompatabilities' do 6 | expect do # missing invariant name, missing block argument 7 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 8 | type.define_checker { true } 9 | type.invariant 10 | end 11 | end.to raise_error(::ArgumentError) 12 | 13 | expect do # missing invariant chain name, missing block argument 14 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 15 | type.define_checker { true } 16 | type.invariant_chain 17 | end 18 | end.to raise_error(::ArgumentError) 19 | 20 | expect do # missing block argument 21 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 22 | type.define_checker { true } 23 | type.invariant(:some_check) 24 | end 25 | end.to raise_error(::SmartCore::Types::ArgumentError) 26 | 27 | expect do # empty invariant name 28 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 29 | type.define_checker { true } 30 | type.invariant('') {} 31 | end 32 | end.to raise_error(::SmartCore::Types::ArgumentError) 33 | 34 | expect do # empty invariant name 35 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 36 | type.define_checker { true } 37 | type.invariant(:"") {} 38 | end 39 | end.to raise_error(::SmartCore::Types::ArgumentError) 40 | 41 | expect do # incompatible invariant name 42 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 43 | type.define_checker { true } 44 | type.invariant(Object.new) {} 45 | end 46 | end.to raise_error(::SmartCore::Types::ArgumentError) 47 | 48 | expect do # incompatible invariant name 49 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 50 | type.define_checker { true } 51 | type.invariant(nil) {} 52 | end 53 | end.to raise_error(::SmartCore::Types::ArgumentError) 54 | 55 | expect do # missing block argument 56 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 57 | type.define_checker { true } 58 | type.invariant_chain(:some_check) 59 | end 60 | end.to raise_error(::SmartCore::Types::ArgumentError) 61 | 62 | expect do # empty invariant chain name 63 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 64 | type.define_checker { true } 65 | type.invariant_chain('') {} 66 | end 67 | end.to raise_error(::SmartCore::Types::ArgumentError) 68 | 69 | expect do # empty invariant chain name 70 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 71 | type.define_checker { true } 72 | type.invariant_chain(:"") {} 73 | end 74 | end.to raise_error(::SmartCore::Types::ArgumentError) 75 | 76 | expect do # incompatible invariant name 77 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 78 | type.define_checker { true } 79 | type.invariant_chain(Object.new) {} 80 | end 81 | end.to raise_error(::SmartCore::Types::ArgumentError) 82 | 83 | expect do # incompatible invariant name 84 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 85 | type.define_checker { true } 86 | type.invariant_chain(nil) {} 87 | end 88 | end.to raise_error(::SmartCore::Types::ArgumentError) 89 | 90 | expect do # invariant without name (inside nested chain) 91 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 92 | type.define_checker { true } 93 | type.invariant_chain('test') do 94 | invariant 95 | end 96 | end 97 | end.to raise_error(::ArgumentError) 98 | 99 | expect do # missing invariant block (inside nested chain) 100 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 101 | type.define_checker { true } 102 | type.invariant_chain('test') do 103 | invariant(:test_invariant) 104 | end 105 | end 106 | end.to raise_error(::ArgumentError) 107 | 108 | expect do # empty invariant name (inside nested chain) 109 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 110 | type.define_checker { true } 111 | type.invariant_chain('test') do 112 | invariant(:"") {} 113 | end 114 | end 115 | end.to raise_error(::SmartCore::Types::ArgumentError) 116 | 117 | expect do # empty invariant name (inside nested chain) 118 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 119 | type.define_checker { true } 120 | type.invariant_chain('test') do 121 | invariant('') {} 122 | end 123 | end 124 | end.to raise_error(::SmartCore::Types::ArgumentError) 125 | 126 | expect do # incompatible invariant name (inside nested chain) 127 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 128 | type.define_checker { true } 129 | type.invariant_chain('test') do 130 | invariant(nil) {} 131 | end 132 | end 133 | end.to raise_error(::SmartCore::Types::ArgumentError) 134 | 135 | expect do # incompatible invariant name (inside nested chain) 136 | SmartCore::Types::Value.define_type(:DefIncFirstSpec) do |type| 137 | type.define_checker { true } 138 | type.invariant_chain('test') do 139 | invariant(Object.new) {} 140 | end 141 | end 142 | end.to raise_error(::SmartCore::Types::ArgumentError) 143 | end 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /spec/types/variadic/tuple_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Variadic::Tuple' do 4 | describe 'runtime-based behavior' do 5 | specify 'fails on non-class objects (should work only with classes)' do 6 | expect do 7 | SmartCore::Types::Variadic::Tuple(1, 5.0, :test) 8 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 9 | 10 | expect do 11 | SmartCore::Types::Variadic::Tuple(::Symbol, 123) 12 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 13 | 14 | expect do 15 | SmartCore::Types::Variadic::Tuple(Module.new, ::String) 16 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 17 | end 18 | 19 | specify 'requires tuple signature (runtime attributes)' do 20 | expect do 21 | SmartCore::Types::Variadic::Tuple() 22 | end.to raise_error(SmartCore::Types::IncorrectRuntimeAttributesError) 23 | end 24 | end 25 | 26 | describe 'logic' do 27 | specify 'type-casting (does not supported)' do 28 | type = SmartCore::Types::Variadic::Tuple(::String, ::Integer, ::Time) 29 | expect { type.cast(['test', 1, Time.now]) }.to raise_error( 30 | SmartCore::Types::TypeCastingUnsupportedError 31 | ) 32 | end 33 | 34 | # rubocop:disable Layout/LineLength 35 | specify 'type-checking / type-validation' do 36 | tuple_type = SmartCore::Types::Variadic::Tuple(::String, ::Float, ::Time) 37 | nilable_tuple_type = tuple_type.nilable 38 | 39 | aggregate_failures 'non-nilable (String, Float, Time) tuple' do 40 | expect(tuple_type.valid?(['test', 25.0, Time.now])).to eq(true) 41 | expect(tuple_type.valid?(['another-test', 7.11, Time.now])).to eq(true) 42 | expect { tuple_type.validate!(['test', 25.0, Time.now]) }.not_to raise_error 43 | expect { tuple_type.validate!(['another-test', 7.11, Time.now]) }.not_to raise_error 44 | 45 | expect(tuple_type.valid?([])).to eq(false) 46 | expect(tuple_type.valid?([:test, 5, Date.new])).to eq(false) 47 | expect(tuple_type.valid?([123, 456])).to eq(false) 48 | expect(tuple_type.valid?(nil)).to eq(false) 49 | expect(tuple_type.valid?(Set.new([]))).to eq(false) 50 | expect(tuple_type.valid?(Object.new)).to eq(false) 51 | expect(tuple_type.valid?(BasicObject.new)).to eq(false) 52 | expect(tuple_type.valid?('test')).to eq(false) 53 | 54 | expect { tuple_type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 55 | expect { tuple_type.validate!([:test, 5, Date.new]) }.to raise_error(SmartCore::Types::TypeError) 56 | expect { tuple_type.validate!([123, 456]) }.to raise_error(SmartCore::Types::TypeError) 57 | expect { tuple_type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 58 | expect { tuple_type.validate!(Set.new([])) }.to raise_error(SmartCore::Types::TypeError) 59 | expect { tuple_type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 60 | expect { tuple_type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 61 | end 62 | 63 | aggregate_failures 'nilable (String, Float, Time) tuple' do 64 | expect(nilable_tuple_type.valid?(['test', 25.0, Time.now])).to eq(true) 65 | expect(nilable_tuple_type.valid?(['another-test', 7.11, Time.now])).to eq(true) 66 | expect(nilable_tuple_type.valid?(nil)).to eq(true) 67 | expect { nilable_tuple_type.validate!(['test', 25.0, Time.now]) }.not_to raise_error 68 | expect { nilable_tuple_type.validate!(['another-test', 7.11, Time.now]) }.not_to raise_error 69 | 70 | expect(nilable_tuple_type.valid?([])).to eq(false) 71 | expect(nilable_tuple_type.valid?([:test, 5, Date.new])).to eq(false) 72 | expect(nilable_tuple_type.valid?([123, 456])).to eq(false) 73 | expect(nilable_tuple_type.valid?(Set.new([]))).to eq(false) 74 | expect(nilable_tuple_type.valid?(BasicObject.new)).to eq(false) 75 | expect(nilable_tuple_type.valid?('test')).to eq(false) 76 | 77 | expect { nilable_tuple_type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 78 | expect { nilable_tuple_type.validate!([:test, 5, Date.new]) }.to raise_error(SmartCore::Types::TypeError) 79 | expect { nilable_tuple_type.validate!([123, 456]) }.to raise_error(SmartCore::Types::TypeError) 80 | expect { nilable_tuple_type.validate!(Set.new([])) }.to raise_error(SmartCore::Types::TypeError) 81 | expect { nilable_tuple_type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 82 | expect { nilable_tuple_type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 83 | end 84 | 85 | simple_tuple_type = SmartCore::Types::Variadic::Tuple(::Time, ::Symbol) 86 | nilable_simple_tuple_type = simple_tuple_type.nilable 87 | 88 | aggregate_failures 'non-nilable (Time, Symbol) tuple' do 89 | expect(simple_tuple_type.valid?([Time.now, :test])).to eq(true) 90 | expect(simple_tuple_type.valid?([:test, Time.now])).to eq(false) 91 | expect(simple_tuple_type.valid?(nil)).to eq(false) 92 | 93 | expect { simple_tuple_type.validate!([Time.now, :test]) }.not_to raise_error 94 | expect { simple_tuple_type.validate!([:test, Time.now]) }.to raise_error(SmartCore::Types::TypeError) 95 | expect { simple_tuple_type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 96 | end 97 | 98 | aggregate_failures 'nilable (Time, Symbol) tuple' do 99 | expect(nilable_simple_tuple_type.valid?([Time.now, :test])).to eq(true) 100 | expect(nilable_simple_tuple_type.valid?([:test, Time.now])).to eq(false) 101 | expect(nilable_simple_tuple_type.valid?(nil)).to eq(true) 102 | 103 | expect { nilable_simple_tuple_type.validate!([Time.now, :test]) }.not_to raise_error 104 | expect { nilable_simple_tuple_type.validate!([:test, Time.now]) }.to raise_error(SmartCore::Types::TypeError) 105 | expect { nilable_simple_tuple_type.validate!(nil) }.not_to raise_error 106 | end 107 | end 108 | # rubocop:enable Layout/LineLength 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /spec/types/value/date_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'SmartCore::Types::Value::Date' do 4 | shared_examples 'type casting' do 5 | specify 'type casting' do 6 | expect(type.cast('2020-05-01')).to eq(Date.new(2020, 5, 1)).and be_a(::Date) 7 | expect(type.cast('20210417')).to eq(Date.new(2021, 4, 17)).and be_a(::Date) 8 | expect(type.cast('3rd Feb 2019')).to eq(Date.new(2019, 2, 3)).and be_a(::Date) 9 | expect(type.cast(Date.new(2021, 1, 7))).to eq(Date.new(2021, 1, 7)).and be_a(::Date) 10 | expect(type.cast(Date::Infinity)).to eq(Date::Infinity) 11 | expect(type.cast(DateTime::Infinity)).to eq(DateTime::Infinity) 12 | 13 | expect { type.cast('2001') }.to raise_error(SmartCore::Types::TypeCastingError) 14 | expect { type.cast(nil) }.to raise_error(SmartCore::Types::TypeCastingError) 15 | expect { type.cast(Object.new) }.to raise_error(SmartCore::Types::TypeCastingError) 16 | expect { type.cast(BasicObject.new) }.to raise_error(SmartCore::Types::TypeCastingError) 17 | expect { type.cast(Hash.new({})) }.to raise_error(SmartCore::Types::TypeCastingError) 18 | expect { type.cast([]) }.to raise_error(SmartCore::Types::TypeCastingError) 19 | end 20 | end 21 | 22 | shared_examples 'type-checking / type-validation (non-nilable)' do 23 | specify 'type-checking' do 24 | expect(type.valid?(Date.new)).to eq(true) 25 | expect(type.valid?(DateTime.new)).to eq(true) 26 | expect(type.valid?(Date::Infinity)).to eq(true) 27 | expect(type.valid?(DateTime::Infinity)).to eq(true) 28 | 29 | expect(type.valid?(nil)).to eq(false) 30 | expect(type.valid?(Time.new)).to eq(false) 31 | expect(type.valid?(123)).to eq(false) 32 | expect(type.valid?(Hash.new({}))).to eq(false) 33 | expect(type.valid?('test')).to eq(false) 34 | expect(type.valid?(:test)).to eq(false) 35 | expect(type.valid?(Object.new)).to eq(false) 36 | expect(type.valid?(BasicObject.new)).to eq(false) 37 | expect(type.valid?([])).to eq(false) 38 | end 39 | 40 | specify 'type-validation' do 41 | expect { type.validate!(Date.new) }.not_to raise_error 42 | expect { type.validate!(DateTime.new) }.not_to raise_error 43 | expect { type.validate!(Date::Infinity) }.not_to raise_error 44 | expect { type.validate!(DateTime::Infinity) }.not_to raise_error 45 | 46 | expect { type.validate!(nil) }.to raise_error(SmartCore::Types::TypeError) 47 | expect { type.validate!(Time.new) }.to raise_error(SmartCore::Types::TypeError) 48 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 49 | expect { type.validate!(Hash.new({})) }.to raise_error(SmartCore::Types::TypeError) 50 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 51 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 52 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 53 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 54 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 55 | end 56 | end 57 | 58 | shared_examples 'type-checking / type-validation (nilable)' do 59 | specify 'type-checking' do 60 | expect(type.valid?(Date.new)).to eq(true) 61 | expect(type.valid?(DateTime.new)).to eq(true) 62 | expect(type.valid?(Date::Infinity)).to eq(true) 63 | expect(type.valid?(DateTime::Infinity)).to eq(true) 64 | expect(type.valid?(nil)).to eq(true) 65 | 66 | expect(type.valid?(Time.new)).to eq(false) 67 | expect(type.valid?(123)).to eq(false) 68 | expect(type.valid?(Hash.new({}))).to eq(false) 69 | expect(type.valid?('test')).to eq(false) 70 | expect(type.valid?(:test)).to eq(false) 71 | expect(type.valid?(Object.new)).to eq(false) 72 | expect(type.valid?(BasicObject.new)).to eq(false) 73 | expect(type.valid?([])).to eq(false) 74 | end 75 | 76 | specify 'type-validation' do 77 | expect { type.validate!(Date.new) }.not_to raise_error 78 | expect { type.validate!(DateTime.new) }.not_to raise_error 79 | expect { type.validate!(Date::Infinity) }.not_to raise_error 80 | expect { type.validate!(DateTime::Infinity) }.not_to raise_error 81 | expect { type.validate!(nil) }.not_to raise_error 82 | 83 | expect { type.validate!(Time.new) }.to raise_error(SmartCore::Types::TypeError) 84 | expect { type.validate!(123) }.to raise_error(SmartCore::Types::TypeError) 85 | expect { type.validate!(Hash.new({})) }.to raise_error(SmartCore::Types::TypeError) 86 | expect { type.validate!('test') }.to raise_error(SmartCore::Types::TypeError) 87 | expect { type.validate!(:test) }.to raise_error(SmartCore::Types::TypeError) 88 | expect { type.validate!(Object.new) }.to raise_error(SmartCore::Types::TypeError) 89 | expect { type.validate!(BasicObject.new) }.to raise_error(SmartCore::Types::TypeError) 90 | expect { type.validate!([]) }.to raise_error(SmartCore::Types::TypeError) 91 | end 92 | end 93 | 94 | context 'non-nilable type' do 95 | let(:type) { SmartCore::Types::Value::Date } 96 | 97 | include_examples 'type casting' 98 | include_examples 'type-checking / type-validation (non-nilable)' 99 | end 100 | 101 | context 'runtime-based non-nilable type' do 102 | let(:type) { SmartCore::Types::Value::Date() } 103 | 104 | include_examples 'type casting' 105 | include_examples 'type-checking / type-validation (non-nilable)' 106 | end 107 | 108 | context 'nilable type' do 109 | let(:type) { SmartCore::Types::Value::Date.nilable } 110 | 111 | include_examples 'type casting' 112 | include_examples 'type-checking / type-validation (nilable)' 113 | end 114 | 115 | context 'runtime-based nilable type' do 116 | let(:type) { SmartCore::Types::Value::Date().nilable } 117 | 118 | include_examples 'type casting' 119 | include_examples 'type-checking / type-validation (nilable)' 120 | end 121 | 122 | specify 'has no support for runtime attributes' do 123 | expect { SmartCore::Types::Value::Date(Date.new) }.to raise_error( 124 | SmartCore::Types::RuntimeAttriburtesUnsupportedError 125 | ) 126 | end 127 | end 128 | --------------------------------------------------------------------------------