Как избавиться от Postman с помощью Emacs Org Mode

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

К старту курса по тестированию на Python при помощи шаблона Read the Docs, пакетов restclient, ob-restclient и Org Mode в Emacs напишем красивую, полезную документацию API, которая генерируется автоматически и легко отображается на статическом сайте.


По шкале полезности от «NFT» до «Integer» грамотное программирование обычно занимает чуть ниже нижнего уровня. Это несправедливо и не совсем заслуженно, но есть вещи, для которых LitProg подходит очень хорошо.

Вы сможете полностью избавиться от Postman или любого другого проприетарного клиента REST API. При таком подходе документация API — это программа. Единственное требование здесь — наличие API, с которым можно общаться. Мы воспользуемся бесплатным и общедоступным API — JSON Placeholder.

Я писал этот пост в режиме Org Mode, поэтому включённые в него примеры Org экспортируются без подсветки синтаксиса. Достаточно сказать, что текст Org выглядит в Emacs гораздо приятнее.

TL;DR. Законченный пример файла Org.

Начинаем

Сначала установите и настройте в вашем Emacs пакеты restclient и ob-restclient. Затем создайте в Emacs файл jsonplaceholder.org. В верхней части этого файла включите следующую информацию заголовка, измените автора и электронную почту:

#+title: JSON Placeholder API Documentation
#+author: Joseph Edwards VIII
#+email: foobar@example.com

#+startup: indent
#+export_file_name: index.html
#+options: num:nil ^:nil H:5 toc:2

#+setupfile: https://fniessen.github.io/org-html-themes/org/theme-readtheorg.setup

Не сразу понятно, что происходит, поэтому давайте немного поговорим об этом заголовке. Верхний раздел не требует пояснений. Второй раздел просто указывает Emacs на отображение org-режима с отступами, задаёт имя файла для экспорта и управляет отображением заголовков разделов и оглавления.

Последний раздел загружает HTML-шаблон setupfile, исрользуемый при экспорте в HTML. На практике для настройки CSS-темы ReadTheOrg я также добавляю несколько элементов #+html_head:.

Инициализация

Первый раздел документа будет служить функциональной цели и инициализировать документ. На практике этот раздел может быть довольно подробным, позволяя получить токен Bearer и значения из базы данных или иным образом инициализировать элементы, которые будут использоваться в программе позже. Для нашего простого примера нужен только URL-адрес API.

Параметризация URL API может показаться странной. Разве мы не хотим, чтобы параметры отображались в наших запросах? Возможно. Но, может быть, как у большинства инженеров-программистов, у нас есть отдельные среды — разработка, стейджинг и продакшн. Для изменения среды пришлось бы найти и заменить каждый URL. А так мы можем просто изменить адрес в одном месте.

Одно небольшое замечание: не хочется экспортировать этот раздел в HTML. Можно сообщить об этом Emacs, добавив в заголовок раздела тег :noexport:.

В файл jsonplaceholder.org добавьте этот код:

* Init :noexport:

#+name: api-url
: https://jsonplaceholder.typicode.com

Установка #+name: api-url означает, что можно ссылаться на скалярное значение : https://jsonplaceholder.typicode.com из других блоков src нашего документа. api-url можно взять как входные данные.

Введение для документации

Хотя на практике для выполнения запросов API мы будем использовать jsonplaceholder.org. При экспорте в HTML для создания документации API-запросы также будет выполнять Emacs. Это напоминает о том, что мы работаем с документацией. Поэтому напишите для своих читателей хорошее введение.

В файле jsonplaceholder.org добавьте этот код. Он скопирован из документации по API JSON Placeholder:

* Introduction

JSONPlaceholder is a free online REST API that you can use whenever you need some fake data. It can be in a README on GitHub, for a demo on CodeSandbox, in code examples on Stack Overflow, ...or simply to test things locally.

Выполнение запросов

Давайте задокументируем наш первый запрос и ответ. JSON Placeholder предоставляет шесть ресурсов и маршруты для всех методов HTTP. Начнём с ресурса /posts.

В файл jsonplaceholder.org добавьте этот код:

* Posts

The ~/posts~ resource allows us to create, read, update, and delete posts.

#+name: get-posts
#+begin_src restclient :var api=api-url
  GET :api/posts
#+end_src

Обратите внимание, что в +begin_src мы указываем Org в качестве языка использовать restclient.

Мы также устанавливаем переменную api в значение, хранящееся в скаляре с именем api-url. Вы просто используете переменную внутри блока src Org, как обычно в этом языке. В restclient переменные предваряются двоеточием, поэтому в запросе она используется так: GET :api/posts.

Теперь выполните вызов API. Для этого внутри блока src наберите C-c C-c. Ответ во всей своей красе отобразится под блоком запроса. И это иллюстрирует полезность грамотной документации API.

Мы не собираемся включать примеры запросов и ответов, которые должны обновляться каждый раз, когда изменяются ответы или конечные точки API. Включены только реальные запросы!

Экспорт

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

Для экспорта в index.html в том же каталоге, что и ваш файл org, в файле jsonplaceholder.org введите C-c C-e h o. Затем откройте этот файл в браузере:

Хорошо, но где же ответ? Emacs не сделал запрос API и не включил ответ. Но всё в порядке.

По умолчанию Emacs экспортирует только блок src. Чтобы экспортировать результаты, отредактируйте заголовок блока src с именем get-posts. Добавьте :exports both, чтобы код выглядел так:

#+begin_src restclient :var api=api-url :exports both

И экспорт. Теперь вы должны увидеть ответ:

Больше не нужно обновлять примеры ответов, внося в них досадные опечатки. Это реальные, интерактивные данные ответа.

Внешний вид

Прежде чем мы продолжим «программирование» как часть «грамотного программирования», давайте на минуту вспомним, что мы пишем документацию API и хотим, чтобы она была чистой и читабельной. Потратим немного времени на внешний вид документации.

Сейчас у нас некрасивый документ:

  1. Якоря к заголовкам выглядят некрасиво и читаются как index.html#orga1b1b2d.

  2. Ответ слишком большой. Чтобы добраться до следующего запроса, придётся прокручивать страницы.

  3. Заголовки ответа выгружаются в нижней части ответа. Это не чистый JSON.

Некрасивые якоря

Проблема некрасивых якорей решается легко. Когда Emacs выполняет экспорт в HTML, для заголовков он генерирует случайные теги якорей, но легко можно указать пользовательский идентификатор.

В файле jsonplaceholder.org установите курсор в конец заголовка Introduction. Чтобы добавить свойство, введите C-c C-x p. В качестве имени свойства введите CUSTOM_ID, а в качестве значения — introduction.

То же самое сделайте для заголовка Posts, установив CUSTOM_ID в resource-posts.

Заголовок теперь должен выглядеть так:

* Posts
:PROPERTIES:
:CUSTOM_ID: resource-posts
:END:

Огромный ответ

Осмотр index.html показывает, что размер блока ответа определяется тегом и классом pre.src. После некоторой работы с моей стороны получился такой фрагмент:

<style>pre.src{background:#343131;color:white;max-height:500px;overflow-y:auto;}</style>

При помощи заголовка #+html_head: можно очень легко добавить его в наш экспортированный index.html в Org Mode. После строки, которая с началом #+setupfile: добавьте:

#+html_head: <style>pre.src{background:#343131;color:white;max-height:500px;overflow-y:auto;} </style>

Кроме того, никому не нравится светлая тема, поэтому я перешёл на тёмную.

Некрасивый ответ

Некрасивые заголовки ответа приходят из пакета restclient. Без взлома самого restclient подавить их нельзя. Нет, это, конечно, можно сделать… В конце концов, это… это Emacs. Воспользуемся грамотным решением.

Одна из самых мощных особенностей грамотного программирования — языковая диагностика. Из Org Mode выполняется SQL-запрос, его данные передаются в блок src Python, где мы манипулируем ими, а затем передаём их в Bash, R, Elisp, Common Lisp и обратно в Python, сохраняя их в базе данных.

Мы воспользуемся оболочкой и CLI-процессором JSON jq. Установим jq и изменим запрос Posts:

#+name: get-posts
#+begin_src restclient :var api=api-url :results value
  GET :api/posts
#+end_src

#+begin_src shell :var response=get-posts :results value raw :wrap src js :exports results
  echo $response | jq
#+end_src

Поместим курсор в нижний блок src (shell) и введём C-c C-c. Блок ответа вызывает блок запроса get-posts и передаёт $response в jq. И больше никаких заголовков ответов. Только чистый, приятный JSON.

Объясним чёрную магию

Давайте посмотрим, что под капотом. Первое значительное изменение коснулось запроса. Мы больше не говорим: :exports both, но говорим :results value. Как уже упоминалось, по умолчанию для :exports используется только блок src. Удалив :exports both, мы вернёмся к умолчанию. Этот блок src не будет вычисляться, а результаты не будут экспортироваться.

Теперь мы говорим запросу: :results value. Что это значит? Это сложно, но в основном это означает, что Org не использует внешний процесс, а получает значение из самого вычисляемого кода.

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

  1. На этот раз мы вызываем оболочку. Любую (bash, cmd.exe, fish и так далее), которую вы используете.

  2. Устанавливаем новую переменную :var response=get-posts. Теперь этот блок src будет вызывать блок запроса с именем get-posts и помещать результаты в переменную с именем response.

  3. Устанавливаем :results value raw, потому что не хотим, чтобы Org обернул результаты в тип shell.

  4. Устанавливаем :wrap src js, потому что на самом деле хочется, чтобы Org обернул результаты в тип js.

  5. Устанавливаем :exports results, потому что хочется видеть только результаты, а не исходный код.

Наконец, внутри блока shell мы передаём переменную $results в jq, который удаляет строки комментариев в заголовке ответа. Для блока результатов он выводит красиво отформатированный JSON.

Собираем всё вместе

Давайте посмотрим, как сработали наши изменения внешнего вида. Экспортируйте ваш файл jsonplaceholder.org снова с помощью C-c C-e h o. Вы должны увидеть нечто подобное:

Дополнительные штрихи

Выглядит намного лучше, но есть несколько недоработок. Быстро пройдём до конца, потому что применяются те же принципы, которые мы уже обсуждали.

Измените файл jsonplaceholder.org, чтобы он выглядел так:

#+title: JSON Placeholder API Documentation
#+author: Joseph Edwards VIII
#+email: foobar@example.com

#+startup: indent
#+export_file_name: index.html
#+options: num:nil ^:nil H:5 toc:2

#+setupfile: https://fniessen.github.io/org-html-themes/org/theme-readtheorg.setup
#+html_head: <style>pre.src{background:#343131;color:white;max-height:500px;overflow-y:auto;} </style>
#+html_head: <style>p{margin-bottom:1em;}</style>
#+html_head: <style>h2{padding-top:1em;margin-top:2em;border-top:darkgray 2px solid;}</style>
#+html_head: <style>h3{padding-top:1em;margin-top:2em;border-top:lightblue 1px dashed;}</style>
#+html_head: <style>h4{padding-top:1em;margin-bottom:1em;}</style>
#+html_head: <style>h5{color:black;font-size:1em;padding-top:1em;margin-bottom:1em;} </style>
#+html_head: <style>div.response>h5,div.request>h5{padding-top:0;margin-bottom:0.5em;color:darkgray;font-size:0.8em;}</style>
#+html_head: <style>h6{margin-bottom:1em;}</style>

* Init :noexport:

#+name: api-url
: https://jsonplaceholder.typicode.com

* Introduction
:PROPERTIES:
:CUSTOM_ID: introduction
:END:

JSONPlaceholder is a free online REST API that you can use whenever you need some fake data. It can be in a README on GitHub, for a demo on CodeSandbox, in code examples on Stack Overflow, ...or simply to test things locally.

* Posts
:PROPERTIES:
:CUSTOM_ID: resource-posts
:END:

The ~/posts~ resource allows us to create, read, update, and delete posts.

** ~GET /posts~
:PROPERTIES:
:CUSTOM_ID: method-get-posts
:END:

Obtain all posts.

**** Request
:PROPERTIES:
:HTML_CONTAINER_CLASS: request
:END:

#+name: get-posts
#+begin_src restclient :var api=api-url :results value
  GET :api/posts
#+end_src

**** Response
:PROPERTIES:
:HTML_CONTAINER_CLASS: response
:END:

#+begin_src shell :var response=get-posts :results value raw :wrap src js :exports results
  echo $response | jq
#+end_src

Файл index.html в браузере должен выглядеть так:

Немного поговорим

Вкратце остановимся на нескольких пунктах вышеуказанных изменений.

Обратите внимание на новые свойства :HTML_CONTAINER_CLASS: request и :HTML_CONTAINER_CLASS: response.

При экспорте это свойство прикрепляет соответствующий CSS-класс (.request или .response) к элементу div, который содержит заголовок. Таким образом, чтобы было понятно, для чего предназначен каждый из этих блоков, можно стилизовать div.request>h5 и div.response>h5.

Также обратите внимание, что разделы запроса и ответа перенесены в новый подзаголовок Posts, данные для которого вызываются запросом GET /posts. Теперь, нажимая на "Posts" в меню боковой панели, мы увидим GET /posts как пункт подменю.

Это поведение контролируется включением директивы заголовка Org #+options: toc:2. Установив значение toc:1, мы не увидим подменю. А установив значение toc:3 в подменю ниже "GET /posts" увидим "Request" и "Response". Попробуйте и посмотрите!

Заканчиваем работу

Оставшаяся часть API документируется по установленной схеме. Процесс можно ускорить, используя yasnippet для вставки нового раздела метода. Однако это не отражает полезность подхода грамотного программирования к документации API.

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

Например, я написал бы то, что мы имеем на данный момент, в то же время, когда писал GET :api/posts, и широко использовал этот файл, делая заметки обо всех необходимых параметрах. После публикации кода любой пользователь Emacs из моей команды может открыть README.org и протестировать API самостоятельно.

Прежде чем я начну писать код, я могу сгенерировать index.html и поместить его в общедоступный статический каталог. Переходя по базовому URL моего API, вместо сообщения об ошибке вы увидите документацию API.

Позвольте завершить этот пост документированием остальных методов ресурса /posts. Документирование проиллюстрирует, как использовать restclient для выполнения HTTP-методов POST, PUT и DELETE.

Добавление метода POST

Добавим заголовок POST /posts под GET /posts. Вы можете скопировать, вставить и изменить эти части кода:

  1. :CUSTOM_ID: method-get-posts на method-post-posts.

  2. "Запрос" #+name: c get-posts в post-posts

  3. "Ответ" :var response get-posts на post-posts

В заголовок блока src запроса post-posts добавьте переменную :var user-id=1. В requests нужно указать тип содержимого и полезную нагрузку JSON для доставки.

Обратите внимание, что мы можем использовать переменные Org внутри полезной нагрузки JSON.

Окончательный POST-запрос должен выглядеть так:

#+name: post-posts
#+begin_src restclient :var api=api-url :var user-id=1 :results value
  POST :api/posts
  Content-Type: application/json

  {
    "title": "foo",
    "body": "bar",
    "userId": :user-id
  }
#+end_src

Теперь, когда вы поместите курсор в поле «Response» и введёте C-c C-c, вы должны получить данные нового POST, которые создали в JSON Placeholder. Это должно выглядеть примерно так:

{
  "title": "foo",
  "body": "bar",
  "userId": 1,
  "id": 101
}

Добавление метода PUT

Как вы можете себе представить, процесс происходит аналогичным образом. Скопируйте и вставьте POST /posts в PUT /posts/:id, измените пользовательский ID, имя запроса и переменную ответа на put-posts.

Теперь наш запрос должен выглядеть аналогично POST-запросу, только нужно указать id поста в URL. Используемый JSON будет обновлять существующий POST, а не создавать новый. Финальный запрос PUT должен быть таким:

#+name: put-posts
#+begin_src restclient :var api=api-url :var id=1 :var user-id=1 :results value
  PUT :api/posts/:id
  Content-Type: application/json

  {
    "title": "foo",
    "body": "bar",
    "userId": :user-id
  }
#+end_src

Добавление метода DELETE

Снова скопируйте, вставьте, измените и выполните.

#+name: delete-posts
#+begin_src restclient :var api=api-url :var id=1 :results value
  DELETE :api/posts/:id
#+end_src

Заключение

Окончательная документация не очень подробна (мы задокументировали только ресурс /posts), но остальное — это просто повторение того, что мы уже обсуждали. Тем не менее она довольно приятная. И легко сделать её ещё лучше:

Кроме того, как уже говорилось, для автоматизации создания новых разделов метода достаточно просто было бы написать yasnippet, а затем переходить от поля к полю, заполняя его.

Также повторюсь, что это не самый полезный подход к написанию документации API в стиле грамотного программирования. Полезнее писать её по ходу дела, используя документ вместо Postman. Тогда ваша документация не только никогда не будет ошибочной или не синхронизированной с API. Ещё вам не придётся возвращаться назад и тратить время на документирование того, что вы уже закончили!

Что дальше?

При написании документации API в стиле грамотного программирования могут оказаться полезными более сложные методы грамотного программирования.

У меня есть один документ, который я написал для Wurkzen API. Он позволяет получить и кешировать токен Bearer, который используется в последующих запросах. Затем с помощью API он конструирует ряд объектов и использует их, чтобы создать фиктивного клиента [программу], клиентов [компании], местоположения и выполнить все действия API над созданными документом фиктивными данными, а затем удалить эти данные при помощи самого документа.

При этом если в моём API есть ошибки, то они отображаются в документации API. Он также действует как интеграционный тест, подробный настолько, насколько вы захотите.

Каждый запрос, выполненный Emacs при экспорте в HTML, отображается в Emacs minibuffer и логируется в буфере Emacs *Messages*, так что ошибки можно найти с помощью инкрементного поиска, не читая index.html.

Исходник org

P. S.

В итоге я написал yasnippet. Введите lit-api и C-<tab> и переходите от поля к полю.

А продолжить изучение тестирования или разработки вы сможете на наших курсах:

  • Курс QA-инженер на Python

  • Профессия Fullstack-разработчик на Python

Узнайте подробности здесь.

Другие профессии и курсы

Data Science и Machine Learning

  • Профессия Data Scientist

  • Профессия Data Analyst

  • Курс «Математика для Data Science»

  • Курс «Математика и Machine Learning для Data Science»

  • Курс по Data Engineering

  • Курс «Machine Learning и Deep Learning»

  • Курс по Machine Learning

Python, веб-разработка

  • Профессия Fullstack-разработчик на Python

  • Курс «Python для веб-разработки»

  • Профессия Frontend-разработчик

  • Профессия Веб-разработчик

Мобильная разработка

  • Профессия iOS-разработчик

  • Профессия Android-разработчик

Java и C#

  • Профессия Java-разработчик

  • Профессия QA-инженер на JAVA

  • Профессия C#-разработчик

  • Профессия Разработчик игр на Unity

От основ — в глубину

  • Курс «Алгоритмы и структуры данных»

  • Профессия C++ разработчик

  • Профессия Этичный хакер

А также

  • Курс по DevOps

  • Все курсы

Источник: https://habr.com/ru/company/skillfactory/blog/649331/


Интересные статьи

Интересные статьи

Выдача XORShift кажется случайной Исследователь Мостафа Хассан (Mostafa Hassan) сумел взломать два генератора псведослучайных чисел (ГПСЧ) с помощью машинного обучения. Обученная двуслойная нейр...
25 июля, у Вовы и Вани Ильиных, основателей Email Soldiers, день рождения. Мы решили подготовить для них квест и чат-бота, который помогал бы двигаться по квесту.В&n...
Lenovo помогла DreamWorks Animation переоборудовать дата-центр, установив HPC-кластер с системой жидкостного охлаждения Neptune. Несмотря на перебои с поставками из-за COVID-19 и неожидан...
Введение Модель дисплея называется H016IT01. Данный дисплей интересен прежде всего тем, что он является транфлективным(transflective). Это означает, что изображение на нем должно быть видно даже...
Эта публикация написана после неоднократных обращений как клиентов, так и (к горести моей) партнеров. Темы обращений были разные, но причиной в итоге оказывался один и тот же сценарий, реализу...