Packages

A bare bones interface for the Pokemon API. It can get selected information for a pokemon by name, as well as the complete list of pokemons. This is a student project to learn about caching, ETS, GenServers, supervisors, mix, APIs, hex, git, documentation, testing, and patience.

Current section

Files

Jump to
pokelixir lib pokelixir.ex
Raw

lib/pokelixir.ex

defmodule Pokelixir do
@moduledoc """
This module:
- Defines a pokelixir struct
- Defines functions to read and write from cache PokelixirCache
- Defines functions to read from the Pokemon API
-- Read a single pokemon - get_by_name/1
-- Retreive all pokemons - all/0
"""
defstruct [
:id,
:name,
:hp,
:attack,
:defense,
:special_attack,
:special_defense,
:speed,
:height,
:weight,
:types,
:abilities
]
@type pokelixir() :: Pokelixir
@doc """
get_by_name/1
Checks if the pokemon is already in the cache and retreives it if it exists.
If not, makes a call to the Pokemon API and compiles the data into a %Pokelixir struct, which is then stored in the cache
It calls private function get_from_api/1 and PokelixirCache.get/1 to get the information about a given pokemon name
## Examples
iex> Pokelixir.get_by_name("charizard")
%Pokelixir{
id: 6,
name: "charizard",
hp: 78,
attack: 84,
defense: 78,
special_attack: 109,
special_defense: 85,
speed: 100,
height: 17,
weight: 905,
types: ["fire", "flying"],
abilities: [
%{
description: "Strengthens fire moves to inflict 1.5× damage at 1/3 max HP or less.",
name: "blaze"
},
%{
description: "Increases Special Attack to 1.5× but costs 1/8 max HP after each turn during strong sunlight.",
name: "solar-power"
}
]
}
"""
@spec get_by_name(any) :: pokelixir()
def get_by_name(name) do
case PokelixirCache.get(name) do
nil ->
with pokemon <- get_from_api(name) do
PokelixirCache.put(pokemon)
pokemon
end
pokemon ->
pokemon
end
end
@spec get_from_api(String.t()) :: pokelixir()
defp get_from_api(name) do
HTTPoison.get("https://pokeapi.co/api/v2/pokemon/#{name}")
|> decode_response()
|> to_struct()
end
@doc """
all/1 gets a list of Pokelixir structs of all pokemon
The function makes a first call to the API to retreive the total number of pokemons.
Then it populates a local cache of pokemons
"""
@spec all() :: list()
def all() do
get_all_names() |> Enum.map(&get_by_name(&1))
end
@spec get_all_names() :: list()
defp get_all_names() do
map_of_all_pokemons = pokemon_count() |> get_all_pokemons()
Enum.map(map_of_all_pokemons["results"], fn pokemon -> pokemon["name"] end)
end
@spec pokemon_count() :: integer()
defp pokemon_count() do
first_response =
HTTPoison.get("https://pokeapi.co/api/v2/pokemon?offset=0&limit=1") |> decode_response()
first_response["count"]
end
@spec get_all_pokemons(integer()) :: map()
defp get_all_pokemons(total) do
HTTPoison.get("https://pokeapi.co/api/v2/pokemon?offset=0&limit=#{total}")
|> decode_response()
end
@doc """
to_struct/1 takes the decoded map from Poison.decode! and returns a populated Pokelixir struct
To achieve this, it makes use of private functions:
- extract_stat/2, which returns the numerical value associated with the second argument passed ("attack", "defense"...)
- extract_feature/2, which returns a list of features, retrieved by passing "type" or "ability".
When retrieving abilities, there will be a call to the PokelixirDescriptions module, which makes an additional API call
"""
@spec to_struct(map) :: pokelixir()
def to_struct(decoded_map) do
stats = decoded_map["stats"]
%Pokelixir{
id: decoded_map["id"],
name: decoded_map["name"],
hp: extract_stat(stats, "hp"),
attack: extract_stat(stats, "attack"),
defense: extract_stat(stats, "defense"),
special_attack: extract_stat(stats, "special-attack"),
special_defense: extract_stat(stats, "special-defense"),
speed: extract_stat(stats, "speed"),
height: decoded_map["height"],
weight: decoded_map["weight"],
types: extract_feature(decoded_map["types"], "type"),
abilities: extract_feature(decoded_map["abilities"], "ability")
}
end
@doc """
decode_response/1 returns the JSON "body" of the API response
"""
@spec decode_response(tuple()) :: map()
def decode_response({:ok, response}), do: response.body |> Poison.decode!()
@spec extract_feature(list(), String.t()) :: list()
defp extract_feature(list_of_features, feature) do
case feature do
"type" ->
Enum.map(list_of_features, &get_in(&1, [feature, "name"]))
"ability" ->
Enum.map(list_of_features, fn each ->
%{
name: get_in(each, [feature, "name"]),
description: get_in(each, [feature, "url"]) |> PokelixirDescriptions.get_description()
}
# ,
# changes:
end)
end
end
@spec extract_stat(list(), String.t()) :: integer()
defp extract_stat(pokemon_stats, request) do
hd(Enum.filter(pokemon_stats, &(get_in(&1, ["stat", "name"]) == request)))["base_stat"]
end
end