refactor: name the workspace catalog list response

This commit is contained in:
2026-09-01 02:24:11 +09:00
parent 15e8d7365c
commit 9013754a3a
5 changed files with 45 additions and 16 deletions
+5 -2
View File
@@ -2,7 +2,10 @@ use crate::{BackendApiClient, BackendApiClientError};
use reqwest::Method;
use serde::{Deserialize, Serialize};
use std::fmt;
use workspace_api::{WorkspaceCreateResponse, WorkspaceRepositoryRecord, WorkspaceSummary};
use workspace_api::{
WorkspaceCatalogListResponse, WorkspaceCreateResponse, WorkspaceRepositoryRecord,
WorkspaceSummary,
};
const DEFAULT_WORKSPACE_LIMIT: usize = 200;
@@ -88,7 +91,7 @@ async fn list_backend_workspaces_with_client(
.send()
.await?;
client.check_status(response.status())?;
Ok(response.json::<Vec<BackendWorkspace>>().await?)
Ok(response.json::<WorkspaceCatalogListResponse>().await?.0)
}
pub async fn create_backend_workspace(
+23
View File
@@ -105,6 +105,14 @@ pub struct WorkspaceSummary {
pub updated_at: String,
}
/// Public response returned by `GET /api/workspaces`.
///
/// The transparent newtype keeps the established top-level JSON array while making the
/// complete list response a named cross-crate and generated-TypeScript authority.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct WorkspaceCatalogListResponse(pub Vec<WorkspaceSummary>);
/// Public Repository record embedded in Workspace creation responses.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
@@ -754,6 +762,7 @@ pub fn catalog_typescript() -> String {
let config = ts_rs::Config::default();
let declarations = [
WorkspaceSummary::decl(&config),
WorkspaceCatalogListResponse::decl(&config),
WorkspaceRepositoryRecord::decl(&config),
WorkspaceCreateResponse::decl(&config),
WorkspaceAuthConfig::decl(&config),
@@ -814,6 +823,17 @@ mod tests {
let parsed: WorkspaceResponse = serde_json::from_value(workspace.clone()).unwrap();
assert_eq!(serde_json::to_value(parsed).unwrap(), workspace);
let catalog = serde_json::json!([{
"workspace_id": "workspace-test",
"owner_account_id": "user-test",
"display_name": "Test",
"state": "active",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}]);
let parsed: WorkspaceCatalogListResponse = serde_json::from_value(catalog.clone()).unwrap();
assert_eq!(serde_json::to_value(parsed).unwrap(), catalog);
let repositories = serde_json::json!({
"workspace_id": "workspace-test",
"items": [{
@@ -860,6 +880,9 @@ mod tests {
#[test]
fn generated_catalog_typescript_keeps_public_wrappers_and_nullability() {
let output = catalog_typescript();
assert!(
output.contains("export type WorkspaceCatalogListResponse = Array<WorkspaceSummary>")
);
assert!(output.contains("export type WorkspaceResponse ="));
assert!(output.contains("permissions: WorkspacePermissionSummary"));
assert!(output.contains("export type RepositoryListResponse ="));
+12 -13
View File
@@ -67,10 +67,10 @@ use workspace_api::{
RepositoryDetailResponse, RepositoryListResponse, RepositoryLogResponse,
RepositorySshCredential, RepositorySshHostTrust, RotateRepositorySshCredentialRequest,
RuntimeConnectionTestResponse, RuntimeManagementSummary, TICKET_ORCHESTRATION_PLANS_QUERY_PATH,
TICKET_RELATIONS_QUERY_PATH, WorkspaceCreateResponse, WorkspaceExtensionPointState,
WorkspaceExtensionPoints, WorkspacePermissionSummary, WorkspaceRepositoryRecord,
WorkspaceResponse, WorkspaceRuntimeResource, WorkspaceSummary, WorkspaceWorkerDiscoveryItem,
WorkspaceWorkerDiscoveryPage, WorkspaceWorkerSubject,
TICKET_RELATIONS_QUERY_PATH, WorkspaceCatalogListResponse, WorkspaceCreateResponse,
WorkspaceExtensionPointState, WorkspaceExtensionPoints, WorkspacePermissionSummary,
WorkspaceRepositoryRecord, WorkspaceResponse, WorkspaceRuntimeResource, WorkspaceSummary,
WorkspaceWorkerDiscoveryItem, WorkspaceWorkerDiscoveryPage, WorkspaceWorkerSubject,
};
use crate::auth::{
@@ -980,7 +980,9 @@ async fn list_server_workspaces(
let owner = match resolve_server_actor(&api, &headers).await {
Ok(Some(actor)) => Some(actor.account_id),
Ok(None) => match api.catalog.is_empty() {
Ok(true) => return Json(Vec::<WorkspaceSummary>::new()).into_response(),
Ok(true) => {
return Json(WorkspaceCatalogListResponse(Vec::new())).into_response();
}
Ok(false) => return StatusCode::UNAUTHORIZED.into_response(),
Err(error) => return server_error_response(error),
},
@@ -990,12 +992,9 @@ async fn list_server_workspaces(
.catalog
.list(owner.as_deref(), query.limit.unwrap_or(100))
{
Ok(workspaces) => Json(
workspaces
.into_iter()
.map(workspace_summary)
.collect::<Vec<_>>(),
)
Ok(workspaces) => Json(WorkspaceCatalogListResponse(
workspaces.into_iter().map(workspace_summary).collect(),
))
.into_response(),
Err(error) => server_error_response(error),
}
@@ -17784,10 +17783,10 @@ mod tests {
let catalog_body = to_bytes(authenticated_catalog.into_body(), usize::MAX)
.await
.unwrap();
let typed_catalog: Vec<workspace_api::WorkspaceSummary> =
let typed_catalog: workspace_api::WorkspaceCatalogListResponse =
serde_json::from_slice(&catalog_body).unwrap();
assert_eq!(
typed_catalog[0].workspace_id,
typed_catalog.0[0].workspace_id,
workspace.workspace.workspace_id
);
@@ -10,6 +10,8 @@ export type WorkspaceSummary = {
updated_at: string;
};
export type WorkspaceCatalogListResponse = Array<WorkspaceSummary>;
export type WorkspaceRepositoryRecord = {
workspace_id: string;
repository_id: string;
@@ -11,6 +11,7 @@ import type {
RepositorySourceKind,
RepositorySummary,
WorkspaceAuthConfig,
WorkspaceCatalogListResponse,
WorkspaceCreateResponse,
WorkspaceExtensionPoints,
WorkspaceExtensionPointState,
@@ -28,6 +29,7 @@ export type {
RepositoryListResponse,
RepositoryLogResponse,
RepositorySummary,
WorkspaceCatalogListResponse,
WorkspaceCreateResponse,
WorkspacePermissionSummary,
WorkspaceResponse,
@@ -412,7 +414,7 @@ function commitSummary(value: unknown, path: string): GitCommitSummary {
export function parseWorkspaceCatalogResponse(
value: unknown,
): WorkspaceSummary[] {
): WorkspaceCatalogListResponse {
return array(value, "workspaces").map((item, index) =>
workspaceSummary(item, `workspaces[${index}]`)
);