ApiMethod

Описывает API контроллер для обработки HTTP запросов.

Шаблон ApiMethod

<ApiMethod Name="" Route="" Method="" VersionCode="">
  <Enabled></Enabled>
  <Parameters>
    <Parameter Name="" Type="" Source="" Array="" Required="" />
  </Parameters>
  <Commands>
    <Command Name="" />
  </Commands>
  <Response>
    <Objects>
      <Object Name="">
        <Command Name="" />
      </Object>
      <Object Name="" Array="">
        <Command Name="" />
      </Object>
    </Objects>
    <Relations>
      <Relation ChildObject="" ChildField="" ParentObject="" ParentField="" />
    </Relations>
  </Response>
</ApiMethod>

Описание ApiMethod

<ApiMethod Name="" Route="" Method="" VersionCode="">
  <!--Тэги, специфичные для ApiMethod-->
</ApiMethod>

Атрибуты ApiMethod

Name

Имя кастомного API-метода.

Обязательный атрибут. Значение атрибута Name может быть любым, но уникальным среди всех описанных API-методов.

Route

Описывает конечную точку маршрута, по которому будет выполняться действие, описанное в ApiMethod.

Обязательный атрибут. Значение атрибута Route: ожидается любое значение, допустимое в URL.

Полный маршрут будет иметь вид http://<host>:<port>/data_api/{Route}, где <host> и <port> - IP-адрес и порт компьютера, на котором запущена серверная часть. Например, для Route="user_list" URL будет иметь вид http://localhost:5005/data_api/user_list.

Method

Метод HTTP запроса.

Обязательный атрибут. Ожидается один из вариантов: Get, Post, Put или Delete.

Поле носит информативный характер.

VersionCode

Версия используемого кода.

Обязательный атрибут. Ожидается положительное число больше нуля.

По умолчанию равно текущей версии кода. Зарезервировано для разделения изменений кода и соблюдения обратной совместимости.

Тэги, специфичные для ApiMethod

Enabled

Признак активности api-метода (только v3).

Необязательный тэг. Ожидается логическое выражение, результат условия или константа.

Если тэг <Enabled> отсутствует, то используется значение True.

Если значение тэга <Enabled> равно False, то в ответ на запрос сервер вернет статус 404 Not Found.

<Enabled>True</Enabled>

Parameters

Параметры, передаваемые в запрос.

Необязательный тэг. В качестве значения тэга ожидается список тэгов <Parameter>.

<Parameters>
  <Parameter Name="" Type="" Source="" Required="" />
</Parameters>

Тэг <Parameter>

Параметр, который будет передан в команду при выполнении.

Необязательный тэг.

Атрибуты тэга <Parameter>

Name

Название параметра, по которому будет доступен в команде.

Обязательный атрибут.

Type

Тип значения передаваемого в параметре. Обязательный атрибут. Ожидается одно из значений:

  • Integer

  • Decimal

  • DateTime

  • String

  • File

  • Boolean

  • Json

Source

Источник в HTTP-запросе для получения значения параметра. Обязательный атрибут. Ожидается одно из значений:

  • FromQuery - возвращает значения из строки запроса

  • FormData - multipart/form-data

  • FromBody - возвращает значения из текста запроса

  • FromHeader - возвращает значения из заголовков HTTP

Required

Признак, обязательный параметр или нет. Необязательный атрибут. Ожидается логическое значение. По умолчанию атрибут имеет значение True.

1. Для FromBody есть ограничение на обработку параметров относительно уровня вложенности объектов. В качестве параметров запроса можем указывать только поля основного объекта, которые принимают значение простых типов (строка, число, дата, логическое) и массив простых типов. Например, для объекта

{
  "id" : 1,
  "title" : "qwerty",
  "object" : {
    "field1" : "value1",
    "field2" : "value2"
  } 
}

мы укажем параметры "id"(Integer), "title"(String), но не можем указать параметры для "field1" и "field2". В этом случае мы должны указать параметр "object" с типом String. И вручную разбирать строку как json-объкт. PostgreSQL это позволяет.

2. Type="Array" будет возвращать массив строк.

Commands

Команды, выполняемые последовательно.

Необязательный тэг.

Полностью соответствует команде SequentialCommand на серверной части.

Во всех командах доступны все параметры описанные в тэге <Parameters>.

<Commands>
  <Command Name="" />
</Commands>

Response

Описывает дополнительные поля в ответе на запрос.

Необязательный тэг. Значение тэга <Response>: список тэгов <Objects> и <Relations>.

Если тэг <Response> не указан, то по умолчанию в json-объекте будут поля "result_code" и "error", если при выполнении запроса возникли ошибки.

<Response>
  <Objects>
    <Object Name="">
      <Command Name="" />
    </Object>
    <Object Name="" Array="">
      <Command Name="" />
    </Object>
  </Objects>
  <Relations>
    <Relation ChildObject="" ChildField="" ParentObject="" ParentField="" />
  </Relations>
</Response>

Тэг <Objects>

Описывает дополнительные поля в ответе на запрос.

Необязательный тэг. Значение тэга <Objects>: список тэгов <Object>.

Тэг <Object>

Дополнительное поле в ответе на запрос.

Необязательный тэг. В качестве значения тэга <Object> ожидается тэг <Command> - обращение к команде. При этом если команда не выполнялась ранее, то она будет выполнена, иначе будет получено значение предыдущего выполнения. Так же можно обратится к конкретному параметру в результате команды по его имени через атрибут Parameter.

Атрибуты тэга <Object>

Name

Название поля, которое будет указываться в json-объекте в ответе на запрос.

Обязательный атрибут.

Array

Будет ли поле представлено как массив или скалярное значение.

Необязательный атрибут. Ожидается логическое значение.

По умолчанию используется значение False. Если указан тэг <Relation> с именем этого объекта, то значение атрибута игнорируется и берется значение True.

Тэг <Relations>

Описывает отношение дополнительных полей между собой. Для указания вложенных объектов. Если не указан, то все таблицы - как самостоятельные поля json.

Необязательный тэг. Значение тэга <Relations>: список тэгов <Relation>.

Необходимо соблюдать порядок объявления тэгов <Relation> для объектов с большой вложенностью: описываем каждый уровень последовательно, начиная с верхнего уровня, и проходя дерево в глубину.

Тэг <Relation>

Внешний ключ, по которому будет строиться зависимость вложенных объектов.

Необязательный тэг. Значение тэга <Relation>: не ожидается.

Атрибуты тэга <Relation>

ChildObject

Подчинённый объект.

Обязательный атрибут. Ожидается имя одного из Object.

ChildField

Ссылающееся поле. Обязательный атрибут. Ожидается название поля подчинённого объекта.

ParentObject

Главный объект.

Обязательный атрибут. Ожидается имя одного из Object.

ParentField

Целевое поле. Обязательный атрибут. Ожидается название поля главного объекта.

Возможные ситуации с тэгом <Relation>:

  1. Указан неверный ChildField или ParentField - при выполнении запроса вывалится ошибка KeyNotFoundException.

  2. Указан неверный ChildObject или ParentObject - при выполнении запроса такой Relation будет игнорироваться.

  3. В качестве ChildObject и ParentObject указан один и тот же объект - выкинет ошибку при старте сервера.

Примеры

Простой пример на получение списка пользователей

URL запроса http://localhost:5005/data_api/user_list.

<ApiMethod Route="user_list" Method="Get">
  <Response>
    <Objects>
      <Object Name="users" Array="True">
        <Command Name="UserListSelectSqlQueryCommand" />
      </Object>
    </Objects>
  </Response>
</ApiMethod>

UserListSelectSqlQueryCommand - SqlQueryCommand с текстом select запроса на получения списка.

В качестве ответа будет получен json объект вида:

{
  "result_code": 0,
  "users": [
    {
      "user_name": "user2",
      "user_title": "Администратор"
    },
    {
      "user_name": "user",
      "user_title": "Разработчики"
    }
  ]
}

Пример обновления записи в базе

URL запроса http://localhost:5005/data_api/shift_inspection.

Тело запроса содержит объект:

{
  "id":66,
  "date":"2019-12-08 07:00:00Z",
  "reason_id":1,
  "comment":"комментарий"
}
<ApiMethod Route="shift_inspection" Method="Put">
  <Parameters>
    <Parameter Name="id" Type="Integer" Source="FromBody" />
    <Parameter Name="date" Type="DateTime" Source="FromBody" />
    <Parameter Name="reason_id" Type="Integer" Source="FromBody" />
    <Parameter Name="comment" Type="String" Source="FromBody" />
  </Parameters>
  <Commands>
    <Command Name="ShiftInspectionUpdateSqlQueryCommand" />
  </Commands>
</ApiMethod>

ShiftInspectionUpdateSqlQueryCommand содержит запрос вида:

UPDATE carrent.inspection
SET
  date = {date}::timestamp,
  reason_shift_id = {reason_id},
  comment = {comment}
WHERE
  inspection_id = {id};

В качестве ответа вернется стандартный объект:

{
  "result_code": 0
}

Пример построения вложенных объектов

URL запроса http://localhost:5005/data_api/inspections?user_id=41.

<ApiMethod Route="inspections" Method="Get">
  <Parameters>
    <Parameter Name="id" Type="Integer" Source="FromQuery" />
  </Parameters>
  <Response>
    <Objects>
      <Object Name="inspections" Array="True">
        <Command Name="InspectionSelectSqlQueryCommand" />
      </Object>
      <Object Name="damages" Array="True">
        <Command Name="DamageListSelectSqlQueryCommand" />
      </Object>
    </Objects>
    <Relations>
      <Relation ChildObject="damages" ChildField="inspection_id" ParentObject="inspections" ParentField="inspection_id" />
    </Relations>
  </Response>
</ApiMethod>

InspectionSelectSqlQueryCommand и DamageListSelectSqlQueryCommand - select запросы на получение списка записей для user_id = 41.

Вариант ответа:

{
  "result_code": 0,
  "inspections": [
    {
      "inspection_id": 11,
      "type": "inspection_pickup",
      "damages": [
        {
          "damage_id": 13,
          "description": "царапина"
        },
        {
          "damage_id": 14,
          "description": "вмятина"
        }
      ]
    },
    {
      "inspection_id": 12,
      "type": "inspection_pickup",
      "damages": [
        {
          "damage_id": 23,
          "description": "царапина"
        },
        {
          "damage_id": 41,
          "description": "вмятина"
        }
      ]
    }
  ]
}

Если бы в примере не был указан тэг <Relation>, то ответ имел бы вид:

{
  "result_code": 0,
  "inspection": [
    {
      "inspection_id": 11,
      "type": "inspection_pickup"
    },
    {
      "inspection_id": 12,
      "type": "inspection_pickup"
    }
  ],
  "damages": [
    {
      "damage_id": 13,
      "description": "царапина"
    },
    {
      "damage_id": 14,
      "description": "вмятина"
    },
    {
      "damage_id": 23,
      "description": "царапина"
    },
    {
      "damage_id": 41,
      "description": "вмятина"
    }
  ]
}

Пример передачи массива объектов в качестве параметра в теле запроса

URL запроса http://localhost:5005/data_api/get_fines.

Тело запроса:

{
  "cars": [
    {
      "title":"car1",
      "number":"num_1"
    },
    {
      "title":"car2",
      "number":"num_2"
    },
    {
      "title":"car3",
      "number":"num_3"
    },
    {
      "title":"car4",
      "number":"num_4"
    }
  ]
}
<ApiMethod Route="get_fines" Method="Post">
  <Parameters>
    <Parameter Name="cars" Type="Array" Source="FromBody" />
  </Parameters>
  <Commands>
    <Command Name="TestSqlQueryCommand" />
  </Commands>
</ApiMethod>

Команда TestSqlQueryCommand содержит примерный код запроса:

SELECT
  cars ->> 'title' AS title,
  cars ->> 'number' AS number
FROM(
  SELECT
    unnest({cars}::json[]) AS cars
) T 

После замены параметра будет иметь вид:

SELECT
  cars ->> 'title' AS title,
  cars ->> 'number' AS number
FROM(
  SELECT
    unnest(ARRAY[
      '{"title": "car1","number": "num_1"}',
      '{"title": "car2","number": "num_2"}',
      '{"title": "car3","number": "num_3"}',
      '{"title": "car4","number": "num_4"}'
    ]::json[]) AS cars
) T

Примеры обработки результатов кастомных команд

DataTable

  1. <Object Name="inspections">
      <Command Name="MyCustomCommand"/>
    </Object>

    Вернёт первый элемент таблицы:

    {
      "result_code": 0,
      "inspections": {
        "inspection_id": 56,
        "state": "new",
        "client": "Алёшин Пётр Алексеевич"
      }
    }
  2. <Object Name="inspections">
      <Command Name="MyCustomCommand" Parameter="client"/>
    </Object>

    Вернёт значение поля "client" для первого элемента таблицы:

    {
      "result_code": 0,
      "inspections": "Алёшин Пётр Алексеевич"
    }
  3. <Object Name="inspections" Array="True">
      <Command Name="MyCustomCommand"/>
    </Object>

    Вернёт массив всех строк таблицы:

    {
      "result_code": 0,
      "inspections": [
        {
          "inspection_id": 56,
          "state": "new",
          "client": "Алёшин Пётр Алексеевич"
        },
        {
          "inspection_id": 53,
          "state": "new",
          "client": "Иванов Иван Иванович"
        },
        {
          "inspection_id": 51,
          "state": "new",
          "client": "Сидоров Олег Петрович"
        }
      ]
    }
  4. <Object Name="inspections" Array="True">
      <Command Name="MyCustomCommand" Parameter="client"/>
    </Object>

    Вернёт массив значений поля "client" для всех строк таблицы:

    {
      "result_code": 0,
      "inspections": [
        "Алёшин Пётр Алексеевич",
        "Крутиков Сергей Владимирович",
        "Сидоров Петр Викторович"
      ]
    }

List<object> OR object[]

  1. <Object Name="inspections">
      <Command Name="MyCustomCommand"/>
    </Object>

    Вернёт первый элемент списка/массива:

    {
      "result_code": 0,
      "inspections": "Алёшин Пётр Алексеевич"
    }
  2. <Object Name="inspections" Array="True">
      <Command Name="MyCustomCommand"/>
    </Object>

    Вернёт массив всех значений списка/массива:

    {
      "result_code": 0,
      "inspections": [
        "Алёшин Пётр Алексеевич",
        "Крутиков Сергей Владимирович",
        "Сидоров Петр Викторович"
      ]
    }

Dictionary<string, object>

  1. <Object Name="inspections">
      <Command Name="MyCustomCommand"/>
    </Object>

    Вернёт первый элемент словаря:

    {
      "result_code": 0,
      "inspection": {
        "inspection_id": 56,
        "state": "new"
      }
    }
  2. <Object Name="inspections" Array="True">
      <Command Name="MyCustomCommand"/>
    </Object>

    Вернёт массив всех значений словаря:

    {
      "result_code": 0,
      "inspections": [
        {
          "inspection_id": 56,
          "state": "new"
        }
      ]
    }
  3. <Object Name="inspections">
      <Command Name="MyCustomCommand" Parameter="client"/>
    </Object>

    Вернёт:

    {
      "result_code": 0,
      "inspections": "Алёшин Пётр Алексеевич"
    }
  4. <Object Name="inspections" Array="True">
      <Command Name="MyCustomCommand" Parameter="client"/>
    </Object>

    Вернёт:

    {
      "result_code": 0,
      "inspections": [
        "Алёшин Пётр Алексеевич"
      ]
    }

List<Dictionary<string, object>>

  1. <Object Name="inspections">
      <Command Name="MyCustomCommand"/>
    </Object>

    Вернёт:

    {
      "result_code": 0,
      "inspections": {
        "inspection_id": 56,
        "state": "new"
      }
    }
  2. <Object Name="inspections" Array="True">
      <Command Name="MyCustomCommand"/>
    </Object>

    Вернёт:

    {
      "result_code": 0,
      "inspections": [
        {
          "inspection_id": 56,
          "client": "Алёшин Пётр Алексеевич",
          "state": "new"
        },
        {
          "inspection_id": 53,
          "client": "Крутиков Сергей Владимирович",
          "state": "new"
        },
        {
          "inspection_id": 51,
          "client": "Сидоров Петр Викторович",
          "state": "new"
        }
      ]
    }
  3. <Object Name="inspections">
      <Command Name="MyCustomCommand" Parameter="client"/>
    </Object>

    Вернёт:

    {
      "result_code": 0,
      "inspections": "Алёшин Пётр Алексеевич"
    }
  4. <Object Name="inspections" Array="True">
      <Command Name="MyCustomCommand" Parameter="client"/>
    </Object>

    Вернёт:

    {
      "result_code": 0,
      "inspections": [
        "Алёшин Пётр Алексеевич",
        "Крутиков Сергей Владимирович",
        "Сидоров Петр Викторович"
      ]
    }

Dictionary<string, List<Dictionary<string, object>>>

В процессе доработки!

<Object Name="data">
  <Command Name="MyCustomCommand" />
</Object>
<Object Name="inspections">
  <Command Name="MyCustomCommand"  Parameter="inspections"/>
</Object>
<Object Name="damages" Array="True">
  <Command Name="MyCustomCommand"  Parameter="damages"/>
</Object>

добавить поддержку Array

{
  "result_code": 0,
  "inspections": [
    {
      "inspection_id": 56,
      "state": "new"
    },
    {
      "inspection_id": 53,
      "state": "new"
    },
    {
      "inspection_id": 51,
      "state": "new"
    }
  ],
  "damages": [
    {
      "damage_id": 17,
      "inspection_id": 56,
      "open_description": "test 1"
    },
    {
      "damage_id": 18,
      "inspection_id": 51,
      "open_description": "test 2"
    }
  ]
}

Last updated