Loading deploy_keys.go +9 −4 Original line number Diff line number Diff line Loading @@ -59,10 +59,8 @@ type ( // ListUserProjectDeployKeys gets a list of a user's deploy keys. // // GitLab API docs: // https://docs.gitlab.com/api/deploy_keys/#list-project-deploy-keys-for-user // ListUserProjectDeployKeys gets a list of a user's deploy keys. // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/deploy_keys/#list-project-deploy-keys-for-user Loading Loading @@ -225,6 +223,13 @@ type ListUserProjectDeployKeysOptions struct { ListOptions } // ListUserProjectDeployKeys gets a list of a user's deploy keys. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/deploy_keys/#list-project-deploy-keys-for-user func (s *DeployKeysService) ListUserProjectDeployKeys(uid any, opt *ListUserProjectDeployKeysOptions, options ...RequestOptionFunc) ([]*ProjectDeployKey, *Response, error) { return do[[]*ProjectDeployKey](s.client, withPath("users/%s/project_deploy_keys", UserID{uid}), Loading projects.go +24 −1 Original line number Diff line number Diff line Loading @@ -32,8 +32,21 @@ type ( // GitLab API docs: https://docs.gitlab.com/api/projects/ ProjectsServiceInterface interface { ListProjects(opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) // ListUserProjects gets a list of projects for the given user. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. ListUserProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) // ListUserContributedProjects gets a list of visible projects a given user // has contributed to. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. ListUserContributedProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) // ListUserStarredProjects gets a list of projects starred by the given user. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. ListUserStarredProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) ListProjectsUsers(pid any, opt *ListProjectUserOptions, options ...RequestOptionFunc) ([]*ProjectUser, *Response, error) ListProjectsGroups(pid any, opt *ListProjectGroupOptions, options ...RequestOptionFunc) ([]*ProjectGroup, *Response, error) Loading Loading @@ -464,6 +477,9 @@ func (s *ProjectsService) ListProjects(opt *ListProjectsOptions, options ...Requ // ListUserProjects gets a list of projects for the given user. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/projects/#list-a-users-projects func (s *ProjectsService) ListUserProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) { Loading @@ -474,7 +490,11 @@ func (s *ProjectsService) ListUserProjects(uid any, opt *ListProjectsOptions, op ) } // ListUserContributedProjects gets a list of visible projects a given user has contributed to. // ListUserContributedProjects gets a list of visible projects a given user has // contributed to. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/projects/#list-projects-a-user-has-contributed-to Loading @@ -488,6 +508,9 @@ func (s *ProjectsService) ListUserContributedProjects(uid any, opt *ListProjects // ListUserStarredProjects gets a list of projects starred by the given user. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/project_starring/#list-projects-starred-by-a-user func (s *ProjectsService) ListUserStarredProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) { Loading request_handler.go +5 −1 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import ( "io" "net/http" "reflect" "strings" "github.com/hashicorp/go-retryablehttp" ) Loading Loading @@ -52,6 +53,9 @@ func (i RunnerID) forPath() (string, error) { return PathEscape(id), nil } // UserID represents a user identifier for API paths. It accepts either a // numeric user ID or a username string. If a username is provided with a // leading "@" character (e.g., "@johndoe"), the "@" will be trimmed. type UserID struct { Value any } Loading @@ -62,7 +66,7 @@ func (i UserID) forPath() (string, error) { return "", err } return PathEscape(id), nil return PathEscape(strings.TrimPrefix(id, "@")), nil } type LabelID struct { Loading request_handler_test.go +61 −9 Original line number Diff line number Diff line Loading @@ -361,22 +361,74 @@ func TestDoRequestRunnerID(t *testing.T) { func TestDoRequestUserID(t *testing.T) { t.Parallel() tests := []struct { name string uid any expectedPath string }{ { name: "numeric user ID", uid: 123, expectedPath: "/api/v4/users/123/status", }, { name: "username string", uid: "johndoe", expectedPath: "/api/v4/users/johndoe/status", }, { name: "username with @ prefix is trimmed", uid: "@johndoe", expectedPath: "/api/v4/users/johndoe/status", }, { name: "username with slash is escaped", uid: "test/user", expectedPath: "/api/v4/users/test%2Fuser/status", }, { name: "username with @ prefix and slash", uid: "@test/user", expectedPath: "/api/v4/users/test%2Fuser/status", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() mux, client := setup(t) // GIVEN mux.HandleFunc("/api/v4/users/test%2Fuser", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc(tt.expectedPath, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }) // WHEN _, resp, err := do[none]( client, withPath("users/%s", UserID{"test/user"}), withPath("users/%s/status", UserID{tt.uid}), ) // THEN assert.NoError(t, err) assert.Equal(t, 200, resp.StatusCode) }) } } func TestDoRequestUserIDInvalidType(t *testing.T) { t.Parallel() _, client := setup(t) // WHEN _, _, err := do[none]( client, withPath("users/%s/status", UserID{struct{ ID int }{ID: 1}}), ) // THEN assert.ErrorIs(t, err, ErrInvalidIDType) } func TestDoRequestUploadSuccess(t *testing.T) { Loading users.go +21 −20 Original line number Diff line number Diff line Loading @@ -23,7 +23,6 @@ import ( "io" "net" "net/http" "strings" "time" ) Loading Loading @@ -87,6 +86,9 @@ type ( ListSSHKeys(opt *ListSSHKeysOptions, options ...RequestOptionFunc) ([]*SSHKey, *Response, error) // ListSSHKeysForUser gets a list of a specified user's SSH keys. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/user_keys/#list-all-ssh-keys-for-a-user ListSSHKeysForUser(uid any, opt *ListSSHKeysForUserOptions, options ...RequestOptionFunc) ([]*SSHKey, *Response, error) Loading Loading @@ -606,26 +608,18 @@ func (s *UsersService) CurrentUserStatus(options ...RequestOptionFunc) (*UserSta ) } // GetUserStatus retrieves a user's status. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/users/#get-the-status-of-a-user func (s *UsersService) GetUserStatus(uid any, options ...RequestOptionFunc) (*UserStatus, *Response, error) { user, err := parseID(uid) if err != nil { return nil, nil, err } u := fmt.Sprintf("users/%s/status", strings.TrimPrefix(user, "@")) req, err := s.client.NewRequest(http.MethodGet, u, nil, options) if err != nil { return nil, nil, err } status := new(UserStatus) resp, err := s.client.Do(req, status) if err != nil { return nil, resp, err } return status, resp, nil return do[*UserStatus](s.client, withPath("users/%s/status", UserID{uid}), withRequestOpts(options...), ) } // UserStatusOptions represents the options required to set the status Loading Loading @@ -705,6 +699,13 @@ type ListSSHKeysForUserOptions struct { ListOptions } // ListSSHKeysForUser gets a list of a specified user's SSH keys. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/user_keys/#list-all-ssh-keys-for-a-user func (s *UsersService) ListSSHKeysForUser(uid any, opt *ListSSHKeysForUserOptions, options ...RequestOptionFunc) ([]*SSHKey, *Response, error) { return do[[]*SSHKey](s.client, withPath("users/%s/keys", UserID{uid}), Loading Loading
deploy_keys.go +9 −4 Original line number Diff line number Diff line Loading @@ -59,10 +59,8 @@ type ( // ListUserProjectDeployKeys gets a list of a user's deploy keys. // // GitLab API docs: // https://docs.gitlab.com/api/deploy_keys/#list-project-deploy-keys-for-user // ListUserProjectDeployKeys gets a list of a user's deploy keys. // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/deploy_keys/#list-project-deploy-keys-for-user Loading Loading @@ -225,6 +223,13 @@ type ListUserProjectDeployKeysOptions struct { ListOptions } // ListUserProjectDeployKeys gets a list of a user's deploy keys. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/deploy_keys/#list-project-deploy-keys-for-user func (s *DeployKeysService) ListUserProjectDeployKeys(uid any, opt *ListUserProjectDeployKeysOptions, options ...RequestOptionFunc) ([]*ProjectDeployKey, *Response, error) { return do[[]*ProjectDeployKey](s.client, withPath("users/%s/project_deploy_keys", UserID{uid}), Loading
projects.go +24 −1 Original line number Diff line number Diff line Loading @@ -32,8 +32,21 @@ type ( // GitLab API docs: https://docs.gitlab.com/api/projects/ ProjectsServiceInterface interface { ListProjects(opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) // ListUserProjects gets a list of projects for the given user. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. ListUserProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) // ListUserContributedProjects gets a list of visible projects a given user // has contributed to. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. ListUserContributedProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) // ListUserStarredProjects gets a list of projects starred by the given user. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. ListUserStarredProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) ListProjectsUsers(pid any, opt *ListProjectUserOptions, options ...RequestOptionFunc) ([]*ProjectUser, *Response, error) ListProjectsGroups(pid any, opt *ListProjectGroupOptions, options ...RequestOptionFunc) ([]*ProjectGroup, *Response, error) Loading Loading @@ -464,6 +477,9 @@ func (s *ProjectsService) ListProjects(opt *ListProjectsOptions, options ...Requ // ListUserProjects gets a list of projects for the given user. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/projects/#list-a-users-projects func (s *ProjectsService) ListUserProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) { Loading @@ -474,7 +490,11 @@ func (s *ProjectsService) ListUserProjects(uid any, opt *ListProjectsOptions, op ) } // ListUserContributedProjects gets a list of visible projects a given user has contributed to. // ListUserContributedProjects gets a list of visible projects a given user has // contributed to. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/projects/#list-projects-a-user-has-contributed-to Loading @@ -488,6 +508,9 @@ func (s *ProjectsService) ListUserContributedProjects(uid any, opt *ListProjects // ListUserStarredProjects gets a list of projects starred by the given user. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/project_starring/#list-projects-starred-by-a-user func (s *ProjectsService) ListUserStarredProjects(uid any, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) { Loading
request_handler.go +5 −1 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import ( "io" "net/http" "reflect" "strings" "github.com/hashicorp/go-retryablehttp" ) Loading Loading @@ -52,6 +53,9 @@ func (i RunnerID) forPath() (string, error) { return PathEscape(id), nil } // UserID represents a user identifier for API paths. It accepts either a // numeric user ID or a username string. If a username is provided with a // leading "@" character (e.g., "@johndoe"), the "@" will be trimmed. type UserID struct { Value any } Loading @@ -62,7 +66,7 @@ func (i UserID) forPath() (string, error) { return "", err } return PathEscape(id), nil return PathEscape(strings.TrimPrefix(id, "@")), nil } type LabelID struct { Loading
request_handler_test.go +61 −9 Original line number Diff line number Diff line Loading @@ -361,22 +361,74 @@ func TestDoRequestRunnerID(t *testing.T) { func TestDoRequestUserID(t *testing.T) { t.Parallel() tests := []struct { name string uid any expectedPath string }{ { name: "numeric user ID", uid: 123, expectedPath: "/api/v4/users/123/status", }, { name: "username string", uid: "johndoe", expectedPath: "/api/v4/users/johndoe/status", }, { name: "username with @ prefix is trimmed", uid: "@johndoe", expectedPath: "/api/v4/users/johndoe/status", }, { name: "username with slash is escaped", uid: "test/user", expectedPath: "/api/v4/users/test%2Fuser/status", }, { name: "username with @ prefix and slash", uid: "@test/user", expectedPath: "/api/v4/users/test%2Fuser/status", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() mux, client := setup(t) // GIVEN mux.HandleFunc("/api/v4/users/test%2Fuser", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc(tt.expectedPath, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }) // WHEN _, resp, err := do[none]( client, withPath("users/%s", UserID{"test/user"}), withPath("users/%s/status", UserID{tt.uid}), ) // THEN assert.NoError(t, err) assert.Equal(t, 200, resp.StatusCode) }) } } func TestDoRequestUserIDInvalidType(t *testing.T) { t.Parallel() _, client := setup(t) // WHEN _, _, err := do[none]( client, withPath("users/%s/status", UserID{struct{ ID int }{ID: 1}}), ) // THEN assert.ErrorIs(t, err, ErrInvalidIDType) } func TestDoRequestUploadSuccess(t *testing.T) { Loading
users.go +21 −20 Original line number Diff line number Diff line Loading @@ -23,7 +23,6 @@ import ( "io" "net" "net/http" "strings" "time" ) Loading Loading @@ -87,6 +86,9 @@ type ( ListSSHKeys(opt *ListSSHKeysOptions, options ...RequestOptionFunc) ([]*SSHKey, *Response, error) // ListSSHKeysForUser gets a list of a specified user's SSH keys. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/user_keys/#list-all-ssh-keys-for-a-user ListSSHKeysForUser(uid any, opt *ListSSHKeysForUserOptions, options ...RequestOptionFunc) ([]*SSHKey, *Response, error) Loading Loading @@ -606,26 +608,18 @@ func (s *UsersService) CurrentUserStatus(options ...RequestOptionFunc) (*UserSta ) } // GetUserStatus retrieves a user's status. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/users/#get-the-status-of-a-user func (s *UsersService) GetUserStatus(uid any, options ...RequestOptionFunc) (*UserStatus, *Response, error) { user, err := parseID(uid) if err != nil { return nil, nil, err } u := fmt.Sprintf("users/%s/status", strings.TrimPrefix(user, "@")) req, err := s.client.NewRequest(http.MethodGet, u, nil, options) if err != nil { return nil, nil, err } status := new(UserStatus) resp, err := s.client.Do(req, status) if err != nil { return nil, resp, err } return status, resp, nil return do[*UserStatus](s.client, withPath("users/%s/status", UserID{uid}), withRequestOpts(options...), ) } // UserStatusOptions represents the options required to set the status Loading Loading @@ -705,6 +699,13 @@ type ListSSHKeysForUserOptions struct { ListOptions } // ListSSHKeysForUser gets a list of a specified user's SSH keys. // // uid can be either a user ID (int) or a username (string). If a username // is provided with a leading "@" (e.g., "@johndoe"), it will be trimmed. // // GitLab API docs: // https://docs.gitlab.com/api/user_keys/#list-all-ssh-keys-for-a-user func (s *UsersService) ListSSHKeysForUser(uid any, opt *ListSSHKeysForUserOptions, options ...RequestOptionFunc) ([]*SSHKey, *Response, error) { return do[[]*SSHKey](s.client, withPath("users/%s/keys", UserID{uid}), Loading