# Документация API

Source: https://docs.sisk-framework.org/ru/docs/extensions/api-documentation.html

Расширение `Sisk.Documenting` позволяет автоматически генерировать документацию API для вашего приложения Sisk. Оно использует структуру вашего кода и атрибуты для создания полноценного сайта документации, поддерживая экспорт в формат Open API (Swagger).

> [!WARNING]
> Этот пакет опубликован как предварительная версия (1.7.0-rc3). Его поведение и API могут измениться в будущих обновлениях.

Установите пакет [Sisk.Documenting](https://www.nuget.org/packages/Sisk.Documenting/) из NuGet. Чтобы обслуживать документацию в виде HTML‑страницы, также установите [Sisk.Documenting.Html](https://www.nuget.org/packages/Sisk.Documenting.Html/):

```sh
dotnet add package Sisk.Documenting --prerelease
dotnet add package Sisk.Documenting.Html --prerelease
```

Исходный код доступен [здесь](https://github.com/sisk-http/core/tree/main/extensions/Sisk.Documenting).

Чтобы использовать `Sisk.Documenting`, необходимо зарегистрировать его в построителе приложения и пометить обработчики маршрутов атрибутами документации.

### Регистрация генерации документации

Вызовите метод расширения `UseApiDocumentation` у вашего `HttpServerHostContextBuilder`, чтобы открыть сгенерированную документацию API из того же роутера, который обслуживает приложение.

```csharp
using Sisk.Documenting;
using Sisk.Documenting.Exporters;

// ...

host.UseApiDocumentation(
    context: new ApiGenerationContext()
    {
        ApplicationName = "My Application",
        ApplicationDescription = "Description of my application.",
        ApplicationVersion = "1.0.0"
    },
    routerPath: "/api/docs",
    exporter: new OpenApiExporter() { ServerUrls = ["http://localhost:5555/"] });
```

- **context**: определяет метаданные о вашем приложении, такие как имя, описание и версия.
- **routerPath**: URL‑путь, по которому будет доступен пользовательский интерфейс документации (или JSON).
- **exporter**: настраивает способ экспорта документации. `OpenApiExporter` включает поддержку Open API (Swagger).

### Документирование конечных точек

Вы можете описывать свои конечные точки, используя атрибуты `[ApiEndpoint]` и `[ApiQueryParameter]` на методах‑обработчиках маршрутов.

### `ApiEndpoint`

Атрибут `[ApiEndpoint]` позволяет задать описание для конечной точки.

```csharp
[ApiEndpoint(Description = "Returns a greeting message.")]
public HttpResponse Index(HttpRequest request) { ... }
```

### `ApiQueryParameter`

Атрибут `[ApiQueryParameter]` документирует параметры строки запроса, которые принимает конечная точка.

```csharp
[ApiQueryParameter(name: "name", IsRequired = false, Description = "The name of the person to greet.", Type = "string")]
public HttpResponse Index(HttpRequest request) { ... }
```

- **name**: имя параметра запроса.
- **IsRequired**: указывает, обязателен ли параметр.
- **Description**: человекочитаемое описание параметра.
- **Type**: ожидаемый тип данных (например, `"string"`, `"int"`).

### `ApiEndpoint`

Аннотирует конечную точку общей информацией.

*   **Name** (string, обязателен в конструкторе): имя конечной точки API.
*   **Description** (string): краткое описание того, что делает конечная точка.
*   **Group** (string): позволяет группировать конечные точки (например, по контроллеру или модулю).
*   **InheritDescriptionFromXmlDocumentation** (bool, по умолчанию: `true`): если `true`, пытается использовать сводку XML‑документации метода, если `Description` не задано.

### `ApiHeader`

Документирует конкретный HTTP‑заголовок, который ожидает или использует конечная точка.

*   **HeaderName** (string, обязателен в конструкторе): ключ заголовка (например, `"Authorization"`).
*   **Description** (string): описание назначения заголовка.
*   **IsRequired** (bool): указывает, обязателен ли заголовок для запроса.

### `ApiParameter`

Определяет общий параметр для конечной точки, часто используемый для полей формы или параметров тела, не покрытых другими атрибутами.

*   **Name** (string, обязателен в конструкторе): имя параметра.
*   **TypeName** (string, обязателен в конструкторе): тип данных параметра (например, `"string"`, `"int"`).
*   **Description** (string): описание параметра.
*   **IsRequired** (bool): указывает, обязателен ли параметр.

### `ApiParameterExample`

Добавляет пример значения для документированного параметра в конкретной ситуации. Атрибут можно применять несколько раз к одному методу.

*   **ParameterName** (string, обязателен в конструкторе): имя параметра, к которому относится пример.
*   **Title** (string, обязателен в конструкторе): заголовок, идентифицирующий иллюстрируемую ситуацию.
*   **Description** (string): когда или почему применяется пример.
*   **Example** (string): значение примера.
*   **ExampleLanguage** (string): язык, используемый для форматирования значения примера, если применимо.

```csharp
[ApiQueryParameter("status", Type = "string", Description = "Filters orders by status.")]
[ApiParameterExample("status", "Pending orders", Example = "pending")]
[ApiParameterExample("status", "Shipped orders", Example = "shipped", Description = "Orders that left the warehouse.")]
```

### `ApiParametersFrom`

Автоматически генерирует документацию параметров из свойств указанного класса или типа.

*   **Type** (Type, обязателен в конструкторе): тип класса, свойства которого следует отразить.

### `ApiPathParameter`

Документирует переменную пути (например, в `/users/{id}`).

*   **Name** (string, обязателен в конструкторе): имя параметра пути.
*   **Description** (string): описание того, что представляет параметр.
*   **Type** (string): ожидаемый тип данных.

### `ApiQueryParameter`

Документирует параметр строки запроса (например, `?page=1`).

*   **Name** (string, обязателен в конструкторе): ключ параметра запроса.
*   **Description** (string): описание параметра.
*   **Type** (string): ожидаемый тип данных.
*   **IsRequired** (bool): указывает, обязателен ли параметр в запросе.

### `ApiRequest`

Описывает ожидаемое тело запроса.

*   **Description** (string, обязателен в конструкторе): описание тела запроса.
*   **Example** (string): необработанная строка с примером тела запроса.
*   **ExampleLanguage** (string): язык примера (например, `"json"`, `"xml"`).
*   **PayloadType** (Type): если задан, пример и схема будут сгенерированы автоматически из этого типа, когда соответствующие обработчики контекста поддерживают это.

### `ApiResponse`

Описывает возможный ответ конечной точки.

*   **StatusCode** (HttpStatusCode, обязателен в конструкторе): возвращаемый HTTP‑статус (например, `HttpStatusCode.OK`).
*   **Description** (string): описание условия, при котором возвращается этот ответ.
*   **Example** (string): необработанная строка с примером тела ответа.
*   **ExampleLanguage** (string): язык примера.
*   **PayloadType** (Type): если задан, пример и схема будут сгенерированы автоматически из этого типа, когда соответствующие обработчики контекста поддерживают это.

## Обработчики типов

Обработчики типов отвечают за преобразование ваших .NET‑типов (классов, перечислений и т.д.) в примеры для документации. Это особенно полезно для автоматической генерации примеров запросов и ответов на основе ваших моделей данных.

Эти обработчики настраиваются в `ApiGenerationContext`.

```csharp
using Sisk.Documenting.Content;

var context = new ApiGenerationContext()
{
    // ...
    BodyExampleTypeHandler = new JsonContentTypeHandler(),
    ParameterExampleTypeHandler = new JsonContentTypeHandler(),
    ContentSchemaTypeHandler = new JsonContentTypeHandler()
};
```

### JsonContentTypeHandler

`JsonContentTypeHandler` — встроенный обработчик, генерирующий JSON‑примеры, примеры параметров и JSON‑схемы. Он реализует `IExampleBodyTypeHandler`, `IExampleParameterTypeHandler` и `IContentSchemaTypeHandler`.

Его можно настроить с помощью конкретных `JsonSerializerOptions` или `IJsonTypeInfoResolver`, чтобы соответствовать логике сериализации вашего приложения.

```csharp
var jsonHandler = new JsonContentTypeHandler(new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
});

context.BodyExampleTypeHandler = jsonHandler;
context.ParameterExampleTypeHandler = jsonHandler;
context.ContentSchemaTypeHandler = jsonHandler;
```

### Пользовательские обработчики типов

Вы можете реализовать собственные обработчики для поддержки других форматов (например, XML) или для кастомизации генерации примеров.

#### IExampleBodyTypeHandler

Реализуйте этот интерфейс, чтобы генерировать примеры тела для типов запросов и ответов.

```csharp
public class XmlExampleTypeHandler : IExampleBodyTypeHandler
{
    public BodyExampleResult? GetBodyExampleForType(Type type)
    {
        // Генерировать XML‑строку для типа
        string xmlContent = MyXmlGenerator.Generate(type);

        return new BodyExampleResult(xmlContent, "xml");
    }
}
```

#### IExampleParameterTypeHandler

Реализуйте этот интерфейс, чтобы генерировать подробные описания параметров из типа (используется `[ApiParametersFrom]`).

```csharp
public class CustomParameterHandler : IExampleParameterTypeHandler
{
    public ParameterExampleResult[] GetParameterExamplesForType(Type type)
    {
        var properties = type.GetProperties();
        var examples = new List<ParameterExampleResult>();

        foreach (var prop in properties)
        {
            examples.Add(new ParameterExampleResult(
                name: prop.Name,
                typeName: prop.PropertyType.Name,
                isRequired: true,
                description: "Generated description"
            ));
        }

        return examples.ToArray();
    }
}
```

## Экспортеры

Экспортеры отвечают за преобразование собранных метаданных документации API в конкретный формат, который может быть использован другими инструментами или отображён пользователю.

### OpenApiExporter

Стандартный экспортер — `OpenApiExporter`, который генерирует JSON‑файл в соответствии со [спецификацией OpenAPI 3.0.0](https://spec.openapis.org/oas/v3.0.0).

```csharp
new OpenApiExporter()
{
    OpenApiVersion = "3.0.0",
    ServerUrls = new[] { "http://localhost:5555" },
    Contact = new OpenApiContact()
    {
        Name = "Support",
        Email = "support@example.com",
        Url = "https://example.com/support"
    },
    License = new OpenApiLicense()
    {
        Name = "MIT",
        Url = "https://opensource.org/licenses/MIT"
    },
    TermsOfService = "https://example.com/terms"
}
```

### LlmsApiExporter

`LlmsApiExporter` генерирует простой текстовый документ в стиле [llms.txt](https://llmstxt.org/), предназначенный для чтения языковыми моделями. Вы можете задать `Hostname`, используемый в примерах, а также `HeaderText` и `FooterText`, которые будут записаны в начале и в конце документа:

```csharp
new LlmsApiExporter(
    hostname: "https://api.example.com",
    headerText: "Public API of the Example service.",
    footerText: "Contact support@example.com for an API key.")
```

### Создание собственного экспортера

Вы можете создать собственный экспортер, реализовав интерфейс `IApiDocumentationExporter`. Это позволит выводить документацию в форматах, таких как Markdown, HTML, коллекция Postman или любой другой пользовательский формат.

Интерфейс требует реализации единственного метода: `ExportDocumentationContent`.

```csharp
using Sisk.Core.Http;
using Sisk.Documenting;

public class MyCustomExporter : IApiDocumentationExporter
{
    public HttpContent ExportDocumentationContent(ApiDocumentation documentation)
    {
        // 1. Обработать объект документации
        var sb = new StringBuilder();
        sb.AppendLine($"# {documentation.ApplicationName}");

        foreach(var endpoint in documentation.Endpoints)
        {
            sb.AppendLine($"## {endpoint.Method} {endpoint.Path}");
            sb.AppendLine(endpoint.Description);
        }

        // 2. Вернуть содержимое как HttpContent
        return new StringContent(sb.ToString(), Encoding.UTF8, "text/markdown");
    }
}
```

Затем просто используйте его в конфигурации:

```csharp
host.UseApiDocumentation(
    // ...
    exporter: new MyCustomExporter()
);
```

### Полный пример

Ниже приведён полный пример, демонстрирующий настройку `Sisk.Documenting` и документирование простого контроллера.

```csharp
using Sisk.Core.Entity;
using Sisk.Core.Http;
using Sisk.Core.Routing;
using Sisk.Documenting;
using Sisk.Documenting.Annotations;
using Sisk.Documenting.Exporters;

using var host = HttpServer.CreateBuilder(5555)
    .UseCors(CrossOriginResourceSharingHeaders.CreatePublicContext())
    .UseApiDocumentation(
        context: new ApiGenerationContext()
        {
            ApplicationName = "My application",
            ApplicationDescription = "It greets someone."
        },
        routerPath: "/api/docs",
        exporter: new OpenApiExporter() { ServerUrls = ["http://localhost:5555/"] })
    .UseRouter(router =>
    {
        router.MapInstance(new MyController());
    })
    .Build();

await host.StartAsync();

class MyController
{
    [RouteGet]
    [ApiEndpoint(Description = "Returns a greeting message.")]
    [ApiQueryParameter(name: "name", IsRequired = false, Description = "The name of the person to greet.", Type = "string")]
    public HttpResponse Index(HttpRequest request)
    {
        string? name = request.Query["name"].MaybeNullOrEmpty() ?? "world";
        return new HttpResponse($"Hello, {name}!");
    }
}
```

В этом примере обращение к `/api/docs` будет обслуживать сгенерированную документацию для API «My application», описывая конечную точку `GET /` и её параметр `name`.
