docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api
en/docs/api/json-rpc-bundle/documentation.md
new file +134
−0
1
+
# API documentation
2
+
3
+
You don't need to write documentation separately: the bundle builds it from the code, i.e. PHP types, PHPDoc, DTOs, enums, validation constraints and attributes.
4
+
5
+
| Request | Response |
6
+
| --- | --- |
7
+
| `GET /rpc` | [OpenRPC](https://spec.open-rpc.org/) document |
8
+
| `GET /rpc?info` | Only the document header (`info`, `servers`), without methods and schemas |
9
+
| `GET /rpc/postman` | Postman collection |
10
+
| `GET /rpc/v2`, `GET /rpc/v2/postman` | The same for an [API version](/docs/api/json-rpc-bundle/functionality/versions) |
11
+
| `GET /rpc/internal`, … | The same for an [API group](/docs/api/json-rpc-bundle/functionality/groups) |
12
+
13
+
If `protected_doc: true` is set in the [configuration](/docs/api/json-rpc-bundle/config), these requests also require a token.
14
+
15
+
## What goes into OpenRPC
16
+
17
+
* **Methods** are grouped by tags using the class alias from [`#[RPC\Info]`](/docs/api/json-rpc-bundle/add_rpc_service/info); the full class name is in the tag description.
18
+
* **Parameters and result** are JSON Schema built from native types and PHPDoc: union, nullable, `Dto[]`, `array<string, Dto>`; default values and whether a parameter is required.
19
+
* **DTOs and enums** are moved to `#/components/schemas` and referenced via `$ref`, so each is described once.
20
+
* **Constraints** from [`#[RPC\Assertions]`](/docs/api/json-rpc-bundle/add_rpc_service/assertions) are converted into schema rules.
21
+
* **Errors** of a method come from `@throws` tags in the docblock (the code and message of each exception).
22
+
* **Deprecated methods** (`@deprecated`, `#[\Deprecated]`) are marked with `"deprecated": true`.
23
+
* **Versions** are listed in `info.x-versions`; UFO service information is in the `x-ufo` extensions.
24
+
25
+
```php
26
+
/**
27
+
* Returns a user by id
28
+
* @param int $id User identifier
29
+
* @return UserDto
30
+
* @throws UserNotFoundException
31
+
*/
32
+
public function get(int $id): UserDto
33
+
```
34
+
35
+
## Postman collection
36
+
37
+
`GET /rpc/postman` returns a collection with all methods and sample parameters. The collection name is `RPC: <project_name> <version>`, for a group with the `[group]` suffix. The server address is in the `base_url` variable, the token is in the header named by `token_name`.
38
+
39
+
## Translating method names and descriptions
40
+
41
+
A method's name (`summary` in OpenRPC) and description can be set and translated without changing the code. Translations go into `translations/rpc.<locale>.yaml` (or `.yml`), the key is the RPC method name:
42
+
43
+
```yaml
44
+
# translations/rpc.uk.yaml
45
+
catalog.list:
46
+
title: Каталог
47
+
description: Список товарів
48
+
```
49
+
50
+
```yaml
51
+
# translations/rpc.en.yaml
52
+
catalog.list:
53
+
title: Catalog
54
+
description: List of products
55
+
```
56
+
57
+
The language is chosen from the `Accept-Language` header among the languages that have translation files (`uk-UA` gives `uk`); if nothing matches, `kernel.default_locale` is used. The response gets `Content-Language` and `Vary: Accept-Language`.
58
+
59
+
### Translations for an API group
60
+
61
+
Overrides for a single [group](/docs/api/json-rpc-bundle/functionality/groups) go into the `rpc/` subdirectory:
62
+
63
+
```
64
+
translations/rpc.uk.yaml # base, for all groups
65
+
translations/rpc/bfa.uk.yaml # only for the bfa group
66
+
```
67
+
68
+
```yaml
69
+
# translations/rpc/bfa.uk.yaml
70
+
catalog.list:
71
+
title: Каталог для партнерів # description is taken from the base translation
72
+
```
73
+
74
+
Each field is looked up in the following order (group takes precedence over language):
75
+
76
+
| Step | Source |
77
+
| --- | --- |
78
+
| 1 | group, client language |
79
+
| 2 | group, `kernel.default_locale` |
80
+
| 3 | base, client language |
81
+
| 4 | base, `kernel.default_locale` |
82
+
| 5 | `#[RPC\Info]` / PHPDoc |
83
+
84
+
The language is the last segment of the file name, the group is everything before it: `rpc/partner.v2.en.yaml` belongs to the `partner.v2` group.
85
+
86
+
:::warning
87
+
Don't name a group after a Symfony Translator domain (`messages`, `validators`, `security` or a custom one): the Translator scans `translations/` recursively and will treat `rpc/messages.uk.yaml` as part of the `messages` domain.
88
+
:::
89
+
90
+
:::info subtitle="Known issue in 10.4"
91
+
The list of languages is shared by all groups; see [Open issues](/docs/api/json-rpc-bundle/developers/known_issues/open).
92
+
:::
93
+
94
+
### Translations from extensions
95
+
96
+
A bundle that adds RPC methods can ship their translations. Additional directories with the same structure (`rpc.<locale>.yaml`, `rpc/<group>.<locale>.yaml`) are registered via `translations.paths`:
97
+
98
+
```yaml
99
+
ufo_json_rpc:
100
+
translations:
101
+
paths:
102
+
- '%kernel.project_dir%/vendor/acme/rpc-extension/translations'
103
+
```
104
+
105
+
or from the extension itself in `prepend()`:
106
+
107
+
```php
108
+
public function prepend(ContainerBuilder $container): void
109
+
{
110
+
$container->prependExtensionConfig('ufo_json_rpc', [
111
+
'translations' => ['paths' => [__DIR__.'/../../translations']],
112
+
]);
113
+
}
114
+
```
115
+
116
+
Directories are merged field by field, a later one overrides an earlier one; the application's `translations/` always comes last. Translations are collected during container compilation; in debug mode the container is rebuilt when the files change.
117
+
118
+
### `RpcLocaleNegotiator`
119
+
120
+
Extensions with their own translations can use the language selection service that the documentation uses:
121
+
122
+
```php
123
+
use Ufo\JsonRpcBundle\Server\RpcLocaleNegotiator;
124
+
125
+
public function __construct(private RpcLocaleNegotiator $localeNegotiator) {}
126
+
127
+
$locale = $this->localeNegotiator->negotiate($request, ['en', 'uk']); // null if the list is empty
128
+
$this->localeNegotiator->applyLocale($request); // after LocaleListener (priority 16)
129
+
$this->localeNegotiator->addLanguageHeaders($request, $response); // Content-Language + Vary
130
+
```
131
+
132
+
## Documentation caching
133
+
134
+
Outside debug mode the generated document is cached and served with an `ETag`. A client that sends `If-None-Match` gets `304 Not Modified` without regeneration. The cache key takes into account the route, version, `?info`, group, host, language, the set of methods and `container.build_id`, so the cache is refreshed on any change to the methods or container rebuild. Nothing needs to be configured; the pool is set by `cache.pool`.