sources: raise Invalid when the new-list form falls short
Assisted-by: Claude Opus 5.5 via Claude Code
diff --git a/lib/rank_king/sources.rb b/lib/rank_king/sources.rb
index c3bf080..b26d969 100644
--- a/lib/rank_king/sources.rb
+++ b/lib/rank_king/sources.rb
@@ -8,6 +8,10 @@ module RankKing
# Signed in sig/rank_king/sources.rbs: a Data class.
# @rbs skip
Entry = Data.define(:key, :name)
+
+ # Raised by a kind's create when the new-list form did not post enough
+ # to set the list up; the message says what is missing.
+ class Invalid < StandardError; end
end
end
diff --git a/lib/rank_king/sources/text.rb b/lib/rank_king/sources/text.rb
index d057702..1282e25 100644
--- a/lib/rank_king/sources/text.rb
+++ b/lib/rank_king/sources/text.rb
@@ -22,15 +22,14 @@ module RankKing
# Adds the items typed into the new-list form, of which there must
# be at least two.
- #: (List list, Hash[String, untyped] params, Config config) -> bool
+ #: (List list, Hash[String, untyped] params, Config config) -> void
def self.create(list, params, config)
names = params["items"].to_s.lines.map(&:strip).reject(&:empty?)
- return false if names.size < 2
+ raise Invalid, "A list needs at least two items." if names.size < 2
names.each do |name|
Item.create(list_id: list.id, name:)
end
- true
end
#: (List list) -> void
diff --git a/lib/rank_king/sources/youtube.rb b/lib/rank_king/sources/youtube.rb
index 2f3d232..5cb25a3 100644
--- a/lib/rank_king/sources/youtube.rb
+++ b/lib/rank_king/sources/youtube.rb
@@ -53,15 +53,13 @@ module RankKing
# Follows the playlist chosen on the new-list form and fetches it
# with the cookie pasted there.
- #: (List list, Hash[String, untyped] params, _InnerTubeConfig config) -> bool
+ #: (List list, Hash[String, untyped] params, _InnerTubeConfig config) -> void
def self.create(list, params, config)
- playlist_id = params["playlist"].to_s[/\A[\w-]+\z/]
- cookie = InnerTube.cookie(params["cookie"].to_s)
- return false unless playlist_id && cookie
+ playlist_id = params["playlist"].to_s[/\A[\w-]+\z/] or raise Invalid, "Choose a playlist to follow."
+ cookie = InnerTube.cookie(params["cookie"].to_s) or raise Invalid, "That cookie has no SAPISID to sign requests with."
DB[TABLE].insert(list_id: list.id, playlist_id:)
new(list, config).fetch(cookie)
- true
end
#: (List list, _InnerTubeConfig config) -> void
diff --git a/lib/rank_king/web.rb b/lib/rank_king/web.rb
index d4a862d..1101777 100644
--- a/lib/rank_king/web.rb
+++ b/lib/rank_king/web.rb
@@ -17,9 +17,12 @@ module RankKing
plugin :static, %w[ /css /js /vendor ]
plugin :sessions, secret: CONFIG.session_secret
plugin :route_csrf, csrf_failure: :empty_403, check_request_methods: %w[POST]
- plugin :error_handler, classes: [ProxyAuth::MissingLogin, InnerTube::Error] do |e|
+ plugin :error_handler, classes: [ProxyAuth::MissingLogin, InnerTube::Error, Sources::Invalid] do |e|
response["Content-Type"] = "text/plain"
- if e.is_a?(InnerTube::Error)
+ if e.is_a?(Sources::Invalid)
+ response.status = 422
+ "#{e.message}\n"
+ elsif e.is_a?(InnerTube::Error)
response.status = 502
"YouTube: #{e.message}\n"
else
@@ -50,18 +53,17 @@ module RankKing
axis_names = names_param(r.params["axes"])
- # The source fills the list from its own fields, or turns it
- # down and nothing is made.
+ # The source fills the list from its own fields, or raises and
+ # nothing is made.
list = DB.transaction {
l = List.create(name: name, user_id: user.id, source_kind: source_kind)
- raise Sequel::Rollback unless Sources.fetch(source_kind).create(l, r.params, config)
+ Sources.fetch(source_kind).create(l, r.params, config)
axis_names.each do |n|
Axis.create(list_id: l.id, name: n)
end
l
}
- r.halt 422 unless list
r.redirect "/lists/#{list.id}"
end
diff --git a/sig/rank_king/sources.rbs b/sig/rank_king/sources.rbs
index 39c51a4..66e12e3 100644
--- a/sig/rank_king/sources.rbs
+++ b/sig/rank_king/sources.rbs
@@ -46,9 +46,9 @@ module RankKing
# routes of their own as they are filled in.
def fields: (csrf: Method) -> _Component
- # Sets a new list up from what those fields posted: false, and the
- # list is not made, when they are not enough to go on.
- def create: (List list, Hash[String, untyped] params, Config config) -> bool
+ # Sets a new list up from what those fields posted, raising Invalid,
+ # and the list is not made, when they are not enough to go on.
+ def create: (List list, Hash[String, untyped] params, Config config) -> void
# Mounts the routes the kind's own fields and dialogs post to, ahead
# of the app's.
diff --git a/test/test_web.rb b/test/test_web.rb
index cbbf488..d2d0a3e 100644
--- a/test/test_web.rb
+++ b/test/test_web.rb
@@ -51,6 +51,7 @@ class TestWeb < Minitest::Test
def test_create_list_rejects_fewer_than_two_items
create_list(name: "Solo", items: "OnlyOne")
assert_equal 422, last_response.status
+ assert_equal "A list needs at least two items.\n", last_response.body
end
def test_create_list_without_axes
diff --git a/test/test_youtube.rb b/test/test_youtube.rb
index bc8eff8..71a6817 100644
--- a/test/test_youtube.rb
+++ b/test/test_youtube.rb
@@ -190,6 +190,7 @@ class TestYouTube < Minitest::Test
def test_create_needs_a_cookie
create_list(playlist: PLAYLIST, cookie: "SID=sid")
assert_equal 422, last_response.status
+ assert_equal "That cookie has no SAPISID to sign requests with.\n", last_response.body
assert_equal 0, RankKing::List.count
assert_empty @inner_tube.calls
end
@@ -197,6 +198,7 @@ class TestYouTube < Minitest::Test
def test_create_needs_a_playlist
create_list(playlist: "not a playlist")
assert_equal 422, last_response.status
+ assert_equal "Choose a playlist to follow.\n", last_response.body
assert_equal 0, RankKing::List.count
end