DevGang
Авторизоваться

Использование метапрограммирования на Ruby для создания REST API из файла JSON

В этом посте мы рассмотрим AutoAPI, инструмент, который позволяет генерировать сервер sinatra на основе спецификаций конечных точек, записанных в JSON-файле. 

Ruby известен своей мощной поддержкой метапрограммирования, которое позволяет коду модифицировать себя во время выполнения. Метапрограммирование — это метод, когда программа обрабатывает другие программы как данные, и Ruby в этом преуспевает.

AutoAPI в настоящее время работает только с конечными точками GET, но будет дополняться в будущем. Он также возвращает данные в формате JSON или статических HTML-файлов, с планируемой поддержкой других типов MIME. 

В целях удобства и практики написания сценариев, AutoAPI реализован как shell-скрипт. Он запускается из любой папки и ищет файл endpoints.json в текущей директории.

# Get the file and parse the entire file into a ruby hash
file = File.read("#{Dir.pwd}/endpoints.json")
endpoints_hash = JSON.parse(file)

Мы используем JSON для преобразования файла в ruby Hash. Это обеспечит нас качественными методами, которые мы сможем использовать позжепо необходимости.

Теперь, поскольку мы используем Sinatra в качестве нашего сервера, нам понадобится способ динамического определения новых конечных точек из файла. Sinatra — это DSL для быстрого создания веб-приложений на Ruby с минимальными усилиями.

Давайте сначала рассмотрим метод send или public_send. Назначение этих методов заключается в том, что они в основном выполняют вызов метода экземпляра класса. Например, если у нас есть класс

class Class
    def hello
        puts "hello"
    end
end

Если бы мы сделали class.send(:hello), то наш результат был бы hello.

Вы также можете передать параметры для send, если метод принимает какие-либо аргументы.

Чтобы создать конечную точку GET в Sinatra, мы должны написать

get '/hello' do
  'Hello world!'
end

Мы могли бы разобрать это и обнаружить, что :get — это имя метода, '/hello — это имя пути, и все в блоке do мы будем называть do_block. Поэтому мы могли бы также мысленно переписать это как

get('/hello') do
    'Hello World'
end

или

send(:get, '/hello', &block)

где &block — это do_block, переданный методу send.

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

def create_endpoint(method, name, &block)
  Sinatra::Application.instance_eval do
    name = "/#{name}" if not name.start_with?(/\//)
    send(method, name, &block)
  end
end

Он принимает метод REST (GET, POST, PUT, DELETE), имя конечной точки и блок кода. Затем он использует instance_eval для создания нового маршрута на запущенном экземпляре Sinatra. Я добавил проверку, чтобы убедиться, что имя конечной точки предваряется косой чертой, потому что я столкнулся с этой случайной ошибкой, когда маршрут, казалось бы, определен, но недоступен, потому что он не начинается с косой черты. Наконец, мы просто отправляем метод, как показано выше. Просто, правда?? Честно говоря, это так просто.

Теперь мы должны указать, что файл endpoints.json имеет определенную структуру.

{
    "GET": {
        "json_response": {
            "header": {"Content-Type": "application/json"},
            "response": {
                "content": { "message": "Hello AutoAPI"},
                "file": false
            }
        },
        "json_response_file": {
            "header": {"Content-Type": "application/json"},
            "response": {
                "file": true,
                "content": "endpoints.json"
            }
        },
        "html_file": {
            "header": {"Content-Type": "text/html"},
            "response": {
                "file": true,
                "content": "test.html"
            }
        }
    }
}

Это пример файла конечных точек. Мы пройдемся по каждой конечной точке по одной. Таким образом, все глаголы REST действуют как ключи, таким образом, все маршруты GET находятся в значениях GET и т. д. и т. п. Наша первая конечная точка — json_response. Она должна была проверить, могу ли я получить жестко закодированный ответ. Заголовок — это вложенный объект, содержащий то, что вы ожидаете в типичном запросе HTTP GET. Здесь мы передаем только Content-Type. Для ответа у нас есть ключ файла, который указывает, должна ли конечная точка отправлять файл или она должна отправлять значение содержимого как JSON. Этот файл также должен находиться в той же папке, что и скрипт при его запуске. В этом примере вторая конечная точка на самом деле просто возвращает файл endpoints.json. Третья конечная точка имеет другой тип содержимого и возвращает файл HTML.

Для обработки этих конечных точек я написал следующий код

endpoints_hash.each do |method, paths|
  paths.each do |path, params|
    create_endpoint(method.downcase.to_s, "#{path.to_s}") do
      content_type :json if params["Content-Type"] == "application/json"
      content_type :html if params["Content-type"] == "text/html"
      if params["response"]["file"]
        send_file "#{Dir.pwd}/#{params["response"]["content"]}"
      else
        params["response"]["content"].to_json 
      end
    end
  end
end

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

Для всех 3 определенных маршрутов нам удается получить ответ. И все это примерно с 29 строками кода.

Весь код доступен github: https://github.com/W3NDO/AutoAPI.

Источник:

#Ruby #JSON
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу