Выбрать главу

Наконец, нам нужно создать экземпляр ATH::View::FormatHandlerInterface, который будет обрабатывать процесс подключения всего, чтобы возвращаемое значение действия контроллера отображалось через Crinja и возвращалось клиенту. Создайте src/services/html_format_handler.cr со следующим содержимым:

@[ADI::Register]

class HTMLFormatHandler

  include Athena::Framework::View::FormatHandlerInterface

  private CRINJA = Crinja.new loader: Crinja::Loader::

    FileSystem

    Loader.new "#{__DIR__}/../views"

  def call(view_handler : ATH::View::ViewHandlerInterface, view

    : ATH::ViewBase, request : ATH::Request, format : String) :

      ATH::Response

    ann_configs = request.action.annotation_configurations

    unless template_ann = ann_configs[Blog::Annotations::

      Template]?

      raise "Unable to determine the template for the

        '#{request.attributes.get "_route"}' route."

    end

    unless (data = view.data).is_a? Crinja::Object

      raise ATH::Exceptions::NotAcceptable.new "Cannot convert value of type '#{view.data.class}' to '#{format}'."

    end

    content = CRINJA.get_template(template_ann.name). render({data: view.data})

    ATH::Response.new content, headers: HTTP::Headers{"content- type" => "text/html"}

  end

  def format : String

    "html"

  end

end

Помимо выполнения некоторых вещей, с которыми мы уже должны быть знакомы, таких как регистрация службы и включение модуля интерфейса, мы также определяем метод #format, который возвращает формат, который обрабатывает этот тип. Мы также создали одноэлементный экземпляр Crinja, который будет загружать шаблоны из папки src/views. Crinja считывает шаблоны при каждом вызове #get_template, поэтому нет необходимости перезапускать сервер, если вы только внесли изменения в шаблон. Однако в его нынешнем виде для этого потребуется, чтобы путь существовал и был действительным как в среде разработки, так и в производственной среде. Рассмотрите возможность использования переменной среды для указания пути.

Наконец, мы определили метод #call, который имеет доступ к различной информации, которую можно частично использовать для обработки ответа. В нашем случае нам нужны только параметры view и request, последний из которых используется для получения всех конфигураций аннотаций, определенных на соответствующем маршруте. Здесь в игру вступает аннотация, которую мы создали ранее, поскольку мы можем проверить, применяется ли ее экземпляр к действию контроллера, связанному с текущим запросом. См. https://athenaframework.org/Framework/View/ для получения дополнительной информации о том, что отображается через эти параметры.

Далее мы обрабатываем некоторые контексты ошибок, например, если конечная точка не имеет аннотации шаблона или возвращаемое значение не может быть отображено через Crinja. Я намеренно создаю общие исключения, чтобы возвращался ответ об ошибке 500, поскольку мы не хотим утечки внутренней информации за пределы API.

Наконец, мы используем Crinja для получения шаблона на основе имени в аннотации и его визуализации, используя значение, возвращаемое из действия контроллера, в качестве значения объекта данных. Затем мы используем визуализированное содержимое в качестве тела ответа для ATH::Response, устанавливая тип содержимого ответа на text/html.

Чтобы включить такое поведение, нам просто нужно применить аннотацию @ [Blog::Annotations::Template("article.html.j2")] к нашему методу #article в ArticleController. Мы можем все проверить, сделав еще один запрос:

curl --request GET 'http://localhost:3000/article/1' --header

'accept: text/html'

Ответом в этом контексте должен быть наш HTML-шаблон. Если вы установите заголовок application/json или вообще удалите его, ответом должен быть JSON.

Резюме

И вот она, реализация блога, в которой используются некоторые интересные функции Athena, которые, в свою очередь, сделали реализацию простой и очень гибкой. Мы использовали преобразователи параметров для обработки как десериализации тела запроса, так и для поиска и предоставления значения из базы данных. Мы создали специальный обработчик аннотаций и форматов для поддержки ответов в нескольких форматах посредством согласования содержимого. И самое главное, мы прикоснулись к компоненту DI, показав, как он упрощает повторное использование объектов, а также как можно использовать концепцию контейнера на запрос для предотвращения утечки состояния между запросами.

Как вы можете себе представить, Athena использует немало концепций метапрограммирования для реализации своих функций. В следующей главе мы собираемся изучить основную функцию метапрограммирования — макросы.