Вопрос или проблема
Я использую Grape-Swagger для генерации документации Swagger для моего API. У меня есть конечная точка, которая возвращает объект JSON с вложенным объектом с динамическими ключами.
У меня возникли проблемы с определением этого вложенного объекта в моей документации Swagger, чтобы он корректно генерировал клиентский API для TS.
Для интерфейса я использую typescript-swagger-api
для генерации API для клиента, но при генерации API интерфейса ResourcePermissions
поле groups заполняется объектом следующим образом:
export interface ResourcePermissions {
...,
groups?: object;
}
Но мне нужно что-то вроде этого:
export interface ResourcePermissions {
...,
groups?: Record<string, ResourcePermission>;
}
Вот пример JSON-ответа:
{
"resource_type": "user",
"resource_id": 1,
"groups": {
"group1": {
"rule": "read",
"id": 1,
"group_id": 1,
"group_name": "Group 1"
},
"group2": {
"rule": "write",
"id": 2,
"group_id": 2,
"group_name": "Group 2"
}
}
}
Я попробовал определить GrapeEntity вот так, но value_type
не учитывается.
class ResourcePermissions < Grape::Entity
expose :key
expose :key2
expose :groups,
documentation: { type: Hash,
value_type: Entities::ResourcePermission } do |resource_permissions, options|
resource_permissions[:groups].each_value { |value| Entities::ResourcePermission.represent(value, options) }
end
end
Я также попробовал другой подход с динамическими ключами вот так:
class MapEntity < Grape::Entity
expose :**, using: Entities::ResourcePermission
end
Но результат далек от ожидаемого.
export interface ResourcePermissions {
...,
"**"?: ResourcePermission;
}
Я даже попробовал что-то вроде этого по совету Copilot
documentation: { type: { Hash => Entities::ResourcePermission } }
но это полностью сломало генерацию.
Буду признателен за любую помощь.
Ответ или решение
Для успешного определения вложенного объекта с динамическими ключами в документации Swagger с использованием Grape-Swagger вам потребуется учесть несколько аспектов. Ниже приведены рекомендации и примеры, позволяющие достичь требуемого результата, чтобы ваш клиентский API корректно обрабатывал интерфейс ResourcePermissions
.
Понимание Структуры Ответа
В вашем случае, JSON-ответ API содержит объект groups
, который имеет динамические ключи, соответствующие группам, и каждый из этих ключей указывает на объект типа ResourcePermission
. Структура ответа выглядит следующим образом:
{
"resource_type": "user",
"resource_id": 1,
"groups": {
"group1": {
"rule": "read",
"id": 1,
"group_id": 1,
"group_name": "Group 1"
},
"group2": {
"rule": "write",
"id": 2,
"group_id": 2,
"group_name": "Group 2"
}
}
}
Вам нужно, чтобы groups
имел тип Record<string, ResourcePermission>
в сгенерированном TypeScript интерфейсе, а не просто object
.
Создание Grape Entity
Для этого мы можем использовать Grape::Entity
следующим образом. Вы можете экспонировать groups
как хэш, где значение будет представлено вашим Entities::ResourcePermission
. Используйте следующий код:
class ResourcePermissions < Grape::Entity
expose :resource_type
expose :resource_id
expose :groups, documentation: {
type: Hash,
description: "Dynamic groups of resource permissions",
value_type: Entities::ResourcePermission
} do |resource_permissions, options|
resource_permissions.groups
end
end
Инкапсуляция Динамических Ключей
Здесь важно правильно представить динамические ключи. Вам нужно будет явно указывать тип значения, соответствующего каждому ключу. Grape
не всегда может обработать динамические ключи напрямую, но можно сделать следующее:
class GroupsEntity < Grape::Entity
expose :**,
using: Entities::ResourcePermission
end
class ResourcePermissions < Grape::Entity
expose :resource_type
expose :resource_id
expose :groups, using: GroupsEntity
end
Генерация API-клиента
С использованием этого подхода Swagger сможет правильно интерпретировать и сгенерировать TypeScript
интерфейс, как вы ожидаете:
export interface ResourcePermissions {
resourceType: string;
resourceId: number;
groups?: Record<string, ResourcePermission>;
}
Заключение
При правильном обосновании структуры вложенного объекта и использовании Grape::Entity
с динамическими ключами, вы получите ожидаемую структуру интерфейса в генерируемом клиенте. Это поможет избежать создания интерфейсов с неопределенными типами и значительно упростит взаимодействие ваших разработчиков с API.
Не забывайте также проверить документацию Grape и Grape-Swagger на предмет актуальных изменений или улучшений, которые могут облегчить вашу задачу.