refactor: reduce cyclomatic complexity in filesConnector.Connect
Split the Connect handler into smaller focused functions: - handleRequest: main request processing - createDualReadWriter: setup dependencies - parseRequestOptions: extract request options - handleDirectoryListing: GET directory requests - handleMethodRequest: route to method handlers - handleGet/handlePost/handlePut/handleDelete: method-specific logic - handleMove: move operation logic
This commit is contained in:
@@ -75,172 +75,219 @@ func (c *filesConnector) Connect(ctx context.Context, name string, opts runtime.
|
||||
ctx = logging.Context(ctx, logger)
|
||||
|
||||
return WithTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
repo, err := c.getRepo(ctx, r.Method, name)
|
||||
if err != nil {
|
||||
logger.Debug("failed to find repository", "error", err)
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
readWriter, ok := repo.(repository.ReaderWriter)
|
||||
if !ok {
|
||||
responder.Error(apierrors.NewBadRequest("repository does not support read-writing"))
|
||||
return
|
||||
}
|
||||
|
||||
parser, err := c.parsers.GetParser(ctx, readWriter)
|
||||
if err != nil {
|
||||
responder.Error(fmt.Errorf("failed to get parser: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
clients, err := c.clients.Clients(ctx, repo.Config().Namespace)
|
||||
if err != nil {
|
||||
responder.Error(fmt.Errorf("failed to get clients: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
folderClient, err := clients.Folder(ctx)
|
||||
if err != nil {
|
||||
responder.Error(fmt.Errorf("failed to get folder client: %w", err))
|
||||
return
|
||||
}
|
||||
folders := resources.NewFolderManager(readWriter, folderClient, resources.NewEmptyFolderTree())
|
||||
dualReadWriter := resources.NewDualReadWriter(readWriter, parser, folders, c.access)
|
||||
query := r.URL.Query()
|
||||
opts := resources.DualWriteOptions{
|
||||
Ref: query.Get("ref"),
|
||||
Message: query.Get("message"),
|
||||
SkipDryRun: query.Get("skipDryRun") == "true",
|
||||
OriginalPath: query.Get("originalPath"),
|
||||
Branch: repo.Config().Branch(),
|
||||
}
|
||||
logger := logger.With("url", r.URL.Path, "ref", opts.Ref, "message", opts.Message)
|
||||
ctx := logging.Context(r.Context(), logger)
|
||||
|
||||
opts.Path, err = pathAfterPrefix(r.URL.Path, fmt.Sprintf("/%s/files", name))
|
||||
if err != nil {
|
||||
responder.Error(apierrors.NewBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := resources.IsPathSupported(opts.Path); err != nil {
|
||||
responder.Error(apierrors.NewBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
isDir := safepath.IsDir(opts.Path)
|
||||
if r.Method == http.MethodGet && isDir {
|
||||
// Directory listing requires repositories:read permission
|
||||
if err := c.authorizeListFiles(ctx, name); err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := c.listFolderFiles(ctx, opts.Path, opts.Ref, readWriter)
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
responder.Object(http.StatusOK, files)
|
||||
return
|
||||
}
|
||||
|
||||
if opts.Path == "" {
|
||||
responder.Error(apierrors.NewBadRequest("missing request path"))
|
||||
return
|
||||
}
|
||||
|
||||
var obj *provisioning.ResourceWrapper
|
||||
code := http.StatusOK
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
resource, err := dualReadWriter.Read(ctx, opts.Path, opts.Ref)
|
||||
if err != nil {
|
||||
respondWithError(responder, err)
|
||||
return
|
||||
}
|
||||
obj = resource.AsResourceWrapper()
|
||||
case http.MethodPost:
|
||||
// Check if this is a move operation first (originalPath query parameter is present)
|
||||
if opts.OriginalPath != "" {
|
||||
// For move operations, only read body for file moves (not directory moves)
|
||||
if !isDir {
|
||||
opts.Data, err = readBody(r, filesMaxBodySize)
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resource, err := dualReadWriter.MoveResource(ctx, opts)
|
||||
if err != nil {
|
||||
respondWithError(responder, err)
|
||||
return
|
||||
}
|
||||
obj = resource.AsResourceWrapper()
|
||||
} else if isDir {
|
||||
obj, err = dualReadWriter.CreateFolder(ctx, opts)
|
||||
} else {
|
||||
opts.Data, err = readBody(r, filesMaxBodySize)
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
var resource *resources.ParsedResource
|
||||
resource, err = dualReadWriter.CreateResource(ctx, opts)
|
||||
if err != nil {
|
||||
respondWithError(responder, err)
|
||||
return
|
||||
}
|
||||
obj = resource.AsResourceWrapper()
|
||||
}
|
||||
case http.MethodPut:
|
||||
// TODO: document in API specification
|
||||
if isDir {
|
||||
err = apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method)
|
||||
} else {
|
||||
opts.Data, err = readBody(r, filesMaxBodySize)
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
resource, err := dualReadWriter.UpdateResource(ctx, opts)
|
||||
if err != nil {
|
||||
respondWithError(responder, err)
|
||||
return
|
||||
}
|
||||
obj = resource.AsResourceWrapper()
|
||||
}
|
||||
case http.MethodDelete:
|
||||
resource, err := dualReadWriter.Delete(ctx, opts)
|
||||
if err != nil {
|
||||
respondWithError(responder, err)
|
||||
return
|
||||
}
|
||||
obj = resource.AsResourceWrapper()
|
||||
default:
|
||||
err = apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Debug("got an error after processing request", "error", err)
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(obj.Errors) > 0 {
|
||||
code = http.StatusPartialContent
|
||||
}
|
||||
|
||||
logger.Debug("request resulted in valid object", "object", obj)
|
||||
responder.Object(code, obj)
|
||||
c.handleRequest(ctx, name, r, responder, logger)
|
||||
}), 30*time.Second), nil
|
||||
}
|
||||
|
||||
// handleRequest processes the HTTP request for files operations.
|
||||
func (c *filesConnector) handleRequest(ctx context.Context, name string, r *http.Request, responder rest.Responder, logger logging.Logger) {
|
||||
repo, err := c.getRepo(ctx, r.Method, name)
|
||||
if err != nil {
|
||||
logger.Debug("failed to find repository", "error", err)
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
readWriter, ok := repo.(repository.ReaderWriter)
|
||||
if !ok {
|
||||
responder.Error(apierrors.NewBadRequest("repository does not support read-writing"))
|
||||
return
|
||||
}
|
||||
|
||||
dualReadWriter, err := c.createDualReadWriter(ctx, repo, readWriter)
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
opts, err := c.parseRequestOptions(r, name, repo)
|
||||
if err != nil {
|
||||
responder.Error(apierrors.NewBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
logger = logger.With("url", r.URL.Path, "ref", opts.Ref, "message", opts.Message)
|
||||
ctx = logging.Context(r.Context(), logger)
|
||||
|
||||
// Handle directory listing separately
|
||||
isDir := safepath.IsDir(opts.Path)
|
||||
if r.Method == http.MethodGet && isDir {
|
||||
c.handleDirectoryListing(ctx, name, opts, readWriter, responder)
|
||||
return
|
||||
}
|
||||
|
||||
if opts.Path == "" {
|
||||
responder.Error(apierrors.NewBadRequest("missing request path"))
|
||||
return
|
||||
}
|
||||
|
||||
obj, err := c.handleMethodRequest(ctx, r, opts, isDir, dualReadWriter)
|
||||
if err != nil {
|
||||
logger.Debug("got an error after processing request", "error", err)
|
||||
respondWithError(responder, err)
|
||||
return
|
||||
}
|
||||
|
||||
code := http.StatusOK
|
||||
if len(obj.Errors) > 0 {
|
||||
code = http.StatusPartialContent
|
||||
}
|
||||
|
||||
logger.Debug("request resulted in valid object", "object", obj)
|
||||
responder.Object(code, obj)
|
||||
}
|
||||
|
||||
// createDualReadWriter sets up the dual read writer with all required dependencies.
|
||||
func (c *filesConnector) createDualReadWriter(ctx context.Context, repo repository.Repository, readWriter repository.ReaderWriter) (*resources.DualReadWriter, error) {
|
||||
parser, err := c.parsers.GetParser(ctx, readWriter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get parser: %w", err)
|
||||
}
|
||||
|
||||
clients, err := c.clients.Clients(ctx, repo.Config().Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get clients: %w", err)
|
||||
}
|
||||
|
||||
folderClient, err := clients.Folder(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get folder client: %w", err)
|
||||
}
|
||||
|
||||
folders := resources.NewFolderManager(readWriter, folderClient, resources.NewEmptyFolderTree())
|
||||
return resources.NewDualReadWriter(readWriter, parser, folders, c.access), nil
|
||||
}
|
||||
|
||||
// parseRequestOptions extracts options from the HTTP request.
|
||||
func (c *filesConnector) parseRequestOptions(r *http.Request, name string, repo repository.Repository) (resources.DualWriteOptions, error) {
|
||||
query := r.URL.Query()
|
||||
opts := resources.DualWriteOptions{
|
||||
Ref: query.Get("ref"),
|
||||
Message: query.Get("message"),
|
||||
SkipDryRun: query.Get("skipDryRun") == "true",
|
||||
OriginalPath: query.Get("originalPath"),
|
||||
Branch: repo.Config().Branch(),
|
||||
}
|
||||
|
||||
path, err := pathAfterPrefix(r.URL.Path, fmt.Sprintf("/%s/files", name))
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.Path = path
|
||||
|
||||
if err := resources.IsPathSupported(opts.Path); err != nil {
|
||||
return opts, err
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// handleDirectoryListing handles GET requests for directory listing.
|
||||
func (c *filesConnector) handleDirectoryListing(ctx context.Context, name string, opts resources.DualWriteOptions, readWriter repository.ReaderWriter, responder rest.Responder) {
|
||||
if err := c.authorizeListFiles(ctx, name); err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := c.listFolderFiles(ctx, opts.Path, opts.Ref, readWriter)
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
responder.Object(http.StatusOK, files)
|
||||
}
|
||||
|
||||
// handleMethodRequest routes the request to the appropriate handler based on HTTP method.
|
||||
func (c *filesConnector) handleMethodRequest(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, isDir bool, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
return c.handleGet(ctx, opts, dualReadWriter)
|
||||
case http.MethodPost:
|
||||
return c.handlePost(ctx, r, opts, isDir, dualReadWriter)
|
||||
case http.MethodPut:
|
||||
return c.handlePut(ctx, r, opts, isDir, dualReadWriter)
|
||||
case http.MethodDelete:
|
||||
return c.handleDelete(ctx, opts, dualReadWriter)
|
||||
default:
|
||||
return nil, apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *filesConnector) handleGet(ctx context.Context, opts resources.DualWriteOptions, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) {
|
||||
resource, err := dualReadWriter.Read(ctx, opts.Path, opts.Ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource.AsResourceWrapper(), nil
|
||||
}
|
||||
|
||||
func (c *filesConnector) handlePost(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, isDir bool, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) {
|
||||
// Check if this is a move operation (originalPath query parameter is present)
|
||||
if opts.OriginalPath != "" {
|
||||
return c.handleMove(ctx, r, opts, isDir, dualReadWriter)
|
||||
}
|
||||
|
||||
if isDir {
|
||||
return dualReadWriter.CreateFolder(ctx, opts)
|
||||
}
|
||||
|
||||
data, err := readBody(r, filesMaxBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.Data = data
|
||||
|
||||
resource, err := dualReadWriter.CreateResource(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource.AsResourceWrapper(), nil
|
||||
}
|
||||
|
||||
func (c *filesConnector) handleMove(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, isDir bool, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) {
|
||||
// For move operations, only read body for file moves (not directory moves)
|
||||
if !isDir {
|
||||
data, err := readBody(r, filesMaxBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.Data = data
|
||||
}
|
||||
|
||||
resource, err := dualReadWriter.MoveResource(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource.AsResourceWrapper(), nil
|
||||
}
|
||||
|
||||
func (c *filesConnector) handlePut(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, isDir bool, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) {
|
||||
if isDir {
|
||||
return nil, apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method)
|
||||
}
|
||||
|
||||
data, err := readBody(r, filesMaxBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.Data = data
|
||||
|
||||
resource, err := dualReadWriter.UpdateResource(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource.AsResourceWrapper(), nil
|
||||
}
|
||||
|
||||
func (c *filesConnector) handleDelete(ctx context.Context, opts resources.DualWriteOptions, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) {
|
||||
resource, err := dualReadWriter.Delete(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource.AsResourceWrapper(), nil
|
||||
}
|
||||
|
||||
// authorizeListFiles checks if the user has repositories:read permission for listing files.
|
||||
// The access checker handles AccessPolicy identities, namespace resolution, and role-based fallback internally.
|
||||
func (c *filesConnector) authorizeListFiles(ctx context.Context, repoName string) error {
|
||||
|
||||
Reference in New Issue
Block a user