First pass at uniformed types, specs and docs

- spec public functions (skipping controllers, views, live views and
components)
- use types instead of explanations in docs
- remove redundant docs
- fix typos
This commit is contained in:
Claudio Ortolina
2026-03-06 08:33:11 +00:00
parent 99e30d5fdf
commit 7cf9b4e7f8
81 changed files with 652 additions and 300 deletions
+1
View File
@@ -13,6 +13,7 @@ defmodule LastFm.Album do
field :title, :string
end
@spec changeset(t(), map()) :: Ecto.Changeset.t()
def changeset(album, attrs) do
album
|> cast(attrs, [:musicbrainz_id, :title])
+10
View File
@@ -4,6 +4,9 @@ defmodule LastFm.API do
require Logger
@type id_or_name :: {:musicbrainz_id, String.t()} | {:name, String.t()}
@spec get_session(String.t(), LastFm.Config.t()) :: {:ok, Session.t()} | {:error, term()}
def get_session(token, config) do
params =
%{
@@ -24,6 +27,7 @@ defmodule LastFm.API do
|> get_request()
end
@spec scrobble([map()], String.t(), LastFm.Config.t()) :: {:ok, map()} | {:error, term()}
def scrobble(tracks, session_key, config) do
params =
%{"api_key" => config.api_key, "method" => "track.scrobble", "sk" => session_key}
@@ -51,6 +55,7 @@ defmodule LastFm.API do
|> post_request()
end
@spec get_recent_tracks(keyword(), LastFm.Config.t()) :: {:ok, [Track.t()]} | {:error, term()}
def get_recent_tracks(opts \\ [], config) do
to_uts = Keyword.get(opts, :to_uts)
limit = Keyword.get(opts, :limit, 100)
@@ -69,6 +74,7 @@ defmodule LastFm.API do
|> get_request()
end
@spec get_artist_info(id_or_name(), LastFm.Config.t()) :: {:ok, Artist.t()} | {:error, term()}
def get_artist_info(id_or_name_option, config) do
params =
config
@@ -83,6 +89,8 @@ defmodule LastFm.API do
|> get_request()
end
@spec get_similar_artists(id_or_name(), LastFm.Config.t()) ::
{:ok, [Artist.t()]} | {:error, term()}
def get_similar_artists(id_or_name_option, config) do
params =
config
@@ -97,6 +105,8 @@ defmodule LastFm.API do
|> get_request()
end
@spec get_artist_tags(id_or_name(), LastFm.Config.t()) ::
{:ok, [{String.t(), integer()}]} | {:error, term()}
def get_artist_tags(id_or_name_option, config) do
params =
config
+24
View File
@@ -1,6 +1,28 @@
defmodule LastFm.API.ErrorResponse do
defstruct [:error, :message]
@type error_atom ::
:invalid_service
| :invalid_method
| :authentication_failed
| :invalid_format
| :invalid_parameters
| :invalid_resource
| :operation_failed
| :invalid_session_key
| :invalid_api_key
| :service_offline
| :invalid_method_signature
| :transient_error
| :suspended_api_key
| :rate_limit_exceeded
@type t :: %__MODULE__{
error: error_atom(),
message: String.t()
}
@spec new(integer(), String.t()) :: t()
def new(error_code, message) do
%__MODULE__{error: map_error(error_code), message: message}
end
@@ -23,6 +45,7 @@ defmodule LastFm.API.ErrorResponse do
@doc """
Returns true if the error is retryable, false otherwise.
"""
@spec retryable_error?(atom()) :: boolean()
def retryable_error?(error)
when error in [:transient_error, :service_offline, :rate_limit_exceeded, :operation_failed],
do: true
@@ -33,6 +56,7 @@ defmodule LastFm.API.ErrorResponse do
Returns the recommended retry delay in milliseconds for retryable errors.
Returns nil for non-retryable errors.
"""
@spec retry_delay(atom()) :: pos_integer() | nil
# 1 minute
def retry_delay(:rate_limit_exceeded), do: 60_000
# 30 seconds
+1
View File
@@ -1,4 +1,5 @@
defmodule LastFm.API.Signature do
@spec generate(map() | keyword(), String.t()) :: String.t()
def generate(params, shared_secret) do
encoded_params =
params
+3
View File
@@ -28,6 +28,7 @@ defmodule LastFm.Artist do
field :image_data_hash, :string
end
@spec from_api_response(map()) :: t()
def from_api_response(api_response) do
%__MODULE__{
musicbrainz_id: api_response["mbid"],
@@ -41,6 +42,7 @@ defmodule LastFm.Artist do
}
end
@spec changeset(t(), map()) :: Ecto.Changeset.t()
def changeset(artist, attrs) do
artist
|> cast(attrs, [
@@ -56,6 +58,7 @@ defmodule LastFm.Artist do
|> validate_required([:name])
end
@spec events_url(t()) :: String.t()
def events_url(artist) do
artist.base_url <> "/+events"
end
+1
View File
@@ -1,4 +1,5 @@
defmodule LastFm.Import do
@spec batch(keyword()) :: {:ok, non_neg_integer()} | {:error, term()}
def batch(opts) do
with {:ok, tracks} <- LastFm.get_tracks(opts) do
LastFm.Feed.update(tracks)
+10
View File
@@ -1,6 +1,16 @@
defmodule LastFm.Scrobble do
defstruct [:track, :artist, :timestamp, :album, :album_artist, :mbid]
@type t :: %__MODULE__{
track: String.t(),
artist: String.t(),
timestamp: integer(),
album: String.t() | nil,
album_artist: String.t() | nil,
mbid: String.t() | nil
}
@spec encode(t()) :: map()
def encode(scrobble) do
scrobble
|> Map.from_struct()
+8
View File
@@ -3,6 +3,12 @@ defmodule LastFm.Session do
defstruct [:name, :key, :pro]
@type t :: %__MODULE__{
name: String.t() | nil,
key: String.t() | nil,
pro: boolean() | nil
}
Record.defrecord(
:xmlAttribute,
Record.extract(:xmlAttribute, from_lib: "xmerl/include/xmerl.hrl")
@@ -32,6 +38,7 @@ defmodule LastFm.Session do
pro: true
}
"""
@spec parse(String.t()) :: t()
def parse(xml_string) do
doc = scan(xml_string)
@@ -77,6 +84,7 @@ defmodule LastFm.Session do
:xmerl_xpath.string(to_charlist(path), node)
end
@spec text(tuple() | nil) :: String.t() | nil
def text(node), do: node |> xpath(~c"./text()") |> extract_text()
defp extract_text([xmlText(value: value)]), do: List.to_string(value)
+2
View File
@@ -35,6 +35,7 @@ defmodule LastFm.Track do
field :last_fm_data, :map, default: %{}
end
@spec from_api_response([map()]) :: [t()]
def from_api_response(raw_tracks) do
raw_tracks
|> Enum.reject(&now_playing?/1)
@@ -79,6 +80,7 @@ defmodule LastFm.Track do
end
end
@spec changeset(t(), map()) :: Ecto.Changeset.t()
def changeset(track, attrs) do
track
|> cast(attrs, [