Мы также используем не слишком широко известную функцию Crystal, чтобы сделать нашу логику настройки более краткой. Определению может быть присвоен префикс типа и точка перед именем метода в качестве ярлыка при определении метода класса для определенного типа. Например, предыдущий пример будет эквивалентен следующему:
struct ATH::Config::CORS
def self.conРисунок : ATH::Config::CORS?
new(
allow_credentials: true, allow_origin: ["*"],
)
end
end
Помимо того, что сокращенный синтаксис является более кратким, он устраняет необходимость выяснять, является ли тип структурой или классом. На этом этапе мы можем сделать запрос и получить обратно созданную статью, но, учитывая, что статья, возвращаемая из этой конечной точки, жестко запрограммирована, это бесполезно. Давайте проведем рефакторинг, чтобы мы могли создать статью на основе тела запроса.
Обработка тела запроса
Как мы видели ранее, поскольку мы включили JSON::Serializable
в нашу сущность, мы можем преобразовать его в представление JSON. Мы также можем сделать обратное: создать экземпляр на основе строки JSON или I/O. Для этого мы можем обновить действие нашего контроллера, обновив его так:
def create_article(request : ATH::Request) :
Blog::Entities::Article
if !(body = request.body) || body.peek.try &.empty?
raise ATH::Exceptions::BadRequest.new "Request does not have a body."
end
Blog::Entities::Article.from_json body
end
Параметры действия контроллера, например параметры пути маршрута или запроса, передаются действию в качестве аргументов метода. Например, если путь действия был "/add/{val1}/{val2}"
, метод действия контроллера будет следующим: def add(val1 : Int32, val2 : Int32) : Int32
, где разрешаются два добавляемых значения. из пути, преобразуются в ожидаемые типы и передаются методу. Аргументы действия также могут поступать из значений по умолчанию, аргументов типа ATH::Request
или атрибутов запроса.
В этом примере мы используем типизированный параметр ATH::Request
для получения доступа к телу запроса и его десериализации. Также технически возможно, что запрос не имеет тела, поэтому мы проверяем его существование, прежде чем продолжить, возвращая ответ об ошибке, если оно равно nil
или если тело запроса отсутствует. Мы также выполняем десериализацию непосредственно из I/O тела запроса, поэтому не нужно создавать промежуточную строку, что приводит к более эффективному использованию памяти.
Обработка ошибок в Athena очень похожа на любую другую программу Crystal, поскольку для представления ошибок она использует исключения. Athena определяет набор общих типов исключений в пространстве имен ATH::Exceptions
. Каждое из этих исключений наследуется от Athena::Exceptions::HTTPException
, который представляет собой особый тип исключения, используемый для возврата ответов об ошибках HTTP. Например, если тела не было, оно будет возвращено клиенту с кодом состояния 400
:
{
"code": 400,
"message": "Request does not have a body."
}
Базовый тип или дочерний тип также могут быть унаследованы для сбора дополнительных данных или добавления дополнительных функций. Любое возникающее исключение, не являющееся экземпляром Athena::Exceptions::HTTPException
, рассматривается как внутренняя ошибка сервера 500
. По умолчанию эти ответы об ошибках сериализуются в формате JSON, однако это поведение можно настроить. См. https://athenaframework.org/Framework/ErrorRendererInterface/ для получения дополнительной информации.
Теперь, когда мы убедились, что есть тело, мы можем продолжить и создать экземпляр нашей статьи, вернув тело Blog::Entities::Article.from_json
. Если бы вы сделали тот же запрос, что и раньше, но с этой полезной нагрузкой, вы бы увидели, что все, что вы отправляете, вы получите обратно в ответ:
{
"title": "My Title",
"body": "My Body"
}
Соответствующая команда cURL будет выглядеть следующим образом:
curl --request POST 'http://localhost:3000/article' \
--header 'Content-Type: application/json' \
--data-raw '{
"title": "My Title",
"body": "My Body"
}'