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
+12
View File
@@ -206,6 +206,7 @@ defmodule MusicBrainz.API do
}
"""
@spec get_release_group(String.t(), MusicBrainz.Config.t()) :: {:ok, map()} | {:error, term()}
def get_release_group(id, config) do
config
|> new_request()
@@ -282,6 +283,7 @@ defmodule MusicBrainz.API do
"title": "Clark (Soundtrack From the Netflix Series)"
}
"""
@spec get_release(String.t(), MusicBrainz.Config.t()) :: {:ok, map()} | {:error, term()}
def get_release(id, config) do
config
|> new_request()
@@ -295,6 +297,8 @@ defmodule MusicBrainz.API do
|> get_request()
end
@spec get_releases(String.t(), keyword(), MusicBrainz.Config.t()) ::
{:ok, map()} | {:error, term()}
def get_releases(release_group_id, opts, config) do
Keyword.validate!(opts, [:limit, :offset])
@@ -314,6 +318,8 @@ defmodule MusicBrainz.API do
|> get_request()
end
@spec search_release_by_barcode(String.t(), MusicBrainz.Config.t()) ::
{:ok, [ReleaseSearchResult.t()]} | {:error, term()}
def search_release_by_barcode(barcode, config) do
config
|> new_request()
@@ -430,6 +436,9 @@ defmodule MusicBrainz.API do
]
}
"""
@spec search_release_group(String.t(), keyword(), MusicBrainz.Config.t()) ::
{:ok, %{total_count: non_neg_integer(), release_groups: [ReleaseGroupSearchResult.t()]}}
| {:error, term()}
def search_release_group(query, opts, config) do
Keyword.validate!(opts, [:limit, :offset])
@@ -451,6 +460,7 @@ defmodule MusicBrainz.API do
|> get_request()
end
@spec get_artist(String.t(), MusicBrainz.Config.t()) :: {:ok, Artist.t()} | {:error, term()}
def get_artist(musicbrainz_id, config) do
config
|> new_request()
@@ -468,6 +478,8 @@ defmodule MusicBrainz.API do
@doc """
Uses the [cover art](https://musicbrainz.org/doc/Cover_Art_Archive/API) endpoint with the release group id to get the cover image.
"""
@spec get_cover_art({:musicbrainz_id, String.t()} | {:url, String.t()}, MusicBrainz.Config.t()) ::
{:ok, binary()} | {:error, :cover_not_available}
def get_cover_art({:musicbrainz_id, musicbrainz_id}, config) do
url = "https://coverartarchive.org/release-group/#{musicbrainz_id}/front"
+13
View File
@@ -2,6 +2,16 @@ defmodule MusicBrainz.Artist do
@enforce_keys [:id, :name, :sort_name]
defstruct [:id, :name, :sort_name, :country, :relations, :musicbrainz_data]
@type t :: %__MODULE__{
id: String.t(),
name: String.t(),
sort_name: String.t(),
country: String.t() | nil,
relations: [map()] | nil,
musicbrainz_data: map() | nil
}
@spec from_api_response(map()) :: t()
def from_api_response(r) do
%__MODULE__{
id: r["id"],
@@ -15,6 +25,7 @@ defmodule MusicBrainz.Artist do
# MASSIVE ASSUMPTION: if there's more than one Discogs link,
# take the one with the lowest ID, as it's likely to be the main one.
@spec get_discogs_id(t()) :: integer() | nil
def get_discogs_id(r) do
candidates =
r.relations
@@ -32,6 +43,7 @@ defmodule MusicBrainz.Artist do
end
end
@spec get_wikidata_id(t()) :: String.t() | nil
def get_wikidata_id(r) do
Enum.find_value(r.relations, fn relation ->
if relation.type == "wikidata" do
@@ -40,6 +52,7 @@ defmodule MusicBrainz.Artist do
end)
end
@spec url(String.t()) :: String.t()
def url(id) do
"https://musicbrainz.org/artist/#{id}"
end
+6
View File
@@ -1,6 +1,12 @@
defmodule MusicBrainz.ExternalLink do
defstruct [:name, :url]
@type t :: %__MODULE__{
name: atom(),
url: String.t()
}
@spec external_links(map(), map() | String.t() | nil) :: [t()] | [String.t()]
def external_links(musicbrainz_data, patterns) when is_map(patterns) do
Enum.reduce(patterns, [], fn {name, pattern}, acc ->
case external_links(musicbrainz_data, pattern) do
+44
View File
@@ -24,33 +24,73 @@ defmodule MusicBrainz.Release do
:media
]
@type t :: %__MODULE__{
id: String.t(),
title: String.t(),
disambiguation: String.t() | nil,
packaging: String.t() | nil,
artists: [Artist.t()],
date: String.t() | nil,
barcode: String.t() | nil,
catalog_number: String.t(),
country: String.t() | nil,
media: [Medium.t()]
}
defmodule Artist do
@enforce_keys [:id, :name, :sort_name]
defstruct [:id, :name, :sort_name]
@type t :: %__MODULE__{
id: String.t(),
name: String.t(),
sort_name: String.t()
}
end
defmodule Medium do
@enforce_keys [:title, :format, :number, :track_count, :tracks]
defstruct [:title, :format, :number, :track_count, :tracks]
@type t :: %__MODULE__{
title: String.t() | nil,
format: String.t() | nil,
number: non_neg_integer(),
track_count: non_neg_integer(),
tracks: [MusicBrainz.Release.Track.t()]
}
end
defmodule Track do
@enforce_keys [:id, :title, :artists, :length, :number, :position]
defstruct [:id, :title, :artists, :length, :number, :position]
@type t :: %__MODULE__{
id: String.t(),
title: String.t(),
artists: [MusicBrainz.Release.Artist.t()],
length: non_neg_integer() | nil,
number: String.t(),
position: non_neg_integer()
}
end
@spec media_count(t()) :: non_neg_integer()
def media_count(release) do
Enum.count(release.media)
end
@spec get_medium(t(), non_neg_integer()) :: Medium.t() | nil
def get_medium(release, medium_number) do
Enum.find(release.media, fn m -> m.number == medium_number end)
end
@spec medium_duration(Medium.t()) :: non_neg_integer()
def medium_duration(medium) do
Enum.sum_by(medium.tracks, fn track -> track.length || 0 end)
end
@spec medium_tracks(t(), non_neg_integer()) :: [Track.t()]
def medium_tracks(release, medium_number) do
case Enum.find(release.media, fn m -> m.number == medium_number end) do
nil -> []
@@ -58,14 +98,17 @@ defmodule MusicBrainz.Release do
end
end
@spec release_duration(t()) :: non_neg_integer()
def release_duration(release) do
Enum.sum_by(release.media, fn medium -> medium_duration(medium) end)
end
@spec tracks(t()) :: [Track.t()]
def tracks(release) do
Enum.flat_map(release.media, fn medium -> medium.tracks end)
end
@spec from_api_response(map()) :: t()
def from_api_response(r) do
%__MODULE__{
id: r["id"],
@@ -81,6 +124,7 @@ defmodule MusicBrainz.Release do
}
end
@spec thumb_url(t()) :: String.t()
def thumb_url(release) do
"https://coverartarchive.org/release/#{release.id}/front-250"
end
+6
View File
@@ -1,6 +1,7 @@
defmodule MusicBrainz.ReleaseGroup do
alias MusicBrainz.ReleaseGroupSearchResult
@spec included_release_groups(map()) :: [ReleaseGroupSearchResult.t()]
def included_release_groups(release_group) do
release_group
|> get_release_groups()
@@ -9,27 +10,32 @@ defmodule MusicBrainz.ReleaseGroup do
end)
end
@spec releases(map()) :: [map()]
def releases(release_group) do
release_group
|> Map.get("releases", [])
end
@spec release_ids(map()) :: [String.t()]
def release_ids(release_group) do
release_group
|> Map.get("releases", [])
|> Enum.map(fn release -> release["id"] end)
end
@spec included_release_group_ids(map()) :: [String.t()]
def included_release_group_ids(release_group) do
release_group
|> included_release_groups()
|> Enum.map(fn rg -> rg.id end)
end
@spec url(String.t()) :: String.t()
def url(id) do
"https://musicbrainz.org/release-group/#{id}"
end
@spec parse_type(String.t() | nil) :: :album | :ep | :live | :compilation | :single | :other
def parse_type("Album"), do: :album
def parse_type("EP"), do: :ep
def parse_type("Live"), do: :live
@@ -4,6 +4,15 @@ defmodule MusicBrainz.ReleaseGroupSearchResult do
@enforce_keys [:id, :type, :title, :artists, :release_date]
defstruct [:id, :type, :title, :artists, :release_date]
@type t :: %__MODULE__{
id: String.t(),
type: :album | :ep | :live | :compilation | :single | :other,
title: String.t(),
artists: String.t(),
release_date: String.t() | nil
}
@spec from_api_response(map()) :: t()
def from_api_response(rg) do
%__MODULE__{
id: rg["id"],
@@ -14,6 +23,7 @@ defmodule MusicBrainz.ReleaseGroupSearchResult do
}
end
@spec thumb_url(t()) :: String.t()
def thumb_url(rgr) do
"https://coverartarchive.org/release-group/#{rgr.id}/front-250"
end
+14
View File
@@ -4,6 +4,17 @@ defmodule MusicBrainz.ReleaseSearchResult do
@enforce_keys [:id, :title, :release_group, :artists, :date, :barcode, :media]
defstruct [:id, :title, :release_group, :artists, :date, :barcode, :media]
@type t :: %__MODULE__{
id: String.t(),
title: String.t(),
release_group: map() | nil,
artists: String.t(),
date: String.t() | nil,
barcode: String.t() | nil,
media: [map()]
}
@spec from_api_response(map()) :: t()
def from_api_response(r) do
%__MODULE__{
id: r["id"],
@@ -54,6 +65,8 @@ defmodule MusicBrainz.ReleaseSearchResult do
:multi
"""
@spec format(t()) ::
:cd | :vinyl | :dvd | :blu_ray | :digital_download | :vhs | :multi | :unknown
def format(release_search_result) do
sorted_frequencies =
release_search_result.media
@@ -74,6 +87,7 @@ defmodule MusicBrainz.ReleaseSearchResult do
}
end
@spec parse_media([map()]) :: [map()]
def parse_media(media) do
Enum.map(media, fn m ->
%{