go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/teams/rpc/status.go (about)

     1  // Copyright 2024 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package rpc contains the RPC handlers for the tree status service.
    16  package rpc
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"regexp"
    22  
    23  	"google.golang.org/grpc/codes"
    24  	"google.golang.org/protobuf/types/known/timestamppb"
    25  
    26  	"go.chromium.org/luci/common/errors"
    27  	"go.chromium.org/luci/grpc/appstatus"
    28  	"go.chromium.org/luci/server/span"
    29  
    30  	"go.chromium.org/luci/teams/internal/teams"
    31  	pb "go.chromium.org/luci/teams/proto/v1"
    32  )
    33  
    34  type teamsServer struct{}
    35  
    36  var _ pb.TeamsServer = &teamsServer{}
    37  
    38  // NewTeamsServer creates a new server to handle Teams requests.
    39  func NewTeamsServer() *pb.DecoratedTeams {
    40  	return &pb.DecoratedTeams{
    41  		Prelude:  checkAllowedPrelude,
    42  		Service:  &teamsServer{},
    43  		Postlude: gRPCifyAndLogPostlude,
    44  	}
    45  }
    46  
    47  // toTeamProto converts a teams.Team value to a pb.Team proto.
    48  func toTeamProto(value *teams.Team) *pb.Team {
    49  	return &pb.Team{
    50  		Name:       fmt.Sprintf("teams/%s", value.ID),
    51  		CreateTime: timestamppb.New(value.CreateTime),
    52  	}
    53  }
    54  
    55  // Get gets a team.
    56  // Use the resource alias 'my' to get just the current user's team.
    57  func (*teamsServer) Get(ctx context.Context, request *pb.GetTeamRequest) (*pb.Team, error) {
    58  	id, err := parseTeamName(request.Name)
    59  	if err != nil {
    60  		return nil, invalidArgumentError(errors.Annotate(err, "name").Err())
    61  	}
    62  
    63  	if id == "my" {
    64  		// TODO: Resolve team for user.
    65  		return nil, appstatus.Errorf(codes.Unimplemented, "resolving team for current user not yet implemented")
    66  	}
    67  	s, err := teams.Read(span.Single(ctx), id)
    68  	if errors.Is(err, teams.NotExistsErr) {
    69  		return nil, notFoundError(err)
    70  	} else if err != nil {
    71  		return nil, errors.Annotate(err, "reading team").Err()
    72  	}
    73  
    74  	return toTeamProto(s), nil
    75  }
    76  
    77  var teamNameRE = regexp.MustCompile(`^teams/(` + teams.TeamIDExpression + `|my)$`)
    78  
    79  // parseTeamName parses a team resource name into its constituent ID
    80  // parts.
    81  func parseTeamName(name string) (id string, err error) {
    82  	if name == "" {
    83  		return "", errors.Reason("must be specified").Err()
    84  	}
    85  	match := teamNameRE.FindStringSubmatch(name)
    86  	if match == nil {
    87  		return "", errors.Reason("expected format: %s", teamNameRE).Err()
    88  	}
    89  	return match[1], nil
    90  }
    91  
    92  // invalidArgumentError annotates err as having an invalid argument.
    93  // The error message is shared with the requester as is.
    94  //
    95  // Note that this differs from FailedPrecondition. It indicates arguments
    96  // that are problematic regardless of the state of the system
    97  // (e.g., a malformed file name).
    98  func invalidArgumentError(err error) error {
    99  	return appstatus.Attachf(err, codes.InvalidArgument, "%s", err)
   100  }
   101  
   102  // notFoundError annotates err as being not found (HTTP 404).
   103  // The error message is shared with the requester as is.
   104  func notFoundError(err error) error {
   105  	return appstatus.Attachf(err, codes.NotFound, "%s", err)
   106  }