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
25 changes: 25 additions & 0 deletions demo/pkg/subgraphs/projects/generated/mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,21 @@
"request": "RequireEmployeeTaggedProjectSummaryByIdRequest",
"response": "RequireEmployeeTaggedProjectSummaryByIdResponse"
},
{
"fieldMapping": {
"original": "filteredProjectSummary",
"mapped": "filtered_project_summary",
"argumentMappings": [
{
"original": "tag",
"mapped": "tag"
}
]
},
"rpc": "RequireEmployeeFilteredProjectSummaryById",
"request": "RequireEmployeeFilteredProjectSummaryByIdRequest",
"response": "RequireEmployeeFilteredProjectSummaryByIdResponse"
},
{
"fieldMapping": {
"original": "workItemInfo",
Expand Down Expand Up @@ -1177,6 +1192,16 @@
"mapped": "tagged_project_summary",
"argumentMappings": []
},
{
"original": "filteredProjectSummary",
"mapped": "filtered_project_summary",
"argumentMappings": [
{
"original": "tag",
"mapped": "tag"
}
]
},
{
"original": "primaryWorkItem",
"mapped": "primary_work_item",
Expand Down
7,011 changes: 3,729 additions & 3,282 deletions demo/pkg/subgraphs/projects/generated/service.pb.go

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions demo/pkg/subgraphs/projects/generated/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ service ProjectsService {
rpc QueryTasks(QueryTasksRequest) returns (QueryTasksResponse) {}
rpc QueryTasksByPriority(QueryTasksByPriorityRequest) returns (QueryTasksByPriorityResponse) {}
rpc RequireEmployeeDeepWorkItemInfoById(RequireEmployeeDeepWorkItemInfoByIdRequest) returns (RequireEmployeeDeepWorkItemInfoByIdResponse) {}
rpc RequireEmployeeFilteredProjectSummaryById(RequireEmployeeFilteredProjectSummaryByIdRequest) returns (RequireEmployeeFilteredProjectSummaryByIdResponse) {}
rpc RequireEmployeeReviewReportById(RequireEmployeeReviewReportByIdRequest) returns (RequireEmployeeReviewReportByIdResponse) {}
rpc RequireEmployeeTaggedProjectSummaryById(RequireEmployeeTaggedProjectSummaryByIdRequest) returns (RequireEmployeeTaggedProjectSummaryByIdResponse) {}
rpc RequireEmployeeWorkItemHandlerInfoById(RequireEmployeeWorkItemHandlerInfoByIdRequest) returns (RequireEmployeeWorkItemHandlerInfoByIdResponse) {}
Expand Down Expand Up @@ -847,6 +848,35 @@ message RequireEmployeeTaggedProjectSummaryByIdFields {
string expertise = 1;
}

message RequireEmployeeFilteredProjectSummaryByIdRequest {
// RequireEmployeeFilteredProjectSummaryByIdContext provides the context for the required fields method RequireEmployeeFilteredProjectSummaryById.
repeated RequireEmployeeFilteredProjectSummaryByIdContext context = 1;
// RequireEmployeeFilteredProjectSummaryByIdArgs provides the field arguments for the required field with method RequireEmployeeFilteredProjectSummaryById.
RequireEmployeeFilteredProjectSummaryByIdArgs field_args = 2;
}

message RequireEmployeeFilteredProjectSummaryByIdContext {
LookupEmployeeByIdRequestKey key = 1;
RequireEmployeeFilteredProjectSummaryByIdFields fields = 2;
}

message RequireEmployeeFilteredProjectSummaryByIdArgs {
string tag = 1;
}

message RequireEmployeeFilteredProjectSummaryByIdResponse {
// RequireEmployeeFilteredProjectSummaryByIdResult provides the result for the required fields method RequireEmployeeFilteredProjectSummaryById.
repeated RequireEmployeeFilteredProjectSummaryByIdResult result = 1;
}

message RequireEmployeeFilteredProjectSummaryByIdResult {
string filtered_project_summary = 1;
}

message RequireEmployeeFilteredProjectSummaryByIdFields {
string expertise = 1;
}

message RequireEmployeeWorkItemInfoByIdRequest {
// RequireEmployeeWorkItemInfoByIdContext provides the context for the required fields method RequireEmployeeWorkItemInfoById.
repeated RequireEmployeeWorkItemInfoByIdContext context = 1;
Expand Down
132 changes: 85 additions & 47 deletions demo/pkg/subgraphs/projects/generated/service_grpc.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions demo/pkg/subgraphs/projects/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ type Employee @key(fields: "id") {

# Field requiring expertise from employees subgraph
taggedProjectSummary: String! @requires(fields: "expertise")
filteredProjectSummary(tag: String!): String! @requires(fields: "expertise")

# External abstract type fields for @requires testing
primaryWorkItem: EmployeeWorkItem @external
Expand Down
63 changes: 63 additions & 0 deletions demo/pkg/subgraphs/projects/src/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,69 @@ func (p *ProjectsService) LookupEmployeeById(ctx context.Context, req *service.L
return &service.LookupEmployeeByIdResponse{Result: result}, nil
}

// RequireEmployeeFilteredProjectSummaryById implements projects.ProjectsServiceServer.
// It resolves the filteredProjectSummary field on Employee, which requires the `expertise` field
// from the employees subgraph via @requires(fields: "expertise") and accepts a `tag` argument to filter.
func (p *ProjectsService) RequireEmployeeFilteredProjectSummaryById(_ context.Context, req *service.RequireEmployeeFilteredProjectSummaryByIdRequest) (*service.RequireEmployeeFilteredProjectSummaryByIdResponse, error) {
p.lock.RLock()
defer p.lock.RUnlock()

tagFilter := ""
if req.GetFieldArgs() != nil {
tagFilter = req.GetFieldArgs().GetTag()
}

result := make([]*service.RequireEmployeeFilteredProjectSummaryByIdResult, 0, len(req.Context))

for _, ctx := range req.Context {
keyID := ctx.GetKey().GetId()
if keyID == "" {
return nil, status.Errorf(codes.InvalidArgument, "missing employee id")
}
id, err := strconv.ParseInt(keyID, 10, 32)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid employee id %q: %v", keyID, err)
}

var matchingTags []string
emp := data.GetEmployeeByID(int32(id))
if emp != nil && emp.Projects != nil && emp.Projects.List != nil {
seen := make(map[string]struct{})
for _, project := range emp.Projects.List.Items {
if project.Tags == nil || project.Tags.List == nil {
continue
}
for _, tag := range project.Tags.List.Items {
if _, ok := seen[tag]; ok {
continue
}
seen[tag] = struct{}{}
if tagFilter == "" || strings.EqualFold(tag, tagFilter) {
matchingTags = append(matchingTags, tag)
}
}
}
}

employeeExpertise := ctx.GetFields().GetExpertise()
if employeeExpertise == "" {
employeeExpertise = "none"
}

var summary string
if len(matchingTags) > 0 {
summary = fmt.Sprintf("expertise: %s, filtered tags (tag=%s): [%s]", employeeExpertise, tagFilter, strings.Join(matchingTags, ", "))
} else {
summary = fmt.Sprintf("expertise: %s, no tags matched (tag=%s)", employeeExpertise, tagFilter)
}
result = append(result, &service.RequireEmployeeFilteredProjectSummaryByIdResult{
FilteredProjectSummary: summary,
})
}

return &service.RequireEmployeeFilteredProjectSummaryByIdResponse{Result: result}, nil
}

// RequireEmployeeTaggedProjectSummaryById implements projects.ProjectsServiceServer.
// It resolves the taggedProjectSummary field on Employee, which requires the `tag` field
// from the employees subgraph via @requires(fields: "tag").
Expand Down
2 changes: 1 addition & 1 deletion router-tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ require (
github.com/wundergraph/cosmo/demo/pkg/subgraphs/projects v0.0.0-20250715110703-10f2e5f9c79e
github.com/wundergraph/cosmo/router v0.0.0-20260319123623-f186a0f724f6
github.com/wundergraph/cosmo/router-plugin v0.0.0-20250808194725-de123ba1c65e
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.267
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.268
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/sdk v1.39.0
go.opentelemetry.io/otel/sdk/metric v1.39.0
Expand Down
4 changes: 2 additions & 2 deletions router-tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV
github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw=
github.com/wundergraph/go-arena v1.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc=
github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.267 h1:qMkYR0oq0Cw61aDZs9VsCCVwNVSxRxT13ytz6WqCwJg=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.267/go.mod h1:HjTAO/cuICpu31IfHY9qmSPygx6Gza7Wt9hTSReTI+A=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.268 h1:lP9kWLiPO2U3JuwpQ/WX7nTVfKeMtVab2G3DAFblVA0=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.268/go.mod h1:HjTAO/cuICpu31IfHY9qmSPygx6Gza7Wt9hTSReTI+A=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
Expand Down
10 changes: 10 additions & 0 deletions router-tests/protocol/grpc_subgraph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,16 @@ func TestGRPCSubgraph(t *testing.T) {
query: `{ employee(id: 999) { id taggedProjectSummary } }`,
expected: `{"data":{"employee":null}}`,
},
{
name: "query employee @requires field with argument resolved with expertise",
query: `{ employee(id: 1) { id filteredProjectSummary(tag: "cloud") } }`,
expected: `{"data":{"employee":{"id":1,"filteredProjectSummary":"expertise: Backend Architecture, filtered tags (tag=cloud): [cloud]"}}}`,
},
{
name: "query employee @requires field with argument — no matching tags",
query: `{ employee(id: 1) { id filteredProjectSummary(tag: "nonexistent") } }`,
expected: `{"data":{"employee":{"id":1,"filteredProjectSummary":"expertise: Backend Architecture, no tags matched (tag=nonexistent)"}}}`,
},
// Pattern 1: Flat abstract — interface
{
name: "query employee @requires flat interface (technical)",
Expand Down
10 changes: 10 additions & 0 deletions router-tests/protocol/router_plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,16 @@ func TestRouterPluginRequests(t *testing.T) {
query: `{ employee(id: 999) { id taggedProjectSummary } }`,
expected: `{"data":{"employee":null}}`,
},
{
name: "query employee @requires field with argument resolved with expertise",
query: `{ employee(id: 1) { id filteredProjectSummary(tag: "cloud") } }`,
expected: `{"data":{"employee":{"id":1,"filteredProjectSummary":"expertise: Backend Architecture, filtered tags (tag=cloud): [cloud]"}}}`,
},
{
name: "query employee @requires field with argument — no matching tags",
query: `{ employee(id: 1) { id filteredProjectSummary(tag: "nonexistent") } }`,
expected: `{"data":{"employee":{"id":1,"filteredProjectSummary":"expertise: Backend Architecture, no tags matched (tag=nonexistent)"}}}`,
},
// Pattern 1: Flat abstract — interface
{
name: "query employee @requires flat interface (technical)",
Expand Down
Loading
Loading