diff --git a/internal/httpapi/api_test.go b/internal/httpapi/api_test.go index 692ba44..c59f0a2 100644 --- a/internal/httpapi/api_test.go +++ b/internal/httpapi/api_test.go @@ -263,6 +263,20 @@ func TestAPIEndToEnd(t *testing.T) { } }) + // Regression: rel_type is an optional array param (*[]string); when + // omitted entirely (not an empty list), passing the nil pointer straight + // through to pgx as a query arg panics because pgx can't infer the array + // element type from a nil *[]string. root+depth alone must still work. + t.Run("graph without rel_type", func(t *testing.T) { + rec, body := get(t, h, "/api/v1/graph?root=host:hubris&depth=1", nil) + if rec.Code != 200 { + t.Fatalf("status %d", rec.Code) + } + if len(body["nodes"].([]any)) < 2 { + t.Errorf("graph too small: %d nodes", len(body["nodes"].([]any))) + } + }) + t.Run("ontology", func(t *testing.T) { rec, body := get(t, h, "/api/v1/ontology", nil) if rec.Code != 200 { diff --git a/internal/httpapi/impl.go b/internal/httpapi/impl.go index 80a9cb1..9203337 100644 --- a/internal/httpapi/impl.go +++ b/internal/httpapi/impl.go @@ -211,7 +211,7 @@ func (s *Server) GetEntityRelations(ctx context.Context, req gen.GetEntityRelati Type: r.Type, Attributes: attrs, ValidFrom: r.ValidFrom, - ValidTo: validTo, + ValidTo: validTo, }) } return gen.GetEntityRelations200JSONResponse{Items: items}, nil @@ -282,6 +282,15 @@ func (s *Server) GetGraph(ctx context.Context, req gen.GetGraphRequestObject) (g var err error truncated := false + // pgx can't infer the array element type from a nil *[]string (the + // param is absent from the request, not an empty list), so dereference + // to a plain []string first — nil there still encodes as SQL NULL, but + // pgx has a concrete type to work with. + var relTypes []string + if req.Params.RelType != nil { + relTypes = *req.Params.RelType + } + if req.Params.Root != nil && *req.Params.Root != "" { rootID, rerr := s.resolveEntityID(ctx, *req.Params.Root) if rerr != nil { @@ -291,7 +300,7 @@ func (s *Server) GetGraph(ctx context.Context, req gen.GetGraphRequestObject) (g SELECT `+entityCols+` FROM blast_radius($1, $2, $3) b JOIN entities e ON e.id = b.entity_id LEFT JOIN entity_status st ON st.entity_id = e.id - ORDER BY e.slug`, rootID, depth, req.Params.RelType) + ORDER BY e.slug`, rootID, depth, relTypes) } else { // Whole-graph view: pick the most-connected entities first so the // graph shows actual topology, not just whatever sorts first @@ -327,7 +336,7 @@ func (s *Server) GetGraph(ctx context.Context, req gen.GetGraphRequestObject) (g } edgeRows, err := sqlcgen.New(s.pool).ListGraphEdges(ctx, sqlcgen.ListGraphEdgesParams{ Ids: ids, - RelTypes: *req.Params.RelType, + RelTypes: relTypes, }) if err != nil { return nil, err @@ -348,7 +357,7 @@ func (s *Server) GetGraph(ctx context.Context, req gen.GetGraphRequestObject) (g Type: r.Type, Attributes: attrs, ValidFrom: r.ValidFrom, - ValidTo: validTo, + ValidTo: validTo, }) }