Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe ADI::ServiceContainer::RegisterServices do

describe "tags" do
it "errors if not all tags have a `name` field" do
assert_compile_time_error "Failed to register service 'foo'. Tag must have a name.", <<-CR
assert_compile_time_error "Failed to register service 'foo' (Foo). Tag must have a name.", <<-CR
@[ADI::Register(tags: [{priority: 100}])]
record Foo
CR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module Athena::DependencyInjection::ServiceContainer::NormalizeDefinitions
end

unless definition_keys.includes? "tags"
definition["tags"] = {} of Nil => Nil
definition["tags"] = [] of Nil
end

unless definition_keys.includes? "bindings"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,41 +104,9 @@ module Athena::DependencyInjection::ServiceContainer::ProcessAutoconfigureAnnota
end
end

# TODO: Centralize tag handling logic between AutoConfigure and RegisterServices
# Append raw tags - will be normalized by ProcessTags pass
tags.each do |tag|
name, attributes = if tag.is_a?(StringLiteral)
{tag, {} of Nil => Nil}
elsif tag.is_a?(Path)
{tag.resolve.id.stringify, {} of Nil => Nil}
elsif tag.is_a?(NamedTupleLiteral) || tag.is_a?(HashLiteral)
unless tag[:name]
tag.raise "Failed to register service '#{service_id.id}' (#{klass}). Tag must have a name."
end

# Resolve a constant to its value if used as a tag name
if tag["name"].is_a? Path
tag["name"] = tag["name"].resolve
end

attributes = {} of Nil => Nil

# TODO: Replace this with `#delete`...
tag.each do |k, v|
attributes[k.id.stringify] = v unless k.id.stringify == "name"
end

{tag["name"], attributes}
else
tag.raise "Tag must be a 'StringLiteral' or 'NamedTupleLiteral', got '#{tag.class_name.id}'."
end

definition["tags"][name] = [] of Nil if definition["tags"][name] == nil
definition["tags"][name] << attributes
definition["tags"][name] = definition["tags"][name].uniq

TAG_HASH[name] = [] of Nil if TAG_HASH[name] == nil
TAG_HASH[name] << {service_id, attributes}
TAG_HASH[name] = TAG_HASH[name].uniq
definition["tags"] << tag
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# :nodoc:
#
# Normalizes service tags and populates TAG_HASH.
# Runs after RegisterServices and ProcessAutoconfigureAnnotations.
module Athena::DependencyInjection::ServiceContainer::ProcessTags
macro included
macro finished
{% verbatim do %}
{%
SERVICE_HASH.each do |service_id, definition|
klass = definition["class"]
normalized_tags = {} of Nil => Nil

(definition["tags"] || [] of Nil).each do |tag|
name, attributes = if tag.is_a?(StringLiteral)
{tag, {} of Nil => Nil}
elsif tag.is_a?(Path)
{tag.resolve.id.stringify, {} of Nil => Nil}
elsif tag.is_a?(NamedTupleLiteral) || tag.is_a?(HashLiteral)
unless tag[:name]
tag.raise "Failed to register service '#{service_id.id}' (#{klass}). Tag must have a name."
end

# Resolve a constant to its value if used as a tag name
if tag["name"].is_a? Path
tag["name"] = tag["name"].resolve
end

# TODO: Replace this with `#delete` if/when it's ever released
# https://github.com/crystal-lang/crystal/pull/9837
attributes = {} of Nil => Nil

tag.each do |k, v|
attributes[k.id.stringify] = v unless k.id.stringify == "name"
end

{tag["name"], attributes}
else
tag.raise "Tag must be a 'StringLiteral' or 'NamedTupleLiteral', got '#{tag.class_name.id}'."
end

normalized_tags[name] = [] of Nil if normalized_tags[name] == nil
normalized_tags[name] << attributes
normalized_tags[name] = normalized_tags[name].uniq

TAG_HASH[name] = [] of Nil if TAG_HASH[name] == nil
TAG_HASH[name] << {service_id, attributes}
TAG_HASH[name] = TAG_HASH[name].uniq
end

definition["tags"] = normalized_tags
end
%}
{% end %}
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -50,51 +50,13 @@ module Athena::DependencyInjection::ServiceContainer::RegisterServices
end
%}

{%
definition_tags = {} of Nil => Nil
tags = ann["tags"] || [] of Nil
{% # Store raw tags - will be normalized by ProcessTags pass

unless tags.is_a? ArrayLiteral
ann["tags"].raise "'tags' field of service '#{service_id.id}' must be an 'ArrayLiteral', got '#{tags.class_name.id}'."
end
tags = ann["tags"] || [] of Nil

# TODO: Centralize tag handling logic between AutoConfigure and RegisterServices
tags.each do |tag|
name, attributes = if tag.is_a?(StringLiteral)
{tag, {} of Nil => Nil}
elsif tag.is_a?(Path)
{tag.resolve.id.stringify, {} of Nil => Nil}
elsif tag.is_a?(NamedTupleLiteral) || tag.is_a?(HashLiteral)
unless tag[:name]
tag.raise "Failed to register service '#{service_id.id}'. Tag must have a name."
end

# Resolve a constant to its value if used as a tag name
if tag["name"].is_a? Path
tag["name"] = tag["name"].resolve
end

attributes = {} of Nil => Nil

# TODO: Replace this with `#delete`...
tag.each do |k, v|
attributes[k.id.stringify] = v unless k.id.stringify == "name"
end

{tag["name"], attributes}
else
tag.raise "Tag must be a 'StringLiteral' or 'NamedTupleLiteral', got '#{tag.class_name.id}'."
end

definition_tags[name] = [] of Nil if definition_tags[name] == nil
definition_tags[name] << attributes
definition_tags[name] = definition_tags[name].uniq

TAG_HASH[name] = [] of Nil if TAG_HASH[name] == nil
TAG_HASH[name] << {service_id, attributes}
TAG_HASH[name] = TAG_HASH[name].uniq
end
%}
unless tags.is_a? ArrayLiteral
ann["tags"].raise "'tags' field of service '#{service_id.id}' must be an 'ArrayLiteral', got '#{tags.class_name.id}'."
end %}

# Generic services are somewhat coupled to the annotation, so do a check here in addition to those in `ResolveGenerics`.
{%
Expand Down Expand Up @@ -138,8 +100,8 @@ module Athena::DependencyInjection::ServiceContainer::RegisterServices
shared: klass.class?,
calls: calls,
configurator: nil,
tags: definition_tags,
public: ann[:public] == true,
tags: tags,
public: ann["public"] == true,
decorated_service: nil,
bindings: {} of Nil => Nil,
generics: ann.args,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class Athena::DependencyInjection::ServiceContainer
RegisterServices,
ProcessAliases,
ProcessAutoconfigureAnnotations,
ProcessTags,
ProcessParameters,
ValidateGenerics,
],
Expand Down