Skip to content
Merged
69 changes: 69 additions & 0 deletions cmd/modusgraph-gen/internal/generator/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,7 @@ func TestGenerate_WrapperQuery(t *testing.T) {
for _, want := range []string{
`package entity`,
`"github.com/matthewmcneely/modusgraph/typed"`,
`"iter"`,
`type StudioQuery struct {`,
`typed *typed.Query[schema.Studio]`,
`func (q *StudioQuery) Filter(filter string, params ...any) *StudioQuery`,
Expand All @@ -1055,6 +1056,7 @@ func TestGenerate_WrapperQuery(t *testing.T) {
`func (q *StudioQuery) Cascade(predicates ...string) *StudioQuery`,
`func (q *StudioQuery) Nodes() ([]*Studio, error)`,
`func (q *StudioQuery) First() (*Studio, error)`,
`func (q *StudioQuery) IterNodes() iter.Seq2[*Studio, error]`,
`return WrapStudio(s), nil`,
} {
if !strings.Contains(data, want) {
Expand All @@ -1066,6 +1068,73 @@ func TestGenerate_WrapperQuery(t *testing.T) {
}
}

// TestGenerate_WrapperQueryEdgeFilter checks that <Entity>Query gains a
// Where<Edge> method for each edge field — delegating to typed.WhereEdge — and
// that an entity with no edges gains none.
func TestGenerate_WrapperQueryEdgeFilter(t *testing.T) {
srcDir := t.TempDir()
if err := os.WriteFile(filepath.Join(srcDir, "go.mod"),
[]byte("module example.com/test\n\ngo 1.25\n"), 0o644); err != nil {
t.Fatalf("writing go.mod: %v", err)
}
src := "package schema\n\n" +
"type Owner struct {\n" +
"\tUID string `json:\"uid,omitempty\"`\n" +
"\tDType []string `json:\"dgraph.type,omitempty\"`\n" +
"\tName string `json:\"name\"`\n" +
"\tPets []*Pet `json:\"pets,omitempty\"`\n" +
"}\n\n" +
"type Pet struct {\n" +
"\tUID string `json:\"uid,omitempty\"`\n" +
"\tDType []string `json:\"dgraph.type,omitempty\"`\n" +
"\tName string `json:\"name\"`\n" +
"}\n"
if err := os.WriteFile(filepath.Join(srcDir, "schema.go"), []byte(src), 0o644); err != nil {
t.Fatalf("writing schema.go: %v", err)
}
pkg, err := parser.Parse(srcDir)
if err != nil {
t.Fatalf("parse: %v", err)
}
entityDir := filepath.Join(t.TempDir(), "entity")
if err := os.MkdirAll(entityDir, 0o755); err != nil {
t.Fatalf("mkdir entityDir: %v", err)
}
cfg := Config{
SchemaDir: srcDir,
SchemaClientDir: srcDir,
EntityDir: entityDir,
EntityClientDir: entityDir,
EntityPackageName: "entity",
EntityClientPackageName: "entity",
SchemaClientPackageName: "schema",
SchemaAlias: "schema",
SchemaImportPath: "example.com/test",
CLIName: "test",
}
if err := Generate(pkg, cfg); err != nil {
t.Fatalf("generate: %v", err)
}

// Owner has a Pets edge → OwnerQuery gains WherePets, delegating to the
// typed substrate with the resolved predicate.
ownerQuery := mustReadGen(t, entityDir, "owner_query_gen.go")
for _, want := range []string{
`func (q *OwnerQuery) WherePets(filter string, params ...any) *OwnerQuery`,
`q.typed.WhereEdge("pets", filter, params...)`,
} {
if !strings.Contains(ownerQuery, want) {
t.Errorf("owner_query_gen.go missing %q; got:\n%s", want, ownerQuery)
}
}

// Pet has no edges → PetQuery must carry no Where* method.
petQuery := mustReadGen(t, entityDir, "pet_query_gen.go")
if strings.Contains(petQuery, "func (q *PetQuery) Where") {
t.Errorf("pet_query_gen.go has a Where* method but Pet has no edges:\n%s", petQuery)
}
}

func TestGenerate_NoIterFileEmitted(t *testing.T) {
_, _, entityDir := generateFromMinimalSchema(t)
if _, err := os.Stat(filepath.Join(entityDir, "iter_gen.go")); !os.IsNotExist(err) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package {{ .EntityPackageName }}

import (
"iter"

"github.com/matthewmcneely/modusgraph/typed"

"{{ .SchemaImportPath }}"
Expand All @@ -10,8 +12,8 @@ import (
{{- $sAlias := .SchemaAlias }}

// {{ $E }}Query is the wrapper-side fluent query builder for {{ $E }}. Builder
// methods return *{{ $E }}Query for chaining; terminal methods (Nodes, First)
// execute the query and wrap results.
// methods return *{{ $E }}Query for chaining; terminal methods (Nodes, First,
// IterNodes) execute the query and wrap results.
type {{ $E }}Query struct {
typed *typed.Query[{{ $sAlias }}.{{ $E }}]
}
Expand Down Expand Up @@ -57,6 +59,16 @@ func (q *{{ $E }}Query) Cascade(predicates ...string) *{{ $E }}Query {
q.typed.Cascade(predicates...)
return q
}
{{- range edgeFields .Entity.Fields }}

// Where{{ accessorName . }} keeps only {{ $E }} records that have a {{ .Predicate }}
// edge whose target node matches the dgraph @filter expression. params bind to
// $N placeholders. Multiple Where* calls are combined with AND.
func (q *{{ $E }}Query) Where{{ accessorName . }}(filter string, params ...any) *{{ $E }}Query {
q.typed.WhereEdge("{{ .Predicate }}", filter, params...)
return q
}
{{- end }}

// Nodes executes the query and returns wrapped {{ $E }} results.
func (q *{{ $E }}Query) Nodes() ([]*{{ $E }}, error) {
Expand All @@ -80,3 +92,19 @@ func (q *{{ $E }}Query) First() (*{{ $E }}, error) {
}
return Wrap{{ $E }}(s), nil
}

// IterNodes streams the query's results as wrapped {{ $E }} values, paging
// transparently. It is a terminal operation; see typed.Query.IterNodes.
func (q *{{ $E }}Query) IterNodes() iter.Seq2[*{{ $E }}, error] {
return func(yield func(*{{ $E }}, error) bool) {
for s, err := range q.typed.IterNodes() {
if err != nil {
yield(nil, err)
return
}
if !yield(Wrap{{ $E }}(s), nil) {
return
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading