diff --git a/.typos.toml b/.typos.toml index 0ad8f77f6..a9e2132dd 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,6 +1,8 @@ [default.extend-words] # Remove this once https://github.com/crate-ci/typos/issues/316 is resolved referer = "referer" +recruse = "recurse" +recruses = "recurses" [files] extend-exclude = [ diff --git a/src/components/framework/spec/ext/console/register_commands_spec.cr b/src/components/framework/spec/ext/console/register_commands_spec.cr index bead6cf66..7c193a3f7 100644 --- a/src/components/framework/spec/ext/console/register_commands_spec.cr +++ b/src/components/framework/spec/ext/console/register_commands_spec.cr @@ -17,7 +17,7 @@ private def assert_success(code : String, *, line : Int32 = __LINE__) : Nil end describe ATH do - describe "Console" do + describe "Console", tags: "compiler" do it "errors if no name is provided" do assert_error "Console command 'TestCommand' has an 'ACONA::AsCommand' annotation but is missing the commands's name. It was not provided as the first positional argument nor via the 'name' field.", <<-CR require "../../spec_helper.cr" diff --git a/src/components/framework/spec/view_controller_spec.cr b/src/components/framework/spec/view_controller_spec.cr index 4638e8950..0bb13f8c3 100644 --- a/src/components/framework/spec/view_controller_spec.cr +++ b/src/components/framework/spec/view_controller_spec.cr @@ -9,9 +9,8 @@ struct ViewControllerTest < ATH::Spec::APITestCase self.get("/view/json-array").body.should eq %([{"id":10,"name":"Bob"},{"id":20,"name":"Sally"}]) end - @[Pending] def test_json_serializable_nested_array : Nil - self.get("/view//json-array-nested").body.should eq %([[{"id":10,"name":"Bob"}]]) + self.get("/view/json-array-nested").body.should eq %([[{"id":10,"name":"Bob"}]]) end def test_json_serializable_empty_array : Nil diff --git a/src/components/framework/src/view/view.cr b/src/components/framework/src/view/view.cr index a9d84af1e..726fefac3 100644 --- a/src/components/framework/src/view/view.cr +++ b/src/components/framework/src/view/view.cr @@ -134,30 +134,43 @@ class Athena::Framework::View(T) self.response.headers.merge! headers end - # Does type reduction logic to determine what serializer the data should use. - # `nil` for `ASR::Serializable`, otherwise `JSON::Serializable`. + # Recurses over all the types within `T` to determine what serializer the data should use. protected def serializable_data : T? - {% if (T <= JSON::Serializable) && !(T <= ASR::Serializable) %} - # Single JSON::Serializable object - self.data - {% elsif (T <= NamedTuple) && (T.keys.any? do |k| - ntt = T[k] - - ((ntt <= JSON::Serializable) && !(ntt <= ASR::Serializable)) || - (ntt.union? && ntt.union_types.any? { |ut| ((ut <= JSON::Serializable) && !(ut <= ASR::Serializable)) }) || - (ntt.type_vars.any? { |t| ((t <= JSON::Serializable) && !(t <= ASR::Serializable)) }) - end) %} - # Namedtuple with a value of JSON::Serializable - self.data - {% elsif (T <= Enumerable) && T.type_vars.any? do |t| - ((t <= JSON::Serializable) && !(t <= ASR::Serializable)) || - (t.union? && t.union_types.any? { |ut| ((ut <= JSON::Serializable) && !(ut <= ASR::Serializable)) || - (ut.type_vars.any? { |utt| ((utt <= JSON::Serializable) && !(utt <= ASR::Serializable)) }) }) - end %} - # Enumerable (Hash, Array, Set, ...) of JSON::Serializable - self.data - {% else %} - nil + {% begin %} + {% + types_to_recurse = [T] + + types_to_recurse.each do |t| + t = t.resolve + + if t.union? + t.union_types.each do |ut| + types_to_recurse << ut + end + elsif t <= NamedTuple + t.keys.each do |k| + types_to_recurse << t[k].resolve + end + elsif t <= Enumerable + t.type_vars.each do |ut| + types_to_recurse << ut + end + + # Use `to_json` if a type includes `JSON::Serializable` but not `ASR::Serializable` + elsif (t <= JSON::Serializable) && !(t <= ASR::Serializable) + use_serializer_component = false + + # Use the serializer component if the type is `ASR::Serializable` + elsif (t <= ASR::Serializable) + use_serializer_component = true + else + # Fallback on the serializer component + use_serializer_component = true + end + end + %} + + {{ use_serializer_component == true ? nil : "self.data".id }} {% end %} end end