feat: centralize workspace profile DTOs

This commit is contained in:
2026-09-02 15:24:12 +09:00
parent b29b003ea3
commit bad37ddc7d
4 changed files with 282 additions and 101 deletions
@@ -12,11 +12,15 @@ use worker_runtime::config_bundle::{
ConfigBundle, ConfigBundleMetadata, ConfigBundleProvenance, ConfigProfileDescriptor,
};
use worker_runtime::profile_archive::{ProfileSourceArchive, ProfileSourceArchiveInput};
use workspace_api::{
Diagnostic, DiagnosticSeverity, ProfileSettingsResponse, UpdateWorkspaceMetadataRequest,
WorkspaceMetadataSettingsResponse, WorkspaceProfileSourceProvenance,
WorkspaceProfileSourceSummary, WorkspaceProfileSummary,
};
use crate::config_source::{
WorkspaceConfigSchemaProvider, WorkspaceConfigState, evaluate_workspace_config_state,
};
use crate::hosts::{DiagnosticSeverity, RuntimeDiagnostic};
use crate::{Error, Result};
const PROFILE_SCHEMA_SOURCE: &str = r#"{
@@ -427,81 +431,6 @@ fn build_virtual_profile_archive(
.map_err(|error| profile_validation_error("profile_source_archive_invalid", &error.to_string()))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceMetadataSettingsResponse {
pub workspace_id: String,
pub display_name: String,
pub created_at: String,
pub revision: String,
pub source: String,
pub diagnostics: Vec<RuntimeDiagnostic>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UpdateWorkspaceMetadataRequest {
pub display_name: String,
pub revision: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceMetadataMutationResponse {
pub workspace: WorkspaceMetadataSettingsResponse,
pub diagnostics: Vec<RuntimeDiagnostic>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileSettingsResponse {
pub workspace_id: String,
pub registry_revision: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub config_revision: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tree_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub projection_digest: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_profile: Option<String>,
pub profiles: Vec<WorkspaceProfileSummary>,
pub sources: Vec<WorkspaceProfileSourceSummary>,
pub diagnostics: Vec<RuntimeDiagnostic>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceProfileSummary {
pub profile_id: String,
pub selector: String,
pub label: String,
pub source_kind: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile_source_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub editable: bool,
pub is_default: bool,
pub diagnostics: Vec<RuntimeDiagnostic>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceProfileSourceSummary {
pub profile_source_id: String,
pub display_path: String,
pub kind: String,
pub content_type: String,
pub content_digest: String,
pub provenance: WorkspaceProfileSourceProvenance,
pub editable: bool,
pub revision: String,
pub size_bytes: u64,
pub diagnostics: Vec<RuntimeDiagnostic>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WorkspaceProfileSourceProvenance {
ProjectProfileSourceTree,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct WorkspaceIdentityFile {
@@ -804,8 +733,8 @@ fn diagnostic(
code: impl Into<String>,
severity: DiagnosticSeverity,
message: impl Into<String>,
) -> RuntimeDiagnostic {
RuntimeDiagnostic {
) -> Diagnostic {
Diagnostic {
code: code.into(),
severity,
message: message.into(),
+31 -23
View File
@@ -61,20 +61,22 @@ use workspace_api::{
CreateWorkspaceRepositoryRequest, CreateWorkspaceRepositoryResponse,
DeleteRepositorySshCredentialRequest, DeleteRepositorySshHostTrustRequest,
ObjectiveCreateRequest, ObjectiveEditRequest, ObjectiveLinkTicketRequest,
ObjectiveStateRequest, PutRepositorySshHostTrustRequest, RepositoryAccessProjection,
RepositoryDetailResponse, RepositoryListResponse, RepositoryLogResponse,
RepositorySshCredential, RepositorySshHostTrust, RotateRepositorySshCredentialRequest,
RuntimeConnectionTestResponse, RuntimeManagementSummary, TICKET_ORCHESTRATION_PLANS_QUERY_PATH,
TICKET_RELATIONS_QUERY_PATH,
ObjectiveStateRequest, ProfileSettingsResponse, PutRepositorySshHostTrustRequest,
RepositoryAccessProjection, RepositoryDetailResponse, RepositoryListResponse,
RepositoryLogResponse, RepositorySshCredential, RepositorySshHostTrust,
RotateRepositorySshCredentialRequest, RuntimeConnectionTestResponse, RuntimeManagementSummary,
TICKET_ORCHESTRATION_PLANS_QUERY_PATH, TICKET_RELATIONS_QUERY_PATH,
UpdateWorkspaceMetadataRequest,
WorkingDirectoryCreateRequest as BrowserWorkingDirectoryCreateRequest,
WorkingDirectoryCreateResponse as BrowserWorkingDirectoryCreateResponse,
WorkingDirectoryDetailResponse as BrowserWorkingDirectoryDetailResponse,
WorkingDirectoryListResponse as BrowserWorkingDirectoryListResponse,
WorkingDirectoryRemovalDisposition, WorkingDirectoryRemovalRequest,
WorkingDirectoryRemovalResponse, WorkspaceCatalogListResponse, WorkspaceCreateResponse,
WorkspaceExtensionPointState, WorkspaceExtensionPoints, WorkspacePermissionSummary,
WorkspaceRepositoryRecord, WorkspaceResponse, WorkspaceRuntimeResource, WorkspaceSummary,
WorkspaceWorkerDiscoveryItem, WorkspaceWorkerDiscoveryPage, WorkspaceWorkerSubject,
WorkspaceExtensionPointState, WorkspaceExtensionPoints, WorkspaceMetadataMutationResponse,
WorkspaceMetadataSettingsResponse, WorkspacePermissionSummary, WorkspaceRepositoryRecord,
WorkspaceResponse, WorkspaceRuntimeResource, WorkspaceSummary, WorkspaceWorkerDiscoveryItem,
WorkspaceWorkerDiscoveryPage, WorkspaceWorkerSubject,
};
use crate::auth::{
@@ -114,7 +116,6 @@ use crate::observation::{
BackendObservationProxy, ObservationProxyError, RuntimeObservationClient,
RuntimeObservationSource, RuntimeObservationSourceConfig,
};
use crate::profile_settings::UpdateWorkspaceMetadataRequest;
use crate::records::{
MergeRequestListItem, MergeRequestListResponse, ObjectiveDetail, ObjectiveQueryRequest,
ObjectiveQueryResponse, ObjectiveShowRequest, ProjectRecordList, TicketDetail,
@@ -3360,7 +3361,7 @@ async fn scoped_get_workspace(
async fn scoped_get_workspace_settings(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
) -> ApiResult<Json<crate::profile_settings::WorkspaceMetadataSettingsResponse>> {
) -> ApiResult<Json<WorkspaceMetadataSettingsResponse>> {
validate_workspace_scope(&api, &path.workspace_id)?;
Ok(Json(crate::profile_settings::workspace_metadata_settings(
&api.config.workspace_root,
@@ -3374,20 +3375,18 @@ async fn scoped_update_workspace_settings(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
Json(request): Json<UpdateWorkspaceMetadataRequest>,
) -> ApiResult<Json<crate::profile_settings::WorkspaceMetadataMutationResponse>> {
) -> ApiResult<Json<WorkspaceMetadataMutationResponse>> {
validate_workspace_scope(&api, &path.workspace_id)?;
let workspace =
crate::profile_settings::update_workspace_metadata(&api.config.workspace_root, request)?;
Ok(Json(
crate::profile_settings::WorkspaceMetadataMutationResponse {
workspace,
diagnostics: vec![RuntimeDiagnostic {
code: "workspace_metadata_updated".to_string(),
severity: DiagnosticSeverity::Info,
message: "Workspace display metadata was updated.".to_string(),
}],
},
))
Ok(Json(WorkspaceMetadataMutationResponse {
workspace,
diagnostics: vec![workspace_api::Diagnostic {
code: "workspace_metadata_updated".to_string(),
severity: workspace_api::DiagnosticSeverity::Info,
message: "Workspace display metadata was updated.".to_string(),
}],
}))
}
#[derive(Debug, Deserialize)]
@@ -3763,7 +3762,7 @@ async fn scoped_commit_workspace_config_tree(
async fn scoped_get_profile_settings(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
) -> ApiResult<Json<crate::profile_settings::ProfileSettingsResponse>> {
) -> ApiResult<Json<ProfileSettingsResponse>> {
validate_workspace_scope(&api, &path.workspace_id)?;
let state = api
.config_store
@@ -14353,7 +14352,7 @@ fn worker_launch_options_response(api: &WorkspaceApi) -> ApiResult<WorkerLaunchO
!profile
.diagnostics
.iter()
.any(|diagnostic| diagnostic.severity == DiagnosticSeverity::Error)
.any(|diagnostic| diagnostic.severity == workspace_api::DiagnosticSeverity::Error)
})
.map(|profile| WorkerLaunchProfileCandidate {
id: profile.profile_id,
@@ -26640,6 +26639,13 @@ VALUES ('0192f0e8-4d84-7d6e-a000-000000000001', 'ticket', 3);
#[tokio::test]
async fn profile_settings_are_read_only_virtual_config_projection() {
let dir = tempfile::tempdir().unwrap();
let legacy_profile_dir = dir.path().join(".yoi/profiles");
std::fs::create_dir_all(&legacy_profile_dir).unwrap();
std::fs::write(
legacy_profile_dir.join("legacy.dcdl"),
"LegacyFilesystemProfile = { label = 'must not be projected' }\n",
)
.unwrap();
let app = test_app(dir.path()).await;
let path = format!("/api/w/{TEST_WORKSPACE_ID}/settings/profiles");
let settings = get_json(app.clone(), &path).await;
@@ -26654,6 +26660,8 @@ VALUES ('0192f0e8-4d84-7d6e-a000-000000000001', 'ticket', 3);
.iter()
.all(|profile| profile["editable"] == false)
);
assert!(!settings.to_string().contains("LegacyFilesystemProfile"));
assert!(!settings.to_string().contains("legacy.dcdl"));
let mutation = app
.oneshot(
Request::builder()