diff --git a/crates/server-api/src/lib.rs b/crates/server-api/src/lib.rs index e1c63a767..b90eec39a 100644 --- a/crates/server-api/src/lib.rs +++ b/crates/server-api/src/lib.rs @@ -98,6 +98,9 @@ pub trait ServerApi { #[path] workspace_id: String, ) -> Result; + #[get("/api/repositories", status = 200, error_status = 400)] + async fn repository_list_alias(&self) -> Result; + #[get( "/api/w/{workspace_id}/repositories/{repository_key}", status = 200, @@ -109,6 +112,12 @@ pub trait ServerApi { #[path] repository_key: String, ) -> Result; + #[get("/api/repositories/{repository_key}", status = 200, error_status = 404)] + async fn repository_detail_alias( + &self, + #[path] repository_key: String, + ) -> Result; + #[post( "/api/w/{workspace_id}/repositories", status = 201, @@ -3759,7 +3768,7 @@ mod tests { #[test] fn worker_session_contract_and_flattened_availability_are_stable() { let operations = ServerApiMetadata::OPERATIONS; - assert_eq!(operations.len(), 4); + assert_eq!(operations.len(), 6); let operation = operations .iter() .find(|operation| operation.operation_id == "worker_session") @@ -5143,6 +5152,8 @@ mod openapi_artifact_tests { ["application/json"]["schema"]["$ref"], "#/components/schemas/RepositoryListResponse" ); + assert!(value["paths"]["/api/repositories"]["get"].is_object()); + assert!(value["paths"]["/api/repositories/{repository_key}"]["get"].is_object()); assert!( value["paths"]["/api/w/{workspace_id}/repositories"]["post"]["responses"]["200"] .is_object() diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index a18699bd1..5fe926370 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -3089,6 +3089,16 @@ impl server_api::ServerApi for WorkspaceApi { .map_err(ApiError::into_repository_api_error) } + async fn repository_list_alias( + &self, + ) -> std::result::Result + { + list_repositories(State(self.clone())) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + async fn repository_detail( &self, workspace_id: String, @@ -3107,6 +3117,17 @@ impl server_api::ServerApi for WorkspaceApi { .map_err(ApiError::into_repository_api_error) } + async fn repository_detail_alias( + &self, + repository_key: String, + ) -> std::result::Result + { + repository_detail(State(self.clone()), AxumPath(repository_key)) + .await + .map(|Json(response)| response) + .map_err(ApiError::into_repository_api_error) + } + async fn repository_create( &self, actor: RequestActor, @@ -3448,8 +3469,6 @@ fn build_inner_router(api: WorkspaceApi) -> Router { "/api/w/{workspace_id}/objectives/{objective_id}/ticket-links/{ticket_id}", delete(scoped_unlink_objective_ticket), ) - .route("/api/repositories", get(list_repositories)) - .route("/api/repositories/{repository_key}", get(repository_detail)) .route( "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test", post(scoped_probe_repository_ssh_connection) @@ -19047,6 +19066,28 @@ mod tests { SqliteWorkspaceStore, UserRecord, WorkspaceRecord, WorkspaceRuntimeBinding, }; + #[derive(Clone)] + struct TestBearerAuthorizer(&'static str); + + impl server_api::client_support::RequestAuthorizer for TestBearerAuthorizer { + fn authorize( + &self, + _request: server_api::client_support::AuthorizerRequest<'_>, + ) -> std::result::Result< + axum::http::HeaderMap, + server_api::client_support::AuthorizationError, + > { + let mut headers = axum::http::HeaderMap::new(); + headers.insert( + axum::http::header::AUTHORIZATION, + format!("Bearer {}", self.0) + .parse() + .map_err(|_| server_api::client_support::AuthorizationError::new())?, + ); + Ok(headers) + } + } + #[test] fn backend_resource_timeout_maps_to_gateway_timeout() { assert_eq!( @@ -22968,6 +23009,54 @@ mod tests { String::from_utf8_lossy(&missing_body) ); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let api_base_url = format!("http://{}", listener.local_addr().unwrap()); + let network_app = app.clone(); + let server = tokio::spawn(async move { + axum::serve(listener, network_app).await.unwrap(); + }); + let generated = server_api::ServerApiClient::builder(&api_base_url) + .unwrap() + .authorizer(TestBearerAuthorizer("api-token-auth")) + .build() + .unwrap(); + let generated_workspace_id = workspace.workspace.workspace_id.clone(); + let generated_list = generated + .repository_list(generated_workspace_id.clone()) + .await + .unwrap(); + assert!( + generated_list + .items + .iter() + .any(|repository| repository.repository_key == "documentation") + ); + assert_eq!( + generated + .repository_detail(generated_workspace_id.clone(), "documentation".to_owned()) + .await + .unwrap() + .item + .repository_key, + "documentation" + ); + let generated_request = CreateWorkspaceRepositoryRequest { + repository_key: "generated-client".to_owned(), + source: "/srv/repos/generated-client".to_owned(), + default_ref: Some("main".to_owned()), + }; + let created = generated + .repository_create(generated_workspace_id.clone(), generated_request.clone()) + .await + .unwrap(); + assert!(!created.replayed); + let replayed = generated + .repository_create(generated_workspace_id, generated_request) + .await + .unwrap(); + assert!(replayed.replayed); + server.abort(); + let identity_uri = format!( "/api/w/{}/settings/signing-identity", workspace.workspace.workspace_id diff --git a/openapi/server-api.json b/openapi/server-api.json index b73bca48f..c970e99e0 100644 --- a/openapi/server-api.json +++ b/openapi/server-api.json @@ -342,10 +342,76 @@ "info": { "title": "Yoi Server API", "version": "0.1.0", - "x-yoi-source-digest": "sha256:c7a6b25e1a071ab9a6831e6cd53daf51acdf8aaac20746b0b6db35c12428836f" + "x-yoi-source-digest": "sha256:317cc65b39674bd229abc364d2daea3e909215471da5828313b3cf3ee2142600" }, "openapi": "3.1.0", "paths": { + "/api/repositories": { + "get": { + "operationId": "repository_list_alias", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, + "/api/repositories/{repository_key}": { + "get": { + "operationId": "repository_detail_alias", + "parameters": [ + { + "explode": false, + "in": "path", + "name": "repository_key", + "required": true, + "schema": { + "$ref": "#/components/schemas/string" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryDetailResponse" + } + } + }, + "description": "Successful response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + } + }, + "description": "Error response" + } + } + } + }, "/api/w/{workspace_id}/repositories": { "get": { "operationId": "repository_list",