Using controller
================

Creating endpoints
------------------

Controllers consist of :class:`~dmr.endpoint.Endpoint` objects.
Each HTTP method is an independent endpoint.

The simplest way to create an endpoint is to define sync
or async method with the right name (any HTTP method verb):

.. literalinclude:: /examples/using_controller/first_example.py
  :caption: views.py
  :language: python
  :linenos:

There will be several things that ``django-modern-rest`` will do for you here:

1. It will know that ``post`` endpoint will handle ``POST`` HTTP method,
   it is true for all HTTP methods,
   except :ref:`OPTIONS <meta>` (click to know why)
2. It will know that ``post`` will return :class:`str` as a response type spec.
   There's **no** implicit type conversions in ``django-modern-rest``.
   If your endpoint declares something to be returned, it must return this type
3. It will infer the default status code for ``post``, which will be ``201``.
   All other endpoints would have ``200`` as the default
4. All this metadata will be used to validate responses from this endpoint.
   Returning ``[]`` from ``post`` would trigger
   :exc:`~dmr.exceptions.ResponseSchemaError`,
   unless :ref:`response_validation` is explicitly turned off
5. The same metadata will be used to render OpenAPI spec

``django-modern-rest`` never creates implicit methods for you.
No ``HEAD``, no :ref:`OPTIONS <meta>`,
if you need them – create them explicitly.


Returning responses
-------------------

We have two general modes of working with responses:

1. Returning just raw data from "raw endpoints"
2. Returning real :class:`~django.http.HttpResponse` instances
   with granular configuration from "real endpoints"


Raw endpoints
-------------

.. note::

  "Raw endpoints" always have a response spec generated by default.
  Prefer :func:`~dmr.endpoint.modify` in simpler cases.

"Raw endpoints" can be either undecorated
or can use :func:`~dmr.endpoint.modify` decorator
to modify the response spec that will be **generated by default**.

.. literalinclude:: /examples/using_controller/modify.py
  :caption: views.py
  :language: python
  :linenos:
  :emphasize-lines: 14

Other response specs can be specified via
``extra_responses`` param to :func:`~dmr.endpoint.modify`,
:attr:`~dmr.controller.Controller.responses`
``Controller`` attribute,
or :attr:`~dmr.settings.Settings.responses` global setting.

Make sure that all responses that can be returned are described!

.. important::

  Despite the fact, that ``django-modern-rest`` does not have
  its own request and response primitives
  and uses :class:`~django.http.HttpRequest`
  and :class:`~django.http.HttpResponse`,
  users must not return Django responses directly.

  Instead, use any of the public APIs:

  - :meth:`~dmr.controller.Controller.to_response`
  - :meth:`~dmr.controller.Controller.to_error`
  - :exc:`~dmr.response.APIError`

  In case when you don't have a controller / endpoint instance
  (like in a middleware, for example),
  you can fallback to using :func:`~dmr.response.build_response`
  lower level primitive.

  Why?

  1. You can mess up the default headers / status codes
  2. You won't have the right json serializer / deserializer,
     which can be both slow and error-prone


Real endpoints
--------------

.. note::

  No response spec is generated by default for "real endpoints".
  All response specs must be provided manually.
  But, this way is way more configurable.

"Real endpoints" can use :func:`~dmr.endpoint.validate`
decorator, :attr:`~dmr.controller.Controller.responses`
``Controller`` attribute,
or :attr:`~dmr.settings.Settings.responses`
global setting to specify all possible responses.

To do that we utilize :class:`~dmr.metadata.ResponseSpec`:

.. literalinclude:: /examples/using_controller/validate.py
  :caption: views.py
  :language: python
  :linenos:
  :emphasize-lines: 15-20

``@validate`` decorator is useful but is not required
for real endpoint's declaration.
Instead, you can specify response specs in ``response`` field
of controller / settings.

.. literalinclude:: /examples/using_controller/implicit_validate.py
  :caption: views.py
  :language: python
  :linenos:
  :emphasize-lines: 15-18

If :ref:`response validation <response_validation>` passes, then it is all fine!

.. important::

  At least one explicit response spec is required for ``@validate`` endpoints.

  Note that semantic responses from auth / components / etc
  are not counted when validating real endpoints.
  You still have to use at least one explicit specification declaration.


Request lifecycle
-----------------

Here's the top level view on how request / response lifecycle looks like:

.. mermaid::
  :caption: Request lifecycle
  :config: {"theme": "forest"}

  graph
      Start[New request] --> BeforeThrottle[Throttling based on IP if any or 429];
      BeforeThrottle --> RendererNegotiation[Renderer is negotiated or 406];
      RendererNegotiation --> Auth[Auth if any or 401];
      Auth --> AfterThrottle[Throttling based on auth if any or 429];
      AfterThrottle --> ParserNegotiation[Parser is negotiated if any or 400];
      ParserNegotiation --> DataValidation[Request data is validated if any or 400];
      DataValidation --> BusinessLogic[Business logic];
      BusinessLogic --> Renderer[Response rendering];
      Renderer --> ResponseValidation[Response validation if any or 419];


Customizing controllers
-----------------------

.. tip::

  This is a sneak peek into our advanced API.
  90% of users will never need this.

``Controller`` is built to be customized with a class-level API.
If you need granular control, you can change anything.

- :attr:`~dmr.controller.Controller.allowed_http_methods`
  to support custom HTTP methods like ``QUERY``
  or your custom DSLs on top of HTTP
- :attr:`~dmr.controller.Controller.endpoint_cls`
  to customize how endpoints are created
- :attr:`~dmr.controller.Controller.csrf_exempt`
  to customize whether or not this controller is exempted from the CSRF
- :attr:`~dmr.controller.Controller.controller_validator_cls`
  to customize how controller is validated in import time

You can also customize :class:`~dmr.endpoint.Endpoint`
to change how API methods are executed:

- :attr:`~dmr.endpoint.Endpoint.serializer_context_cls`
  to customize how model for serialization of incoming data is created

Check out our :doc:`Public API <../deep-dive/public-api>`
for the most advanced features.


What's next?
------------

.. grid:: 3 3 2 2
    :class-row: surface
    :padding: 0
    :gutter: 2

    .. grid-item-card:: Headers and cookies
      :link: headers-and-cookies
      :link-type: doc

      Learn how to describe response headers and cookies.

    .. grid-item-card:: Redirects
      :link: redirects
      :link-type: doc

      Learn how to return HTTP redirect responses.

    .. grid-item-card:: Files
      :link: files
      :link-type: doc

      Learn how to return file responses.

    .. grid-item-card:: Validation
      :link: validation
      :link-type: doc

      Learn about optional response validation.


.. toctree::
   :hidden:

   validation.rst
   headers-and-cookies.rst
   redirects.rst
   files.rst
   meta.rst
