πŸ’§πŸ”—Π­Π»ΠΈΠΊΡΠΈΡ€: ΠŸΠΎΡ‚Ρ€Π΅Π±Π»Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· внСшнСго API

Π’ этом постС ΠΌΡ‹ ΡƒΠ·Π½Π°Π΅ΠΌ, ΠΊΠ°ΠΊ ΠΏΠΎΡ‚Ρ€Π΅Π±Π»ΡΡ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈΠ· внСшнСго API. Π’ΠΎ врСмя создания ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΌΡ‹ сдСлаСм Π²Ρ‹Π·ΠΎΠ² ViaCep API для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ Π΅Π³ΠΎ ΠΏΠΎΡ‡Ρ‚ΠΎΠ²ΠΎΠ³ΠΎ индСкса, ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠΌ ΠΈ сохраним ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π³ΠΎΡ€ΠΎΠ΄Π΅ ΠΈ UF этого ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ. ΠœΡ‹ Ρ‚Π°ΠΊΠΆΠ΅ ΡƒΠ·Π½Π°Π΅ΠΌ, ΠΊΠ°ΠΊ ΠΈΠ·Π±Π΅ΠΆΠ°Ρ‚ΡŒ Π½Π΅Π½ΡƒΠΆΠ½Ρ‹Ρ… Π·Π²ΠΎΠ½ΠΊΠΎΠ².

  • Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°
    • Π’Π°Π±Π»ΠΈΡ†Π° ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ
    • Ѐункция для создания ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ
  • ΠŸΠΎΡ‚Ρ€Π΅Π±Π»Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· внСшнСго API
    • Установка Tesla
    • Установка HTTPoison
    • HTTP-ΠΊΠ»ΠΈΠ΅Π½Ρ‚ с Tesla
    • HTTP-ΠΊΠ»ΠΈΠ΅Π½Ρ‚ с HTTPoison
    • ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΏΠΎΡ‡Ρ‚ΠΎΠ²ΠΎΠ³ΠΎ индСкса ΠΏΡ€ΠΈ создании ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ
    • ИзбСганиС Π½Π΅Π½ΡƒΠΆΠ½Ρ‹Ρ… ΠΎΠ±Ρ€Π°Ρ‰Π΅Π½ΠΈΠΉ ΠΊ Π²Π½Π΅ΡˆΠ½Π΅ΠΌΡƒ API (apply_action)
    • Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ User-Agent
  • Π—Π°ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°

$ mix phx.new learning_external_api --app my_app
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Бсылка Π½Π° Π³ΠΎΡ‚ΠΎΠ²Ρ‹ΠΉ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚: https://github.com/maiquitome/learning_external_api

ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ столов

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΈ ΠΈ схСмы:

$ mix phx.gen.schema User users first_name last_name email cep city uf
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

ИзмСнСниС схСмы ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ:

Π£Π΄Π°Π»ΠΈΡ‚Π΅ city ΠΈ uf Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΈΠ· validate_required.

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈ запуск ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΉ:

$ mix ecto.setup
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Ѐункция для создания ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ

Нам понадобится функция для создания ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ, ΠΏΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ViaCEP API для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ ΠΏΠΎΡ‡Ρ‚ΠΎΠ²ΠΎΠ³ΠΎ индСкса ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ. ΠœΡ‹ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ эту ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ ΠΏΠΎΠ·ΠΆΠ΅.

Π’ lib/my_app/users/create.ex:

defmodule MyApp.Users.Create do
  alias MyApp.{Repo, User}

  @type user_params :: %{
          first_name: String.t(),
          last_name: integer,
          cep: String.t(),
          email: String.t()
        }

  @doc """
  Inserts a user into the database.

  ## Examples

      iex> alias MyApp.{User, Users}
      ...>
      ...> user_params = %{
      ...>  first_name: "Mike",
      ...>  last_name: "Wazowski",
      ...>  cep: "95270000",
      ...>  email: "mike_wazowski@monstros_sa.com"
      ...> }
      ...>
      ...> {:ok, %User{}} = Users.Create.call user_params
      ...>
      iex> {:error, %Ecto.Changeset{}} = Users.Create.call %{}
  """
  @spec call(user_params()) :: {:error, Ecto.Changeset.t()} | {:ok, Ecto.Schema.t()}
  def call(params) do
    %User{}
    |> User.changeset(params)
    |> Repo.insert()
  end
end
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

ΠŸΠΎΡ‚Ρ€Π΅Π±Π»Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· внСшнСго API

Π§Ρ‚ΠΎΠ±Ρ‹ ΠΈΠΌΠ΅Ρ‚ΡŒ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΏΠΎΡ‚Ρ€Π΅Π±Π»ΡΡ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈΠ· внСшнСго API, Π½Π°ΠΌ Π½ΡƒΠΆΠ΅Π½ HTTP-ΠΊΠ»ΠΈΠ΅Π½Ρ‚. ΠœΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π»ΠΈΠ±ΠΎ Tesla, Π»ΠΈΠ±ΠΎ HTTPoison. ΠŸΡ€Π΅ΠΈΠΌΡƒΡ‰Π΅ΡΡ‚Π²ΠΎ Tesla Π² Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ Ρƒ Π½Π΅Π΅ Π΅ΡΡ‚ΡŒ Π³ΠΎΡ‚ΠΎΠ²ΠΎΠ΅ ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΡ‡Π½ΠΎΠ΅ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ½ΠΎΠ΅ обСспСчСниС, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ. Π’ этом постС ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΎΠ±Π° ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° для ΠΈΡ… сравнСния.

Установка Tesla

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Tesla, Π΄ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ Π² mix.exs:

defp deps do
  [
    {:tesla, "~> 1.4"},

    # opcional, mas recomendado
    {:hackney, "~> 1.17"}

    # esse nΓ£o precisa pois jΓ‘ vem com o phoenix
    {:jason, ">= 1.0.0"}
  ]
end
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

ПослСднюю Π²Π΅Ρ€ΡΠΈΡŽ Tesla ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΉΡ‚ΠΈ ΠΏΠΎ адрСсу: https://hex.pm/packages/tesla.

Установка зависимостСй:

$ mix deps.get
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Π˜ΡΠΏΡ‹Ρ‚Π°Π½ΠΈΠ΅ Tesla:

$ iex -S mix
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°
iex> Tesla.get "https://viacep.com.br/ws/01001000/json/"
{:ok, %Tesla.Env{}}

iex> Tesla.get ""                                       
{:error, {:no_scheme}}

iex> Tesla.get "https://exemplo.com"                     
{:error, :econnrefused}
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Установка HTTPoison

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ HTTPoison, Π΄ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ Π² mix.exs:

defp deps do
  [
    {:httpoison, "~> 1.8"}
  ]
end
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ Π½Π°ΠΉΡ‚ΠΈ послСднюю Π²Π΅Ρ€ΡΠΈΡŽ HTTPoison ΠΏΠΎ адрСсу: https://hex.pm/packages/httpoison.

Установка зависимостСй:

$ mix deps.get
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

ВСстированиС HTTPoison:

$ iex -S mix
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°
iex> HTTPoison.get "https://viacep.com.br/ws/01001000/json/"
{:ok, %HTTPoison.Response{}}

iex> HTTPoison.get ""                                   
** (CaseClauseError) no case clause matching: []

iex> HTTPoison.get "https://exemplo.com" 
{:error, %HTTPoison.Error{id: nil, reason: :closed}}
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

HTTP-ΠΊΠ»ΠΈΠ΅Π½Ρ‚ с Tesla

Π”Π°Π²Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π΅ΠΈΠΌΠ΅Π½ΡƒΠ΅ΠΌ Ρ„Π°ΠΉΠ» tesla_client.ex, ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ ΠΌΡ‹ создадим Π΄Ρ€ΡƒΠ³ΠΎΠΉ Ρ„Π°ΠΉΠ» ΠΏΠΎΠ΄ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ΠΌ httpoison_client.ex, ΠΈ Ρ‚Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ ΠΌΡ‹ смоТСм ΡΡ€Π°Π²Π½ΠΈΠ²Π°Ρ‚ΡŒ HTTP-ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρ‹ Π»ΡƒΡ‡ΡˆΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ.

Π’ lib/my_app/via_cep/tesla_client.ex:

defmodule MyApp.ViaCep.TeslaClient do
  # Ao invΓ©s de Tesla.get(), vc vai usar apenas get()
  use Tesla

  alias Tesla.Env

  @base_url "https://viacep.com.br/ws/"

  # codifica (encode) os parametros para json
  # e descodifica (decode) a resposta para json automaticamente.
  plug Tesla.Middleware.JSON

  def get_cep_info(url \ @base_url, cep) do
    "#{url}#{cep}/json/"
    |> get()
    |> handle_get()
  end

  # casos abaixo de sucesso e erro
  defp handle_get({:ok, %Env{status: 200, body: %{"erro" => "true"}}}) do
    {:error, %{status: :not_found, result: "CEP not found!"}}
  end

  defp handle_get({:ok, %Env{status: 200, body: body}}) do
    {:ok, body}
  end

  defp handle_get({:ok, %Env{status: 400, body: _body}}) do
    {:error, %{status: :bad_request, result: "Invalid CEP!"}}
  end

  defp handle_get({:error, reason}) do
    {:error, %{status: :bad_request, result: reason}}
  end
end
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Π”Π°Π²Π°ΠΉΡ‚Π΅ запустим тСст:

iex(1)> MyApp.ViaCep.TeslaClient.get_cep_info "95270000"
{:ok,
 %{
   "bairro" => "",
   "cep" => "95270-000",
   "complemento" => "",
   "ddd" => "54",
   "gia" => "",
   "ibge" => "4308201",
   "localidade" => "Flores da Cunha",
   "logradouro" => "",
   "siafi" => "8661",
   "uf" => "RS"
 }}

iex(2)> MyApp.ViaCep.TeslaClient.get_cep_info "95270001"
{:error, %{result: "CEP not found!", status: :not_found}}

iex(3)> MyApp.ViaCep.TeslaClient.get_cep_info ""        
{:error, %{result: "Invalid CEP!", status: :bad_request}}
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

HTTP-ΠΊΠ»ΠΈΠ΅Π½Ρ‚ с HTTPoison

Π’ HTTPoison Ρƒ нас Π½Π΅Ρ‚ Π³ΠΎΡ‚ΠΎΠ²ΠΎΠ³ΠΎ ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΡ‡Π½ΠΎΠ³ΠΎ ПО для прСобразования json Π² map, поэтому Π½Π°ΠΌ придСтся ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Jason.decode(), ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΡƒΠΆΠ΅ установлСн Π² Phoenix.

Π’ lib/my_app/via_cep/httpoison_client.ex:

defmodule MyApp.ViaCep.HttpoisonClient do
  alias HTTPoison.{Error, Response}

  @base_url "https://viacep.com.br/ws/"

  def get_cep_info(url \ @base_url, cep) do
    "#{url}#{cep}/json/"
    |> HTTPoison.get()
    |> json_to_map()
    |> handle_get()
  end

  defp json_to_map({:ok, %Response{body: body} = response}) do
    {_ok_or_error, body} = Jason.decode(body)

    {:ok, Map.put(response, :body, body)}
  end

  defp json_to_map({:error, %Error{}} = error), do: error

  defp handle_get({:ok, %Response{status_code: 200, body: %{"erro" => "true"}}}) do
    {:error, %{status: :not_found, result: "CEP not found!"}}
  end

  defp handle_get({:ok, %Response{status_code: 200, body: body}}) do
    {:ok, body}
  end

  defp handle_get({:ok, %Response{status_code: 400, body: _body}}) do
    {:error, %{status: :bad_request, result: "Invalid CEP!"}}
  end

  defp handle_get({:error, reason}) do
    {:error, %{status: :bad_request, result: reason}}
  end
end

Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Π”Π°Π²Π°ΠΉΡ‚Π΅ запустим тСст:

iex(1)> MyApp.ViaCep.HttpoisonClient.get_cep_info "95270000"
{:ok,
 %{
   "bairro" => "",
   "cep" => "95270-000",
   "complemento" => "",
   "ddd" => "54",
   "gia" => "",
   "ibge" => "4308201",
   "localidade" => "Flores da Cunha",
   "logradouro" => "",
   "siafi" => "8661",
   "uf" => "RS"
 }}

iex(2)> MyApp.ViaCep.HttpoisonClient.get_cep_info "95270001"
{:error, %{result: "CEP not found!", status: :not_found}}

iex(3)> MyApp.ViaCep.HttpoisonClient.get_cep_info ""        
{:error, %{result: "Invalid CEP!", status: :bad_request}}
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΏΠΎΡ‡Ρ‚ΠΎΠ²ΠΎΠ³ΠΎ индСкса ΠΏΡ€ΠΈ создании ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ

Π’ lib/my_app/users/create.ex ΠΈΠ·ΠΌΠ΅Π½ΠΈΠΌ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ ΠΏΠΎΡ‡Ρ‚ΠΎΠ²ΠΎΠ³ΠΎ индСкса ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ, Π° Ρ‚Π°ΠΊΠΆΠ΅ для получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π³ΠΎΡ€ΠΎΠ΄Π΅ ΠΈ ΡˆΡ‚Π°Ρ‚Π΅. Π£ этой Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Π΅ΡΡ‚ΡŒ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ°, Π½ΠΎ ΠΌΡ‹ ΡƒΠ»ΡƒΡ‡ΡˆΠΈΠΌ Π΅Π΅ ΠΏΠΎΠ·ΠΆΠ΅.

alias MyApp.ViaCep.HttpoisonClient, as: Client
...

@spec call(user_params()) :: {:error, Ecto.Changeset.t() | map()} | {:ok, Ecto.Schema.t()}
  def call(params) do
    cep = Map.get(params, :cep)

    with {:ok, %{"localidade" => city, "uf" => uf}} <- Client.get_cep_info(cep),
         params <- Map.merge(params, %{city: city, uf: uf}),
         changeset <- User.changeset(%User{}, params),
         {:ok, %User{}} = user <- Repo.insert(changeset) do
      user
    end
  end
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ:

iex(1)> user_params = %{                   
...(1)>     first_name: "Mike",
...(1)>     last_name: "Wazowski",
...(1)>     cep: "95270000",
...(1)>     email: "mike_wazowski@monstros_sa.com"
...(1)>    }
%{
  cep: "95270000",
  email: "mike_wazowski@monstros_sa.com",
  first_name: "Mike",
  last_name: "Wazowski"
}

iex(2)> MyApp.Users.Create.call user_params       
CEP: "95270000"
{:ok,
 %MyApp.User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   cep: "95270000",
   city: "Flores da Cunha",
   email: "mike_wazowski@monstros_sa.com",
   first_name: "Mike",
   id: 1,
   inserted_at: ~N[2022-05-15 21:38:14],
   last_name: "Wazowski",
   uf: "RS",
   updated_at: ~N[2022-05-15 21:38:14]
 }}
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

НСвСрный ΠΏΠΎΡ‡Ρ‚ΠΎΠ²Ρ‹ΠΉ индСкс

iex(1)> user_params = %{                   
...(1)>     first_name: "Mike",
...(1)>     last_name: "Wazowski",
...(1)>     cep: "123",
...(1)>     email: "mike_wazowski@monstros_sa.com"
...(1)>    }
%{
  cep: "123",
  email: "mike_wazowski@monstros_sa.com",
  first_name: "Mike",
  last_name: "Wazowski"
}
iex(2)> MyApp.Users.Create.call user_params       
CEP: "123"
{:error, %{result: "Invalid CEP!", status: :bad_request}}
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

ΠžΠ±Ρ€Π°Ρ‚ΠΈΡ‚Π΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, Ρ‡Ρ‚ΠΎ сообщСниС Invalid ZIP Code! Π±Ρ‹Π»ΠΎ ΠΏΠΎΠΊΠ°Π·Π°Π½ΠΎ вмСсто Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΡŽ Π½Π°Π±ΠΎΡ€Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ, прСдупрСТдая, Ρ‡Ρ‚ΠΎ ZIP Code Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ 8 символов, Ρ‚Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ, ΠΌΡ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ»ΠΈ Π±Ρ‹ Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΡŽ Π±Π΅Π· лишнСго Π²Ρ‹Π·ΠΎΠ²Π° внСшнСго API.

ИзбСганиС Π½Π΅Π½ΡƒΠΆΠ½Ρ‹Ρ… ΠΎΠ±Ρ€Π°Ρ‰Π΅Π½ΠΈΠΉ ΠΊ Π²Π½Π΅ΡˆΠ½Π΅ΠΌΡƒ API (apply_action)

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π΄Π°Π²Π°ΠΉΡ‚Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΠΌ всС Π΄Π°Π½Π½Ρ‹Π΅ Π² Π½Π°Π±ΠΎΡ€Π΅ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ ΠΏΠ΅Ρ€Π΅Π΄ Π²Ρ‹Π·ΠΎΠ²ΠΎΠΌ ViaCep API.

ВСстированиС Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ Π½Π°Π±ΠΎΡ€Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ Π±Π΅Π· использования Repo.insert():

iex(1)> import Ecto.Changeset

iex(2)> user_params = %{
...(2)>     first_name: "Mike",
...(2)>     last_name: "Wazowski",
...(2)>     cep: "123",
...(2)>     email: "mike_wazowski@monstros_sa.com"
...(2)>    }
%{
  cep: "123",
  email: "mike_wazowski@monstros_sa.com",
  first_name: "Mike",
  last_name: "Wazowski"
}

iex(3)> MyApp.User.changeset(%MyApp.User{}, user_params) |> apply_action(:create)
{:error,
 #Ecto.Changeset<
   action: :create,
   changes: %{
     cep: "123",
     email: "mike_wazowski@monstros_sa.com",
     first_name: "Mike",
     last_name: "Wazowski"
   },
   errors: [
     cep: {"should be %{count} character(s)",
      [count: 8, validation: :length, kind: :is, type: :string]}
   ],
   data: #MyApp.User<>,
   valid?: false
 >}
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

apply_action примСняСт дСйствиС Π½Π°Π±ΠΎΡ€Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли измСнСния Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹.

Если измСнСния Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹, всС измСнСния Π±ΡƒΠ΄ΡƒΡ‚ ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½Ρ‹ ΠΊ Π΄Π°Π½Π½Ρ‹ΠΌ Π½Π°Π±ΠΎΡ€Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ. Если измСнСния Π½Π΅Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹, измСнСния Π½Π΅ Π±ΡƒΠ΄ΡƒΡ‚ ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½Ρ‹, ΠΈ Π±ΡƒΠ΄Π΅Ρ‚ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π΅Π½ ΠΊΠΎΡ€Ρ‚Π΅ΠΆ ошибок с Π½Π°Π±ΠΎΡ€ΠΎΠΌ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ, содСрТащим дСйствиС, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ ΠΏΡ‹Ρ‚Π°Π»ΠΎΡΡŒ Π±Ρ‹Ρ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΎ.

ДСйствиС ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Π»ΡŽΠ±Ρ‹ΠΌ Π°Ρ‚ΠΎΠΌΠΎΠΌ.

Π”Π°Π²Π°ΠΉΡ‚Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠΌ Ρ„Π°ΠΉΠ» lib/my_app/user.ex, Π΄ΠΎΠ±Π°Π²ΠΈΠ² Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ validate_before_insert:

def validate_before_insert(changeset), do: apply_action(changeset, :insert)
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π΄Π°Π²Π°ΠΉΡ‚Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠΌ Ρ„Π°ΠΉΠ» lib/my_app/users/create.ex:

@spec call(user_params()) :: {:error, Ecto.Changeset.t() | map()} | {:ok, Ecto.Schema.t()}
  def call(params) do
    cep = Map.get(params, :cep)

    changeset = User.changeset(%User{}, params)

    with {:ok, %User{}} <- User.validate_before_insert(changeset),
         {:ok, %{"localidade" => city, "uf" => uf}} <- Client.get_cep_info(cep),
         params <- Map.merge(params, %{city: city, uf: uf}),
         changeset <- User.changeset(%User{}, params),
         {:ok, %User{}} = user <- Repo.insert(changeset) do
      user
    end
  end
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΏΠ΅Ρ€Π΅Π΄ Π²Ρ‹Π·ΠΎΠ²ΠΎΠΌ ViaCep ΠΌΡ‹ провСряСм всС Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ changeset:

Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ User-Agent

НСкоторыС внСшниС API ΠΌΠΎΠ³ΡƒΡ‚ Π·Π°ΠΏΡ€ΠΎΡΠΈΡ‚ΡŒ Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ для выполнСния Π²Ρ‹Π·ΠΎΠ²ΠΎΠ², ΠΌΠ½ΠΎΠ³ΠΈΠ΅ ΠΌΠΎΠ³ΡƒΡ‚ Π·Π°ΠΏΡ€ΠΎΡΠΈΡ‚ΡŒ Ρ‚ΠΎΠΊΠ΅Π½, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π²Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈΠ· Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΠΈ, ΠΈΠ»ΠΈ, Π² случаС API github, Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ User-Agent:

Π’ Tesla Π²Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ Π»Π΅Π³ΠΊΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ plug Tesla.Middleware.Headers:

НСт Httpoison:

iex> HTTPoison.get "https://api.github.com/users/maiquitome/repos", [{"User-Agent", "foobar"}]
Π’ΠΎΠΉΠ΄ΠΈΡ‚Π΅ Π² полноэкранный Ρ€Π΅ΠΆΠΈΠΌ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· полноэкранного Ρ€Π΅ΠΆΠΈΠΌΠ°

ΠŸΡ€ΠΎΡ‡ΠΈΡ‚Π°ΠΉΡ‚Π΅ Π² Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΠΈ HTTPoison Ρ‡Π°ΡΡ‚ΡŒ, ΠΏΠΎΡΠ²ΡΡ‰Π΅Π½Π½ΡƒΡŽ опциям.

Π—Π°Ρ‚Π΅ΠΌ ΠΈΠ·ΡƒΡ‡ΠΈΡ‚Π΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΡŽ API, ΠΊ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌΡƒ Π²Ρ‹ ΠΎΠ±Ρ€Π°Ρ‰Π°Π΅Ρ‚Π΅ΡΡŒ.

Π—Π°ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅

Π’ ΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π²Π°ΠΌ понадобится ΠΏΠΎΡ‚Ρ€Π΅Π±Π»ΡΡ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈΠ· внСшнСго API; Π² нашС врСмя это Π²ΠΏΠΎΠ»Π½Π΅ Π½ΠΎΡ€ΠΌΠ°Π»ΡŒΠ½ΠΎΠ΅ явлСниС. Π§Ρ‚Π΅Π½ΠΈΠ΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΠΈ ΠΏΠΎ Π²Π½Π΅ΡˆΠ½Π΅ΠΌΡƒ API — ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ шаг ΠΊ успСху. Π’ Elixir Ρƒ нас Π΅ΡΡ‚ΡŒ ΠΎΡ‚Π»ΠΈΡ‡Π½Ρ‹Π΅ инструмСнты HTTP-ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°, Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌ Ρ‚Π°ΠΊΠΆΠ΅ стоит ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Ρ‚ΡŒ. ΠœΡ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ остороТны ΠΈ Π½Π΅ Π΄Π΅Π»Π°Ρ‚ΡŒ Π»ΠΈΡˆΠ½ΠΈΡ… Π²Ρ‹Π·ΠΎΠ²ΠΎΠ² Π²Π½Π΅ΡˆΠ½ΠΈΡ… API, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π½Π΅ ΡΠ½ΠΈΠ·ΠΈΡ‚ΡŒ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ нашСго прилоТСния. Π‘Π»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΉ шаг — ΡƒΠ·Π½Π°Ρ‚ΡŒ, ΠΊΠ°ΠΊ ΠΏΡ€ΠΎΠ²ΠΎΠ΄ΠΈΡ‚ΡŒ Π°Π²Ρ‚ΠΎΠΌΠ°Ρ‚ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹Π΅ тСсты этих Π²Π½Π΅ΡˆΠ½ΠΈΡ… Π²Ρ‹Π·ΠΎΠ²ΠΎΠ²; это Ρ‚Π΅ΠΌΠ° для Π±ΡƒΠ΄ΡƒΡ‰Π΅Π³ΠΎ поста.

ΠžΡ†Π΅Π½ΠΈΡ‚Π΅ ΡΡ‚Π°Ρ‚ΡŒΡŽ
Procodings.ru
Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ