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 reqwest::Method;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use workspace_api::{WorkspaceCreateResponse, WorkspaceRepositoryRecord, WorkspaceSummary}; use workspace_api::{
WorkspaceCatalogListResponse, WorkspaceCreateResponse, WorkspaceRepositoryRecord,
WorkspaceSummary,
};
const DEFAULT_WORKSPACE_LIMIT: usize = 200; const DEFAULT_WORKSPACE_LIMIT: usize = 200;
@@ -88,7 +91,7 @@ async fn list_backend_workspaces_with_client(
.send() .send()
.await?; .await?;
client.check_status(response.status())?; client.check_status(response.status())?;
Ok(response.json::<Vec<BackendWorkspace>>().await?) Ok(response.json::<WorkspaceCatalogListResponse>().await?.0)
} }
pub async fn create_backend_workspace( pub async fn create_backend_workspace(
+23
View File
@@ -105,6 +105,14 @@ pub struct WorkspaceSummary {
pub updated_at: String, 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. /// Public Repository record embedded in Workspace creation responses.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
@@ -754,6 +762,7 @@ pub fn catalog_typescript() -> String {
let config = ts_rs::Config::default(); let config = ts_rs::Config::default();
let declarations = [ let declarations = [
WorkspaceSummary::decl(&config), WorkspaceSummary::decl(&config),
WorkspaceCatalogListResponse::decl(&config),
WorkspaceRepositoryRecord::decl(&config), WorkspaceRepositoryRecord::decl(&config),
WorkspaceCreateResponse::decl(&config), WorkspaceCreateResponse::decl(&config),
WorkspaceAuthConfig::decl(&config), WorkspaceAuthConfig::decl(&config),
@@ -814,6 +823,17 @@ mod tests {
let parsed: WorkspaceResponse = serde_json::from_value(workspace.clone()).unwrap(); let parsed: WorkspaceResponse = serde_json::from_value(workspace.clone()).unwrap();
assert_eq!(serde_json::to_value(parsed).unwrap(), workspace); 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!({ let repositories = serde_json::json!({
"workspace_id": "workspace-test", "workspace_id": "workspace-test",
"items": [{ "items": [{
@@ -860,6 +880,9 @@ mod tests {
#[test] #[test]
fn generated_catalog_typescript_keeps_public_wrappers_and_nullability() { fn generated_catalog_typescript_keeps_public_wrappers_and_nullability() {
let output = catalog_typescript(); let output = catalog_typescript();
assert!(
output.contains("export type WorkspaceCatalogListResponse = Array<WorkspaceSummary>")
);
assert!(output.contains("export type WorkspaceResponse =")); assert!(output.contains("export type WorkspaceResponse ="));
assert!(output.contains("permissions: WorkspacePermissionSummary")); assert!(output.contains("permissions: WorkspacePermissionSummary"));
assert!(output.contains("export type RepositoryListResponse =")); assert!(output.contains("export type RepositoryListResponse ="));
+12 -13
View File
@@ -67,10 +67,10 @@ use workspace_api::{
RepositoryDetailResponse, RepositoryListResponse, RepositoryLogResponse, RepositoryDetailResponse, RepositoryListResponse, RepositoryLogResponse,
RepositorySshCredential, RepositorySshHostTrust, RotateRepositorySshCredentialRequest, RepositorySshCredential, RepositorySshHostTrust, RotateRepositorySshCredentialRequest,
RuntimeConnectionTestResponse, RuntimeManagementSummary, TICKET_ORCHESTRATION_PLANS_QUERY_PATH, RuntimeConnectionTestResponse, RuntimeManagementSummary, TICKET_ORCHESTRATION_PLANS_QUERY_PATH,
TICKET_RELATIONS_QUERY_PATH, WorkspaceCreateResponse, WorkspaceExtensionPointState, TICKET_RELATIONS_QUERY_PATH, WorkspaceCatalogListResponse, WorkspaceCreateResponse,
WorkspaceExtensionPoints, WorkspacePermissionSummary, WorkspaceRepositoryRecord, WorkspaceExtensionPointState, WorkspaceExtensionPoints, WorkspacePermissionSummary,
WorkspaceResponse, WorkspaceRuntimeResource, WorkspaceSummary, WorkspaceWorkerDiscoveryItem, WorkspaceRepositoryRecord, WorkspaceResponse, WorkspaceRuntimeResource, WorkspaceSummary,
WorkspaceWorkerDiscoveryPage, WorkspaceWorkerSubject, WorkspaceWorkerDiscoveryItem, WorkspaceWorkerDiscoveryPage, WorkspaceWorkerSubject,
}; };
use crate::auth::{ use crate::auth::{
@@ -980,7 +980,9 @@ async fn list_server_workspaces(
let owner = match resolve_server_actor(&api, &headers).await { let owner = match resolve_server_actor(&api, &headers).await {
Ok(Some(actor)) => Some(actor.account_id), Ok(Some(actor)) => Some(actor.account_id),
Ok(None) => match api.catalog.is_empty() { 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(), Ok(false) => return StatusCode::UNAUTHORIZED.into_response(),
Err(error) => return server_error_response(error), Err(error) => return server_error_response(error),
}, },
@@ -990,12 +992,9 @@ async fn list_server_workspaces(
.catalog .catalog
.list(owner.as_deref(), query.limit.unwrap_or(100)) .list(owner.as_deref(), query.limit.unwrap_or(100))
{ {
Ok(workspaces) => Json( Ok(workspaces) => Json(WorkspaceCatalogListResponse(
workspaces workspaces.into_iter().map(workspace_summary).collect(),
.into_iter() ))
.map(workspace_summary)
.collect::<Vec<_>>(),
)
.into_response(), .into_response(),
Err(error) => server_error_response(error), Err(error) => server_error_response(error),
} }
@@ -17784,10 +17783,10 @@ mod tests {
let catalog_body = to_bytes(authenticated_catalog.into_body(), usize::MAX) let catalog_body = to_bytes(authenticated_catalog.into_body(), usize::MAX)
.await .await
.unwrap(); .unwrap();
let typed_catalog: Vec<workspace_api::WorkspaceSummary> = let typed_catalog: workspace_api::WorkspaceCatalogListResponse =
serde_json::from_slice(&catalog_body).unwrap(); serde_json::from_slice(&catalog_body).unwrap();
assert_eq!( assert_eq!(
typed_catalog[0].workspace_id, typed_catalog.0[0].workspace_id,
workspace.workspace.workspace_id workspace.workspace.workspace_id
); );
@@ -10,6 +10,8 @@ export type WorkspaceSummary = {
updated_at: string; updated_at: string;
}; };
export type WorkspaceCatalogListResponse = Array<WorkspaceSummary>;
export type WorkspaceRepositoryRecord = { export type WorkspaceRepositoryRecord = {
workspace_id: string; workspace_id: string;
repository_id: string; repository_id: string;
@@ -11,6 +11,7 @@ import type {
RepositorySourceKind, RepositorySourceKind,
RepositorySummary, RepositorySummary,
WorkspaceAuthConfig, WorkspaceAuthConfig,
WorkspaceCatalogListResponse,
WorkspaceCreateResponse, WorkspaceCreateResponse,
WorkspaceExtensionPoints, WorkspaceExtensionPoints,
WorkspaceExtensionPointState, WorkspaceExtensionPointState,
@@ -28,6 +29,7 @@ export type {
RepositoryListResponse, RepositoryListResponse,
RepositoryLogResponse, RepositoryLogResponse,
RepositorySummary, RepositorySummary,
WorkspaceCatalogListResponse,
WorkspaceCreateResponse, WorkspaceCreateResponse,
WorkspacePermissionSummary, WorkspacePermissionSummary,
WorkspaceResponse, WorkspaceResponse,
@@ -412,7 +414,7 @@ function commitSummary(value: unknown, path: string): GitCommitSummary {
export function parseWorkspaceCatalogResponse( export function parseWorkspaceCatalogResponse(
value: unknown, value: unknown,
): WorkspaceSummary[] { ): WorkspaceCatalogListResponse {
return array(value, "workspaces").map((item, index) => return array(value, "workspaces").map((item, index) =>
workspaceSummary(item, `workspaces[${index}]`) workspaceSummary(item, `workspaces[${index}]`)
); );