173 lines
5.2 KiB
Ruby
173 lines
5.2 KiB
Ruby
module DiasporaFederation
|
|
# Provides a simple DSL for specifying {Entity} properties during class
|
|
# definition.
|
|
#
|
|
# @example
|
|
# property :prop
|
|
# property :optional, default: false
|
|
# property :dynamic_default, default: -> { Time.now }
|
|
# property :another_prop, xml_name: :another_name
|
|
# property :original_prop, alias: :alias_prop
|
|
# entity :nested, NestedEntity
|
|
# entity :multiple, [OtherEntity]
|
|
module PropertiesDSL
|
|
# @return [Hash] hash of declared entity properties
|
|
def class_props
|
|
@class_props ||= {}
|
|
end
|
|
|
|
# Define a generic (string-type) property
|
|
# @param [Symbol] name property name
|
|
# @param [Symbol] type property type
|
|
# @param [Hash] opts further options
|
|
# @option opts [Object, #call] :default a default value, making the
|
|
# property optional
|
|
# @option opts [Symbol] :xml_name another name used for xml generation
|
|
def property(name, type, opts={})
|
|
raise InvalidType unless property_type_valid?(type)
|
|
|
|
define_property name, type, opts
|
|
end
|
|
|
|
# Define a property that should contain another Entity or an array of
|
|
# other Entities
|
|
# @param [Symbol] name property name
|
|
# @param [Entity, Array<Entity>] type Entity subclass or
|
|
# Array with exactly one Entity subclass constant inside
|
|
# @param [Hash] opts further options
|
|
# @option opts [Object, #call] :default a default value, making the
|
|
# property optional
|
|
def entity(name, type, opts={})
|
|
raise InvalidType unless entity_type_valid?(type)
|
|
|
|
define_property name, type, opts
|
|
end
|
|
|
|
# Return array of missing required property names
|
|
# @return [Array<Symbol>] missing required property names
|
|
def missing_props(args)
|
|
class_props.keys - default_props.keys - args.keys
|
|
end
|
|
|
|
# Return a new hash of default values, with dynamic values
|
|
# resolved on each call
|
|
# @return [Hash] default values
|
|
def default_values
|
|
default_props.each_with_object({}) {|(name, prop), hash|
|
|
hash[name] = prop.respond_to?(:call) ? prop.call : prop
|
|
}
|
|
end
|
|
|
|
# @param [Hash] data entity data
|
|
# @return [Hash] hash with resolved aliases
|
|
def resolv_aliases(data)
|
|
data.map {|name, value|
|
|
if class_prop_aliases.has_key? name
|
|
prop_name = class_prop_aliases[name]
|
|
raise InvalidData, "only use '#{name}' OR '#{prop_name}'" if data.has_key? prop_name
|
|
[prop_name, value]
|
|
else
|
|
[name, value]
|
|
end
|
|
}.to_h
|
|
end
|
|
|
|
# @return [Symbol] alias for the xml-generation/parsing
|
|
# @deprecated
|
|
def xml_names
|
|
@xml_names ||= {}
|
|
end
|
|
|
|
# Finds a property by +xml_name+ or +name+
|
|
# @param [String] xml_name name of the property from the received xml
|
|
# @return [Hash] the property data
|
|
def find_property_for_xml_name(xml_name)
|
|
class_props.keys.find {|name| name.to_s == xml_name || xml_names[name].to_s == xml_name }
|
|
end
|
|
|
|
private
|
|
|
|
# @deprecated
|
|
def determine_xml_name(name, type, opts={})
|
|
if !type.instance_of?(Symbol) && opts.has_key?(:xml_name)
|
|
raise ArgumentError, "xml_name is not supported for nested entities"
|
|
end
|
|
|
|
if type.instance_of?(Symbol)
|
|
if opts.has_key? :xml_name
|
|
raise InvalidName, "invalid xml_name" unless name_valid?(opts[:xml_name])
|
|
opts[:xml_name]
|
|
else
|
|
name
|
|
end
|
|
elsif type.instance_of?(Array)
|
|
type.first.entity_name.to_sym
|
|
elsif type.ancestors.include?(Entity)
|
|
type.entity_name.to_sym
|
|
end
|
|
end
|
|
|
|
def define_property(name, type, opts={})
|
|
raise InvalidName unless name_valid?(name)
|
|
|
|
class_props[name] = type
|
|
default_props[name] = opts[:default] if opts.has_key? :default
|
|
xml_names[name] = determine_xml_name(name, type, opts)
|
|
|
|
instance_eval { attr_reader name }
|
|
|
|
define_alias(name, opts[:alias]) if opts.has_key? :alias
|
|
end
|
|
|
|
# Checks if the name is a +Symbol+ or a +String+
|
|
# @param [String, Symbol] name the name to check
|
|
# @return [Boolean]
|
|
def name_valid?(name)
|
|
name.instance_of?(Symbol)
|
|
end
|
|
|
|
def property_type_valid?(type)
|
|
%i(string integer boolean timestamp).include?(type)
|
|
end
|
|
|
|
# Checks if the type extends {Entity}
|
|
# @param [Class] type the type to check
|
|
# @return [Boolean]
|
|
def entity_type_valid?(type)
|
|
[type].flatten.all? {|type|
|
|
type.respond_to?(:ancestors) && type.ancestors.include?(Entity)
|
|
}
|
|
end
|
|
|
|
def default_props
|
|
@default_props ||= {}
|
|
end
|
|
|
|
# Returns all alias mappings
|
|
# @return [Hash] alias properties
|
|
def class_prop_aliases
|
|
@class_prop_aliases ||= {}
|
|
end
|
|
|
|
# @param [Symbol] name property name
|
|
# @param [Symbol] alias_name alias name
|
|
def define_alias(name, alias_name)
|
|
class_prop_aliases[alias_name] = name
|
|
# rubocop:disable Style/Alias
|
|
instance_eval { alias_method alias_name, name }
|
|
# rubocop:enable Style/Alias
|
|
end
|
|
|
|
# Raised, if the name is of an unexpected type
|
|
class InvalidName < RuntimeError
|
|
end
|
|
|
|
# Raised, if the type is of an unexpected type
|
|
class InvalidType < RuntimeError
|
|
end
|
|
|
|
# Raised, if the data contains property twice (with name AND alias)
|
|
class InvalidData < RuntimeError
|
|
end
|
|
end
|
|
end
|