Skip to content

Commit

Permalink
Implemented basic type system
Browse files Browse the repository at this point in the history
  • Loading branch information
etki committed Jul 7, 2017
1 parent 73864eb commit 8e60cb8
Show file tree
Hide file tree
Showing 15 changed files with 397 additions and 44 deletions.
14 changes: 14 additions & 0 deletions lib/mapper/exception/compliance_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module AMA
module Entity
class Mapper
module Exception
# This exception is supposed to be thrown whenever end user provides
# malformed input - too many types, not enough types, not a type, etc.
class ComplianceError < RuntimeError
end
end
end
end
end
19 changes: 0 additions & 19 deletions lib/mapper/generic_type.rb

This file was deleted.

16 changes: 0 additions & 16 deletions lib/mapper/parameter_type.rb

This file was deleted.

20 changes: 20 additions & 0 deletions lib/mapper/type/any.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module AMA
module Entity
class Mapper
module Type
# Used as a wildcard to pass anything through
class Any
def hash
self.class.hash
end

def eql?(*)
true
end
end
end
end
end
end
41 changes: 41 additions & 0 deletions lib/mapper/type/attribute.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_String_literal: true

module AMA
module Entity
class Mapper
# Stores data about single type attribute
class Attribute
# @!attribute
# @return [AMA::Entity::Mapper::Type::Concrete]
attr_accessor :owner
# @!attribute
# @return [Symbol]
attr_accessor :name
# @!attribute type
# @return [AMA::Entity::Mapper::Type::Proxy]
attr_accessor :type
# @!attribute virtual
# @return [TrueClass, FalseClass]
attr_accessor :virtual
# @!attribute sensitive
# @return [TrueClass, FalseClass]
attr_accessor :sensitive
# @!attribute nullable
# @return [TrueClass, FalseClass]
attr_accessor :nullable
# @!attribute values Possible values this attribute can take
# @return [Array]
attr_accessor :values

def initialize(owner, name, type, **options)
@owner = owner
@name = name
@type = type
@nullable = options.fetch(:nullable, true)
@sensitive = options.fetch(:sensitive, false)
@virtual = options.fetch(:virtual, false)
end
end
end
end
end
72 changes: 72 additions & 0 deletions lib/mapper/type/concrete.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

require_relative '../exception/compliance_error'
require_relative 'any'

module AMA
module Entity
class Mapper
module Type
# Used to describe concrete type (that means, class is already known,
# though it's parameters may be resolved later).
class Concrete
# @!attribute type
# @return [Class]
attr_accessor :type
# @!attribute parameters
# @return [Hash{Symbol, AMA::Entity::Mapper::Type::Proxy}]
attr_accessor :parameters
# @!attribute attributes
# @return [Hash{Symbol, AMA::Entity::Mapper::Type::Attribute}]
attr_accessor :attributes

def initialize(type)
@type = type
@parameters = {}
@attributes = {}
end

def resolved?
parameters.values.all?(&:resolved?) &&
attributes.values.map(&:type).all?(&:resolved?)
end

def parameter?(parameter)
parameters.key?(parameter)
end

def resolve_parameter(parameter, substitution)
unless parameter?(parameter)
message = "Tried to resolve nonexistent parameter #{parameter} " \
"on type #{self}"
compliance_error message
end
parameters[parameter] = substitution
end

def hash
@type.hash ^ @parameters.hash ^ @attributes.hash
end

def eql?(other)
return true if other.is_a?(Any)
return false unless other.is_a?(self.class)
@type == other.type &&
@parameters == other.parameters &&
@attributes == other.attributes
end

def to_s
"Concrete type #{@type}"
end

private

def compliance_error(message)
raise ::AMA::Entity::Mapper::Exception::ComplianceError, message
end
end
end
end
end
end
35 changes: 35 additions & 0 deletions lib/mapper/type/parameter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module AMA
module Entity
class Mapper
module Type
# Used to fill in types that would be known later at runtime
class Parameter
attr_reader :owner
attr_reader :id

# @param [Class] owner
# @param [Symbol] id
def initialize(owner, id)
@owner = owner
@id = id
end

def to_s
"Parameter :#{id} (declared in #{owner})"
end

def hash
@owner.hash ^ @id.hash
end

def eql?(other)
return false unless other.is_a?(self.class)
id == other.id && owner == other.owner
end
end
end
end
end
end
52 changes: 52 additions & 0 deletions lib/mapper/type/proxy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

require_relative 'parameter'
require_relative 'any'
require_relative 'concrete'
require_relative '../exception/compliance_error'

module AMA
module Entity
class Mapper
module Type
# This class is used to serve as proxy for real types
class Proxy
attr_accessor :type

def resolved?
return true if type.is_a?(Any)
return false if type.is_a?(Parameter)
type.resolved?
end

def resolvable?
type.is_a?(Concrete)
end

def resolve_parameter(parameter, substitution)
if resolvable?
return type.resolve_parameter(parameter, substitution)
end
message = "Tried to resolve #{self.class} type " \
"with parameter #{parameter} => #{substitution}"
compliance_error(message)
end

def hash
type.hash
end

def eql?(o)
o.is_a?(self.class) && o.type == @type
end

private

def compliance_error(message)
raise ::AMA::Entity::Mapper::Exception::ComplianceError, message
end
end
end
end
end
end
64 changes: 64 additions & 0 deletions lib/mapper/type/registry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_String_literal: true

require 'logger'

module AMA
module Entity
class Mapper
module Type
# Holds all registered types
class Registry
attr_accessor :types

def initialize
@types = {}
end

# @param [AMA::Entity::Mapper::Type::Concrete] type
def register(type)
@types[type.type] = type
end

# @param [Class] klass
def registered?(klass)
@types.key?(klass)
end

def for(klass)
find_class_types(klass) | find_module_types(klass)
end

private

def inheritance_chain(klass)
cursor = klass
chain = []
loop do
chain.push(cursor)
cursor = cursor.superclass
break if cursor.nil?
end
chain
end

def find_class_types(klass)
inheritance_chain(klass).each_with_object([]) do |entry, carrier|
carrier.push(types[entry]) if types[entry]
end
end

def find_module_types(klass)
chain = inheritance_chain(klass).reverse
result = chain.reduce([]) do |carrier, entry|
ancestor_types = entry.ancestors.map do |candidate|
types[candidate]
end
carrier | ancestor_types.reject(&:nil?)
end
result.reverse
end
end
end
end
end
end
Empty file removed test/suite/system/.gitkeep
Empty file.
Loading

0 comments on commit 8e60cb8

Please sign in to comment.