diff --git a/lib/schema_registry/maybe.rb b/lib/schema_registry/maybe.rb index 2ca1182..b100bf1 100644 --- a/lib/schema_registry/maybe.rb +++ b/lib/schema_registry/maybe.rb @@ -20,92 +20,6 @@ module Maybe Value = type_member(:out) { { upper: BasicObject } } - module ClassMethods - extend T::Sig - extend T::Helpers - abstract! - - sig do - type_parameters(:Key) - .params(input: T::Hash[T.type_parameter(:Key), T.untyped]) - .returns(T::Hash[T.type_parameter(:Key), T.untyped]) - end - # You can use this method to easily transform a Hash with `Maybe` values into one without them, - # filtering out `Maybe` instances that are empty and unwrapping the present ones. - # - # Given a hash containing `Maybe` instances, returns a hash with only the values that are present. - # It also unwraps the present values. - # - # For convenience, it also recursively serializes nested T::Structs and strips nested hashes, - # arrays and sets. - # - # ```ruby - # Maybe.strip({ a: Maybe.from(1), b: Maybe.empty, c: Maybe.from(3) }) - # # => { a: 1, c: 3 } - # ``` - def self.strip(input) # rubocop:disable Metrics/PerceivedComplexity - input - .reject { |_key, value| value == empty } - .to_h do |key, value| - unwrapped = value.is_a?(Maybe::Present) ? value.value : value - enumerated = - if unwrapped.is_a?(Array) || unwrapped.is_a?(Set) - unwrapped.map { |v| v.is_a?(T::Struct) ? strip(v.serialize) : strip(v) } - else - unwrapped - end - serialized = enumerated.is_a?(T::Struct) ? enumerated.serialize : enumerated - stripped = serialized.is_a?(Hash) ? strip(serialized) : serialized - - [key, stripped] - end - end - - # Creates an empty instance. - sig { returns(Absent) } - def self.empty - Absent.new - end - - sig { returns(Absent) } - # Creates an empty instance. - # Alias for self.empty - def self.none - empty - end - - sig { returns(Absent) } - # Creates an empty instance. - # Alias for self.empty - def self.absent - empty - end - - sig do - type_parameters(:Value) - .params(value: T.all(BasicObject, T.type_parameter(:Value))) - # .returns(Maybe[T.all(BasicObject, T.type_parameter(:Value))]) - .returns(T.untyped) - end - # Creates an instance containing the specified value. - # Necessary to make this work with sorbet-coerce - def self.new(value) - from(value) - end - - sig do - type_parameters(:Value) - .params(value: T.all(BasicObject, T.type_parameter(:Value))) - .returns(Maybe[T.all(BasicObject, T.type_parameter(:Value))]) - end - # Creates an instance containing the specified value. - def self.from(value) - Present[T.all(BasicObject, T.type_parameter(:Value))].new(value) - end - end - - mixes_in_class_methods(ClassMethods) - sig { abstract.returns(T::Boolean) } # `true` if this `Maybe` contains a value, `false` otherwise. def present?; end diff --git a/lib/schema_registry/maybe/absent.rb b/lib/schema_registry/maybe/absent.rb deleted file mode 100644 index 3bb3bd4..0000000 --- a/lib/schema_registry/maybe/absent.rb +++ /dev/null @@ -1,81 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Maybe - # Class used to represent the empty case - class Absent - extend T::Sig - extend T::Generic - include Maybe - final! - - Value = type_member { { fixed: T.noreturn } } - - sig(:final) { override.returns(FalseClass) } - def present? - false - end - - sig(:final) { override.returns(TrueClass) } - def absent? - true - end - - sig(:final) { override.returns(TrueClass) } - def empty? - absent? - end - - sig(:final) do - override - .type_parameters(:Default) - .params(default: T.type_parameter(:Default)) - .returns(T.type_parameter(:Default)) - end - def or_default(default) - default - end - - sig(:final) do - override - .type_parameters(:Return) - .params(_block: T.proc.params(v: Value).returns(T.type_parameter(:Return))) - .returns(T.nilable(T.type_parameter(:Return))) - end - def when_present(&_block) - nil - end - - sig(:final) do - override - .type_parameters(:Return) - .params(_block: T.proc.returns(T.type_parameter(:Return))) - .returns(T.nilable(T.type_parameter(:Return))) - end - def when_absent(&_block) - yield - end - - sig(:final) do - override.params(_block: T.proc.params(value: Value).returns(T::Boolean)).returns(Maybe[Value]) - end - def filter(&_block) - self - end - - sig(:final) do - override - .type_parameters(:Default) - .params(_block: T.proc.params(value: Value).returns(T.type_parameter(:Default))) - .returns(Maybe[T.all(BasicObject, T.type_parameter(:Default))]) - end - def map(&_block) - self - end - - sig(:final) { override.params(other: BasicObject).returns(T::Boolean) } - def ==(other) - self.class === other # rubocop:disable Style/CaseEquality - end - end -end diff --git a/lib/schema_registry/maybe/present.rb b/lib/schema_registry/maybe/present.rb deleted file mode 100644 index 21808e3..0000000 --- a/lib/schema_registry/maybe/present.rb +++ /dev/null @@ -1,96 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Maybe - # Class used to represent the case when a value is available - class Present - extend T::Sig - extend T::Generic - include Maybe - final! - - Value = type_member(:out) { { upper: BasicObject } } - - requires_ancestor { Object } - - sig(:final) { params(value: Value).void } - def initialize(value) - @value = value - end - - sig(:final) { override.returns(TrueClass) } - def present? - true - end - - sig(:final) { override.returns(FalseClass) } - def absent? - false - end - - sig(:final) { override.returns(FalseClass) } - def empty? - absent? - end - - sig(:final) do - override.type_parameters(:Default).params(_default: T.type_parameter(:Default)).returns(Value) - end - def or_default(_default) - value - end - - sig(:final) do - override - .type_parameters(:Return) - .params(_block: T.proc.params(v: Value).returns(T.type_parameter(:Return))) - .returns(T.nilable(T.type_parameter(:Return))) - end - def when_present(&_block) - yield value - end - - sig(:final) do - override - .type_parameters(:Return) - .params(_block: T.proc.returns(T.type_parameter(:Return))) - .returns(T.nilable(T.type_parameter(:Return))) - end - def when_absent(&_block) - nil - end - - sig(:final) do - override.params(_block: T.proc.params(value: Value).returns(T::Boolean)).returns(Maybe[Value]) - end - def filter(&_block) - return self if yield value - - Absent.new - end - - sig(:final) do - override - .type_parameters(:Default) - .params( - _block: - T.proc.params(value: Value).returns(T.all(BasicObject, T.type_parameter(:Default))) - ) - .returns(Maybe[T.all(BasicObject, T.type_parameter(:Default))]) - end - def map(&_block) - mapped = yield value - Present[T.all(BasicObject, T.type_parameter(:Default))].new(mapped) - end - - sig(:final) { override.params(other: BasicObject).returns(T::Boolean) } - def ==(other) - return false unless self.class === other # rubocop:disable Style/CaseEquality - - value == other.value - end - - sig(:final) { returns(Value) } - attr_reader :value - end -end diff --git a/spec/maybe_spec.rb b/spec/maybe_spec.rb deleted file mode 100644 index 9928986..0000000 --- a/spec/maybe_spec.rb +++ /dev/null @@ -1,284 +0,0 @@ -# typed: false - -require 'spec_helper' # you are forced to load the whole context for this :/ -require_relative '../lib/schema_registry/maybe' # unit tests are currently not possible - -RSpec.describe Maybe do - let(:present) { Maybe.from(6) } - let(:nilable) { Maybe.from(nil) } - let(:absent) { Maybe.empty } - - describe '#strip' do - let(:input) { { present: Maybe.from(6), absent: Maybe.empty, always: 5 } } - - subject { Maybe.strip(input) } - - it 'removes all absent values, unwraps present `Maybe`s' do - expect(subject).to match({ present: 6, always: 5 }) - end - end - - describe '#present?' do - subject { maybe.present? } - - context 'when a value is present' do - let(:maybe) { present } - - it 'returns true' do - expect(subject).to be true - end - end - - context 'when a `nil` value is present' do - let(:maybe) { nilable } - - it 'returns true' do - expect(subject).to be true - end - end - - context 'when a value is not present' do - let(:maybe) { absent } - - it 'returns false' do - expect(subject).to be false - end - end - end - - describe '#absent?' do - subject { maybe.absent? } - - context 'when a value is present' do - let(:maybe) { present } - - it 'returns false' do - expect(subject).to be false - end - end - - context 'when a `nil` value is present' do - let(:maybe) { nilable } - - it 'returns false' do - expect(subject).to be false - end - end - - context 'when a value is not present' do - let(:maybe) { absent } - - it 'returns true' do - expect(subject).to be true - end - end - end - - describe '#or_default' do - subject { maybe.or_default(default) } - - let(:default) { '1337' } - - context 'when a value is present' do - let(:maybe) { present } - - it 'returns the contained value' do - expect(subject).to be 6 - end - end - - context 'when a `nil` value is present' do - let(:maybe) { nilable } - - it 'returns the contained value' do - expect(subject).to be_nil - end - end - - context 'when a value is not present' do - let(:maybe) { absent } - - it 'returns the default value' do - expect(subject).to be default - end - end - end - - describe '#when_present' do - subject { maybe.when_present { |v| (v || 0) + 1 } } - - context 'when a value is present' do - let(:maybe) { present } - - it 'returns the value returned from the block' do - expect(subject).to be 7 - end - end - - context 'when a `nil` value is present' do - let(:maybe) { nilable } - - it 'returns the value returned from the block' do - expect(subject).to be 1 - end - end - - context 'when a value is not present' do - let(:maybe) { absent } - - it 'returns nil' do - expect(subject).to be_nil - end - end - end - - describe '#when_absent' do - subject { maybe.when_absent { 1 + 1 } } - - context 'when a value is present' do - let(:maybe) { present } - - it 'returns nil' do - expect(subject).to be_nil - end - end - - context 'when a `nil` value is present' do - let(:maybe) { nilable } - - it 'returns nil' do - expect(subject).to be_nil - end - end - - context 'when a value is not present' do - let(:maybe) { absent } - - it 'returns the value returned from the block' do - expect(subject).to be 2 - end - end - end - - describe '#filter' do - subject { maybe.filter { |_e| filter_output } } - - context 'when the filter returns false' do - let(:filter_output) { false } - - context 'and a value is present' do - let(:maybe) { present } - - it 'returns an empty instance' do - expect(subject.absent?).to be true - end - end - - context 'and a `nil` value is present' do - let(:maybe) { nilable } - - it 'returns an empty instance' do - expect(subject.absent?).to be true - end - end - - context 'and a value is not present' do - let(:maybe) { absent } - - it 'returns an empty instance' do - expect(subject.absent?).to be true - end - end - end - - context 'when the filter returns true' do - let(:filter_output) { true } - - context 'and a value is present' do - let(:maybe) { present } - - it 'returns the same instance' do - expect(subject).to be maybe - end - end - - context 'and a `nil` value is present' do - let(:maybe) { nilable } - - it 'returns the same instance' do - expect(subject).to be maybe - end - end - - context 'and a value is not present' do - let(:maybe) { absent } - - it 'returns the same instance' do - expect(subject).to be maybe - end - end - end - end - - describe '#map' do - subject { maybe.map { |e| (e || 0) + 1 } } - - context 'when a value is present' do - let(:maybe) { present } - - it 'returns an instance containing the result of the block' do - expect(subject).to eq Maybe.from(7) - end - end - - context 'when a `nil` value is present' do - let(:maybe) { nilable } - - it 'returns an instance containing the result of the block' do - expect(subject).to eq Maybe.from(1) - end - end - - context 'when a value is not present' do - let(:maybe) { absent } - - it 'returns the same instance' do - expect(subject).to be maybe - end - end - end - - describe '#==' do - subject { first == second } - - context 'when both have values' do - context 'and the values are the same' do - let(:first) { Maybe.from('Potato') } - let(:second) { Maybe.from('Potato') } - - it('returns true') { expect(subject).to be true } - end - - context 'and the values are different' do - let(:first) { Maybe.from('Tomato') } - let(:second) { Maybe.from('Carrot') } - - it('returns false') { expect(subject).to be false } - end - end - - context 'when only one has values' do - let(:first) { Maybe.from('Apricot') } - let(:second) { Maybe.empty } - - it('returns false') { expect(subject).to be false } - end - - context 'when neither has values' do - let(:first) { Maybe.empty } - let(:second) { Maybe.empty } - - it('returns true') { expect(subject).to be true } - end - end -end