Split QueueResponse and introduce SearchResponse

Decoupled `QueueResponse` and `SearchResponse` to better align with their distinct API endpoints and simplify response structures. Updated `PlayOutcome.PendingPlaylist` and `SearchResultHandler` to include playlist `name`, ensuring consistency across related features and improving response clarity. Adjusted OpenAPI documentation and routes to reflect the separation.
This commit is contained in:
anthorob committed 2026-10-18 05:35:57 -04:00
1 parent 01bf94c60a
commit 780016b731
5 files changed
+44 -20

No files matched your search

@@ -30,6 +30,7 @@ import me.bobert.netneutrality.http.server.dto.QueuedTrackResponse;
import me.bobert.netneutrality.http.server.dto.ReorderRequest; import me.bobert.netneutrality.http.server.dto.ReorderRequest;
import me.bobert.netneutrality.http.server.dto.ReorderResponse; import me.bobert.netneutrality.http.server.dto.ReorderResponse;
import me.bobert.netneutrality.http.server.dto.SearchRequest; import me.bobert.netneutrality.http.server.dto.SearchRequest;
import me.bobert.netneutrality.http.server.dto.SearchResponse;
import me.bobert.netneutrality.http.server.dto.SeekRequest; import me.bobert.netneutrality.http.server.dto.SeekRequest;
import me.bobert.netneutrality.http.server.dto.SeekResponse; import me.bobert.netneutrality.http.server.dto.SeekResponse;
import me.bobert.netneutrality.http.server.dto.ShuffleResponse; import me.bobert.netneutrality.http.server.dto.ShuffleResponse;
@@ -416,7 +417,7 @@ public final class MusicRoutes {
pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"), pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"),
requestBody = @OpenApiRequestBody(content = @OpenApiContent(from = SearchRequest.class)), requestBody = @OpenApiRequestBody(content = @OpenApiContent(from = SearchRequest.class)),
responses = { responses = {
@OpenApiResponse(status = "200", content = @OpenApiContent(from = QueueResponse.class)), @OpenApiResponse(status = "200", content = @OpenApiContent(from = SearchResponse.class)),
@OpenApiResponse(status = "400", content = @OpenApiContent(from = ErrorResponse.class)), @OpenApiResponse(status = "400", content = @OpenApiContent(from = ErrorResponse.class)),
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class)) @OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
} }
@@ -642,6 +643,7 @@ public final class MusicRoutes {
body.put("status", "pending_playlist"); body.put("status", "pending_playlist");
body.put("search_id", pendingPlaylist.searchId()); body.put("search_id", pendingPlaylist.searchId());
body.put("search_id_ttl_seconds", PendingSearchResults.ttlSeconds()); body.put("search_id_ttl_seconds", PendingSearchResults.ttlSeconds());
body.put("name", pendingPlaylist.name());
body.put("tracks", pendingPlaylist.tracks().stream().map(MusicRoutes::trackSummary).toList()); body.put("tracks", pendingPlaylist.tracks().stream().map(MusicRoutes::trackSummary).toList());
body.put("truncated", pendingPlaylist.truncated()); body.put("truncated", pendingPlaylist.truncated());
yield body; yield body;
@@ -9,28 +9,17 @@ package me.bobert.netneutrality.http.server.dto;
import java.util.List; import java.util.List;
/** /**
* OpenAPI schema for the {@code /music/{guildId}/queue} and {@code /music/{guildId}/search} * OpenAPI schema for the {@code /music/{guildId}/queue} response.
* responses.
* *
* <p>Nothing is ever queued directly by {@code /music/{guildId}/search}: every resolved query is * <p>Fields are nullable since only a subset is populated depending on {@code status}: {@code
* stashed as a pending candidate, to be confirmed via a follow-up {@code /music/{guildId}/queue} * queued} fills {@code track}/{@code queue_position} (confirmed selection) and {@code queued_all}
* call carrying {@code searchId}/{@code index} (or {@code queueAll}). Fields are nullable since only a subset is * fills {@code tracks}/{@code queue_position} (confirmed bulk selection; {@code queue_position} is
* populated depending on {@code status}: {@code queued} fills {@code track}/{@code queue_position} (confirmed * the resulting queue size).
* selection), {@code queued_all} fills {@code tracks}/{@code queue_position} (confirmed bulk selection; {@code
* queue_position} is the resulting queue size), {@code pending_track} fills {@code search_id}/{@code
* search_id_ttl_seconds}/{@code track}, {@code pending_playlist} fills {@code search_id}/{@code
* search_id_ttl_seconds}/{@code tracks}/{@code truncated}, and {@code pending_search} fills {@code search_id}/{@code
* search_id_ttl_seconds}/{@code candidates}.
* {@code search_id_ttl_seconds} indicates how many seconds remain before the {@code search_id} expires.
*/ */
public record QueueResponse( public record QueueResponse(
String status, String status,
TrackSummaryResponse track, TrackSummaryResponse track,
Integer queue_position, Integer queue_position,
String search_id, List<TrackSummaryResponse> tracks
Long search_id_ttl_seconds,
List<TrackSummaryResponse> tracks,
Boolean truncated,
List<TrackSummaryResponse> candidates
) { ) {
} }
@@ -0,0 +1,33 @@
package me.bobert.netneutrality.http.server.dto;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import java.util.List;
/**
* OpenAPI schema for the {@code /music/{guildId}/search} response.
*
* <p>Nothing is ever queued directly by {@code /music/{guildId}/search}: every resolved query is
* stashed as a pending candidate, to be confirmed via a follow-up {@code /music/{guildId}/queue}
* call carrying {@code searchId}/{@code index} (or {@code queueAll}). Fields are nullable since only a
* subset is populated depending on {@code status}: {@code pending_track} fills {@code search_id}/{@code
* search_id_ttl_seconds}/{@code track}, {@code pending_playlist} fills {@code search_id}/{@code
* search_id_ttl_seconds}/{@code name}/{@code tracks}/{@code truncated}, and {@code pending_search} fills
* {@code search_id}/{@code search_id_ttl_seconds}/{@code candidates}. {@code search_id_ttl_seconds}
* indicates how many seconds remain before the {@code search_id} expires.
*/
public record SearchResponse(
String status,
String search_id,
Long search_id_ttl_seconds,
TrackSummaryResponse track,
String name,
List<TrackSummaryResponse> tracks,
Boolean truncated,
List<TrackSummaryResponse> candidates
) {
}
@@ -23,7 +23,7 @@ public sealed interface PlayOutcome {
record PendingTrack(String searchId, Track track) implements PlayOutcome { record PendingTrack(String searchId, Track track) implements PlayOutcome {
} }
record PendingPlaylist(String searchId, List<Track> tracks, boolean truncated) implements PlayOutcome { record PendingPlaylist(String searchId, String name, List<Track> tracks, boolean truncated) implements PlayOutcome {
} }
record PendingSearch(String searchId, List<Track> candidates) implements PlayOutcome { record PendingSearch(String searchId, List<Track> candidates) implements PlayOutcome {
@@ -66,7 +66,7 @@ public class SearchResultHandler extends AbstractAudioLoadResultHandler {
List<Track> candidates = truncated ? tracks.subList(0, maxPlaylistSize) : tracks; List<Track> candidates = truncated ? tracks.subList(0, maxPlaylistSize) : tracks;
String searchId = PendingSearchResults.put(guild.getId(), candidates); String searchId = PendingSearchResults.put(guild.getId(), candidates);
future.complete(new PlayOutcome.PendingPlaylist(searchId, candidates, truncated)); future.complete(new PlayOutcome.PendingPlaylist(searchId, result.getInfo().getName(), candidates, truncated));
} }
@Override @Override