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>

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>

Тэг <Relations>

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

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

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

Тэг <Relation>

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

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

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

Возможные ситуации с тэгом <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