Skip to content

Commit 719554f

Browse files
authored
Merge branch 'main' into batch-action-events-api
2 parents 4816e34 + 8975808 commit 719554f

6 files changed

Lines changed: 84 additions & 39 deletions

File tree

broker/oapi/open-api.yaml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@ components:
9797
name: facets
9898
in: query
9999
description: Comma separated list of facet names to include in the response.
100-
Supported values are "requester_name", "requester_symbol", "supplier_name",
101-
"supplier_symbol"; unsupported facet names result in a 400 response.
100+
Supported values are "requester_symbol" and "supplier_symbol"; each facet
101+
value carries the symbol as "value" and, when available, the peer name as
102+
"label". Unsupported facet names result in a 400 response.
102103
For example "requester_symbol,supplier_symbol"
103104
style: form
104105
explode: false
@@ -109,8 +110,6 @@ components:
109110
enum:
110111
- requester_symbol
111112
- supplier_symbol
112-
- requester_name
113-
- supplier_name
114113
schemas:
115114
Index:
116115
type: object
@@ -241,6 +240,10 @@ components:
241240
value:
242241
type: string
243242
description: Facet value
243+
label:
244+
type: string
245+
description: Human-readable name for the facet value, when one is
246+
available. Omitted when the value has no associated name.
244247
count:
245248
type: integer
246249
format: int64

broker/patron_request/api/api-handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ func (a *PatronRequestApiHandler) GetPatronRequests(w http.ResponseWriter, r *ht
236236
for j, value := range field.Values {
237237
facetResults[i].Values[j] = proapi.FacetResultValue{
238238
Value: value.Value,
239+
Label: value.Label,
239240
Count: value.Count,
240241
}
241242
}

broker/patron_request/db/prcql.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,17 +181,33 @@ func ParsePatronRequestsCql(cqlString string) (pgcql.Query, error) {
181181
return def.Parse(query, NumberBaseArgs+1)
182182
}
183183

184-
// facetFieldPlaceholder is the column name used in the getPatronRequestsFacets SQL template.
185-
// GetPatronRequestsFacetsCql substitutes it with the validated facet field at runtime.
186-
const facetFieldPlaceholder = "requester_symbol"
184+
// facetValuePlaceholder and facetLabelPlaceholder are the placeholder column names
185+
// in the getPatronRequestsFacets SQL template. GetPatronRequestsFacetsCql substitutes
186+
// them with the requested facet's symbol (value) and peer-name (label) columns.
187+
const (
188+
facetValuePlaceholder = "requester_symbol"
189+
facetLabelPlaceholder = "requester_name"
190+
)
191+
192+
// facetLabelColumns maps each supported facet field (a symbol column) to the
193+
// peer-name column used as its display label.
194+
var facetLabelColumns = map[string]string{
195+
"requester_symbol": "requester_name",
196+
"supplier_symbol": "supplier_name",
197+
}
187198

188199
func (q *Queries) GetPatronRequestsFacetsCql(ctx context.Context, db DBTX, facetField string, pgcql pgcql.Query) ([]GetPatronRequestsFacetsRow, error) {
189200
if pgcql == nil {
190201
return nil, fmt.Errorf("pgcql.Query must not be nil; use cql.allRecords=1 for no filter")
191202
}
192-
// facetField is validated against an allowlist by the caller (GetPatronRequestsFacets),
193-
// so it is safe to substitute directly as a column name.
194-
sql := strings.Replace(getPatronRequestsFacets, facetFieldPlaceholder, facetField, 1)
203+
labelField, ok := facetLabelColumns[facetField]
204+
if !ok {
205+
return nil, fmt.Errorf("%w: %s", ErrUnsupportedFacet, facetField)
206+
}
207+
// facetField is validated against an allowlist by the caller (GetPatronRequestsFacets)
208+
// and the map lookup above, so both columns are safe to substitute directly.
209+
sql := strings.ReplaceAll(getPatronRequestsFacets, facetValuePlaceholder, facetField)
210+
sql = strings.ReplaceAll(sql, facetLabelPlaceholder, labelField)
195211

196212
idx := strings.Index(sql, "GROUP BY")
197213
if idx == -1 {
@@ -211,7 +227,7 @@ func (q *Queries) GetPatronRequestsFacetsCql(ctx context.Context, db DBTX, facet
211227
var items []GetPatronRequestsFacetsRow
212228
for rows.Next() {
213229
var i GetPatronRequestsFacetsRow
214-
if err := rows.Scan(&i.Value, &i.Count); err != nil {
230+
if err := rows.Scan(&i.Value, &i.Label, &i.Count); err != nil {
215231
return nil, err
216232
}
217233
items = append(items, i)

broker/patron_request/db/prrepo.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type Facet struct {
5454

5555
type FacetValue struct {
5656
Value string
57+
Label *string
5758
Count int64
5859
}
5960

@@ -128,18 +129,23 @@ func (r *PgPrRepo) GetPatronRequestsFacets(ctx common.ExtendedContext, facetFiel
128129
var facets []Facet
129130
for _, field := range facetFields {
130131
switch field {
131-
case "requester_symbol", "supplier_symbol", "requester_name", "supplier_name":
132+
case "requester_symbol", "supplier_symbol":
132133
rows, err := r.queries.GetPatronRequestsFacetsCql(ctx, r.GetConnOrTx(), field, pgcql)
133134
if err != nil {
134135
return nil, err
135136
}
136137
var values []FacetValue
137138
for _, row := range rows {
138139
if row.Value.Valid {
139-
values = append(values, FacetValue{
140+
value := FacetValue{
140141
Value: row.Value.String,
141142
Count: row.Count,
142-
})
143+
}
144+
if row.Label.Valid {
145+
label := row.Label.String
146+
value.Label = &label
147+
}
148+
values = append(values, value)
143149
}
144150
}
145151
facets = append(facets, Facet{

broker/sqlc/pr_query.sql

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ ORDER BY created_at
2525
LIMIT $1 OFFSET $2;
2626

2727
-- name: GetPatronRequestsFacets :many
28-
-- TEMPLATE: 'requester_symbol' is a placeholder column name. This query is not
29-
-- meant to be called directly; use GetPatronRequestsFacetsCql in prcql.go, which
30-
-- substitutes the column with the validated facet field at runtime.
31-
SELECT requester_symbol AS value, COUNT(*) AS count
28+
-- TEMPLATE: 'requester_symbol' (value) and 'requester_name' (label) are placeholder
29+
-- column names. This query is not meant to be called directly; use
30+
-- GetPatronRequestsFacetsCql in prcql.go, which substitutes both columns with the
31+
-- validated facet field's symbol/name columns at runtime.
32+
SELECT requester_symbol AS value, requester_name AS label, COUNT(*) AS count
3233
FROM patron_request_search_view
3334
WHERE ill_request IS NOT NULL
34-
GROUP BY 1
35+
GROUP BY 1, 2
3536
ORDER BY count DESC, value ASC
3637
LIMIT $1 OFFSET $2;
3738

broker/test/patron_request/api/api-handler_test.go

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
test "github.com/indexdata/crosslink/broker/test/utils"
3232
"github.com/indexdata/go-utils/utils"
3333
"github.com/stretchr/testify/assert"
34+
"github.com/stretchr/testify/require"
3435
"github.com/testcontainers/testcontainers-go"
3536
"github.com/testcontainers/testcontainers-go/modules/postgres"
3637
"github.com/testcontainers/testcontainers-go/wait"
@@ -1250,23 +1251,28 @@ func TestRequesterSupplierNameCQL(t *testing.T) {
12501251
assert.NoError(t, err)
12511252
assert.Equal(t, int64(0), foundPrs.About.Count)
12521253

1253-
// get requester_name and supplier_name facets → 1 result, 2 facets
1254+
// get requester_symbol and supplier_symbol facets → 1 result, 2 facets;
1255+
// each value carries the symbol as Value and the peer name as Label.
12541256
respBytes = httpRequest(t, "GET", basePath+"?cql=requester_name%3D"+url.QueryEscape(reqName)+
1255-
"&facets=requester_name,supplier_name", []byte{}, 200)
1257+
"&facets=requester_symbol,supplier_symbol", []byte{}, 200)
12561258
err = json.Unmarshal(respBytes, &foundPrs)
12571259
assert.NoError(t, err)
12581260
assert.Equal(t, int64(1), foundPrs.About.Count)
12591261
assert.Equal(t, prId, foundPrs.Items[0].Id)
12601262

1261-
assert.NotNil(t, foundPrs.About.Facets)
1262-
assert.Len(t, *foundPrs.About.Facets, 2)
1263-
assert.Equal(t, "requester_name", (*foundPrs.About.Facets)[0].Name)
1264-
assert.Len(t, (*foundPrs.About.Facets)[0].Values, 1)
1265-
assert.Equal(t, reqName, (*foundPrs.About.Facets)[0].Values[0].Value)
1263+
require.NotNil(t, foundPrs.About.Facets)
1264+
require.Len(t, *foundPrs.About.Facets, 2)
1265+
assert.Equal(t, "requester_symbol", (*foundPrs.About.Facets)[0].Name)
1266+
require.Len(t, (*foundPrs.About.Facets)[0].Values, 1)
1267+
assert.Equal(t, reqSymbol, (*foundPrs.About.Facets)[0].Values[0].Value)
1268+
require.NotNil(t, (*foundPrs.About.Facets)[0].Values[0].Label)
1269+
assert.Equal(t, reqName, *(*foundPrs.About.Facets)[0].Values[0].Label)
12661270
assert.Equal(t, int64(1), (*foundPrs.About.Facets)[0].Values[0].Count)
1267-
assert.Equal(t, "supplier_name", (*foundPrs.About.Facets)[1].Name)
1268-
assert.Len(t, (*foundPrs.About.Facets)[1].Values, 1)
1269-
assert.Equal(t, supName, (*foundPrs.About.Facets)[1].Values[0].Value)
1271+
assert.Equal(t, "supplier_symbol", (*foundPrs.About.Facets)[1].Name)
1272+
require.Len(t, (*foundPrs.About.Facets)[1].Values, 1)
1273+
assert.Equal(t, supSymbol, (*foundPrs.About.Facets)[1].Values[0].Value)
1274+
require.NotNil(t, (*foundPrs.About.Facets)[1].Values[0].Label)
1275+
assert.Equal(t, supName, *(*foundPrs.About.Facets)[1].Values[0].Label)
12701276
assert.Equal(t, int64(1), (*foundPrs.About.Facets)[1].Values[0].Count)
12711277
}
12721278

@@ -1361,41 +1367,53 @@ func TestFacetsOK(t *testing.T) {
13611367
assert.NoError(t, err)
13621368
assert.Equal(t, int64(5), foundPrs.About.Count)
13631369
assert.Len(t, foundPrs.Items, 0)
1364-
assert.NotNil(t, foundPrs.About.Facets)
1365-
assert.Len(t, *foundPrs.About.Facets, 1)
1370+
require.NotNil(t, foundPrs.About.Facets)
1371+
require.Len(t, *foundPrs.About.Facets, 1)
13661372
assert.Equal(t, "requester_symbol", (*foundPrs.About.Facets)[0].Name)
1367-
assert.Len(t, (*foundPrs.About.Facets)[0].Values, 2)
1373+
require.Len(t, (*foundPrs.About.Facets)[0].Values, 2)
13681374
assert.Equal(t, requesterSymbols[0], (*foundPrs.About.Facets)[0].Values[0].Value)
1375+
require.NotNil(t, (*foundPrs.About.Facets)[0].Values[0].Label)
1376+
assert.Equal(t, requesterSymbols[0], *(*foundPrs.About.Facets)[0].Values[0].Label)
13691377
assert.Equal(t, int64(3), (*foundPrs.About.Facets)[0].Values[0].Count)
13701378
assert.Equal(t, requesterSymbols[1], (*foundPrs.About.Facets)[0].Values[1].Value)
1379+
require.NotNil(t, (*foundPrs.About.Facets)[0].Values[1].Label)
1380+
assert.Equal(t, requesterSymbols[1], *(*foundPrs.About.Facets)[0].Values[1].Label)
13711381
assert.Equal(t, int64(2), (*foundPrs.About.Facets)[0].Values[1].Count)
13721382

13731383
respBytes = httpRequest(t, "GET", basePath+"?facets=requester_symbol&cql=title%3Dfacets%20title&offset=0&limit=0", []byte{}, 200)
13741384
err = json.Unmarshal(respBytes, &foundPrs)
13751385
assert.NoError(t, err)
13761386
assert.Equal(t, int64(10), foundPrs.About.Count)
13771387
assert.Len(t, foundPrs.Items, 0)
1378-
assert.NotNil(t, foundPrs.About.Facets)
1379-
assert.Len(t, *foundPrs.About.Facets, 1)
1388+
require.NotNil(t, foundPrs.About.Facets)
1389+
require.Len(t, *foundPrs.About.Facets, 1)
13801390
assert.Equal(t, "requester_symbol", (*foundPrs.About.Facets)[0].Name)
1381-
assert.Len(t, (*foundPrs.About.Facets)[0].Values, 2)
1391+
require.Len(t, (*foundPrs.About.Facets)[0].Values, 2)
13821392
assert.Equal(t, requesterSymbols[0], (*foundPrs.About.Facets)[0].Values[0].Value)
1393+
require.NotNil(t, (*foundPrs.About.Facets)[0].Values[0].Label)
1394+
assert.Equal(t, requesterSymbols[0], *(*foundPrs.About.Facets)[0].Values[0].Label)
13831395
assert.Equal(t, int64(7), (*foundPrs.About.Facets)[0].Values[0].Count)
13841396
assert.Equal(t, requesterSymbols[1], (*foundPrs.About.Facets)[0].Values[1].Value)
1397+
require.NotNil(t, (*foundPrs.About.Facets)[0].Values[1].Label)
1398+
assert.Equal(t, requesterSymbols[1], *(*foundPrs.About.Facets)[0].Values[1].Label)
13851399
assert.Equal(t, int64(3), (*foundPrs.About.Facets)[0].Values[1].Count)
13861400

13871401
respBytes = httpRequest(t, "GET", basePath+"?facets=requester_symbol%2Csupplier_symbol&cql=title%3Dfacets%20title&offset=0&limit=0", []byte{}, 200)
13881402
err = json.Unmarshal(respBytes, &foundPrs)
13891403
assert.NoError(t, err)
13901404
assert.Equal(t, int64(10), foundPrs.About.Count)
13911405
assert.Len(t, foundPrs.Items, 0)
1392-
assert.NotNil(t, foundPrs.About.Facets)
1393-
assert.Len(t, *foundPrs.About.Facets, 2)
1406+
require.NotNil(t, foundPrs.About.Facets)
1407+
require.Len(t, *foundPrs.About.Facets, 2)
13941408
assert.Equal(t, "requester_symbol", (*foundPrs.About.Facets)[0].Name)
1395-
assert.Len(t, (*foundPrs.About.Facets)[0].Values, 2)
1409+
require.Len(t, (*foundPrs.About.Facets)[0].Values, 2)
13961410
assert.Equal(t, requesterSymbols[0], (*foundPrs.About.Facets)[0].Values[0].Value)
1411+
require.NotNil(t, (*foundPrs.About.Facets)[0].Values[0].Label)
1412+
assert.Equal(t, requesterSymbols[0], *(*foundPrs.About.Facets)[0].Values[0].Label)
13971413
assert.Equal(t, int64(7), (*foundPrs.About.Facets)[0].Values[0].Count)
13981414
assert.Equal(t, requesterSymbols[1], (*foundPrs.About.Facets)[0].Values[1].Value)
1415+
require.NotNil(t, (*foundPrs.About.Facets)[0].Values[1].Label)
1416+
assert.Equal(t, requesterSymbols[1], *(*foundPrs.About.Facets)[0].Values[1].Label)
13991417
assert.Equal(t, int64(3), (*foundPrs.About.Facets)[0].Values[1].Count)
14001418
assert.Equal(t, "supplier_symbol", (*foundPrs.About.Facets)[1].Name)
14011419
if len((*foundPrs.About.Facets)[1].Values) == 1 {
@@ -1412,7 +1430,7 @@ func TestFacetsOK(t *testing.T) {
14121430
assert.NoError(t, err)
14131431
assert.GreaterOrEqual(t, foundPrs.About.Count, int64(10))
14141432
assert.Len(t, foundPrs.Items, 0)
1415-
assert.NotNil(t, foundPrs.About.Facets)
1433+
require.NotNil(t, foundPrs.About.Facets)
14161434
assert.GreaterOrEqual(t, len(*foundPrs.About.Facets), 2)
14171435
}
14181436

0 commit comments

Comments
 (0)