Grape-Swagger: Как определить вложенный объект с динамическими ключами в документации Swagger?

Вопрос или проблема

Я использую 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 на предмет актуальных изменений или улучшений, которые могут облегчить вашу задачу.

Оцените материал
Добавить комментарий

Капча загружается...