Гайд Делаем API Wrapper с помощью мета-таблиц

Каждое значение в Lua может иметь мета-таблицу. Мета-таблица представляет собой обычную таблицу Lua, которая с помощью набора ключей и мета-методов определяет поведение исходного значения при определенных операциях.
С помощью таких таблиц можно сделать рациональные числа или генерировать ctype для FFI. В этой статье рассмотрим создания простой оболочки API для Telegram Bot API с помощью мета-таблиц и библиотеки lua-requests.

Запрос к API представлена в этой форме:
Код:
https://api.telegram.org/bot<token>/METHOD_NAME
Предположим, что мы для создания запроса будем использовать функцию string.format, то тогда поля <token> и METHOD_NAME заменим на %s:
Lua:
local url = 'https://api.telegram.org/bot%s/%s'
Есть поддержка GET и POST запросов, я буду использовать POST-запросы (т.е. функцию requests.post). Тогда функция для отправки запроса будет таким:
Lua:
local requests = require 'requests'
local token = 'your token'

local function send(method, data)
  local request = url:format(token, method) -- 1
  return requests.post{ request, params = data } -- 2
end
1: вставка токена и метода;
2: POST-запрос с использованием строки запроса.

Теперь надо сделать так, чтобы методы были индексом таблицы:
Lua:
local mt = {} -- 1

local function new(token)
  local bot = { token = token, method = nil } -- 2
  return setmetatable(bot, mt) -- 3
end
1: создание пустой мета-таблицы;
2: создание таблицы с токеном и пустым методом;
3: установка мета-таблицы mt для таблицы bot. Функция возвращает первый аргумент.
После создания мета-таблицы создаем мета-метод __call, который будет вызывать функцию, которая использовалась в send, но токен и метод будет брать с таблицы:
Lua:
function mt.__call(self, data)
  assert(self.method, 'self.method == nil')
  local res = requests.post{ url:format(self.token, self.method), params = data }
  return res.json() -- 2
end
1: если self.method является nil, то будет вызвана ошибка.
2: превращение с текста JSON таблицы в таблицу Lua.
После этого создаем мета-метод индексирования:
Lua:
function mt.__index(self, index)
  local v = rawget(self, index)
  if v ~= nil then return v end -- 1

  local t = shallowcopy(self)
  t.method = index -- 2

  local mtt = shallowcopy(getmetatable(self))
  mtt.__index = nil -- 3
 
  return setmetatable(t, mtt) -- 4
end
1: проверяется, имеется ли значение в таблице с таким индексом. Если условие верно — то возвращаем, то значение.
2: копируем таблицу с помощью функции shallowcopy и вносим в ту таблицу метод.
3: копируем мета-таблицу и удаляем мета-метод __index.
4: возвращаем скопированную таблицу с мета-таблицей.

Теперь можно протестировать данную оболочку:
Lua:
local token = 'your token'
local bot = new(token) -- 1
local res, err = assert(bot.getMe()) -- 2
if not res.ok then
  error(tostring(res.error_code) .. ': ' .. res.description) -- 3
end
for i, k in pairs(res.result) do -- 4
  print(i, k)
end
1: инициализация таблицы с будущими запросами Telegram Bot API;
2: вызов метода getMe и проверка на удачный JSON-парсинг;
3: если API выдал ошибку, то остановить скрипт с причиной;
4: просмотр всех элементов результата.
Другой пример с методом sendMessage:
Lua:
local token = 'your token'
local bot = new(token)
local res, err = assert(bot.sendMessage{ chat_id = 1, text = 'test' })
if not res.ok then
  error(tostring(res.error_code) .. ': ' .. res.description)
end

Полный исходный код: https://gist.github.com/imring/c35bdfce7b37c78652f63c04d0d5473e
Подписывайтесь на мой канал в Telegram!

Источники
Lua 5.1 Reference Manual. URL: https://www.lua.org/manual/5.1/manual.html
Telegram Bot API. URL: https://core.telegram.org/bots/api
lua-requests: https://github.com/JakobGreen/lua-requests
 

Fott

Простреленный
3,430
2,265
Гайд есть, теперь жду обработчик
1620235657905.png