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
8 changes: 8 additions & 0 deletions v2/pkg/engine/datasource/grpc_datasource/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,10 @@ func (p *RPCCompiler) resolveContextDataForPath(message protoref.Message, path a

// resolveListDataForPath resolves the data for a given path in a list message.
func (p *RPCCompiler) resolveListDataForPath(message protoref.List, fd protoref.FieldDescriptor, path ast.Path) []protoref.Value {
if !message.IsValid() {
return nil
}

if path.Len() == 0 {
return nil
}
Expand Down Expand Up @@ -731,6 +735,10 @@ func (p *RPCCompiler) resolveListDataForPath(message protoref.List, fd protoref.

// resolveDataForPath resolves the data for a given path in a message.
func (p *RPCCompiler) resolveDataForPath(messsage protoref.Message, path ast.Path) []protoref.Value {
if !messsage.IsValid() {
return nil
}

if path.Len() == 0 {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,30 @@ type mockServiceSpy struct {
grpctest.MockService

categoriesCalls atomic.Int64
categoryCalls atomic.Int64
normalizedScoreCalls atomic.Int64
relatedCategoryCalls atomic.Int64
productCountCalls atomic.Int64
popularityScoreCalls atomic.Int64
categoryMetricsCalls atomic.Int64
mascotCalls atomic.Int64

queryCategoryFunc func(ctx context.Context, req *productv1.QueryCategoryRequest) (*productv1.QueryCategoryResponse, error)
}

func (s *mockServiceSpy) QueryCategories(ctx context.Context, req *productv1.QueryCategoriesRequest) (*productv1.QueryCategoriesResponse, error) {
s.categoriesCalls.Add(1)
return s.MockService.QueryCategories(ctx, req)
}

func (s *mockServiceSpy) QueryCategory(ctx context.Context, req *productv1.QueryCategoryRequest) (*productv1.QueryCategoryResponse, error) {
s.categoryCalls.Add(1)
if s.queryCategoryFunc != nil {
return s.queryCategoryFunc(ctx, req)
}
return s.MockService.QueryCategory(ctx, req)
}

func (s *mockServiceSpy) ResolveCategoryMetricsNormalizedScore(ctx context.Context, req *productv1.ResolveCategoryMetricsNormalizedScoreRequest) (*productv1.ResolveCategoryMetricsNormalizedScoreResponse, error) {
s.normalizedScoreCalls.Add(1)
return s.MockService.ResolveCategoryMetricsNormalizedScore(ctx, req)
Expand All @@ -47,6 +61,21 @@ func (s *mockServiceSpy) ResolveCategoryProductCount(ctx context.Context, req *p
return s.MockService.ResolveCategoryProductCount(ctx, req)
}

func (s *mockServiceSpy) ResolveCategoryPopularityScore(ctx context.Context, req *productv1.ResolveCategoryPopularityScoreRequest) (*productv1.ResolveCategoryPopularityScoreResponse, error) {
s.popularityScoreCalls.Add(1)
return s.MockService.ResolveCategoryPopularityScore(ctx, req)
}

func (s *mockServiceSpy) ResolveCategoryCategoryMetrics(ctx context.Context, req *productv1.ResolveCategoryCategoryMetricsRequest) (*productv1.ResolveCategoryCategoryMetricsResponse, error) {
s.categoryMetricsCalls.Add(1)
return s.MockService.ResolveCategoryCategoryMetrics(ctx, req)
}

func (s *mockServiceSpy) ResolveCategoryMascot(ctx context.Context, req *productv1.ResolveCategoryMascotRequest) (*productv1.ResolveCategoryMascotResponse, error) {
s.mascotCalls.Add(1)
return s.MockService.ResolveCategoryMascot(ctx, req)
}

func setupSpyServer(t *testing.T) (*mockServiceSpy, *grpc.ClientConn, func()) {
spy := &mockServiceSpy{}
lis := bufconn.Listen(1024 * 1024)
Expand Down Expand Up @@ -108,3 +137,44 @@ func Test_DataSource_Load_NullMetrics_NestedResolversNotInvoked(t *testing.T) {
require.Zero(t, spy.relatedCategoryCalls.Load(), "ResolveCategoryMetricsRelatedCategory must not be called when parent nullMetrics is null")
require.Zero(t, spy.productCountCalls.Load(), "ResolveCategoryProductCount must not be called when parent nullMetrics is null")
}

// Test_DataSource_Load_NullCategory_FieldResolversNotInvoked verifies that when the top-level
// category query returns null, no nested field resolver RPCs are invoked by the engine.
func Test_DataSource_Load_NullCategory_FieldResolversNotInvoked(t *testing.T) {
spy, conn, cleanup := setupSpyServer(t)
t.Cleanup(cleanup)

spy.queryCategoryFunc = func(_ context.Context, _ *productv1.QueryCategoryRequest) (*productv1.QueryCategoryResponse, error) {
return &productv1.QueryCategoryResponse{
Category: nil,
}, nil
}

query := `query CategoryQuery($id: ID!, $threshold: Int, $metricType: String!, $includeVolume: Boolean!) { category(id: $id) { id name popularityScore(threshold: $threshold) categoryMetrics(metricType: $metricType) { id } mascot(includeVolume: $includeVolume) { ... on Cat { id } } } }`
vars := `{"variables":{"id":"cat-1","threshold":10,"metricType":"views","includeVolume":true}}`

schemaDoc := grpctest.MustGraphQLSchema(t)
queryDoc, report := astparser.ParseGraphqlDocumentString(query)
require.False(t, report.HasErrors(), "failed to parse query: %s", report.Error())

compiler, err := NewProtoCompiler(grpctest.MustProtoSchema(t), testMapping())
require.NoError(t, err)

ds, err := NewDataSource(conn, DataSourceConfig{
Operation: &queryDoc,
Definition: &schemaDoc,
SubgraphName: "Products",
Mapping: testMapping(),
Compiler: compiler,
})
require.NoError(t, err)

input := fmt.Sprintf(`{"query":%q,"body":%s}`, query, vars)
_, err = ds.Load(context.Background(), nil, []byte(input))
require.NoError(t, err)

require.Equal(t, int64(1), spy.categoryCalls.Load(), "QueryCategory must be called once")
require.Zero(t, spy.popularityScoreCalls.Load(), "ResolveCategoryPopularityScore must not be called when category is null")
require.Zero(t, spy.categoryMetricsCalls.Load(), "ResolveCategoryCategoryMetrics must not be called when category is null")
require.Zero(t, spy.mascotCalls.Load(), "ResolveCategoryMascot must not be called when category is null")
}
4 changes: 4 additions & 0 deletions v2/pkg/engine/datasource/grpc_datasource/json_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ func (j *jsonBuilder) flattenObject(value *astjson.Value, path ast.Path) ([]*ast

segment := path[0]
current := value.Get(segment.FieldName.String())
if current == nil {
return nil, fmt.Errorf("field %s not found in object", segment.FieldName.String())
}

result := make([]*astjson.Value, 0)
switch current.Type() {
case astjson.TypeObject:
Expand Down
11 changes: 11 additions & 0 deletions v2/pkg/engine/datasource/grpc_datasource/mapping_test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ func testMapping() *GRPCMapping {
Request: "QueryCategoriesRequest",
Response: "QueryCategoriesResponse",
},
"category": {
RPC: "QueryCategory",
Request: "QueryCategoryRequest",
Response: "QueryCategoryResponse",
},
"categoriesByKind": {
RPC: "QueryCategoriesByKind",
Request: "QueryCategoriesByKindRequest",
Expand Down Expand Up @@ -517,6 +522,12 @@ func testMapping() *GRPCMapping {
"categories": {
TargetName: "categories",
},
"category": {
TargetName: "category",
ArgumentMappings: FieldArgumentMap{
"id": "id",
},
},
"categoriesByKind": {
TargetName: "categories_by_kind",
ArgumentMappings: FieldArgumentMap{
Expand Down
28 changes: 28 additions & 0 deletions v2/pkg/grpctest/mapping/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ func DefaultGRPCMapping() *grpcdatasource.GRPCMapping {
Request: "QueryCategoriesRequest",
Response: "QueryCategoriesResponse",
},
"category": {
RPC: "QueryCategory",
Request: "QueryCategoryRequest",
Response: "QueryCategoryResponse",
},
"categoriesByKind": {
RPC: "QueryCategoriesByKind",
Request: "QueryCategoriesByKindRequest",
Expand Down Expand Up @@ -408,6 +413,17 @@ func DefaultGRPCMapping() *grpcdatasource.GRPCMapping {
Request: "ResolveSubcategoryItemCountRequest",
Response: "ResolveSubcategoryItemCountResponse",
},
"featuredCategory": {
FieldMappingData: grpcdatasource.FieldMapData{
TargetName: "featured_category",
ArgumentMappings: grpcdatasource.FieldArgumentMap{
"includeChildren": "include_children",
},
},
RPC: "ResolveSubcategoryFeaturedCategory",
Request: "ResolveSubcategoryFeaturedCategoryRequest",
Response: "ResolveSubcategoryFeaturedCategoryResponse",
},
},
"TestContainer": {
"details": {
Expand Down Expand Up @@ -513,6 +529,12 @@ func DefaultGRPCMapping() *grpcdatasource.GRPCMapping {
"categories": {
TargetName: "categories",
},
"category": {
TargetName: "category",
ArgumentMappings: grpcdatasource.FieldArgumentMap{
"id": "id",
},
},
"categoriesByKind": {
TargetName: "categories_by_kind",
ArgumentMappings: grpcdatasource.FieldArgumentMap{
Expand Down Expand Up @@ -1015,6 +1037,12 @@ func DefaultGRPCMapping() *grpcdatasource.GRPCMapping {
"filters": "filters",
},
},
"featuredCategory": {
TargetName: "featured_category",
ArgumentMappings: grpcdatasource.FieldArgumentMap{
"includeChildren": "include_children",
},
},
},
"CategoryMetrics": {
"id": {
Expand Down
12 changes: 12 additions & 0 deletions v2/pkg/grpctest/mockservice_enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ func (s *MockService) QueryCategories(ctx context.Context, in *productv1.QueryCa
}, nil
}

// Implementation for QueryCategory
func (s *MockService) QueryCategory(ctx context.Context, in *productv1.QueryCategoryRequest) (*productv1.QueryCategoryResponse, error) {
id := in.GetId()
return &productv1.QueryCategoryResponse{
Category: &productv1.Category{
Id: id,
Name: fmt.Sprintf("Category %s", id),
Kind: productv1.CategoryKind_CATEGORY_KIND_BOOK,
},
}, nil
}

// Implementation for QueryCategoriesByKind
func (s *MockService) QueryCategoriesByKind(ctx context.Context, in *productv1.QueryCategoriesByKindRequest) (*productv1.QueryCategoriesByKindResponse, error) {
kind := in.GetKind()
Expand Down
33 changes: 33 additions & 0 deletions v2/pkg/grpctest/product.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ service ProductService {
rpc QueryCategories(QueryCategoriesRequest) returns (QueryCategoriesResponse) {}
rpc QueryCategoriesByKind(QueryCategoriesByKindRequest) returns (QueryCategoriesByKindResponse) {}
rpc QueryCategoriesByKinds(QueryCategoriesByKindsRequest) returns (QueryCategoriesByKindsResponse) {}
rpc QueryCategory(QueryCategoryRequest) returns (QueryCategoryResponse) {}
rpc QueryComplexFilterType(QueryComplexFilterTypeRequest) returns (QueryComplexFilterTypeResponse) {}
rpc QueryFilterCategories(QueryFilterCategoriesRequest) returns (QueryFilterCategoriesResponse) {}
rpc QueryNestedType(QueryNestedTypeRequest) returns (QueryNestedTypeResponse) {}
Expand Down Expand Up @@ -71,6 +72,7 @@ service ProductService {
rpc ResolveProductRecommendedCategory(ResolveProductRecommendedCategoryRequest) returns (ResolveProductRecommendedCategoryResponse) {}
rpc ResolveProductShippingEstimate(ResolveProductShippingEstimateRequest) returns (ResolveProductShippingEstimateResponse) {}
rpc ResolveProductStockStatus(ResolveProductStockStatusRequest) returns (ResolveProductStockStatusResponse) {}
rpc ResolveSubcategoryFeaturedCategory(ResolveSubcategoryFeaturedCategoryRequest) returns (ResolveSubcategoryFeaturedCategoryResponse) {}
rpc ResolveSubcategoryItemCount(ResolveSubcategoryItemCountRequest) returns (ResolveSubcategoryItemCountResponse) {}
rpc ResolveTestContainerDetails(ResolveTestContainerDetailsRequest) returns (ResolveTestContainerDetailsResponse) {}
}
Expand Down Expand Up @@ -386,6 +388,14 @@ message QueryCategoriesRequest {
message QueryCategoriesResponse {
repeated Category categories = 1;
}
// Request message for category operation.
message QueryCategoryRequest {
string id = 1;
}
// Response message for category operation.
message QueryCategoryResponse {
Category category = 1;
}
// Request message for categoriesByKind operation.
message QueryCategoriesByKindRequest {
CategoryKind kind = 1;
Expand Down Expand Up @@ -972,6 +982,29 @@ message ResolveSubcategoryItemCountResponse {
repeated ResolveSubcategoryItemCountResult result = 1;
}

message ResolveSubcategoryFeaturedCategoryArgs {
bool include_children = 1;
}

message ResolveSubcategoryFeaturedCategoryContext {
string id = 1;
}

message ResolveSubcategoryFeaturedCategoryRequest {
// context provides the resolver context for the field featuredCategory of type Subcategory.
repeated ResolveSubcategoryFeaturedCategoryContext context = 1;
// field_args provides the arguments for the resolver field featuredCategory of type Subcategory.
ResolveSubcategoryFeaturedCategoryArgs field_args = 2;
}

message ResolveSubcategoryFeaturedCategoryResult {
Category featured_category = 1;
}

message ResolveSubcategoryFeaturedCategoryResponse {
repeated ResolveSubcategoryFeaturedCategoryResult result = 1;
}

message ResolveCategoryMetricsNormalizedScoreArgs {
double baseline = 1;
}
Expand Down
Loading