feat(ws): add @ID swag annotation to handlers (#488)

- added @ID annotations for all API routes to populate operationId Swagger attribute
- split GetWorkspacesHandler into 2 separate handlers to account for @ID needing to be unique-per-route
    - GetAllWorkspacesHandler now services GET /workspaces
    - GetWorkspacesByNamespaceHandler now services GET /workspaces/{namespace}
    - non-exported getWorkspacesHandler function contains all business logic that existed in GetWorkspacesHandler
- Adjusted test cases to align with the new handler names.

Signed-off-by: Andy Stoneberg <astonebe@redhat.com>
This commit is contained in:
Andy Stoneberg 2025-07-31 13:18:49 -04:00 committed by GitHub
parent d71a3f51ef
commit 639c24e81b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 87 additions and 48 deletions

View File

@ -113,8 +113,8 @@ func (a *App) Routes() http.Handler {
router.GET(AllNamespacesPath, a.GetNamespacesHandler)
// workspaces
router.GET(AllWorkspacesPath, a.GetWorkspacesHandler)
router.GET(WorkspacesByNamespacePath, a.GetWorkspacesHandler)
router.GET(AllWorkspacesPath, a.GetAllWorkspacesHandler)
router.GET(WorkspacesByNamespacePath, a.GetWorkspacesByNamespaceHandler)
router.GET(WorkspacesByNamePath, a.GetWorkspaceHandler)
router.POST(WorkspacesByNamespacePath, a.CreateWorkspaceHandler)
router.DELETE(WorkspacesByNamePath, a.DeleteWorkspaceHandler)

View File

@ -29,6 +29,7 @@ import (
// @Summary Returns the health status of the application
// @Description Provides a healthcheck response indicating the status of key services.
// @Tags healthcheck
// @ID getHealthcheck
// @Produce application/json
// @Success 200 {object} health_check.HealthCheck "Successful healthcheck response"
// @Failure 500 {object} ErrorEnvelope "Internal server error"

View File

@ -33,6 +33,7 @@ type NamespaceListEnvelope Envelope[[]models.Namespace]
// @Summary Returns a list of all namespaces
// @Description Provides a list of all namespaces that the user has access to
// @Tags namespaces
// @ID listNamespaces
// @Produce application/json
// @Success 200 {object} NamespaceListEnvelope "Successful namespaces response"
// @Failure 401 {object} ErrorEnvelope "Unauthorized"

View File

@ -39,6 +39,7 @@ type WorkspaceActionPauseEnvelope Envelope[*models.WorkspaceActionPause]
// @Summary Pause or unpause a workspace
// @Description Pauses or unpauses a workspace, stopping or resuming all associated pods.
// @Tags workspaces
// @ID updateWorkspacePauseState
// @Accept json
// @Produce json
// @Param namespace path string true "Namespace of the workspace" extensions(x-example=default)

View File

@ -47,6 +47,7 @@ type WorkspaceKindEnvelope Envelope[models.WorkspaceKind]
// @Summary Get workspace kind
// @Description Returns details of a specific workspace kind identified by its name. Workspace kinds define the available types of workspaces that can be created.
// @Tags workspacekinds
// @ID getWorkspaceKind
// @Accept json
// @Produce json
// @Param name path string true "Name of the workspace kind" extensions(x-example=jupyterlab)
@ -101,6 +102,7 @@ func (a *App) GetWorkspaceKindHandler(w http.ResponseWriter, r *http.Request, ps
// @Summary List workspace kinds
// @Description Returns a list of all available workspace kinds. Workspace kinds define the different types of workspaces that can be created in the system.
// @Tags workspacekinds
// @ID listWorkspaceKinds
// @Accept json
// @Produce json
// @Success 200 {object} WorkspaceKindListEnvelope "Successful operation. Returns a list of all available workspace kinds."
@ -136,6 +138,7 @@ func (a *App) GetWorkspaceKindsHandler(w http.ResponseWriter, r *http.Request, _
// @Summary Create workspace kind
// @Description Creates a new workspace kind.
// @Tags workspacekinds
// @ID createWorkspaceKind
// @Accept application/yaml
// @Produce json
// @Param body body string true "Kubernetes YAML manifest of a WorkspaceKind"

View File

@ -209,7 +209,7 @@ var _ = Describe("WorkspaceKinds Handler", func() {
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
By("executing GetWorkspaceKindsHandler")
ps := httprouter.Params{}
rr := httptest.NewRecorder()
a.GetWorkspaceKindsHandler(rr, req, ps)

View File

@ -44,6 +44,7 @@ type WorkspaceEnvelope Envelope[models.Workspace]
// @Summary Get workspace
// @Description Returns details of a specific workspace identified by namespace and workspace name.
// @Tags workspaces
// @ID getWorkspace
// @Accept json
// @Produce json
// @Param namespace path string true "Namespace of the workspace" extensions(x-example=kubeflow-user-example-com)
@ -99,24 +100,44 @@ func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps htt
a.dataResponse(w, r, responseEnvelope)
}
// GetWorkspacesHandler returns a list of workspaces.
// GetAllWorkspacesHandler returns a list of all workspaces across all namespaces.
//
// @Summary List workspaces
// @Description Returns a list of workspaces. The endpoint supports two modes:
// @Description 1. List all workspaces across all namespaces (when no namespace is provided)
// @Description 2. List workspaces in a specific namespace (when namespace is provided)
// @Summary List all workspaces
// @Description Returns a list of all workspaces across all namespaces.
// @Tags workspaces
// @ID listAllWorkspaces
// @Accept json
// @Produce json
// @Param namespace path string true "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces." extensions(x-example=kubeflow-user-example-com)
// @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of workspaces."
// @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of all workspaces."
// @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required."
// @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to list workspaces."
// @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server."
// @Router /workspaces [get]
func (a *App) GetAllWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
a.getWorkspacesHandler(w, r, ps)
}
// GetWorkspacesByNamespaceHandler returns a list of workspaces in a specific namespace.
//
// @Summary List workspaces by namespace
// @Description Returns a list of workspaces in a specific namespace.
// @Tags workspaces
// @ID listWorkspacesByNamespace
// @Accept json
// @Produce json
// @Param namespace path string true "Namespace to filter workspaces" extensions(x-example=kubeflow-user-example-com)
// @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of workspaces in the specified namespace."
// @Failure 400 {object} ErrorEnvelope "Bad Request. Invalid namespace format."
// @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required."
// @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to list workspaces."
// @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server."
// @Router /workspaces [get]
// @Router /workspaces/{namespace} [get]
func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
func (a *App) GetWorkspacesByNamespaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
a.getWorkspacesHandler(w, r, ps)
}
// getWorkspacesHandler is the internal implementation for listing workspaces.
func (a *App) getWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
namespace := ps.ByName(NamespacePathParam)
// validate path parameters
@ -167,6 +188,7 @@ func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps ht
// @Summary Create workspace
// @Description Creates a new workspace in the specified namespace.
// @Tags workspaces
// @ID createWorkspace
// @Accept json
// @Produce json
// @Param namespace path string true "Namespace for the workspace" extensions(x-example=kubeflow-user-example-com)
@ -267,6 +289,7 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps
// @Summary Delete workspace
// @Description Deletes a specific workspace identified by namespace and workspace name.
// @Tags workspaces
// @ID deleteWorkspace
// @Accept json
// @Produce json
// @Param namespace path string true "Namespace of the workspace" extensions(x-example=kubeflow-user-example-com)

View File

@ -164,10 +164,10 @@ var _ = Describe("Workspaces Handler", func() {
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
By("executing GetAllWorkspacesHandler")
ps := httprouter.Params{}
rr := httptest.NewRecorder()
a.GetWorkspacesHandler(rr, req, ps)
a.GetAllWorkspacesHandler(rr, req, ps)
rs := rr.Result()
defer rs.Body.Close()
@ -219,12 +219,12 @@ var _ = Describe("Workspaces Handler", func() {
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
By("executing GetWorkspacesByNamespaceHandler")
ps := httprouter.Params{
httprouter.Param{Key: NamespacePathParam, Value: namespaceName1},
}
rr := httptest.NewRecorder()
a.GetWorkspacesHandler(rr, req, ps)
a.GetWorkspacesByNamespaceHandler(rr, req, ps)
rs := rr.Result()
defer rs.Body.Close()
@ -429,12 +429,12 @@ var _ = Describe("Workspaces Handler", func() {
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
By("executing GetWorkspacesByNamespaceHandler")
ps := httprouter.Params{
httprouter.Param{Key: NamespacePathParam, Value: namespaceName1},
}
rr := httptest.NewRecorder()
a.GetWorkspacesHandler(rr, req, ps)
a.GetWorkspacesByNamespaceHandler(rr, req, ps)
rs := rr.Result()
defer rs.Body.Close()
@ -543,10 +543,10 @@ var _ = Describe("Workspaces Handler", func() {
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
By("executing GetAllWorkspacesHandler")
ps := httprouter.Params{}
rr := httptest.NewRecorder()
a.GetWorkspacesHandler(rr, req, ps)
a.GetAllWorkspacesHandler(rr, req, ps)
rs := rr.Result()
defer rs.Body.Close()
@ -577,12 +577,12 @@ var _ = Describe("Workspaces Handler", func() {
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
By("executing GetWorkspacesByNamespaceHandler")
ps := httprouter.Params{
httprouter.Param{Key: NamespacePathParam, Value: missingNamespace},
}
rr := httptest.NewRecorder()
a.GetWorkspacesHandler(rr, req, ps)
a.GetWorkspacesByNamespaceHandler(rr, req, ps)
rs := rr.Result()
defer rs.Body.Close()

View File

@ -29,6 +29,7 @@ const docTemplate = `{
"healthcheck"
],
"summary": "Returns the health status of the application",
"operationId": "getHealthcheck",
"responses": {
"200": {
"description": "Successful healthcheck response",
@ -55,6 +56,7 @@ const docTemplate = `{
"namespaces"
],
"summary": "Returns a list of all namespaces",
"operationId": "listNamespaces",
"responses": {
"200": {
"description": "Successful namespaces response",
@ -96,6 +98,7 @@ const docTemplate = `{
"workspacekinds"
],
"summary": "List workspace kinds",
"operationId": "listWorkspaceKinds",
"responses": {
"200": {
"description": "Successful operation. Returns a list of all available workspace kinds.",
@ -135,6 +138,7 @@ const docTemplate = `{
"workspacekinds"
],
"summary": "Create workspace kind",
"operationId": "createWorkspaceKind",
"parameters": [
{
"description": "Kubernetes YAML manifest of a WorkspaceKind",
@ -217,6 +221,7 @@ const docTemplate = `{
"workspacekinds"
],
"summary": "Get workspace kind",
"operationId": "getWorkspaceKind",
"parameters": [
{
"type": "string",
@ -269,7 +274,7 @@ const docTemplate = `{
},
"/workspaces": {
"get": {
"description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)",
"description": "Returns a list of all workspaces across all namespaces.",
"consumes": [
"application/json"
],
@ -279,20 +284,15 @@ const docTemplate = `{
"tags": [
"workspaces"
],
"summary": "List workspaces",
"summary": "List all workspaces",
"operationId": "listAllWorkspaces",
"responses": {
"200": {
"description": "Successful operation. Returns a list of workspaces.",
"description": "Successful operation. Returns a list of all workspaces.",
"schema": {
"$ref": "#/definitions/api.WorkspaceListEnvelope"
}
},
"400": {
"description": "Bad Request. Invalid namespace format.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"401": {
"description": "Unauthorized. Authentication is required.",
"schema": {
@ -316,7 +316,7 @@ const docTemplate = `{
},
"/workspaces/{namespace}": {
"get": {
"description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)",
"description": "Returns a list of workspaces in a specific namespace.",
"consumes": [
"application/json"
],
@ -326,12 +326,13 @@ const docTemplate = `{
"tags": [
"workspaces"
],
"summary": "List workspaces",
"summary": "List workspaces by namespace",
"operationId": "listWorkspacesByNamespace",
"parameters": [
{
"type": "string",
"x-example": "kubeflow-user-example-com",
"description": "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces.",
"description": "Namespace to filter workspaces",
"name": "namespace",
"in": "path",
"required": true
@ -339,7 +340,7 @@ const docTemplate = `{
],
"responses": {
"200": {
"description": "Successful operation. Returns a list of workspaces.",
"description": "Successful operation. Returns a list of workspaces in the specified namespace.",
"schema": {
"$ref": "#/definitions/api.WorkspaceListEnvelope"
}
@ -382,6 +383,7 @@ const docTemplate = `{
"workspaces"
],
"summary": "Create workspace",
"operationId": "createWorkspace",
"parameters": [
{
"type": "string",
@ -466,6 +468,7 @@ const docTemplate = `{
"workspaces"
],
"summary": "Pause or unpause a workspace",
"operationId": "updateWorkspacePauseState",
"parameters": [
{
"type": "string",
@ -564,6 +567,7 @@ const docTemplate = `{
"workspaces"
],
"summary": "Get workspace",
"operationId": "getWorkspace",
"parameters": [
{
"type": "string",
@ -633,6 +637,7 @@ const docTemplate = `{
"workspaces"
],
"summary": "Delete workspace",
"operationId": "deleteWorkspace",
"parameters": [
{
"type": "string",

View File

@ -27,6 +27,7 @@
"healthcheck"
],
"summary": "Returns the health status of the application",
"operationId": "getHealthcheck",
"responses": {
"200": {
"description": "Successful healthcheck response",
@ -53,6 +54,7 @@
"namespaces"
],
"summary": "Returns a list of all namespaces",
"operationId": "listNamespaces",
"responses": {
"200": {
"description": "Successful namespaces response",
@ -94,6 +96,7 @@
"workspacekinds"
],
"summary": "List workspace kinds",
"operationId": "listWorkspaceKinds",
"responses": {
"200": {
"description": "Successful operation. Returns a list of all available workspace kinds.",
@ -133,6 +136,7 @@
"workspacekinds"
],
"summary": "Create workspace kind",
"operationId": "createWorkspaceKind",
"parameters": [
{
"description": "Kubernetes YAML manifest of a WorkspaceKind",
@ -215,6 +219,7 @@
"workspacekinds"
],
"summary": "Get workspace kind",
"operationId": "getWorkspaceKind",
"parameters": [
{
"type": "string",
@ -267,7 +272,7 @@
},
"/workspaces": {
"get": {
"description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)",
"description": "Returns a list of all workspaces across all namespaces.",
"consumes": [
"application/json"
],
@ -277,20 +282,15 @@
"tags": [
"workspaces"
],
"summary": "List workspaces",
"summary": "List all workspaces",
"operationId": "listAllWorkspaces",
"responses": {
"200": {
"description": "Successful operation. Returns a list of workspaces.",
"description": "Successful operation. Returns a list of all workspaces.",
"schema": {
"$ref": "#/definitions/api.WorkspaceListEnvelope"
}
},
"400": {
"description": "Bad Request. Invalid namespace format.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"401": {
"description": "Unauthorized. Authentication is required.",
"schema": {
@ -314,7 +314,7 @@
},
"/workspaces/{namespace}": {
"get": {
"description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)",
"description": "Returns a list of workspaces in a specific namespace.",
"consumes": [
"application/json"
],
@ -324,12 +324,13 @@
"tags": [
"workspaces"
],
"summary": "List workspaces",
"summary": "List workspaces by namespace",
"operationId": "listWorkspacesByNamespace",
"parameters": [
{
"type": "string",
"x-example": "kubeflow-user-example-com",
"description": "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces.",
"description": "Namespace to filter workspaces",
"name": "namespace",
"in": "path",
"required": true
@ -337,7 +338,7 @@
],
"responses": {
"200": {
"description": "Successful operation. Returns a list of workspaces.",
"description": "Successful operation. Returns a list of workspaces in the specified namespace.",
"schema": {
"$ref": "#/definitions/api.WorkspaceListEnvelope"
}
@ -380,6 +381,7 @@
"workspaces"
],
"summary": "Create workspace",
"operationId": "createWorkspace",
"parameters": [
{
"type": "string",
@ -464,6 +466,7 @@
"workspaces"
],
"summary": "Pause or unpause a workspace",
"operationId": "updateWorkspacePauseState",
"parameters": [
{
"type": "string",
@ -562,6 +565,7 @@
"workspaces"
],
"summary": "Get workspace",
"operationId": "getWorkspace",
"parameters": [
{
"type": "string",
@ -631,6 +635,7 @@
"workspaces"
],
"summary": "Delete workspace",
"operationId": "deleteWorkspace",
"parameters": [
{
"type": "string",