Este documento serve de referência para a instalação da gem drasil. Além disso, ele inclui o passo a passo para a criação, configuração e testes de novos API Client's.
Esta gem foi construída utilizando a biblioteca Spyke e usa o Faraday como cliente HTTP. Ela inclui a base para a criação de novos API Client's. Por utilizar a gem spyke, ela fornece uma interface similar ao ActiveRecord.
Exemplos de uso:
# Criar um novo seller utilizando o método create
ZoopApiClient::Seller.create(
first_name: "John"
)
# Criar um novo seller utilizando o método save
seller = ZoopApiClient::Seller.new(
first_name: "John"
)
seller.save
# Buscar um seller pelo id
seller = ZoopApiClient::Seller.find("123456789")
# Atualizar o atributo `first_name`
seller.update(first_name: "John")
# Deletar o seller
seller.destroy # app/service_layers/zoop/base_object/seller/api/endpoints.rb:25
def create_bank_account(bank_account_params, zoop_seller_id)
@bank_account = Zoop::BaseObject::Seller::Api::Bank::BankAccounts.new(@client)
token = @bank_account.generate_token(data: bank_account_params)["id"]
response = @bank_account.associate_to_seller(seller_id: zoop_seller_id, token: token)
begin
JSON.parse(response)
rescue StandardError => e
puts "error => #{e}"
end
end# Creates a card token
def create_new_card_token(data:)
path = "cards/tokens"
response = @client.post(path: path, data: data)
end
# Creates a buyer
def create_buyer(data:)
path = "buyers"
response = @client.post(path: path, data: data)
JSON.parse(response) if response.present?
endNão há uma interface bem definida para acessar rotas paginadas:
# Page and limit are present in params
Api::Zoop::Client.account_balance.find_historic_account_balance_by_seller(
seller_id: @zoop_id,
params: params,
positive: positive,
negative: negative
)
# But here the attribute limit is hardcoded
def get_receivables_by_seller(seller_id:, page: 1)
path = "sellers/#{seller_id}/receivables?limit=1000&page=#{page}"
response = @client.get(path: path, data: false)
JSON.parse(response)
endComo serão tratados os erros? Retorno nil, false, hash?
Crie uma nova gem:
bundle gem example_api_clientCaso necessário, instale a versão indicada do Ruby:
rbenv install 3.0.6
rbenv global 3.0.6
ruby -vEdite o arquivo example_api_client.gemspec e preencha os campos que possuem a tag TODO:.
No arquivo Rakefile da gem criada, adicione:
require "drasil/tasks"No arquivo Gemfile, adicione:
gem "drasil", git: "git@github.com:rsv-ink/drasil.git"
group :test do
gem "rspec"
gem "webmock"
endPara instalar as dependências, execute:
bundle installPara gerar a estrutura de pastas do drasil, execute:
bundle exec rake drasil:installexemple/drasil/lib/exemplo_api_client.rb
lib/{nome_client}_api_client.rb = este arquivo configura a biblioteca de cliente da API Zoop, especificando URLs, cabeçalhos, parsers e outras configurações importantes necessárias para interagir eficazmente o client. Ele também requer outros arquivos e bibliotecas para funcionar corretamente.
lib/{nome_client}_api_client = é a pasta raiz do client. Ela serve como o diretório principal onde as pastas serão estruturadas. Nesta pasta, você encontrará os principais arquivos e subdiretórios que compõem a estrutura da biblioteca.
lib/{nome_client}_api_client/parsers = contém módulos responsáveis por interpretar (parsear) as respostas do Client e transformá-las em formatos utilizáveis, como objetos ou dados estruturados, para facilitar o processamento no código do cliente. A gem já disponibiliza um template em lib/{nome_client}_api_client/parsers/default_parser.rb:
module ZoopApiClient
module Parsers
class DefaultParser < Drasil::Parser
def parse
data = @response
metadata = {}
[data, metadata]
end
end
end
endlib/{nome_client}_api_client/resources = abriga módulos e classes que representam recursos específicos da API Client, permitindo a interação com esses recursos de forma conveniente e abstrata, exemplo: "seller.rb"
{nome_client}_api_client/spec= A pasta spec contém arquivos de teste que são usados para verificar se a biblioteca do cliente funciona corretamente.
{nome_client}_api_client/spec/fixtures/{resource} = é usada para armazenar arquivos de "fixtures" que contêm respostas predefinidas de requisições de teste. Essas respostas são usadas nos testes para simular as respostas da API Zoop de maneira controlada e previsível, permitindo que os testes verifiquem o comportamento da biblioteca de cliente em diferentes cenários sem depender das respostas reais da API em um ambiente de produção ou de teste ao vivo. Isso ajuda a garantir que os testes sejam consistentes e repetíveis.
{nome_client}_api_client/spec/resources = contém os testes propriamente ditos relacionados aos resources da biblioteca de cliente.
{nome_client}_api_client/spec/support = guardam os helpers para testes rspecs, exemplo: WebMock.
Quando a resposta da requisição não contém um status de sucesso, uma das exceções a seguir é lançado:
| Classe de Erro | Código de Status | Descrição |
|---|---|---|
Drasil::BadRequestError |
400 | Requisição inválida. |
Drasil::UnauthorizedError |
401 | Falha na autenticação ou falta de autorização. |
Drasil::ForbiddenError |
403 | Acesso proibido ao recurso solicitado. |
Drasil::ResourceNotFound |
404 | O recurso solicitado não foi encontrado. |
Drasil::ProxyAuthError |
407 | Falha na autenticação de proxy. |
Drasil::RequestTimeoutError |
408 | A requisição atingiu o tempo limite. |
Drasil::ConflictError |
409 | Conflito com o estado atual do recurso. |
Drasil::UnprocessableEntityError |
422 | A entidade enviada na requisição não pode ser processada. |
Drasil::TimeoutError |
- | Erro de tempo limite genérico. |
Drasil::NilStatusError |
- | Resposta com status nulo. |
Drasil::ConnectionFailed |
- | Falha na conexão com o servidor. |
Drasil::SSLError |
- | Erro de SSL/TLS na conexão. |
Drasil::ParsingError |
- | Erro ao analisar a resposta. |
Os resources podem ser paginados utilizando os métodos de classe page e per_page. Segue o exemplo:
sellers = ZoopApiClient::Seller.all.page(2).per_page(10)Segundo o exemplo acima, uma requisição será feita para a rota /sellers?page=2&limit=10. A seguir, temos a documentação dos métodos relacionados à paginação:
| Método | Descrição |
|---|---|
| total_pages | Retorna o total de páginas disponíveis |
| next_page? | Retorna true se houver mais páginas na API externa |
| current_page | Retorna o número da página atual |
Os parsers são as classes responsáveis por mapear a resposta de uma API externa para o padrão esperado pela drasil. Eles herdam da classe Drasil::Parser.
Um mesmo parser pode ser utilizado para rotas diferentes. Um novo parser deve ser criado sempre que não houver nenhum parser capaz de tratar a resposta de uma nova rota.
{
"data": [
{
"id": 1,
"first_name": "John",
"last_name": "Doe"
}
],
"info": {
"pages_count": 10,
"current_page": 1
}
}class Parser < Drasil::Parser
def parser
data = @response[:data]
metadata = {
total_pages: @response[:info][:pages_count],
page: @response[:info][:current_page]
}
return [data, metadata]
end
end{
{
"id": 1,
"first_name": "John",
"last_name": "Doe"
}
}class Parser < Drasil::Parser
def parser
data = @response
metadata = {}
return [data, metadata]
end
end- Crie uma classe, que herda da classe
Drasil::Parser, na pastalib/{your_api_client}/parsers.
# lib/{your_api_client}/parsers/your_parser.rb
module YourApiClient
module Parsers
class YourParser
def parse
...
end
end
end
end- No método
Drasil.configureno arquivo de entrada do seu API client adicione a seguinte linha:
config.add_parser "/resource-path", YourApiClient::Parsers::YourParser-
Crie um novo arquivo para o recurso: O nome do arquivo deve refletir o nome do recurso, por convenção em Ruby, deve estar em snake_case. Por exemplo, se você estiver criando um recurso para representar "vendedores" de uma API, pode nomear o arquivo como seller.rb.
-
Defina a classe do recurso: No arquivo que você criou, defina a classe do recurso, herdando de Drasil::Base. Isso estabelece uma base para a classe do recurso.
module ZoopApiClient
class Seller < Drasil::Base
end
end- Defina as associações: Use o método
has_oneouhas_manypara definir as associações do recurso. Isso define como o recurso está relacionado a outros recursos na API.
class Seller < Drasil::Base
has_one :resource, "/resources/:id"
has_many :resources, "/resources"
end- Defina os atributos: Liste os atributos que pertencem a este recurso usando o método attributes. Esses atributos correspondem aos campos ou propriedades que você espera encontrar nas respostas da API relacionadas a este recurso.
class Seller < Drasil::Base
has_one :resource, "/resources/:id"
has_many :resources, "/resources"
attributes :id, :name, :email, :created_at, :updated_at
end- Personalize os métodos, se necessário: Dependendo das necessidades específicas do recurso e da API, você pode adicionar métodos personalizados à classe do recurso para realizar ações específicas relacionadas a ele, como atualizações ou exclusões.
class Seller < Drasil::Base
# ... outras definições de classe ...
# https://api.zoop.ws/v1/marketplaces/{marketplace_id}/sellers/search
# query_params:
# taxpayer_id
# ein
def self.find_by_cpf_or_cnpj(cpf: nil, cnpj: nil)
Seller.with(:search).where(taxpayer_id: cpf, ein: cnpj)
end
endPara cada API Client devem ser construídos testes automatizados com a finalidade de garantir que todas as requisições e respostas estão sendo tratadas. A seguir serão listados alguns pontos interessantes ao criar um teste de uma API.
Durante os testes não devem ser realizados requisições para sites externos, dessa forma é preciso que as respostas sejam simuladas. Para fazer isso, utilizamos a gem Webmock que bloqueia requisições para endpoints externos e permite a definição de um retorno específico.
Para criar simulações de resposta no teste basta utilizar o método helper mock_request. Através desse método é possível impedir que requisições sejam feitas para endpoints externos e o desenvolvedor defina uma resposta específica.
Ao utilizar o mock_request são definidos dois parâmetros, o primeiro é o tipo de requisição (:POST, :GET, :UPDATE) e o segundo a url do endpoint. Além disso, deve ser definido o retorno quando a requisição é feita, através do .to_return. Neste método, são passados o status da resposta e o body da resposta.
Observe que como body é passado um json criado dentro da pasta /spec/fixture. Os bodys devem ser agrupados por tipo de resource e tenha por nome o status da resposta, como por exemplo "sellers/200.json".
A seguir é apresentado um código que faz a simulação de respostas quando é feito um método :GET para #{BASE_URL}//sellers/1234.
Nesse caso de teste, está sendo testado o método find do resource Seller. São implementados duas simulações de resposta, uma para caso de seller encontrado e outra para o caso de recurso não encontrado.
mock_request(:get, "/sellers/1234").to_return(status: 200, body: fixture("sellers/200.json"))require "spec_helper"
RSpec.describe ZoopApiClient::Seller do
describe "#find" do
context "when seller exists" do
before do
mock_request(:get, "/sellers/1234")
.to_return(status: 200, body: fixture("sellers/200.json"))
end
...
end
context "when seller does not exist" do
before do
mock_request(:get, "/sellers/1234")
.to_return(status: 404, body: fixture("sellers/404.json"))
end
...
end
end
endAlém de realizar a simulação de respostas é importante testar todos os possíveis tipos de retornos. Por exemplo, para o método find do resource seller.
require "spec_helper"
RSpec.describe ZoopApiClient::Seller do
describe "#find" do
context "when seller exists" do
...
end
context "when seller does not exist" do
...
end
end
endAlém do que foi descrito antes, também é interessante realmente validar as respostas esperadas. Como por exemplo, a validação do tipo de status retornado e os valores retornados. Para auxiliar neste último existe um método helper que compara os atributos do resources com a fixture definida na resposta da definição.
require "spec_helper"
RSpec.describe ZoopApiClient::Seller do
describe "#find" do
context "when seller exists" do
it { expect(subject).to be_a(ZoopApiClient::Seller) }
it { expect(subject.attributes).to match_fixture("sellers/200.json") }
end
context "when seller does not exist" do
it { expect { subject }.to raise_error(Faraday::ResourceNotFound) }
end
end
end- Data Mapper Pattern - ROM
- Shopify API
- Swagger Code Generator - OpenApi
- Octokit (Github client)
- Dropbox
- Google Api Client
- Spyke
- Twitter API Client (biblioteca Ruby que fornece uma interface para acessar a API RESTful do Twitter)
- SoundCloud Ruby API Client (biblioteca DEPRECATED Ruby que fornece uma interface para acessar a API RESTful do SoundCloud)
- JsonApiClient
- ActiveResource Base (Ruby on Rails)
- Onde colocar código de chamada de API externa em um projeto Ruby on Rails ➝ Usando Service Objects em Ruby on Rails
- RESTful Web Services Cookbook (PDF, ano 2010)