Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions service/submitqueue/gateway/server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ go_library(
"//platform/extension/counter/mysql:go_default_library",
"//platform/extension/messagequeue:go_default_library",
"//platform/extension/messagequeue/mysql:go_default_library",
"//service/submitqueue/gateway/server/mapper:go_default_library",
"//submitqueue/core/topickey:go_default_library",
"//submitqueue/extension/queueconfig/yaml:go_default_library",
"//submitqueue/extension/storage/mysql:go_default_library",
Expand Down
14 changes: 12 additions & 2 deletions service/submitqueue/gateway/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
mysqlcounter "github.com/uber/submitqueue/platform/extension/counter/mysql"
extqueue "github.com/uber/submitqueue/platform/extension/messagequeue"
queueMySQL "github.com/uber/submitqueue/platform/extension/messagequeue/mysql"
"github.com/uber/submitqueue/service/submitqueue/gateway/server/mapper"
"github.com/uber/submitqueue/submitqueue/core/topickey"
yamlqueueconfig "github.com/uber/submitqueue/submitqueue/extension/queueconfig/yaml"
mysqlstorage "github.com/uber/submitqueue/submitqueue/extension/storage/mysql"
Expand All @@ -62,9 +63,18 @@ func (s *GatewayServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.Ping
return s.pingController.Ping(ctx, req)
}

// Land delegates to the controller
// Land maps the wire request to an entity, delegates to the controller, and maps
// the result back to the wire response.
func (s *GatewayServer) Land(ctx context.Context, req *pb.LandRequest) (*pb.LandResponse, error) {
return s.landController.Land(ctx, req)
landReq, err := mapper.ProtoToLandRequest(req)
if err != nil {
return nil, err
}
result, err := s.landController.Land(ctx, landReq)
if err != nil {
return nil, err
}
return &pb.LandResponse{Sqid: result.ID}, nil
}

// Cancel delegates to the controller
Expand Down
31 changes: 31 additions & 0 deletions service/submitqueue/gateway/server/mapper/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
srcs = ["land.go"],
importpath = "github.com/uber/submitqueue/service/submitqueue/gateway/server/mapper",
visibility = ["//visibility:public"],
deps = [
"//api/base/mergestrategy/protopb:go_default_library",
"//api/submitqueue/gateway/protopb:go_default_library",
"//platform/base/change:go_default_library",
"//platform/base/mergestrategy:go_default_library",
"//submitqueue/entity:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = ["land_test.go"],
embed = [":go_default_library"],
deps = [
"//api/base/change/protopb:go_default_library",
"//api/base/mergestrategy/protopb:go_default_library",
"//api/submitqueue/gateway/protopb:go_default_library",
"//platform/base/change:go_default_library",
"//platform/base/mergestrategy:go_default_library",
"//submitqueue/entity:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
"@com_github_stretchr_testify//require:go_default_library",
],
)
65 changes: 65 additions & 0 deletions service/submitqueue/gateway/server/mapper/land.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) 2025 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package mapper translates gateway wire (proto) types to and from the domain
// entities the controllers operate on. Each RPC gets its own file (land.go,
// status.go, cancel.go, …); translation lives here so controllers stay
// proto-free.
package mapper

import (
"errors"
"fmt"

mergestrategypb "github.com/uber/submitqueue/api/base/mergestrategy/protopb"
pb "github.com/uber/submitqueue/api/submitqueue/gateway/protopb"
"github.com/uber/submitqueue/platform/base/change"
"github.com/uber/submitqueue/platform/base/mergestrategy"
"github.com/uber/submitqueue/submitqueue/entity"
)

// errUnknownStrategy is returned when a proto Strategy enum has no known
// mergestrategy.MergeStrategy mapping.
var errUnknownStrategy = errors.New("unknown land strategy in proto message")

// ProtoToLandRequest maps the wire LandRequest to the entity.LandRequest the controller operates on.
// The ID is left empty; the controller assigns it.
func ProtoToLandRequest(req *pb.LandRequest) (entity.LandRequest, error) {
strategy, err := resolveMergeStrategy(req.GetStrategy())
if err != nil {
return entity.LandRequest{}, fmt.Errorf("failed to map land strategy: %w", err)
}
return entity.LandRequest{
Queue: req.GetQueue(),
Change: change.Change{URIs: req.GetChange().GetUris()},
LandStrategy: strategy,
}, nil
}

// resolveMergeStrategy maps a proto Strategy enum to the shared mergestrategy.MergeStrategy.
func resolveMergeStrategy(s mergestrategypb.Strategy) (mergestrategy.MergeStrategy, error) {
switch s {
case mergestrategypb.Strategy_DEFAULT:
// TODO: resolve default strategy based on queue configuration
return mergestrategy.MergeStrategyRebase, nil
case mergestrategypb.Strategy_REBASE:
return mergestrategy.MergeStrategyRebase, nil
case mergestrategypb.Strategy_SQUASH_REBASE:
return mergestrategy.MergeStrategySquashRebase, nil
case mergestrategypb.Strategy_MERGE:
return mergestrategy.MergeStrategyMerge, nil
default:
return mergestrategy.MergeStrategyUnknown, fmt.Errorf("%w: %v", errUnknownStrategy, s)
}
}
111 changes: 111 additions & 0 deletions service/submitqueue/gateway/server/mapper/land_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) 2025 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mapper

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
changepb "github.com/uber/submitqueue/api/base/change/protopb"
mergestrategypb "github.com/uber/submitqueue/api/base/mergestrategy/protopb"
pb "github.com/uber/submitqueue/api/submitqueue/gateway/protopb"
"github.com/uber/submitqueue/platform/base/change"
"github.com/uber/submitqueue/platform/base/mergestrategy"
"github.com/uber/submitqueue/submitqueue/entity"
)

func TestProtoToLandRequest(t *testing.T) {
const uri = "github://github.example.com/uber/test-repo/pull/1/c3a4d5e6f7890123456789abcdef0123456789ab"

tests := []struct {
name string
req *pb.LandRequest
expected entity.LandRequest
expectedErr error
}{
{
name: "maps all fields and leaves ID empty",
req: &pb.LandRequest{
Queue: "test-queue",
Change: &changepb.Change{Uris: []string{uri}},
Strategy: mergestrategypb.Strategy_SQUASH_REBASE,
},
// ID is not assigned by the mapper — the controller mints it.
expected: entity.LandRequest{
Queue: "test-queue",
Change: change.Change{URIs: []string{uri}},
LandStrategy: mergestrategy.MergeStrategySquashRebase,
},
},
{
name: "nil change yields empty URIs without erroring",
req: &pb.LandRequest{Queue: "test-queue", Change: nil},
// The mapper does not validate; it leaves URIs empty for the controller to reject.
expected: entity.LandRequest{
Queue: "test-queue",
LandStrategy: mergestrategy.MergeStrategyRebase,
},
},
{
name: "unknown strategy errors",
req: &pb.LandRequest{
Queue: "test-queue",
Change: &changepb.Change{Uris: []string{uri}},
Strategy: mergestrategypb.Strategy(9999),
},
expectedErr: errUnknownStrategy,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ProtoToLandRequest(tt.req)
if tt.expectedErr != nil {
require.ErrorIs(t, err, tt.expectedErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, got)
})
}
}

func TestResolveMergeStrategy(t *testing.T) {
tests := []struct {
name string
in mergestrategypb.Strategy
want mergestrategy.MergeStrategy
errMsg string
}{
{name: "default", in: mergestrategypb.Strategy_DEFAULT, want: mergestrategy.MergeStrategyRebase},
{name: "rebase", in: mergestrategypb.Strategy_REBASE, want: mergestrategy.MergeStrategyRebase},
{name: "squash_rebase", in: mergestrategypb.Strategy_SQUASH_REBASE, want: mergestrategy.MergeStrategySquashRebase},
{name: "merge", in: mergestrategypb.Strategy_MERGE, want: mergestrategy.MergeStrategyMerge},
{name: "unknown", in: mergestrategypb.Strategy(9999), errMsg: "unknown land strategy in proto message"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := resolveMergeStrategy(tt.in)
if tt.errMsg != "" {
assert.ErrorContains(t, err, tt.errMsg)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
4 changes: 2 additions & 2 deletions submitqueue/entity/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ go_library(
"change_provider.go",
"change_record.go",
"conflict.go",
"land_request.go",
"land.go",
"merge_result.go",
"push_result.go",
"queue.go",
Expand All @@ -36,7 +36,7 @@ go_test(
"batch_test.go",
"build_test.go",
"cancel_request_test.go",
"land_request_test.go",
"land_test.go",
"queue_test.go",
"request_log_test.go",
"request_test.go",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ func LandRequestFromBytes(data []byte) (LandRequest, error) {
err := json.Unmarshal(data, &req)
return req, err
}

// LandResult is the outcome of accepting a land request. It carries the ID the
// controller assigned to the request so the transport layer can echo it back to
// the caller.
type LandResult struct {
// ID is the globally unique identifier assigned to the accepted land request.
// Format: "<queue>/<counter_value>".
ID string
}
6 changes: 1 addition & 5 deletions submitqueue/gateway/controller/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ go_library(
importpath = "github.com/uber/submitqueue/submitqueue/gateway/controller",
visibility = ["//visibility:public"],
deps = [
"//api/base/mergestrategy/protopb:go_default_library",
"//api/submitqueue/gateway/protopb:go_default_library",
"//platform/base/change:go_default_library",
"//platform/base/mergestrategy:go_default_library",
"//platform/base/messagequeue:go_default_library",
"//platform/consumer:go_default_library",
"//platform/errs:go_default_library",
Expand All @@ -40,9 +37,8 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//api/base/change/protopb:go_default_library",
"//api/base/mergestrategy/protopb:go_default_library",
"//api/submitqueue/gateway/protopb:go_default_library",
"//platform/base/change:go_default_library",
"//platform/base/mergestrategy:go_default_library",
"//platform/base/messagequeue:go_default_library",
"//platform/consumer:go_default_library",
Expand Down
Loading
Loading