Repository: aiogram/aiogram (python)

Issue:
docs: broken formatting
Code blocks in [docs](https://docs.aiogram.dev/en/dev-3.x/dispatcher/router.html#nested-routers) are broken

![Image](https://github.com/user-attachments/assets/4e54a1bf-0e80-4163-bd6d-5375ca5e500b)

Pull request:
[#1653, #1654] docs: fix code block formatting in router.rst
# Description

This pull request fixes issue #1653, where code blocks in the documentation were broken due to incorrect indentation. The affected syntax was:

```rst
.. code-block:: python
    :caption: module_1.py
:name: module_1
```

The issue was caused by a missing indentation for the `:name:` directive, which broke Sphinx rendering. The fix was straightforward and involved properly aligning directive options under the `.. code-block::` line.

Additionally, this PR supersedes #1654, which aimed to fix the same issue but has been inactive for over three weeks.

Fixes #1653  
Closes #1654

## Type of change

- [x] Documentation (typos, code examples or any documentation update)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Tested by cloning the repository and building the documentation locally using Sphinx. The affected code blocks now render correctly without any warnings or formatting issues.

**Test Configuration**:
* Operating System: Ubuntu 24.04 LTS
* Python version: 3.12.9

# Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have made corresponding changes to the documentation
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes

Patch:
diff --git a/CHANGES/1664.bugfix.rst b/CHANGES/1664.bugfix.rst
new file mode 100644
index 00000000..9a4fb2a3
--- /dev/null
+++ b/CHANGES/1664.bugfix.rst
@@ -0,0 +1,25 @@
+Fix empty response into webhook.
+
+We need to return something “empty”, and “empty” form doesn’t work since
+it’s sending only “end” boundary w/o “start”.
+
+An empty formdata should look smth like this for Telegram to understand:
+
+::
+
+   --webhookBoundaryvsF_aMHhspPjfOq7O0JNRg
+   --webhookBoundaryvsF_aMHhspPjfOq7O0JNRg--
+
+But aiohttp sends only the ending boundary:
+
+::
+
+   --webhookBoundaryvsF_aMHhspPjfOq7O0JNRg--
+
+Such response doesn't suit Telegram servers.
+
+The fix replaces empty response with empty JSON response:
+
+::
+
+   {}
diff --git a/CHANGES/1666.doc.rst b/CHANGES/1666.doc.rst
new file mode 100644
index 00000000..ac768c87
--- /dev/null
+++ b/CHANGES/1666.doc.rst
@@ -0,0 +1,1 @@
+Fixed broken code block formatting in ``router.rst`` caused by incorrect indentation of directive options.
diff --git a/aiogram/webhook/aiohttp_server.py b/aiogram/webhook/aiohttp_server.py
index 7caa0e15..a453c9d8 100644
--- a/aiogram/webhook/aiohttp_server.py
+++ b/aiogram/webhook/aiohttp_server.py
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
 from asyncio import Transport
 from typing import Any, Awaitable, Callable, Dict, Optional, Set, Tuple, cast
 
-from aiohttp import MultipartWriter, web
+from aiohttp import JsonPayload, MultipartWriter, Payload, web
 from aiohttp.abc import Application
 from aiohttp.typedefs import Handler
 from aiohttp.web_middlewares import middleware
@@ -151,13 +151,17 @@ class BaseRequestHandler(ABC):
 
     def _build_response_writer(
         self, bot: Bot, result: Optional[TelegramMethod[TelegramType]]
-    ) -> MultipartWriter:
+    ) -> Payload:
+        if not result:
+            # we need to return something "empty"
+            # and "empty" form doesn't work
+            # since it's sending only "end" boundary w/o "start"
+            return JsonPayload({})
+
         writer = MultipartWriter(
             "form-data",
             boundary=f"webhookBoundary{secrets.token_urlsafe(16)}",
         )
-        if not result:
-            return writer
 
         payload = writer.append(result.__api_method__)
         payload.set_content_disposition("form-data", name="method")
diff --git a/docs/dispatcher/router.rst b/docs/dispatcher/router.rst
index 9f59492a..317f4e8d 100644
--- a/docs/dispatcher/router.rst
+++ b/docs/dispatcher/router.rst
@@ -232,7 +232,7 @@ Example:
 
 .. code-block:: python
     :caption: module_1.py
-:name: module_1
+    :name: module_1
 
     router2 = Router()
 
@@ -242,7 +242,7 @@ Example:
 
 .. code-block:: python
     :caption: module_2.py
-:name: module_2
+    :name: module_2
 
     from module_2 import router2
 


Test patch:
diff --git a/tests/test_webhook/test_aiohtt_server.py b/tests/test_webhook/test_aiohtt_server.py
index f29895ba..c185b841 100644
--- a/tests/test_webhook/test_aiohtt_server.py
+++ b/tests/test_webhook/test_aiohtt_server.py
@@ -150,13 +150,9 @@ class TestSimpleRequestHandler:
 
         resp = await self.make_reqest(client=client, text="spam")
         assert resp.status == 200
-        assert resp.content_type == "multipart/form-data"
-        result = {}
-        reader = MultipartReader.from_response(resp)
-        while part := await reader.next():
-            value = await part.read()
-            result[part.name] = value.decode()
-        assert not result
+        assert resp.content_type == "application/json"
+        expected_result = {}
+        assert await resp.json() == expected_result
 
     async def test_reply_into_webhook_background(self, bot: MockedBot, aiohttp_client):
         app = Application()