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
20 changes: 3 additions & 17 deletions internal/graphql/resolver/stock_plasmid_fp.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,23 +127,9 @@ func computeListPlasmidParams(
}

func buildListPlasmidFilterQuery(ctx withListPlasmidParams) IOE.IOEither[error, string] {
return F.Pipe2(
ctx.filter,
func(filter *models.PlasmidListFilter) E.Either[error, string] {
if filter == nil {
return E.Right[error]("")
}
return F.Pipe5(
filter,
resolverutils.CheckIDField,
E.Chain(resolverutils.CheckInStockField),
E.Chain(resolverutils.CheckUnverifiedPlasmidType),
E.Chain(resolverutils.CheckValidPlasmidType),
E.Map[error](resolverutils.BuildPlasmidFieldQuery),
)
},
IOE.FromEither[error, string],
)
return IOE.TryCatchError(func() (string, error) {
return resolverutils.PlasmidFilterToQuery(ctx.filter)
})
}

func fetchListPlasmidCollection(
Expand Down
58 changes: 40 additions & 18 deletions internal/graphql/resolver/stock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,42 +629,64 @@ func TestListPlasmidsUnsupportedIDFilter(t *testing.T) {
require.Empty(result.Plasmids)
}

func TestListPlasmidsUnverifiedRegularTypeFilter(t *testing.T) {
func TestListPlasmidsRegularTypeFilter(t *testing.T) {
t.Parallel()
require := require.New(t)

resolver := &QueryResolver{
Registry: &mocks.MockRegistry{},
Logger: mocks.TestLogger(),
mockedStockClient := new(clients.StockServiceClient)
mockedStockClient.On(
"ListPlasmids",
mock.MatchedBy(func(ctx context.Context) bool { return true }),
mock.MatchedBy(func(params *pb.StockParameters) bool {
return params.Cursor == 0 &&
params.Limit == 10 &&
params.Filter == "ontology==dicty_plasmid_keyword;tag==vector"
}),
).Return(mocks.MockPlasmidCollection(), nil)

reg := &stockClientRegistry{
MockRegistry: &mocks.MockRegistry{ConnMap: nil},
stockClient: mockedStockClient,
}
resolver := &QueryResolver{Registry: reg, Logger: mocks.TestLogger()}
cursor := 0
limit := 10

ctx := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, graphql.DefaultRecover)
result, err := resolver.ListPlasmids(ctx, &cursor, &limit, &models.PlasmidListFilter{
result, err := resolver.ListPlasmids(context.Background(), &cursor, &limit, &models.PlasmidListFilter{
PlasmidType: models.PlasmidTypeRegular,
})
require.Error(err)
require.Contains(err.Error(), "plasmid_type filter is not yet verified")
require.Empty(result.Plasmids)
require.NoError(err)
require.Len(result.Plasmids, 3)
mockedStockClient.AssertExpectations(t)
}

func TestListPlasmidsUnverifiedGoldenBraidTypeFilter(t *testing.T) {
func TestListPlasmidsGoldenBraidTypeFilter(t *testing.T) {
t.Parallel()
require := require.New(t)

resolver := &QueryResolver{
Registry: &mocks.MockRegistry{},
Logger: mocks.TestLogger(),
mockedStockClient := new(clients.StockServiceClient)
mockedStockClient.On(
"ListPlasmids",
mock.MatchedBy(func(ctx context.Context) bool { return true }),
mock.MatchedBy(func(params *pb.StockParameters) bool {
return params.Cursor == 0 &&
params.Limit == 10 &&
params.Filter == "ontology==dicty_plasmid_keyword;tag==GB vector"
}),
).Return(mocks.MockPlasmidCollection(), nil)

reg := &stockClientRegistry{
MockRegistry: &mocks.MockRegistry{ConnMap: nil},
stockClient: mockedStockClient,
}
resolver := &QueryResolver{Registry: reg, Logger: mocks.TestLogger()}
cursor := 0
limit := 10

ctx := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, graphql.DefaultRecover)
result, err := resolver.ListPlasmids(ctx, &cursor, &limit, &models.PlasmidListFilter{
result, err := resolver.ListPlasmids(context.Background(), &cursor, &limit, &models.PlasmidListFilter{
PlasmidType: models.PlasmidTypeGoldenBraid,
})
require.Error(err)
require.Contains(err.Error(), "plasmid_type filter is not yet verified")
require.Empty(result.Plasmids)
require.NoError(err)
require.Len(result.Plasmids, 3)
mockedStockClient.AssertExpectations(t)
}
95 changes: 46 additions & 49 deletions internal/graphql/resolverutils/plasmid_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,19 @@ package resolverutils

import (
"fmt"
"strings"

A "github.com/IBM/fp-go/v2/array"
E "github.com/IBM/fp-go/v2/either"
eq "github.com/IBM/fp-go/v2/eq"
F "github.com/IBM/fp-go/v2/function"
O "github.com/IBM/fp-go/v2/option"
P "github.com/IBM/fp-go/v2/predicate"
S "github.com/IBM/fp-go/v2/string"

"github.com/dictyBase/graphql-server/internal/graphql/models"
"github.com/dictyBase/graphql-server/internal/registry"
)

var (
plasmidTypeEq = eq.FromStrictEquals[models.PlasmidType]()

isNilID = F.Pipe1(
P.IsZero[*string](),
P.ContraMap(func(f *models.PlasmidListFilter) *string {
Expand Down Expand Up @@ -51,56 +49,55 @@ var (
)
},
)
)

isRegularPlasmidType = eq.Equals(plasmidTypeEq)(models.PlasmidTypeRegular)
isGoldenBraidPlasmidType = eq.Equals(plasmidTypeEq)(models.PlasmidTypeGoldenBraid)
isUnverifiedPlasmidType = F.Pipe2(
isRegularPlasmidType,
P.Or(isGoldenBraidPlasmidType),
P.ContraMap(func(f *models.PlasmidListFilter) models.PlasmidType {
return f.PlasmidType
}),
)
CheckUnverifiedPlasmidType = E.FromPredicate(
P.Not(isUnverifiedPlasmidType),
func(filter *models.PlasmidListFilter) error {
return fmt.Errorf(
"plasmid list filter %v: plasmid_type filter is not yet verified for stock query conversion",
filter,
)
},
)

isAllPlasmidType = F.Pipe1(
eq.Equals(plasmidTypeEq)(models.PlasmidTypeAll),
P.ContraMap(func(f *models.PlasmidListFilter) models.PlasmidType {
return f.PlasmidType
}),
)
CheckValidPlasmidType = E.FromPredicate(
isAllPlasmidType,
func(f *models.PlasmidListFilter) error {
return fmt.Errorf("invalid plasmid type %s", f.PlasmidType.String())
},
)
func PlasmidFilterToQuery(filter *models.PlasmidListFilter) (string, error) {
if filter == nil {
return "", nil
}

validateFilterPipeline = F.Flow5(
if _, err := E.UnwrapError(F.Pipe2(
filter,
CheckIDField,
E.Chain(CheckInStockField),
E.Chain(CheckUnverifiedPlasmidType),
E.Chain(CheckValidPlasmidType),
E.Map[error](BuildPlasmidFieldQuery),
)
)
)); err != nil {
return "", err
}

func PlasmidFilterToQuery(filter *models.PlasmidListFilter) (string, error) {
return E.UnwrapError(F.Pipe1(
O.FromNillable(filter),
O.Fold(
F.Constant(E.Right[error]("")),
validateFilterPipeline,
),
))
fieldQuery := BuildPlasmidFieldQuery(filter)
typeQuery, err := plasmidTypeQuery(filter)
if err != nil {
return fieldQuery, err
Comment on lines +68 to +70
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Return an empty query when type validation fails.

Line 70 returns fieldQuery together with err; for a filter like {Summary: ..., PlasmidType: "INVALID"}, direct callers could accidentally use the partial summary query despite the failed full conversion. Return the zero query on error.

🐛 Proposed fix
 	typeQuery, err := plasmidTypeQuery(filter)
 	if err != nil {
-		return fieldQuery, err
+		return "", err
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
typeQuery, err := plasmidTypeQuery(filter)
if err != nil {
return fieldQuery, err
typeQuery, err := plasmidTypeQuery(filter)
if err != nil {
return "", err
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/graphql/resolverutils/plasmid_filter.go` around lines 68 - 70, When
plasmidTypeQuery(filter) returns an error, the current code returns the
partially built fieldQuery which may be misused; change the error path so you
return the zero-value/empty query instead of fieldQuery along with the error.
Locate the call to plasmidTypeQuery in plasmid_filter.go (the variables
typeQuery and fieldQuery) and replace the return of fieldQuery on error with an
empty query value (the zero-value for the query type) plus err so callers won't
get a partial query when type validation fails.

}
if fieldQuery == "" {
return typeQuery, nil
}
if typeQuery == "" {
return fieldQuery, nil
}

return strings.Join([]string{fieldQuery, typeQuery}, ";"), nil
}

func plasmidTypeQuery(filter *models.PlasmidListFilter) (string, error) {
switch filter.PlasmidType {
case models.PlasmidTypeAll:
return "", nil
case models.PlasmidTypeRegular:
return fmt.Sprintf(
"ontology==%s;tag==%s",
registry.DictyPlasmidPropOntology,
registry.RegularPlasmidTag,
), nil
case models.PlasmidTypeGoldenBraid:
return fmt.Sprintf(
"ontology==%s;tag==%s",
registry.DictyPlasmidPropOntology,
registry.GoldenBraidPlasmidTag,
), nil
default:
return "", fmt.Errorf("invalid plasmid type %s", filter.PlasmidType.String())
}
}

func BuildPlasmidFieldQuery(filter *models.PlasmidListFilter) string {
Expand Down
40 changes: 32 additions & 8 deletions internal/graphql/resolverutils/plasmid_filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/dictyBase/graphql-server/internal/graphql/models"
"github.com/dictyBase/graphql-server/internal/registry"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -58,22 +59,45 @@ func TestPlasmidFilterToQueryPlasmidTypeAllOnly(t *testing.T) {
require.Equal(t, "", query)
}

func TestPlasmidFilterToQueryPlasmidTypeRegularUnverified(t *testing.T) {
func TestPlasmidFilterToQueryPlasmidTypeRegularOnly(t *testing.T) {
t.Parallel()
_, err := PlasmidFilterToQuery(&models.PlasmidListFilter{
query, err := PlasmidFilterToQuery(&models.PlasmidListFilter{
PlasmidType: models.PlasmidTypeRegular,
})
require.Error(t, err)
require.Contains(t, err.Error(), "plasmid_type filter is not yet verified")
require.NoError(t, err)
require.Equal(
t,
"ontology=="+registry.DictyPlasmidPropOntology+";tag=="+registry.RegularPlasmidTag,
query,
)
}

func TestPlasmidFilterToQueryPlasmidTypeGoldenBraidUnverified(t *testing.T) {
func TestPlasmidFilterToQueryPlasmidTypeGoldenBraidOnly(t *testing.T) {
t.Parallel()
_, err := PlasmidFilterToQuery(&models.PlasmidListFilter{
query, err := PlasmidFilterToQuery(&models.PlasmidListFilter{
PlasmidType: models.PlasmidTypeGoldenBraid,
})
require.Error(t, err)
require.Contains(t, err.Error(), "plasmid_type filter is not yet verified")
require.NoError(t, err)
require.Equal(
t,
"ontology=="+registry.DictyPlasmidPropOntology+";tag=="+registry.GoldenBraidPlasmidTag,
query,
)
}

func TestPlasmidFilterToQueryCombinedSummaryAndRegularType(t *testing.T) {
t.Parallel()
summary := "test"
query, err := PlasmidFilterToQuery(&models.PlasmidListFilter{
Summary: &summary,
PlasmidType: models.PlasmidTypeRegular,
})
require.NoError(t, err)
require.Equal(
t,
"summary=~test;ontology=="+registry.DictyPlasmidPropOntology+";tag=="+registry.RegularPlasmidTag,
query,
)
}

func TestPlasmidFilterToQueryInvalidPlasmidType(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions internal/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const (
MutmethodTag = "mutagenesis method"
MuttypeTag = "mutant type"
GenoTag = "genotype"
GoldenBraidPlasmidTag = "golden braid"
RegularPlasmidTag = "regular plasmid"
GoldenBraidPlasmidTag = "GB vector"
RegularPlasmidTag = "vector"
SynTag = "synonym"
EmptyValue = "novalue"
S3Bucket = "s3bucket"
Expand Down
Loading