lib/attributor/dsl_compiler.rb in attributor-6.5 vs lib/attributor/dsl_compiler.rb in attributor-7.0
- old
+ new
@@ -10,10 +10,11 @@
# The reference option for an attribute is passed if a block is given
class DSLCompiler
attr_accessor :options, :target
+ include Attributor
def initialize(target, **options)
@target = target
@options = options
end
@@ -31,10 +32,19 @@
end
end
def attribute(name, attr_type = nil, **opts, &block)
raise AttributorException, "Attribute names must be symbols, got: #{name.inspect}" unless name.is_a? ::Symbol
+ if opts[:reference]
+ raise AttributorException, ":reference option can only be used when defining blocks" unless block_given?
+ if opts[:reference] < Attributor::Collection
+ err = ":reference option cannot be a collection. It must be a concrete Struct-like type containing the attribute #{name} you are defining.\n"
+ location_file, location_line = block.source_location
+ err += "The place where you are trying to define the attribute is here:\n#{location_file} line #{location_line}\n#{block.source}\n"
+ raise AttributorException, err
+ end
+ end
target.attributes[name] = define(name, attr_type, **opts, &block)
end
def key(name, attr_type = nil, **opts, &block)
unless name.is_a?(options.fetch(:key_type, Attributor::Object).native_type)
@@ -77,50 +87,83 @@
# attribute :state, String
# end
# @api semiprivate
def define(name, attr_type = nil, **opts, &block)
example_given = opts.key? :example
-
# add to existing attribute if present
if (existing_attribute = attributes[name])
if existing_attribute.attributes
existing_attribute.type.attributes(&block)
return existing_attribute
end
end
-
- # determine inherited type (giving preference to the direct attribute options)
- inherited_type = opts[:reference]
- unless inherited_type
- reference = options[:reference]
- if reference && reference.respond_to?(:attributes) && reference.attributes.key?(name)
- inherited_attribute = reference.attributes[name]
- opts = inherited_attribute.options.merge(opts) unless attr_type
- inherited_type = inherited_attribute.type
- opts[:reference] = inherited_type if block_given?
+
+ if attr_type.nil?
+ if block_given?
+ final_type, carried_options = resolve_type_for_block(name, **opts, &block)
+ else
+ final_type, carried_options = resolve_type_for_no_block(name, **opts)
end
+ else
+ final_type = attr_type
+ carried_options = {}
end
- # determine attribute type to use
- if attr_type.nil?
- if block_given?
- # Don't inherit explicit examples if we've redefined the structure
- # (but preserve the direct example if given here)
- opts.delete :example unless example_given
- attr_type = if inherited_type && inherited_type < Attributor::Collection
- # override the reference to be the member_attribute's type for collections
- opts[:reference] = inherited_type.member_attribute.type
- Attributor::Collection.of(Struct)
- else
- Attributor::Struct
- end
- elsif inherited_type
- attr_type = inherited_type
+ final_opts = opts.dup
+ final_opts.delete(:reference)
+
+ # Possibly add a reference for block definitions (No reference for leaves)
+ final_opts.merge!(add_reference_to_block(name, opts)) if block_given?
+ final_opts = carried_options.merge(final_opts)
+ Attributor::Attribute.new(final_type, final_opts, &block)
+ end
+
+
+ def resolve_type_for_block(name, **opts)
+ resolved_type = nil
+ carried_options = {}
+ ref = options[:reference]
+ if ref && ref.respond_to?(:attributes) && ref.attributes.key?(name)
+ type_from_ref = ref.attributes[name]&.type
+ resolved_type = type_from_ref < Attributor::Collection ? Attributor::Struct[] : Attributor::Struct
+ else
+ # Type for attribute with given name could not be determined from reference...or ther is not refrence: defaulting to Struct"
+ resolved_type = Attributor::Struct
+ end
+ [resolved_type, carried_options]
+ end
+
+ def resolve_type_for_no_block(name, **opts)
+ resolved_type = nil
+ carried_options = {}
+ ref = options[:reference]
+ if ref && ref.respond_to?(:attributes) && ref.attributes.key?(name)
+ resolved_type = ref.attributes[name].type
+ carried_options = ref.attributes[name].options
+ else
+ message = "Type for attribute with name: #{name} could not be determined.\n"
+ if ref
+ message += "You are defining '#{name}' without a type, and the passed in :reference type (#{ref}) does not have an attribute named '#{name}'.\n" \
else
- raise AttributorException, "type for attribute with name: #{name} could not be determined"
+ message += "You are defining '#{name}' without a type, and there is no :reference type to infer it from (Did you forget to add the type?).\n" \
end
+ message += "\nIf you are omiting a type thinking that would be inherited from the reference, make sure the right one is passed in," \
+ ", which has a #{name} defined, otherwise simply specify the type of the attribute you want.\n"
+ raise AttributorException, message
end
+ [resolved_type, carried_options]
+ end
- Attributor::Attribute.new(attr_type, opts, &block)
+ def add_reference_to_block(name, opts)
+ base_reference = options[:reference]
+ if opts[:reference] # Direct reference specified in the attribute, just pass it to the block
+ {reference: opts[:reference]}
+ elsif( base_reference && base_reference.respond_to?(:attributes) && base_reference.attributes.key?(name))
+ selected_type = base_reference.attributes[name].type
+ selected_type = selected_type.member_attribute.type if selected_type < Attributor::Collection
+ {reference: selected_type}
+ else
+ {}
+ end
end
end
end