From 6664a7808c14f94e211fd584e880ff1f04059b25 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Mon, 11 May 2026 16:32:47 +0200 Subject: [PATCH 01/35] Begin --- broker/adapter/create_holdings.go | 17 ++++- broker/adapter/marc21_plus_holdings_parser.go | 62 +++++++++++++++++++ broker/app/app.go | 2 + 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 broker/adapter/marc21_plus_holdings_parser.go diff --git a/broker/adapter/create_holdings.go b/broker/adapter/create_holdings.go index 25468ab3..8b2246d7 100644 --- a/broker/adapter/create_holdings.go +++ b/broker/adapter/create_holdings.go @@ -10,8 +10,20 @@ const ( HoldingsAdapter string = "HOLDINGS_ADAPTER" HoldingsSruURL string = "HOLDINGS_SRU_URL" HoldingsIsxnLookup string = "HOLDINGS_ISXN_LOOKUP" + HoldingsFormat string = "HOLDINGS_FORMAT" ) +func getParser(format string) (HoldingsParser, error) { + switch format { + case "reservoir": + return &ReservoirHoldingsParser{}, nil + case "MARC-21plus-1": + return &Marc21Plus1HoldingsParser{}, nil + default: + return nil, fmt.Errorf("unsupported holdings format: %s", format) + } +} + func CreateHoldingsLookupAdapter(cfg map[string]any) (LookupAdapter, error) { holdingsAdapterVal, ok := cfg[HoldingsAdapter].(string) if !ok { @@ -32,7 +44,10 @@ func CreateHoldingsLookupAdapter(cfg map[string]any) (LookupAdapter, error) { return nil, fmt.Errorf("invalid value for %s: %v", HoldingsIsxnLookup, isxnLookup) } queryBuilder := QueryBuilderIsxn{isxn: isxnLookup} - parser := &ReservoirHoldingsParser{} + parser, err := getParser(cfg[HoldingsFormat].(string)) + if err != nil { + return nil, err + } return CreateSruHoldingsLookupAdapter(http.DefaultClient, strings.Split(sruURLVal, ","), "", &queryBuilder, parser, "marcxml"), nil } if holdingsAdapterVal == "mock" { diff --git a/broker/adapter/marc21_plus_holdings_parser.go b/broker/adapter/marc21_plus_holdings_parser.go new file mode 100644 index 00000000..c4aff5f9 --- /dev/null +++ b/broker/adapter/marc21_plus_holdings_parser.go @@ -0,0 +1,62 @@ +package adapter + +import ( + "encoding/xml" + "fmt" + "strings" + + "github.com/indexdata/crosslink/directory" + "github.com/indexdata/crosslink/marcxml" +) + +type Marc21Plus1HoldingsParser struct { + config directory.MarcParserConfig +} + +func NewMarc21Plus1HoldingsParser() HoldingsParser { + return &Marc21Plus1HoldingsParser{} +} + +func (p *Marc21Plus1HoldingsParser) Parse(record []byte) ([]Holding, error) { + var marcRecord marcxml.Record + err := xml.Unmarshal(record, &marcRecord) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal MARC XML: %w", err) + } + var holdings []Holding + for _, field := range marcRecord.Datafield { + if p.config.MainField != nil && field.Tag == *p.config.MainField { + restricted := false + var location string + var shelvingLocation string + var callNumber string + var itemId string + for _, subfield := range field.Subfield { + if p.config.LocationSubField != nil && subfield.Code == *p.config.LocationSubField { + location = strings.TrimSpace(string(subfield.Text)) + } + if p.config.ShelvingLocationSubField != nil && subfield.Code == *p.config.ShelvingLocationSubField { + shelvingLocation = strings.TrimSpace(string(subfield.Text)) + } + if p.config.CallNumberSubField != nil && subfield.Code == *p.config.CallNumberSubField { + callNumber = strings.TrimSpace(string(subfield.Text)) + } + if p.config.ItemIdSubField != nil && subfield.Code == *p.config.ItemIdSubField { + itemId = strings.TrimSpace(string(subfield.Text)) + } + if p.config.RestrictedSubField != nil && subfield.Code == *p.config.RestrictedSubField { + restricted = true + } + } + if !restricted && location != "" { + holdings = append(holdings, Holding{ + Location: location, + ShelvingLocation: shelvingLocation, + CallNumber: callNumber, + ItemId: itemId, + }) + } + } + } + return holdings, nil +} diff --git a/broker/app/app.go b/broker/app/app.go index 0a16166f..419ab7eb 100644 --- a/broker/app/app.go +++ b/broker/app/app.go @@ -64,6 +64,7 @@ var LOG_LEVEL = utils.GetEnv("LOG_LEVEL", "INFO") var HOLDINGS_ADAPTER = utils.GetEnv("HOLDINGS_ADAPTER", "mock") var HOLDINGS_SRU_URL = common.GetEnvWithDeprecated("HOLDINGS_SRU_URL", "SRU_URL", "http://localhost:8081/sru") var HOLDINGS_ISXN_LOOKUP, _ = utils.GetEnvBool("HOLDINGS_ISXN_LOOKUP", false) +var HOLDINGS_FORMAT = utils.GetEnv("HOLDINGS_FORMAT", "reservoir") var DIRECTORY_ADAPTER = utils.GetEnv("DIRECTORY_ADAPTER", "mock") var AVAILABILITY_ADAPTER = utils.GetEnv("AVAILABILITY_ADAPTER", "zoom") var DIRECTORY_API_URL = utils.GetEnv("DIRECTORY_API_URL", "http://localhost:8081/directory/entries") @@ -136,6 +137,7 @@ func Init(ctx context.Context) (Context, error) { adapter.HoldingsAdapter: HOLDINGS_ADAPTER, adapter.HoldingsSruURL: HOLDINGS_SRU_URL, adapter.HoldingsIsxnLookup: HOLDINGS_ISXN_LOOKUP, + adapter.HoldingsFormat: HOLDINGS_FORMAT, }) if err != nil { return Context{}, err From 3635b881f42d2c6364ebc52a5d8a9a8fd62e24c8 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Mon, 11 May 2026 17:20:13 +0200 Subject: [PATCH 02/35] More --- broker/adapter/marc21_plus_holdings_parser.go | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/broker/adapter/marc21_plus_holdings_parser.go b/broker/adapter/marc21_plus_holdings_parser.go index c4aff5f9..910d08c3 100644 --- a/broker/adapter/marc21_plus_holdings_parser.go +++ b/broker/adapter/marc21_plus_holdings_parser.go @@ -5,12 +5,37 @@ import ( "fmt" "strings" - "github.com/indexdata/crosslink/directory" "github.com/indexdata/crosslink/marcxml" ) +// Holding Information to be used for routing is part of +// repeatable MARC 924 fields (one for each holding library). + +// First Indicator Resource Type + +// "0" Non-electronic (= default) + +// "1" Electronic + +// $a (NR) Local IDN of the holding record +// $b (NR) ISIL as an identifier of the owning institution +// $c (NR) Interlibrary loan region +// $d (NR) Interlibrary loan indicator +// "a" - Loan of volumes possible, no copies +// "b" - No loan of volumes, only paper copies aresent +// "c" - Unrestricted interlibrary loan, copying and loan +// "d" - No interlibrary loan +// "e" - No loan of volumes, the end user receives an +// electronic copy +// $k (R) Electronic address (URL) for a remotely accessed file +// $1 (R) Identification "Produktsigel" for national licenses +// and digital collections, so called "ProduktSigel" +// (it is an ISIL according to the German ISIL-Agency) + +// Full documentation Result format is MARC21, see from Deutsche +// Nationalbibliothek (DNB), https://d-nb.info/1282570226/34 + type Marc21Plus1HoldingsParser struct { - config directory.MarcParserConfig } func NewMarc21Plus1HoldingsParser() HoldingsParser { @@ -25,35 +50,22 @@ func (p *Marc21Plus1HoldingsParser) Parse(record []byte) ([]Holding, error) { } var holdings []Holding for _, field := range marcRecord.Datafield { - if p.config.MainField != nil && field.Tag == *p.config.MainField { - restricted := false - var location string - var shelvingLocation string - var callNumber string - var itemId string + if field.Tag == "924" { + var localIdentifier string + var symbol string + // TODO: serviceType subfield d for _, subfield := range field.Subfield { - if p.config.LocationSubField != nil && subfield.Code == *p.config.LocationSubField { - location = strings.TrimSpace(string(subfield.Text)) - } - if p.config.ShelvingLocationSubField != nil && subfield.Code == *p.config.ShelvingLocationSubField { - shelvingLocation = strings.TrimSpace(string(subfield.Text)) - } - if p.config.CallNumberSubField != nil && subfield.Code == *p.config.CallNumberSubField { - callNumber = strings.TrimSpace(string(subfield.Text)) - } - if p.config.ItemIdSubField != nil && subfield.Code == *p.config.ItemIdSubField { - itemId = strings.TrimSpace(string(subfield.Text)) + if subfield.Code == "a" { + localIdentifier = strings.TrimSpace(string(subfield.Text)) } - if p.config.RestrictedSubField != nil && subfield.Code == *p.config.RestrictedSubField { - restricted = true + if subfield.Code == "b" { + symbol = strings.TrimSpace(string(subfield.Text)) } } - if !restricted && location != "" { + if localIdentifier != "" && symbol != "" { holdings = append(holdings, Holding{ - Location: location, - ShelvingLocation: shelvingLocation, - CallNumber: callNumber, - ItemId: itemId, + LocalIdentifier: localIdentifier, + Symbol: symbol, }) } } From b6ee75eca4eb8de7d910b8cc0ae02785c59cf170 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Mon, 11 May 2026 18:14:12 +0200 Subject: [PATCH 03/35] Pass ServiceType --- broker/README.md | 1 + broker/adapter/holdings.go | 11 ++++---- broker/adapter/marc21_plus_holdings_parser.go | 27 ++++++++++++++++--- broker/adapter/marc_holdings_parser.go | 2 +- broker/adapter/opac_holdings_parser.go | 2 +- broker/adapter/reservoir_holdings_parser.go | 2 +- broker/adapter/sru_holdings.go | 12 ++++----- broker/availability/availability_zoom.go | 6 ++--- broker/service/supplierlocator.go | 3 +++ 9 files changed, 46 insertions(+), 20 deletions(-) diff --git a/broker/README.md b/broker/README.md index 602cabb3..7637da6b 100644 --- a/broker/README.md +++ b/broker/README.md @@ -103,6 +103,7 @@ Configuration is provided via environment variables: | `HOLDINGS_ADAPTER` | Holdings lookup method: `mock` or `sru` | `mock` | | `HOLDINGS_SRU_URL` | Comma separated list of URLs when `HOLDINGS_ADAPTER` is `sru` | `http://localhost:8081/sru` | | `HOLDINGS_ISXN_LOOKUP` | Whether to use ISBN/ISSN lookup for `sru` method | `false` | +| `HOLDINGS_FORMAT` | Parser for SRU holdings: `reservoir` or `MARC-21plus-1` | `reservoir` | | `DIRECTORY_ADAPTER` | Directory lookup method: `mock` or `api` | `mock` | | `DIRECTORY_API_URL` | Comma separated list of URLs when `DIRECTORY_ADAPTER` is `api` | `http://localhost:8081/directory/entries` | | `AVAILABILITY_ADAPTER` | Availability adapter: `mock` , `zoom`, `metaproxy` | `zoom` | diff --git a/broker/adapter/holdings.go b/broker/adapter/holdings.go index 93cc545f..d5518c36 100644 --- a/broker/adapter/holdings.go +++ b/broker/adapter/holdings.go @@ -5,10 +5,11 @@ type LookupAdapter interface { } type LookupParams struct { - Identifier string - Isbn string - Issn string - Title string + Identifier string + Isbn string + Issn string + Title string + ServiceType string } type Holding struct { @@ -21,7 +22,7 @@ type Holding struct { } type HoldingsParser interface { - Parse(record []byte) ([]Holding, error) + Parse(record []byte, params LookupParams) ([]Holding, error) } type LookupQueryBuilder interface { diff --git a/broker/adapter/marc21_plus_holdings_parser.go b/broker/adapter/marc21_plus_holdings_parser.go index 910d08c3..b7a4e37f 100644 --- a/broker/adapter/marc21_plus_holdings_parser.go +++ b/broker/adapter/marc21_plus_holdings_parser.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/indexdata/crosslink/iso18626" "github.com/indexdata/crosslink/marcxml" ) @@ -42,18 +43,23 @@ func NewMarc21Plus1HoldingsParser() HoldingsParser { return &Marc21Plus1HoldingsParser{} } -func (p *Marc21Plus1HoldingsParser) Parse(record []byte) ([]Holding, error) { +func (p *Marc21Plus1HoldingsParser) Parse(record []byte, params LookupParams) ([]Holding, error) { var marcRecord marcxml.Record err := xml.Unmarshal(record, &marcRecord) if err != nil { return nil, fmt.Errorf("failed to unmarshal MARC XML: %w", err) } + loanOk := params.ServiceType == string(iso18626.TypeServiceTypeLoan) || + params.ServiceType == string(iso18626.TypeServiceTypeCopyOrLoan) || params.ServiceType == "" + copyOk := params.ServiceType == string(iso18626.TypeServiceTypeCopy) || + params.ServiceType == string(iso18626.TypeServiceTypeCopyOrLoan) || params.ServiceType == "" + var holdings []Holding for _, field := range marcRecord.Datafield { if field.Tag == "924" { var localIdentifier string var symbol string - // TODO: serviceType subfield d + ok := false for _, subfield := range field.Subfield { if subfield.Code == "a" { localIdentifier = strings.TrimSpace(string(subfield.Text)) @@ -61,8 +67,23 @@ func (p *Marc21Plus1HoldingsParser) Parse(record []byte) ([]Holding, error) { if subfield.Code == "b" { symbol = strings.TrimSpace(string(subfield.Text)) } + if subfield.Code == "d" { // loan indicator + indicator := strings.TrimSpace(string(subfield.Text)) + if indicator == "a" { + ok = loanOk + } + if indicator == "b" { + ok = copyOk + } + if indicator == "c" { // unrestricted interlibrary loan, so we can treat it as available + ok = true + } + if indicator == "e" { + ok = copyOk + } + } } - if localIdentifier != "" && symbol != "" { + if ok && localIdentifier != "" && symbol != "" { holdings = append(holdings, Holding{ LocalIdentifier: localIdentifier, Symbol: symbol, diff --git a/broker/adapter/marc_holdings_parser.go b/broker/adapter/marc_holdings_parser.go index bb54792b..c8dd6857 100644 --- a/broker/adapter/marc_holdings_parser.go +++ b/broker/adapter/marc_holdings_parser.go @@ -28,7 +28,7 @@ func NewMarcHoldingsParser(config directory.MarcParserConfig) HoldingsParser { } } -func (p *MarcHoldingsParser) Parse(record []byte) ([]Holding, error) { +func (p *MarcHoldingsParser) Parse(record []byte, params LookupParams) ([]Holding, error) { var marcRecord marcxml.Record err := xml.Unmarshal(record, &marcRecord) // TODO : consider OPAC record as well diff --git a/broker/adapter/opac_holdings_parser.go b/broker/adapter/opac_holdings_parser.go index ea97cab4..fe4f46c7 100644 --- a/broker/adapter/opac_holdings_parser.go +++ b/broker/adapter/opac_holdings_parser.go @@ -14,7 +14,7 @@ func NewOpacHoldingsParser(config directory.OpacParserConfig) HoldingsParser { return &OpacHoldingsParser{} } -func (p *OpacHoldingsParser) Parse(record []byte) ([]Holding, error) { +func (p *OpacHoldingsParser) Parse(record []byte, params LookupParams) ([]Holding, error) { var opacRecord marcxml.OpacRecord err := xml.Unmarshal(record, &opacRecord) if err != nil { diff --git a/broker/adapter/reservoir_holdings_parser.go b/broker/adapter/reservoir_holdings_parser.go index 1592b57b..73156b5f 100644 --- a/broker/adapter/reservoir_holdings_parser.go +++ b/broker/adapter/reservoir_holdings_parser.go @@ -47,7 +47,7 @@ func parseHoldings(rec *marcxml.Record) []Holding { return holdings } -func (p *ReservoirHoldingsParser) Parse(recordData []byte) ([]Holding, error) { +func (p *ReservoirHoldingsParser) Parse(recordData []byte, params LookupParams) ([]Holding, error) { var rec marcxml.Record err := xml.Unmarshal(recordData, &rec) if err != nil { diff --git a/broker/adapter/sru_holdings.go b/broker/adapter/sru_holdings.go index 8008f1a8..5f092d92 100644 --- a/broker/adapter/sru_holdings.go +++ b/broker/adapter/sru_holdings.go @@ -28,7 +28,7 @@ func CreateSruHoldingsLookupAdapter(client *http.Client, sruUrl []string, xTarge return &SruHoldingsLookupAdapter{client: client, sruUrl: sruUrl, queryBuilder: queryBuilder, holdingsParser: parser, xTarget: xTarget, recordSchema: recordSchema} } -func (s *SruHoldingsLookupAdapter) parseRecord(record *sru.RecordDefinition, holdings *[]Holding) error { +func (s *SruHoldingsLookupAdapter) parseRecord(record *sru.RecordDefinition, params LookupParams, holdings *[]Holding) error { if record.RecordXMLEscaping != nil && *record.RecordXMLEscaping != sru.RecordXMLEscapingDefinitionXml { return fmt.Errorf("unsupported RecordXMLEscaping: %s", *record.RecordXMLEscaping) } @@ -48,7 +48,7 @@ func (s *SruHoldingsLookupAdapter) parseRecord(record *sru.RecordDefinition, hol return fmt.Errorf("unsupported RecordSchema: %s", record.RecordSchema) } - ret, err := s.holdingsParser.Parse(record.RecordData.XMLContent) + ret, err := s.holdingsParser.Parse(record.RecordData.XMLContent, params) if err != nil { return fmt.Errorf("parsing holdings failed: %s", err.Error()) } @@ -64,7 +64,7 @@ func encodeCqlSearchClause(field string, value string) (string, error) { return cqlQuery.String(), nil } -func (s *SruHoldingsLookupAdapter) search(sruUrl string, query string) ([]Holding, string, error) { +func (s *SruHoldingsLookupAdapter) search(sruUrl string, params LookupParams, query string) ([]Holding, string, error) { var sruResponse sru.SearchRetrieveResponse query = "?maximumRecords=1000&recordSchema=" + url.QueryEscape(s.recordSchema) + "&" + query if s.xTarget != "" { @@ -85,7 +85,7 @@ func (s *SruHoldingsLookupAdapter) search(sruUrl string, query string) ([]Holdin var holdings []Holding if sruResponse.Records != nil { for _, record := range sruResponse.Records.Record { - err := s.parseRecord(&record, &holdings) + err := s.parseRecord(&record, params, &holdings) if err != nil { return nil, query, err } @@ -103,7 +103,7 @@ func (s *SruHoldingsLookupAdapter) getHoldings(sruUrl string, params LookupParam var queryParams string for _, cql := range cqlList { sruQuery := "query=" + url.QueryEscape(cql) - holdings, queryParams, err = s.search(sruUrl, sruQuery) + holdings, queryParams, err = s.search(sruUrl, params, sruQuery) if err != nil { return nil, queryParams, err } @@ -113,7 +113,7 @@ func (s *SruHoldingsLookupAdapter) getHoldings(sruUrl string, params LookupParam } for _, pqf := range pqfList { sruQuery := "x-pquery=" + url.QueryEscape(pqf) - holdings, queryParams, err = s.search(sruUrl, sruQuery) + holdings, queryParams, err = s.search(sruUrl, params, sruQuery) if err != nil { return nil, queryParams, err } diff --git a/broker/availability/availability_zoom.go b/broker/availability/availability_zoom.go index 71ca801b..8727ec6e 100644 --- a/broker/availability/availability_zoom.go +++ b/broker/availability/availability_zoom.go @@ -39,7 +39,7 @@ func NewZoomAvailabilityAdapter(ctx common.ExtendedContext, config directory.Z39 return a, nil } -func (a *ZoomAvailabilityAdapter) searchRetrieve(conn *zoom.Connection, query string) ([]adapter.Holding, error) { +func (a *ZoomAvailabilityAdapter) searchRetrieve(params adapter.LookupParams, conn *zoom.Connection, query string) ([]adapter.Holding, error) { set, err := conn.Search(query) if err != nil { return nil, err @@ -59,7 +59,7 @@ func (a *ZoomAvailabilityAdapter) searchRetrieve(conn *zoom.Connection, query st if xmlBuffer == nil { continue } - holdings, err := a.holdingsParser.Parse(xmlBuffer) + holdings, err := a.holdingsParser.Parse(xmlBuffer, params) if err != nil { return nil, fmt.Errorf("failed to parse holdings from Z39.50 record: %w", err) } @@ -86,7 +86,7 @@ func (a *ZoomAvailabilityAdapter) Lookup(params adapter.LookupParams) ([]adapter return nil, "", fmt.Errorf("no valid query parameters provided") } for _, pqf := range pqfList { - avail, err := a.searchRetrieve(conn, pqf) + avail, err := a.searchRetrieve(params, conn, pqf) if err != nil { return nil, pqf, fmt.Errorf("failed to search Z39.50 server query: %s err %w", pqf, err) } diff --git a/broker/service/supplierlocator.go b/broker/service/supplierlocator.go index 8ce10661..53ab9c2c 100644 --- a/broker/service/supplierlocator.go +++ b/broker/service/supplierlocator.go @@ -68,6 +68,9 @@ func createHoldingsParams(illTransactionData ill_db.IllTransactionData) adapter. holdingsParams.Issn = id.BibliographicItemIdentifier } } + if illTransactionData.ServiceInfo != nil { + holdingsParams.ServiceType = string(illTransactionData.ServiceInfo.ServiceType) + } return holdingsParams } From 170dd75d6e664ad9a22ad19705270dd9d36adf32 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Mon, 11 May 2026 18:31:38 +0200 Subject: [PATCH 04/35] Fix up test --- broker/adapter/create_holdings.go | 2 +- broker/test/adapter/create_holdings_test.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/broker/adapter/create_holdings.go b/broker/adapter/create_holdings.go index 8b2246d7..f397f9b8 100644 --- a/broker/adapter/create_holdings.go +++ b/broker/adapter/create_holdings.go @@ -20,7 +20,7 @@ func getParser(format string) (HoldingsParser, error) { case "MARC-21plus-1": return &Marc21Plus1HoldingsParser{}, nil default: - return nil, fmt.Errorf("unsupported holdings format: %s", format) + return nil, fmt.Errorf("bad value for %s: %s", HoldingsFormat, format) } } diff --git a/broker/test/adapter/create_holdings_test.go b/broker/test/adapter/create_holdings_test.go index 816a1212..ec5e9e59 100644 --- a/broker/test/adapter/create_holdings_test.go +++ b/broker/test/adapter/create_holdings_test.go @@ -30,9 +30,18 @@ func TestCreateHoldings(t *testing.T) { assert.ErrorContains(t, err, "invalid value for HOLDINGS_ISXN_LOOKUP") m[adapter.HoldingsIsxnLookup] = true + m[adapter.HoldingsFormat] = "reservoir" _, err = adapter.CreateHoldingsLookupAdapter(m) assert.NoError(t, err) + m[adapter.HoldingsFormat] = "MARC-21plus-1" + _, err = adapter.CreateHoldingsLookupAdapter(m) + assert.NoError(t, err) + + m[adapter.HoldingsFormat] = "other" + _, err = adapter.CreateHoldingsLookupAdapter(m) + assert.ErrorContains(t, err, "bad value for HOLDINGS_FORMAT: other") + m["HOLDINGS_ADAPTER"] = "mock" _, err = adapter.CreateHoldingsLookupAdapter(m) assert.NoError(t, err) From 3c171869819f4c4d83be2f40ebb27ec1169ba3ad Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Mon, 11 May 2026 18:34:58 +0200 Subject: [PATCH 05/35] Check type --- broker/adapter/create_holdings.go | 6 +++++- broker/test/adapter/create_holdings_test.go | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/broker/adapter/create_holdings.go b/broker/adapter/create_holdings.go index f397f9b8..34cfa1c9 100644 --- a/broker/adapter/create_holdings.go +++ b/broker/adapter/create_holdings.go @@ -44,7 +44,11 @@ func CreateHoldingsLookupAdapter(cfg map[string]any) (LookupAdapter, error) { return nil, fmt.Errorf("invalid value for %s: %v", HoldingsIsxnLookup, isxnLookup) } queryBuilder := QueryBuilderIsxn{isxn: isxnLookup} - parser, err := getParser(cfg[HoldingsFormat].(string)) + format, ok := cfg[HoldingsFormat].(string) + if !ok { + return nil, fmt.Errorf("missing value for %s", HoldingsFormat) + } + parser, err := getParser(format) if err != nil { return nil, err } diff --git a/broker/test/adapter/create_holdings_test.go b/broker/test/adapter/create_holdings_test.go index ec5e9e59..afb996a9 100644 --- a/broker/test/adapter/create_holdings_test.go +++ b/broker/test/adapter/create_holdings_test.go @@ -42,6 +42,10 @@ func TestCreateHoldings(t *testing.T) { _, err = adapter.CreateHoldingsLookupAdapter(m) assert.ErrorContains(t, err, "bad value for HOLDINGS_FORMAT: other") + m[adapter.HoldingsFormat] = true + _, err = adapter.CreateHoldingsLookupAdapter(m) + assert.ErrorContains(t, err, "missing value for HOLDINGS_FORMAT") + m["HOLDINGS_ADAPTER"] = "mock" _, err = adapter.CreateHoldingsLookupAdapter(m) assert.NoError(t, err) From 7750ab5643a0ad11fedefa111625050757349d04 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 09:40:15 +0200 Subject: [PATCH 06/35] MARC-21plus-1 test --- broker/adapter/create_holdings.go | 14 +- broker/adapter/marc21_plus_holdings_parser.go | 2 +- .../marc21_plus_holdings_parser_test.go | 320 ++++++++++++++++++ 3 files changed, 329 insertions(+), 7 deletions(-) create mode 100644 broker/adapter/marc21_plus_holdings_parser_test.go diff --git a/broker/adapter/create_holdings.go b/broker/adapter/create_holdings.go index 34cfa1c9..81f97a20 100644 --- a/broker/adapter/create_holdings.go +++ b/broker/adapter/create_holdings.go @@ -7,17 +7,19 @@ import ( ) const ( - HoldingsAdapter string = "HOLDINGS_ADAPTER" - HoldingsSruURL string = "HOLDINGS_SRU_URL" - HoldingsIsxnLookup string = "HOLDINGS_ISXN_LOOKUP" - HoldingsFormat string = "HOLDINGS_FORMAT" + HoldingsAdapter string = "HOLDINGS_ADAPTER" + HoldingsSruURL string = "HOLDINGS_SRU_URL" + HoldingsIsxnLookup string = "HOLDINGS_ISXN_LOOKUP" + HoldingsFormat string = "HOLDINGS_FORMAT" + HoldingsFormatReservoir string = "reservoir" + HoldingsFormatMarc21Plus1 string = "MARC-21plus-1" ) func getParser(format string) (HoldingsParser, error) { switch format { - case "reservoir": + case HoldingsFormatReservoir: return &ReservoirHoldingsParser{}, nil - case "MARC-21plus-1": + case HoldingsFormatMarc21Plus1: return &Marc21Plus1HoldingsParser{}, nil default: return nil, fmt.Errorf("bad value for %s: %s", HoldingsFormat, format) diff --git a/broker/adapter/marc21_plus_holdings_parser.go b/broker/adapter/marc21_plus_holdings_parser.go index b7a4e37f..ecb8d4d3 100644 --- a/broker/adapter/marc21_plus_holdings_parser.go +++ b/broker/adapter/marc21_plus_holdings_parser.go @@ -23,7 +23,7 @@ import ( // $c (NR) Interlibrary loan region // $d (NR) Interlibrary loan indicator // "a" - Loan of volumes possible, no copies -// "b" - No loan of volumes, only paper copies aresent +// "b" - No loan of volumes, only paper copies are sent // "c" - Unrestricted interlibrary loan, copying and loan // "d" - No interlibrary loan // "e" - No loan of volumes, the end user receives an diff --git a/broker/adapter/marc21_plus_holdings_parser_test.go b/broker/adapter/marc21_plus_holdings_parser_test.go new file mode 100644 index 00000000..4c1c5468 --- /dev/null +++ b/broker/adapter/marc21_plus_holdings_parser_test.go @@ -0,0 +1,320 @@ +package adapter + +import ( + "testing" + + "github.com/indexdata/crosslink/iso18626" + "github.com/stretchr/testify/assert" +) + +func TestMarc21Plus1HoldingsParser_ParseError(t *testing.T) { + parser := NewMarc21Plus1HoldingsParser() + + marcXML := []byte(` + + + LocalID123 + ISIL123 + Region1 + http://example.com/holding1 + ProduktSigel123 + + `) + + params := LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeLoan), + } + _, err := parser.Parse(marcXML, params) + assert.Error(t, err) +} + +func TestMarc21Plus1HoldingsParser_Parse_da(t *testing.T) { + parser := NewMarc21Plus1HoldingsParser() + + marcXML := []byte(` + + + LocalID123 + ISIL123 + Region1 + a + http://example.com/holding1 + ProduktSigel123 + + `) + + params := LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeLoan), + } + holdings, err := parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + holding := holdings[0] + assert.Equal(t, "LocalID123", holding.LocalIdentifier) + assert.Equal(t, "ISIL123", holding.Symbol) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + + params = LookupParams{ + ServiceType: "", + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopy), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 0) +} + +func TestMarc21Plus1HoldingsParser_Parse_db(t *testing.T) { + parser := NewMarc21Plus1HoldingsParser() + + marcXML := []byte(` + + + LocalID123 + ISIL123 + Region1 + b + http://example.com/holding1 + ProduktSigel123 + + `) + + params := LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopy), + } + holdings, err := parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + holding := holdings[0] + assert.Equal(t, "LocalID123", holding.LocalIdentifier) + assert.Equal(t, "ISIL123", holding.Symbol) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + + params = LookupParams{ + ServiceType: "", + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 0) +} + +func TestMarc21Plus1HoldingsParser_Parse_dc(t *testing.T) { + parser := NewMarc21Plus1HoldingsParser() + + marcXML := []byte(` + + + LocalID123 + ISIL123 + Region1 + c + http://example.com/holding1 + ProduktSigel123 + + `) + + params := LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopy), + } + holdings, err := parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + holding := holdings[0] + assert.Equal(t, "LocalID123", holding.LocalIdentifier) + assert.Equal(t, "ISIL123", holding.Symbol) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + + params = LookupParams{ + ServiceType: "", + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) +} + +func TestMarc21Plus1HoldingsParser_Parse_dd(t *testing.T) { + parser := NewMarc21Plus1HoldingsParser() + + marcXML := []byte(` + + + LocalID123 + ISIL123 + Region1 + d + http://example.com/holding1 + ProduktSigel123 + + `) + + params := LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopy), + } + holdings, err := parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 0) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 0) + + params = LookupParams{ + ServiceType: "", + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 0) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 0) +} + +func TestMarc21Plus1HoldingsParser_Parse_de(t *testing.T) { + parser := NewMarc21Plus1HoldingsParser() + + marcXML := []byte(` + + + LocalID123 + ISIL123 + Region1 + e + http://example.com/holding1 + ProduktSigel123 + + `) + + params := LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopy), + } + holdings, err := parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + holding := holdings[0] + assert.Equal(t, "LocalID123", holding.LocalIdentifier) + assert.Equal(t, "ISIL123", holding.Symbol) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + + params = LookupParams{ + ServiceType: "", + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 0) +} + +func TestMarc21Plus1HoldingsParser_Parse_multiple(t *testing.T) { + parser := NewMarc21Plus1HoldingsParser() + + marcXML := []byte(` + + + LocalID123 + ISIL123 + Region1 + e + http://example.com/holding1 + ProduktSigel123 + + + LocalID124 + ISIL124 + Region1 + e + http://example.com/holding2 + ProduktSigel124 + + `) + + params := LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopy), + } + holdings, err := parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 2) + holding := holdings[0] + assert.Equal(t, "LocalID123", holding.LocalIdentifier) + assert.Equal(t, "ISIL123", holding.Symbol) + holding = holdings[1] + assert.Equal(t, "LocalID124", holding.LocalIdentifier) + assert.Equal(t, "ISIL124", holding.Symbol) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 2) + + params = LookupParams{ + ServiceType: "", + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 2) + + params = LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeLoan), + } + holdings, err = parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 0) +} From f28898361b9e923d6e7666572e6b3c8658f359d4 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 11:47:26 +0200 Subject: [PATCH 07/35] No ctx for NewMetaproxyAvailabilityAdapter, NewZoomAvailabilityAdapter --- broker/availability/availability_creator_impl.go | 4 ++-- broker/availability/availability_metaproxy.go | 3 +-- broker/availability/availability_zoom.go | 3 +-- broker/availability/availability_zoom_nocgo.go | 3 +-- broker/availability/availability_zoom_test.go | 13 ++++--------- 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/broker/availability/availability_creator_impl.go b/broker/availability/availability_creator_impl.go index 4735decb..90f5d5a9 100644 --- a/broker/availability/availability_creator_impl.go +++ b/broker/availability/availability_creator_impl.go @@ -64,9 +64,9 @@ func (c *AvailabilityCreatorImpl) GetAdapter(ctx common.ExtendedContext, peer il if c.metaproxyUrl == "" { return nil, fmt.Errorf("when using %s availability adapter, %s environment variable must be set", AvailabilityAdapterMetaproxy, "METAPROXY_URL") } - return NewMetaproxyAvailabilityAdapter(ctx, *config.Z3950, c.metaproxyUrl, queryBuilder, holdingsParser) + return NewMetaproxyAvailabilityAdapter(*config.Z3950, c.metaproxyUrl, queryBuilder, holdingsParser) case AvailabilityAdapterZoom: - return NewZoomAvailabilityAdapter(ctx, *config.Z3950, queryBuilder, holdingsParser) + return NewZoomAvailabilityAdapter(*config.Z3950, queryBuilder, holdingsParser) default: return nil, fmt.Errorf("unsupported availability adapter type: %s", c.mode) } diff --git a/broker/availability/availability_metaproxy.go b/broker/availability/availability_metaproxy.go index e6506334..e261bea5 100644 --- a/broker/availability/availability_metaproxy.go +++ b/broker/availability/availability_metaproxy.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/directory" ) @@ -12,7 +11,7 @@ type MetaproxyAvailabilityAdapter struct { holdingsLookupAdapter adapter.LookupAdapter } -func NewMetaproxyAvailabilityAdapter(ctx common.ExtendedContext, config directory.Z3950Config, metaproxyUrl string, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { +func NewMetaproxyAvailabilityAdapter(config directory.Z3950Config, metaproxyUrl string, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { a := &MetaproxyAvailabilityAdapter{ holdingsLookupAdapter: adapter.CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{metaproxyUrl}, config.Address, queryBuilder, holdingsParser, "marcxml"), } diff --git a/broker/availability/availability_zoom.go b/broker/availability/availability_zoom.go index 8727ec6e..358e596a 100644 --- a/broker/availability/availability_zoom.go +++ b/broker/availability/availability_zoom.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/directory" "github.com/indexdata/crosslink/zoom" ) @@ -20,7 +19,7 @@ type ZoomAvailabilityAdapter struct { queryBuilder adapter.LookupQueryBuilder } -func NewZoomAvailabilityAdapter(ctx common.ExtendedContext, config directory.Z3950Config, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { +func NewZoomAvailabilityAdapter(config directory.Z3950Config, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { a := &ZoomAvailabilityAdapter{ // default options, can be overridden by config.Options options: zoom.Options{ diff --git a/broker/availability/availability_zoom_nocgo.go b/broker/availability/availability_zoom_nocgo.go index ae9855db..591c31b0 100644 --- a/broker/availability/availability_zoom_nocgo.go +++ b/broker/availability/availability_zoom_nocgo.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/directory" ) @@ -14,6 +13,6 @@ func cgoEnabled() bool { return false } type ZoomAvailabilityAdapter struct{} -func NewZoomAvailabilityAdapter(ctx common.ExtendedContext, config directory.Z3950Config, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { +func NewZoomAvailabilityAdapter(config directory.Z3950Config, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { return nil, fmt.Errorf("ZOOM availability adapter requires cgo, but cgo is not enabled") } diff --git a/broker/availability/availability_zoom_test.go b/broker/availability/availability_zoom_test.go index f6f44fcc..c9d42b71 100644 --- a/broker/availability/availability_zoom_test.go +++ b/broker/availability/availability_zoom_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/directory" zoomtestutil "github.com/indexdata/crosslink/testutil" "github.com/stretchr/testify/assert" @@ -36,7 +35,6 @@ func TestMain(m *testing.M) { } func TestLookupFoundMarc(t *testing.T) { - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) // target does not return holdings, we just use 010$a as fake location to verify that the record was parsed correctly config := directory.MarcParserConfig{ MainField: adapter.NewString("010"), @@ -46,7 +44,7 @@ func TestLookupFoundMarc(t *testing.T) { Title: adapter.NewString("@attr 1=1016 {term}"), }) holdingsParser := adapter.NewMarcHoldingsParser(config) - aa, err := NewZoomAvailabilityAdapter(ctx, + aa, err := NewZoomAvailabilityAdapter( directory.Z3950Config{ Address: containerHost + ":" + mappedPort + "/marc", Options: &map[string]string{ @@ -72,10 +70,9 @@ func TestLookupFoundMarc(t *testing.T) { } func TestLookupFoundOpac(t *testing.T) { - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) queryBuilder := adapter.NewQueryBuilderPqf(nil) holdingsParser := adapter.NewOpacHoldingsParser(directory.OpacParserConfig{}) - aa, err := NewZoomAvailabilityAdapter(ctx, + aa, err := NewZoomAvailabilityAdapter( directory.Z3950Config{ Address: containerHost + ":" + mappedPort + "/marc", Options: &map[string]string{ @@ -101,10 +98,9 @@ func TestLookupFoundOpac(t *testing.T) { } func TestLookupDiagnostics(t *testing.T) { - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) queryBuilder := adapter.NewQueryBuilderPqf(nil) holdingsParser := adapter.NewMarcHoldingsParser(directory.MarcParserConfig{}) - aa, err := NewZoomAvailabilityAdapter(ctx, + aa, err := NewZoomAvailabilityAdapter( directory.Z3950Config{ Address: containerHost + ":" + mappedPort + "/marc", Options: &map[string]string{ @@ -126,10 +122,9 @@ func TestLookupDiagnostics(t *testing.T) { } func TestConnectFailure(t *testing.T) { - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) queryBuilder := adapter.NewQueryBuilderPqf(nil) holdingsParser := adapter.NewMarcHoldingsParser(directory.MarcParserConfig{}) - aa, err := NewZoomAvailabilityAdapter(ctx, + aa, err := NewZoomAvailabilityAdapter( directory.Z3950Config{ Address: "", }, From 2fa441587402c1d9cec340f51b215a36fd21d2d3 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 12:47:08 +0200 Subject: [PATCH 08/35] begin consortia stuff --- broker/README.md | 2 +- broker/adapter/create_holdings.go | 4 ++++ broker/adapter/reservoir_holdings_parser.go | 4 ++++ .../availability/availability_creator_impl.go | 16 +++++++++++----- .../availability/availability_creator_test.go | 18 +++++++++--------- broker/availability/availability_metaproxy.go | 2 +- broker/availability/availability_mock.go | 4 ++-- broker/availability/availability_zoom.go | 2 +- broker/availability/availability_zoom_test.go | 8 ++++---- broker/service/supplierlocator.go | 15 +++++++++++++-- broker/test/service/supplierlocator_test.go | 6 +++--- directory/directory_api.yaml | 16 +++++++++++++--- 12 files changed, 66 insertions(+), 31 deletions(-) diff --git a/broker/README.md b/broker/README.md index e1c6f549..116749bc 100644 --- a/broker/README.md +++ b/broker/README.md @@ -101,7 +101,7 @@ Configuration is provided via environment variables: | `CLIENT_DELAY` | Delay duration for outgoing ISO18626 messages | `0ms` | | `SHUTDOWN_DELAY` | Delay duration for graceful shutdown (in-flight connections) | `15s` | | `MAX_MESSAGE_SIZE` | Max accepted ISO18626 message size | `100KB` | -| `HOLDINGS_ADAPTER` | Holdings lookup method: `mock` or `sru` | `mock` | +| `HOLDINGS_ADAPTER` | Holdings lookup method: `mock`, `sru` or `consortia` | `mock` | | `HOLDINGS_SRU_URL` | Comma separated list of URLs when `HOLDINGS_ADAPTER` is `sru` | `http://localhost:8081/sru` | | `HOLDINGS_ISXN_LOOKUP` | Whether to use ISBN/ISSN lookup for `sru` method | `false` | | `HOLDINGS_FORMAT` | Parser for SRU holdings: `reservoir` or `MARC-21plus-1` | `reservoir` | diff --git a/broker/adapter/create_holdings.go b/broker/adapter/create_holdings.go index 81f97a20..d2375093 100644 --- a/broker/adapter/create_holdings.go +++ b/broker/adapter/create_holdings.go @@ -31,6 +31,10 @@ func CreateHoldingsLookupAdapter(cfg map[string]any) (LookupAdapter, error) { if !ok { return nil, fmt.Errorf("missing value for %s", HoldingsAdapter) } + if holdingsAdapterVal == "consortia" { + // consortia must be determined per-peer, so we can't create a single adapter for all peers + return nil, nil + } if holdingsAdapterVal == "sru" { sruURLVal, ok := cfg[HoldingsSruURL].(string) if !ok { diff --git a/broker/adapter/reservoir_holdings_parser.go b/broker/adapter/reservoir_holdings_parser.go index 73156b5f..1c62b7c4 100644 --- a/broker/adapter/reservoir_holdings_parser.go +++ b/broker/adapter/reservoir_holdings_parser.go @@ -10,6 +10,10 @@ import ( type ReservoirHoldingsParser struct{} +func NewReservoirHoldingsParser() HoldingsParser { + return &ReservoirHoldingsParser{} +} + func parseHoldingsForIndicator(rec *marcxml.Record, ind2 string) []Holding { var holdings []Holding for _, df := range rec.Datafield { diff --git a/broker/availability/availability_creator_impl.go b/broker/availability/availability_creator_impl.go index 90f5d5a9..4484b40c 100644 --- a/broker/availability/availability_creator_impl.go +++ b/broker/availability/availability_creator_impl.go @@ -37,7 +37,13 @@ func getParser(config *directory.ParserConfig) (adapter.HoldingsParser, error) { if config.Opac != nil { return adapter.NewOpacHoldingsParser(*config.Opac), nil } - return nil, fmt.Errorf("availabilityConfig.parserConfig must set marc or opac properties") + if config.Reservoir != nil { + return adapter.NewReservoirHoldingsParser(), nil + } + if config.Marc21plus1 != nil { + return adapter.NewMarc21Plus1HoldingsParser(), nil + } + return nil, fmt.Errorf("availabilityConfig.parserConfig must set marc, opac, reservoir, or marc21plus1 properties") } func (c *AvailabilityCreatorImpl) GetAdapter(ctx common.ExtendedContext, peer ill_db.Peer) (adapter.LookupAdapter, error) { @@ -57,19 +63,19 @@ func (c *AvailabilityCreatorImpl) GetAdapter(ctx common.ExtendedContext, peer il queryBuilder := adapter.NewQueryBuilderCql(config.QueryConfig) return NewSruAvailabilityAdapter(ctx, *config.Sru, queryBuilder, holdingsParser) } - if config.Z3950 != nil { + if config.Zoom != nil { queryBuilder := adapter.NewQueryBuilderPqf(config.QueryConfig) switch c.mode { case AvailabilityAdapterMetaproxy: if c.metaproxyUrl == "" { return nil, fmt.Errorf("when using %s availability adapter, %s environment variable must be set", AvailabilityAdapterMetaproxy, "METAPROXY_URL") } - return NewMetaproxyAvailabilityAdapter(*config.Z3950, c.metaproxyUrl, queryBuilder, holdingsParser) + return NewMetaproxyAvailabilityAdapter(*config.Zoom, c.metaproxyUrl, queryBuilder, holdingsParser) case AvailabilityAdapterZoom: - return NewZoomAvailabilityAdapter(*config.Z3950, queryBuilder, holdingsParser) + return NewZoomAvailabilityAdapter(*config.Zoom, queryBuilder, holdingsParser) default: return nil, fmt.Errorf("unsupported availability adapter type: %s", c.mode) } } - return nil, fmt.Errorf("must specify either sru or z3950 properties for availability adapter type") + return nil, fmt.Errorf("must specify either sru or zoom properties for availability adapter type") } diff --git a/broker/availability/availability_creator_test.go b/broker/availability/availability_creator_test.go index 3916a303..f146cfe1 100644 --- a/broker/availability/availability_creator_test.go +++ b/broker/availability/availability_creator_test.go @@ -39,7 +39,7 @@ func TestParserMissing(t *testing.T) { parserConfig := &directory.ParserConfig{} _, err := getParser(parserConfig) assert.Error(t, err) - assert.Contains(t, err.Error(), "must set marc or opac properties") + assert.Contains(t, err.Error(), "must set marc") } func TestParserMarc(t *testing.T) { @@ -66,7 +66,7 @@ func TestGetAdapterBadParser(t *testing.T) { peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ - Z3950: &directory.Z3950Config{ + Zoom: &directory.ZoomConfig{ Address: "a", }, ParserConfig: &directory.ParserConfig{}, @@ -75,7 +75,7 @@ func TestGetAdapterBadParser(t *testing.T) { } _, err := creator.GetAdapter(ctx, peer) assert.Error(t, err) - assert.Contains(t, err.Error(), "must set marc or opac properties") + assert.Contains(t, err.Error(), "must set marc") } func TestGetAdapterOtherWithConfig(t *testing.T) { @@ -84,7 +84,7 @@ func TestGetAdapterOtherWithConfig(t *testing.T) { peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ - Z3950: &directory.Z3950Config{ + Zoom: &directory.ZoomConfig{ Address: "a", }, }, @@ -105,14 +105,14 @@ func TestGetAdapterMissingProperties(t *testing.T) { } _, err := creator.GetAdapter(ctx, peer) assert.Error(t, err) - assert.Contains(t, err.Error(), "must specify either sru or z3950 properties") + assert.Contains(t, err.Error(), "must specify either sru or zoom properties") } func TestGetAdapterMock(t *testing.T) { peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ - Z3950: &directory.Z3950Config{ + Zoom: &directory.ZoomConfig{ Address: "a", }, }, @@ -129,7 +129,7 @@ func TestGetAdapterZoom(t *testing.T) { peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ - Z3950: &directory.Z3950Config{ + Zoom: &directory.ZoomConfig{ Address: "a", }, }, @@ -152,7 +152,7 @@ func TestGetAdapterMetaproxy(t *testing.T) { peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ - Z3950: &directory.Z3950Config{ + Zoom: &directory.ZoomConfig{ Address: "a", }, }, @@ -169,7 +169,7 @@ func TestGetAdapterMetaproxyMissingProxy(t *testing.T) { peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ - Z3950: &directory.Z3950Config{ + Zoom: &directory.ZoomConfig{ Address: "a", }, }, diff --git a/broker/availability/availability_metaproxy.go b/broker/availability/availability_metaproxy.go index e261bea5..a9e4c2f5 100644 --- a/broker/availability/availability_metaproxy.go +++ b/broker/availability/availability_metaproxy.go @@ -11,7 +11,7 @@ type MetaproxyAvailabilityAdapter struct { holdingsLookupAdapter adapter.LookupAdapter } -func NewMetaproxyAvailabilityAdapter(config directory.Z3950Config, metaproxyUrl string, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { +func NewMetaproxyAvailabilityAdapter(config directory.ZoomConfig, metaproxyUrl string, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { a := &MetaproxyAvailabilityAdapter{ holdingsLookupAdapter: adapter.CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{metaproxyUrl}, config.Address, queryBuilder, holdingsParser, "marcxml"), } diff --git a/broker/availability/availability_mock.go b/broker/availability/availability_mock.go index ebf4e331..9546c0eb 100644 --- a/broker/availability/availability_mock.go +++ b/broker/availability/availability_mock.go @@ -14,8 +14,8 @@ type MockAvailabilityAdapter struct { } func NewMockAvailabilityAdapter(config directory.AvailabilityConfig) (adapter.LookupAdapter, error) { - if config.Z3950 != nil && config.Z3950.Options != nil { - options := *config.Z3950.Options + if config.Zoom != nil && config.Zoom.Options != nil { + options := *config.Zoom.Options // For testing purposes, we can use the presence of "adapter-error" in options to trigger an error response if val, ok := options["adapter-error"]; ok && strings.ToLower(val) == "true" { return nil, fmt.Errorf("mock error triggered by config") diff --git a/broker/availability/availability_zoom.go b/broker/availability/availability_zoom.go index 358e596a..853e3967 100644 --- a/broker/availability/availability_zoom.go +++ b/broker/availability/availability_zoom.go @@ -19,7 +19,7 @@ type ZoomAvailabilityAdapter struct { queryBuilder adapter.LookupQueryBuilder } -func NewZoomAvailabilityAdapter(config directory.Z3950Config, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { +func NewZoomAvailabilityAdapter(config directory.ZoomConfig, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { a := &ZoomAvailabilityAdapter{ // default options, can be overridden by config.Options options: zoom.Options{ diff --git a/broker/availability/availability_zoom_test.go b/broker/availability/availability_zoom_test.go index c9d42b71..3d0d1df2 100644 --- a/broker/availability/availability_zoom_test.go +++ b/broker/availability/availability_zoom_test.go @@ -45,7 +45,7 @@ func TestLookupFoundMarc(t *testing.T) { }) holdingsParser := adapter.NewMarcHoldingsParser(config) aa, err := NewZoomAvailabilityAdapter( - directory.Z3950Config{ + directory.ZoomConfig{ Address: containerHost + ":" + mappedPort + "/marc", Options: &map[string]string{ "count": "20", @@ -73,7 +73,7 @@ func TestLookupFoundOpac(t *testing.T) { queryBuilder := adapter.NewQueryBuilderPqf(nil) holdingsParser := adapter.NewOpacHoldingsParser(directory.OpacParserConfig{}) aa, err := NewZoomAvailabilityAdapter( - directory.Z3950Config{ + directory.ZoomConfig{ Address: containerHost + ":" + mappedPort + "/marc", Options: &map[string]string{ "preferredRecordSyntax": "opac", @@ -101,7 +101,7 @@ func TestLookupDiagnostics(t *testing.T) { queryBuilder := adapter.NewQueryBuilderPqf(nil) holdingsParser := adapter.NewMarcHoldingsParser(directory.MarcParserConfig{}) aa, err := NewZoomAvailabilityAdapter( - directory.Z3950Config{ + directory.ZoomConfig{ Address: containerHost + ":" + mappedPort + "/marc", Options: &map[string]string{ "preferredRecordSyntax": "danmarc", @@ -125,7 +125,7 @@ func TestConnectFailure(t *testing.T) { queryBuilder := adapter.NewQueryBuilderPqf(nil) holdingsParser := adapter.NewMarcHoldingsParser(directory.MarcParserConfig{}) aa, err := NewZoomAvailabilityAdapter( - directory.Z3950Config{ + directory.ZoomConfig{ Address: "", }, queryBuilder, diff --git a/broker/service/supplierlocator.go b/broker/service/supplierlocator.go index 53ab9c2c..17a0d6cf 100644 --- a/broker/service/supplierlocator.go +++ b/broker/service/supplierlocator.go @@ -89,8 +89,19 @@ func (s *SupplierLocator) locateSuppliers(ctx common.ExtendedContext, event even if err != nil { return events.LogErrorAndReturnResult(ctx, "failed to read requester peer", err) } - - holdings, query, err := s.holdingsAdapter.Lookup(holdingsParams) + lookupAdapter := s.holdingsAdapter + if lookupAdapter == nil { // null if consortia adapter is configured, in which case we need to determine adapter by directory + // TODO: use CONSORTIA_SYMBOL or determine parent from peer (if not already consirtia symbol) instead of requester symbol + lookupAdapter, err = s.availabilityCreator.GetAdapter(ctx, requester) + if err != nil { + return events.LogErrorAndReturnResult(ctx, "could not create availability adapter for requester", err) + } + if lookupAdapter == nil { + err := fmt.Errorf("GetAdapter for requester returned nil") + return events.LogErrorAndReturnResult(ctx, "no availability adapter for requester", err) + } + } + holdings, query, err := lookupAdapter.Lookup(holdingsParams) if err != nil { return events.LogErrorAndReturnResult(ctx, fmt.Sprintf("failed to locate holdings for query '%s'", query), err) } diff --git a/broker/test/service/supplierlocator_test.go b/broker/test/service/supplierlocator_test.go index 223adc77..26227de5 100644 --- a/broker/test/service/supplierlocator_test.go +++ b/broker/test/service/supplierlocator_test.go @@ -944,7 +944,7 @@ func TestCheckAvailability_Z3950AdapterSkipped(t *testing.T) { func TestCheckAvailability_Z3950AdapterNotSkipped(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) customData := directory.Entry{AvailabilityConfig: &directory.AvailabilityConfig{ - Z3950: &directory.Z3950Config{ + Zoom: &directory.ZoomConfig{ Address: "a", Options: &map[string]string{ "location": "1234", // ensures that availability lookup returns a result and supplier is not skipped @@ -997,7 +997,7 @@ func TestCheckAvailability_Z3950AdapterError(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) customData := directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ - Z3950: &directory.Z3950Config{ + Zoom: &directory.ZoomConfig{ Address: "a", Options: &map[string]string{ "adapter-error": "true", @@ -1046,7 +1046,7 @@ func TestCheckAvailability_Z3950LookupError(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) customData := directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ - Z3950: &directory.Z3950Config{ + Zoom: &directory.ZoomConfig{ Address: "a", Options: &map[string]string{ "lookup-error": "true", diff --git a/directory/directory_api.yaml b/directory/directory_api.yaml index d7b942e1..61c578cb 100644 --- a/directory/directory_api.yaml +++ b/directory/directory_api.yaml @@ -261,8 +261,8 @@ components: properties: sru: $ref: '#/components/schemas/SruConfig' - z3950: - $ref: '#/components/schemas/Z3950Config' + zoom: + $ref: '#/components/schemas/ZoomConfig' queryConfig: $ref: '#/components/schemas/QueryConfig' parserConfig: @@ -282,7 +282,7 @@ components: description: Record schema to use for parsing holdings records (for example, "marcxml") additionalProperties: false - Z3950Config: + ZoomConfig: type: object required: - address @@ -322,6 +322,16 @@ components: $ref: '#/components/schemas/MarcParserConfig' opac: $ref: '#/components/schemas/OpacParserConfig' + reservoir: + type: object + description: Configuration for Reservoir parser. Currently must be empty; no properties allowed. + properties: {} + additionalProperties: false + marc21plus1: + type: object + description: Configuration for MARC-21plus-1 parser. Currently must be empty; no properties allowed. + properties: {} + additionalProperties: false additionalProperties: false MarcParserConfig: From 9cf0b2fabaf09c0e460afb4c944ffbe3e63c5c89 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 14:24:22 +0200 Subject: [PATCH 09/35] GVI holdings back --- broker/adapter/marc21_plus_holdings_parser.go | 9 ++- .../marc21_plus_holdings_parser_test.go | 26 +++++++ broker/adapter/query_builder_pqf.go | 71 +++++++++---------- broker/availability/availability_creator.go | 3 +- .../availability/availability_creator_impl.go | 11 +-- .../availability/availability_creator_test.go | 32 +++------ broker/availability/availability_sru.go | 3 +- broker/availability/availability_zoom.go | 32 +++++++-- broker/availability/availability_zoom_test.go | 12 ++-- broker/service/supplierlocator.go | 4 +- directory/directory_api.yaml | 6 ++ zoom/zoom.go | 54 ++++++++++++-- zoom/zoom_test.go | 48 +++++++++++-- 13 files changed, 220 insertions(+), 91 deletions(-) diff --git a/broker/adapter/marc21_plus_holdings_parser.go b/broker/adapter/marc21_plus_holdings_parser.go index ecb8d4d3..f04c6e98 100644 --- a/broker/adapter/marc21_plus_holdings_parser.go +++ b/broker/adapter/marc21_plus_holdings_parser.go @@ -2,7 +2,6 @@ package adapter import ( "encoding/xml" - "fmt" "strings" "github.com/indexdata/crosslink/iso18626" @@ -47,7 +46,13 @@ func (p *Marc21Plus1HoldingsParser) Parse(record []byte, params LookupParams) ([ var marcRecord marcxml.Record err := xml.Unmarshal(record, &marcRecord) if err != nil { - return nil, fmt.Errorf("failed to unmarshal MARC XML: %w", err) + // GVI marc does not have the MARC21 slim namespace, so we try again without it + var noNamespaceRecord marcxml.RecordType + err := xml.Unmarshal(record, &noNamespaceRecord) + if err != nil { + return nil, err + } + marcRecord.RecordType = noNamespaceRecord } loanOk := params.ServiceType == string(iso18626.TypeServiceTypeLoan) || params.ServiceType == string(iso18626.TypeServiceTypeCopyOrLoan) || params.ServiceType == "" diff --git a/broker/adapter/marc21_plus_holdings_parser_test.go b/broker/adapter/marc21_plus_holdings_parser_test.go index 4c1c5468..e66c6542 100644 --- a/broker/adapter/marc21_plus_holdings_parser_test.go +++ b/broker/adapter/marc21_plus_holdings_parser_test.go @@ -29,6 +29,32 @@ func TestMarc21Plus1HoldingsParser_ParseError(t *testing.T) { assert.Error(t, err) } +func TestMarc21Plus1HoldingsParser_no_namespace(t *testing.T) { + parser := NewMarc21Plus1HoldingsParser() + + marcXML := []byte(` + + + LocalID123 + ISIL123 + Region1 + a + http://example.com/holding1 + ProduktSigel123 + + `) + + params := LookupParams{ + ServiceType: string(iso18626.TypeServiceTypeLoan), + } + holdings, err := parser.Parse(marcXML, params) + assert.NoError(t, err) + assert.Len(t, holdings, 1) + holding := holdings[0] + assert.Equal(t, "LocalID123", holding.LocalIdentifier) + assert.Equal(t, "ISIL123", holding.Symbol) +} + func TestMarc21Plus1HoldingsParser_Parse_da(t *testing.T) { parser := NewMarc21Plus1HoldingsParser() diff --git a/broker/adapter/query_builder_pqf.go b/broker/adapter/query_builder_pqf.go index 5bb97538..8f080436 100644 --- a/broker/adapter/query_builder_pqf.go +++ b/broker/adapter/query_builder_pqf.go @@ -2,14 +2,14 @@ package adapter import ( "errors" + "fmt" "strings" "github.com/indexdata/crosslink/directory" ) type QueryBuilderPqf struct { - mappings directory.QueryConfig - defaultMap directory.QueryConfig + config directory.QueryConfig } func NewString(s string) *string { @@ -19,28 +19,32 @@ func NewString(s string) *string { return nil } -func NewQueryBuilderPqf(queryConfig *directory.QueryConfig) *QueryBuilderPqf { - if queryConfig == nil { - queryConfig = &directory.QueryConfig{} +func NewQueryBuilder(queryConfig *directory.QueryConfig) (*QueryBuilderPqf, error) { + var config directory.QueryConfig + if queryConfig != nil { + config = *queryConfig } - return &QueryBuilderPqf{mappings: *queryConfig, defaultMap: directory.QueryConfig{ - Identifier: NewString("@attr 1=12 {term}"), - Isbn: NewString("@attr 1=7 {term}"), - Issn: NewString("@attr 1=8 {term}"), - Title: NewString("@attr 1=4 {term}"), - }} -} - -func NewQueryBuilderCql(queryConfig *directory.QueryConfig) *QueryBuilderPqf { - if queryConfig == nil { - queryConfig = &directory.QueryConfig{} + if config.Type == nil || *config.Type == directory.Pqf { + if config.Identifier == nil && config.Isbn == nil && config.Issn == nil && config.Title == nil { + // if no specific mappings are provided, we set default PQF mappings + config.Identifier = NewString("@attr 1=12 {term}") + config.Isbn = NewString("@attr 1=7 {term}") + config.Issn = NewString("@attr 1=8 {term}") + config.Title = NewString("@attr 1=4 {term}") + } + return &QueryBuilderPqf{config: config}, nil + } + if *config.Type == directory.Cql { + if config.Identifier == nil && config.Isbn == nil && config.Issn == nil && config.Title == nil { + // if no specific mappings are provided, we set default CQL mappings + config.Identifier = NewString("rec.id = {term}") + config.Isbn = NewString("isbn = {term}") + config.Issn = NewString("issn = {term}") + config.Title = NewString("title = {term}") + } + return &QueryBuilderPqf{config: config}, nil } - return &QueryBuilderPqf{mappings: *queryConfig, defaultMap: directory.QueryConfig{ - Identifier: NewString("rec.id = {term}"), - Isbn: NewString("isbn = {term}"), - Issn: NewString("issn = {term}"), - Title: NewString("title = {term}"), - }} + return nil, fmt.Errorf("unsupported query builder type: %s", *config.Type) } func pqfEncode(value string) string { @@ -73,29 +77,24 @@ func (s *QueryBuilderPqf) Build(params LookupParams) (cql []string, pqf []string type paramMapping struct { value string mapping *string - dir string } paramMappings := []paramMapping{ - {params.Identifier, s.mappings.Identifier, *s.defaultMap.Identifier}, - {params.Isbn, s.mappings.Isbn, *s.defaultMap.Isbn}, - {params.Issn, s.mappings.Issn, *s.defaultMap.Issn}, - {params.Title, s.mappings.Title, *s.defaultMap.Title}, + {params.Identifier, s.config.Identifier}, + {params.Isbn, s.config.Isbn}, + {params.Issn, s.config.Issn}, + {params.Title, s.config.Title}, } var pqfList []string var cqlList []string for _, pm := range paramMappings { if pm.value != "" { - mapping := pm.dir - if pm.mapping != nil { - mapping = *pm.mapping - } - if strings.HasPrefix(mapping, "@") { - pqf := strings.ReplaceAll(mapping, "{term}", pqfEncode(pm.value)) - pqfList = append(pqfList, pqf) - } else { - cql := strings.ReplaceAll(mapping, "{term}", cqlEncode(pm.value)) + if s.config.Type != nil && *s.config.Type == directory.Cql { + cql := strings.ReplaceAll(*pm.mapping, "{term}", cqlEncode(pm.value)) cqlList = append(cqlList, cql) + } else { + pqf := strings.ReplaceAll(*pm.mapping, "{term}", pqfEncode(pm.value)) + pqfList = append(pqfList, pqf) } } } diff --git a/broker/availability/availability_creator.go b/broker/availability/availability_creator.go index e04bcf71..a557f478 100644 --- a/broker/availability/availability_creator.go +++ b/broker/availability/availability_creator.go @@ -2,10 +2,9 @@ package availability import ( "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/broker/ill_db" ) type AvailabilityCreator interface { - GetAdapter(ctx common.ExtendedContext, peer ill_db.Peer) (adapter.LookupAdapter, error) + GetAdapter(peer ill_db.Peer) (adapter.LookupAdapter, error) } diff --git a/broker/availability/availability_creator_impl.go b/broker/availability/availability_creator_impl.go index 4484b40c..56f8eaa9 100644 --- a/broker/availability/availability_creator_impl.go +++ b/broker/availability/availability_creator_impl.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/broker/ill_db" "github.com/indexdata/crosslink/directory" ) @@ -46,7 +45,7 @@ func getParser(config *directory.ParserConfig) (adapter.HoldingsParser, error) { return nil, fmt.Errorf("availabilityConfig.parserConfig must set marc, opac, reservoir, or marc21plus1 properties") } -func (c *AvailabilityCreatorImpl) GetAdapter(ctx common.ExtendedContext, peer ill_db.Peer) (adapter.LookupAdapter, error) { +func (c *AvailabilityCreatorImpl) GetAdapter(peer ill_db.Peer) (adapter.LookupAdapter, error) { entry := peer.CustomData config := entry.AvailabilityConfig if config == nil { @@ -59,12 +58,14 @@ func (c *AvailabilityCreatorImpl) GetAdapter(ctx common.ExtendedContext, peer il if err != nil { return nil, err } + queryBuilder, err := adapter.NewQueryBuilder(config.QueryConfig) + if err != nil { + return nil, err + } if config.Sru != nil { - queryBuilder := adapter.NewQueryBuilderCql(config.QueryConfig) - return NewSruAvailabilityAdapter(ctx, *config.Sru, queryBuilder, holdingsParser) + return NewSruAvailabilityAdapter(*config.Sru, queryBuilder, holdingsParser) } if config.Zoom != nil { - queryBuilder := adapter.NewQueryBuilderPqf(config.QueryConfig) switch c.mode { case AvailabilityAdapterMetaproxy: if c.metaproxyUrl == "" { diff --git a/broker/availability/availability_creator_test.go b/broker/availability/availability_creator_test.go index f146cfe1..d3e95dc2 100644 --- a/broker/availability/availability_creator_test.go +++ b/broker/availability/availability_creator_test.go @@ -1,11 +1,9 @@ package availability import ( - "context" "testing" "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/broker/ill_db" "github.com/indexdata/crosslink/directory" "github.com/stretchr/testify/assert" @@ -13,18 +11,16 @@ import ( func TestGetAdapterEmpty(t *testing.T) { creator := NewAvailabilityCreator(AvailabilityAdapterZoom, "") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) peer := ill_db.Peer{} - aa, err := creator.GetAdapter(ctx, peer) + aa, err := creator.GetAdapter(peer) assert.NoError(t, err) assert.Nil(t, aa) } func TestGetAdapterOtherNoConfig(t *testing.T) { creator := NewAvailabilityCreator("other", "") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) peer := ill_db.Peer{} - aa, err := creator.GetAdapter(ctx, peer) + aa, err := creator.GetAdapter(peer) assert.NoError(t, err) assert.Nil(t, aa) } @@ -62,7 +58,6 @@ func TestParserOpac(t *testing.T) { func TestGetAdapterBadParser(t *testing.T) { creator := NewAvailabilityCreator(AvailabilityAdapterZoom, "") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ @@ -73,14 +68,13 @@ func TestGetAdapterBadParser(t *testing.T) { }, }, } - _, err := creator.GetAdapter(ctx, peer) + _, err := creator.GetAdapter(peer) assert.Error(t, err) assert.Contains(t, err.Error(), "must set marc") } func TestGetAdapterOtherWithConfig(t *testing.T) { creator := NewAvailabilityCreator("other", "") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ @@ -90,20 +84,19 @@ func TestGetAdapterOtherWithConfig(t *testing.T) { }, }, } - _, err := creator.GetAdapter(ctx, peer) + _, err := creator.GetAdapter(peer) assert.Error(t, err) assert.Contains(t, err.Error(), "unsupported availability adapter type: other") } func TestGetAdapterMissingProperties(t *testing.T) { creator := NewAvailabilityCreator("zoom", "") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{}, }, } - _, err := creator.GetAdapter(ctx, peer) + _, err := creator.GetAdapter(peer) assert.Error(t, err) assert.Contains(t, err.Error(), "must specify either sru or zoom properties") } @@ -119,8 +112,7 @@ func TestGetAdapterMock(t *testing.T) { }, } creator := NewAvailabilityCreator(AvailabilityAdapterMock, "") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) - aa, err := creator.GetAdapter(ctx, peer) + aa, err := creator.GetAdapter(peer) assert.NoError(t, err) assert.IsType(t, &MockAvailabilityAdapter{}, aa) } @@ -136,8 +128,7 @@ func TestGetAdapterZoom(t *testing.T) { }, } creator := NewAvailabilityCreator(AvailabilityAdapterZoom, "") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) - aa, err := creator.GetAdapter(ctx, peer) + aa, err := creator.GetAdapter(peer) if !cgoEnabled() { assert.Error(t, err) assert.Contains(t, err.Error(), "requires cgo") @@ -159,8 +150,7 @@ func TestGetAdapterMetaproxy(t *testing.T) { }, } creator := NewAvailabilityCreator(AvailabilityAdapterMetaproxy, "http://metaproxy.indexdata.com") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) - aa, err := creator.GetAdapter(ctx, peer) + aa, err := creator.GetAdapter(peer) assert.NoError(t, err) assert.IsType(t, &MetaproxyAvailabilityAdapter{}, aa) } @@ -176,8 +166,7 @@ func TestGetAdapterMetaproxyMissingProxy(t *testing.T) { }, } creator := NewAvailabilityCreator(AvailabilityAdapterMetaproxy, "") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) - _, err := creator.GetAdapter(ctx, peer) + _, err := creator.GetAdapter(peer) assert.Error(t, err) assert.Contains(t, err.Error(), "METAPROXY_URL") } @@ -193,8 +182,7 @@ func TestGetAdapterSRU(t *testing.T) { }, } creator := NewAvailabilityCreator(AvailabilityAdapterZoom, "") - ctx := common.CreateExtCtxWithArgs(context.Background(), nil) - aa, err := creator.GetAdapter(ctx, peer) + aa, err := creator.GetAdapter(peer) assert.NoError(t, err) assert.IsType(t, &SruAvailabilityAdapter{}, aa) } diff --git a/broker/availability/availability_sru.go b/broker/availability/availability_sru.go index b61cb874..6cfb0b1f 100644 --- a/broker/availability/availability_sru.go +++ b/broker/availability/availability_sru.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/directory" ) @@ -12,7 +11,7 @@ type SruAvailabilityAdapter struct { holdingsLookupAdapter adapter.LookupAdapter } -func NewSruAvailabilityAdapter(ctx common.ExtendedContext, config directory.SruConfig, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { +func NewSruAvailabilityAdapter(config directory.SruConfig, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { var recordSchema string if config.RecordSchema != nil { recordSchema = *config.RecordSchema diff --git a/broker/availability/availability_zoom.go b/broker/availability/availability_zoom.go index 853e3967..cdd59a2f 100644 --- a/broker/availability/availability_zoom.go +++ b/broker/availability/availability_zoom.go @@ -24,6 +24,7 @@ func NewZoomAvailabilityAdapter(config directory.ZoomConfig, queryBuilder adapte // default options, can be overridden by config.Options options: zoom.Options{ "count": "10", + "presentChunks": "10", "preferredRecordSyntax": "usmarc", }, zurl: config.Address, @@ -38,14 +39,15 @@ func NewZoomAvailabilityAdapter(config directory.ZoomConfig, queryBuilder adapte return a, nil } -func (a *ZoomAvailabilityAdapter) searchRetrieve(params adapter.LookupParams, conn *zoom.Connection, query string) ([]adapter.Holding, error) { +func (a *ZoomAvailabilityAdapter) searchRetrieve(params adapter.LookupParams, conn *zoom.Connection, query *zoom.Query) ([]adapter.Holding, error) { set, err := conn.Search(query) if err != nil { return nil, err } defer set.Close() var avail []adapter.Holding - for i := 0; i < set.Count(); i++ { + limit := min(set.Count(), 100) // safety limit to avoid processing too many records + for i := 0; i < limit; i++ { rec, err := set.GetRecord(i) if err != nil { return nil, err @@ -78,14 +80,16 @@ func (a *ZoomAvailabilityAdapter) Lookup(params adapter.LookupParams) ([]adapter if err != nil { return nil, "", fmt.Errorf("failed to build query: %w", err) } - if len(cqlList) > 0 { - return nil, "", fmt.Errorf("Z39.50 server does not support CQL queries: %v", cqlList) - } - if len(pqfList) == 0 { + if len(pqfList) == 0 && len(cqlList) == 0 { return nil, "", fmt.Errorf("no valid query parameters provided") } for _, pqf := range pqfList { - avail, err := a.searchRetrieve(params, conn, pqf) + query, err := zoom.NewPqfQuery(pqf) + if err != nil { + return nil, pqf, fmt.Errorf("failed to create PQF query: %w", err) + } + avail, err := a.searchRetrieve(params, conn, query) + query.Close() if err != nil { return nil, pqf, fmt.Errorf("failed to search Z39.50 server query: %s err %w", pqf, err) } @@ -93,5 +97,19 @@ func (a *ZoomAvailabilityAdapter) Lookup(params adapter.LookupParams) ([]adapter return avail, pqf, nil } } + for _, cql := range cqlList { + query, err := zoom.NewCqlQuery(cql) + if err != nil { + return nil, cql, fmt.Errorf("failed to create CQL query: %w", err) + } + avail, err := a.searchRetrieve(params, conn, query) + query.Close() + if err != nil { + return nil, cql, fmt.Errorf("failed to search SRU server query: %s err %w", cql, err) + } + if len(avail) > 0 { + return avail, cql, nil + } + } return nil, pqfList[0], nil } diff --git a/broker/availability/availability_zoom_test.go b/broker/availability/availability_zoom_test.go index 3d0d1df2..e79eb8cc 100644 --- a/broker/availability/availability_zoom_test.go +++ b/broker/availability/availability_zoom_test.go @@ -40,9 +40,10 @@ func TestLookupFoundMarc(t *testing.T) { MainField: adapter.NewString("010"), LocationSubField: adapter.NewString("a"), } - queryBuilder := adapter.NewQueryBuilderPqf(&directory.QueryConfig{ + queryBuilder, err := adapter.NewQueryBuilder(&directory.QueryConfig{ Title: adapter.NewString("@attr 1=1016 {term}"), }) + assert.NoError(t, err) holdingsParser := adapter.NewMarcHoldingsParser(config) aa, err := NewZoomAvailabilityAdapter( directory.ZoomConfig{ @@ -70,7 +71,8 @@ func TestLookupFoundMarc(t *testing.T) { } func TestLookupFoundOpac(t *testing.T) { - queryBuilder := adapter.NewQueryBuilderPqf(nil) + queryBuilder, err := adapter.NewQueryBuilder(nil) + assert.NoError(t, err) holdingsParser := adapter.NewOpacHoldingsParser(directory.OpacParserConfig{}) aa, err := NewZoomAvailabilityAdapter( directory.ZoomConfig{ @@ -98,7 +100,8 @@ func TestLookupFoundOpac(t *testing.T) { } func TestLookupDiagnostics(t *testing.T) { - queryBuilder := adapter.NewQueryBuilderPqf(nil) + queryBuilder, err := adapter.NewQueryBuilder(nil) + assert.NoError(t, err) holdingsParser := adapter.NewMarcHoldingsParser(directory.MarcParserConfig{}) aa, err := NewZoomAvailabilityAdapter( directory.ZoomConfig{ @@ -122,7 +125,8 @@ func TestLookupDiagnostics(t *testing.T) { } func TestConnectFailure(t *testing.T) { - queryBuilder := adapter.NewQueryBuilderPqf(nil) + queryBuilder, err := adapter.NewQueryBuilder(nil) + assert.NoError(t, err) holdingsParser := adapter.NewMarcHoldingsParser(directory.MarcParserConfig{}) aa, err := NewZoomAvailabilityAdapter( directory.ZoomConfig{ diff --git a/broker/service/supplierlocator.go b/broker/service/supplierlocator.go index 17a0d6cf..03950805 100644 --- a/broker/service/supplierlocator.go +++ b/broker/service/supplierlocator.go @@ -92,7 +92,7 @@ func (s *SupplierLocator) locateSuppliers(ctx common.ExtendedContext, event even lookupAdapter := s.holdingsAdapter if lookupAdapter == nil { // null if consortia adapter is configured, in which case we need to determine adapter by directory // TODO: use CONSORTIA_SYMBOL or determine parent from peer (if not already consirtia symbol) instead of requester symbol - lookupAdapter, err = s.availabilityCreator.GetAdapter(ctx, requester) + lookupAdapter, err = s.availabilityCreator.GetAdapter(requester) if err != nil { return events.LogErrorAndReturnResult(ctx, "could not create availability adapter for requester", err) } @@ -258,7 +258,7 @@ func (s *SupplierLocator) checkAvailability(ctx common.ExtendedContext, event ev if err != nil { return events.LogErrorAndReturnResult(ctx, "could not get peer", err) } - aa, err := s.availabilityCreator.GetAdapter(ctx, peer) + aa, err := s.availabilityCreator.GetAdapter(peer) if err != nil { return events.LogErrorAndReturnResult(ctx, "could not create availability adapter", err) } diff --git a/directory/directory_api.yaml b/directory/directory_api.yaml index 61c578cb..1b020811 100644 --- a/directory/directory_api.yaml +++ b/directory/directory_api.yaml @@ -301,6 +301,12 @@ components: QueryConfig: type: object properties: + type: + type: string + enum: + - cql + - pqf + description: whether mapping is CQL or PQF. Default is PQF. title: type: string description: PQF/CQL format string for title search. Default is "@attr 1=4 {term}" diff --git a/zoom/zoom.go b/zoom/zoom.go index 246ade03..61257ac8 100644 --- a/zoom/zoom.go +++ b/zoom/zoom.go @@ -36,6 +36,10 @@ type Connection struct { conn C.ZOOM_connection } +type Query struct { + zquery C.ZOOM_query +} + type ResultSet struct { connection *Connection rs C.ZOOM_resultset @@ -57,6 +61,47 @@ func (o *Options) toZoomOptions() C.ZOOM_options { return zo } +func NewPqfQuery(pqf string) (*Query, error) { + cPqf := C.CString(pqf) + defer C.free(unsafe.Pointer(cPqf)) + + query := &Query{zquery: C.ZOOM_query_create()} + runtime.SetFinalizer(query, (*Query).finalize) + + ret := C.ZOOM_query_prefix(query.zquery, cPqf) + if ret != 0 { + query.finalize() + return nil, &ZoomError{Code: int(ret), Message: "failed to create PQF query"} + } + return query, nil +} + +func NewCqlQuery(cql string) (*Query, error) { + cCql := C.CString(cql) + defer C.free(unsafe.Pointer(cCql)) + + query := &Query{zquery: C.ZOOM_query_create()} + runtime.SetFinalizer(query, (*Query).finalize) + + ret := C.ZOOM_query_cql(query.zquery, cCql) + if ret != 0 { + query.finalize() + return nil, &ZoomError{Code: int(ret), Message: "failed to create CQL query"} + } + return query, nil +} + +func (q *Query) finalize() { + if q.zquery != nil { + C.ZOOM_query_destroy(q.zquery) + q.zquery = nil + } +} + +func (q *Query) Close() { + q.finalize() +} + func NewConnection(options Options) *Connection { c := &Connection{} cOptions := options.toZoomOptions() @@ -87,13 +132,14 @@ func (c *Connection) Close() { c.finalize() } -func (c *Connection) Search(query string) (*ResultSet, error) { +func (c *Connection) Search(query *Query) (*ResultSet, error) { if c.conn == nil { return nil, &ZoomError{Code: 0, Message: "connection is not established"} } - cQuery := C.CString(query) - defer C.free(unsafe.Pointer(cQuery)) - cSet := C.ZOOM_connection_search_pqf(c.conn, cQuery) + if query.zquery == nil { + return nil, &ZoomError{Code: 0, Message: "query is not initialized"} + } + cSet := C.ZOOM_connection_search(c.conn, query.zquery) set := &ResultSet{rs: cSet, connection: c} err := c.checkError() if err != nil { diff --git a/zoom/zoom_test.go b/zoom/zoom_test.go index d2b6b0c5..e5bdaecb 100644 --- a/zoom/zoom_test.go +++ b/zoom/zoom_test.go @@ -57,9 +57,25 @@ func TestConnect(t *testing.T) { assert.Equal(t, 10000, err.(*ZoomError).Code) } +func TestPqfQuery(t *testing.T) { + query, err := NewPqfQuery("@attr 1=4 computer") + assert.NoError(t, err) + assert.NotNil(t, query) + query.Close() + + _, err = NewPqfQuery("@attr 1=4") + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to create PQF query") +} + func TestSearch(t *testing.T) { conn := &Connection{} - _, err := conn.Search("@attr 1=4 utah") + + query, err := NewPqfQuery("@attr 1=4 computer") + assert.NoError(t, err) + assert.NotNil(t, query) + + _, err = conn.Search(query) assert.Error(t, err) assert.Contains(t, err.Error(), "connection is not established") @@ -73,7 +89,16 @@ func TestSearch(t *testing.T) { err = conn.Connect(containerHost + ":" + mappedPort) assert.NoError(t, err) - rs, err := conn.Search("@attr 1=4 computer") + query.Close() + _, err = conn.Search(query) + assert.Error(t, err) + assert.Contains(t, err.Error(), "query is not initialized") + + query, err = NewPqfQuery("@attr 1=4 computer") + assert.NoError(t, err) + assert.NotNil(t, query) + + rs, err := conn.Search(query) assert.NoError(t, err) assert.NotNil(t, rs) assert.Equal(t, 42, rs.Count()) @@ -122,8 +147,13 @@ func TestSearchUnsupportedSyntaxOnSearch(t *testing.T) { err := conn.Connect(containerHost + ":" + mappedPort) assert.NoError(t, err) + query, err := NewPqfQuery("@attr 1=4 computer") + assert.NoError(t, err) + assert.NotNil(t, query) + // getting non-surrogate diagnostic for unsupported record syntax - rs, err := conn.Search("@attr 1=4 computer") + + rs, err := conn.Search(query) assert.Error(t, err) assert.Nil(t, rs) assert.Contains(t, err.Error(), "Record syntax not supported") @@ -141,7 +171,11 @@ func TestSearchUnsupportedSyntaxOnPresent(t *testing.T) { err := conn.Connect(containerHost + ":" + mappedPort) assert.NoError(t, err) - rs, err := conn.Search("@attr 1=4 computer") + query, err := NewPqfQuery("@attr 1=4 computer") + assert.NoError(t, err) + assert.NotNil(t, query) + + rs, err := conn.Search(query) assert.NoError(t, err) assert.NotNil(t, rs) @@ -163,7 +197,11 @@ func TestSearchSurrogateDiagnostic(t *testing.T) { err := conn.Connect(containerHost + ":" + mappedPort) assert.NoError(t, err) - rs, err := conn.Search("@attr 1=4 computer") + query, err := NewPqfQuery("@attr 1=4 computer") + assert.NoError(t, err) + assert.NotNil(t, query) + + rs, err := conn.Search(query) assert.NoError(t, err) assert.NotNil(t, rs) From b8c3c3ab987737fb73d5c4d51b6624201df71000 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 14:24:53 +0200 Subject: [PATCH 10/35] Add GVI test (which should use mock) --- broker/test/adapter/gvi_holdings_test.go | 50 ++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 broker/test/adapter/gvi_holdings_test.go diff --git a/broker/test/adapter/gvi_holdings_test.go b/broker/test/adapter/gvi_holdings_test.go new file mode 100644 index 00000000..4f5b82e2 --- /dev/null +++ b/broker/test/adapter/gvi_holdings_test.go @@ -0,0 +1,50 @@ +package adapter + +import ( + "testing" + + "github.com/indexdata/crosslink/broker/adapter" + "github.com/indexdata/crosslink/broker/availability" + "github.com/indexdata/crosslink/broker/ill_db" + "github.com/indexdata/crosslink/directory" + "github.com/stretchr/testify/assert" +) + +func TestGviHoldings(t *testing.T) { + creator := availability.NewAvailabilityCreator(availability.AvailabilityAdapterZoom, "") + + // TODO: should use mock rather than external service + peer := ill_db.Peer{ + CustomData: directory.Entry{ + AvailabilityConfig: &directory.AvailabilityConfig{ + Zoom: &directory.ZoomConfig{ + Address: "https://sru.kobv.de/holdingsgvi", + Options: &map[string]string{ + "sru": "get", + "sru_version": "1.1", + }, + }, + QueryConfig: &directory.QueryConfig{ + Type: new(directory.Cql), + Identifier: adapter.NewString("rec.id = {term}"), + }, + ParserConfig: &directory.ParserConfig{ + Marc21plus1: &map[string]interface{}{}, + }, + }, + }, + } + + aa, err := creator.GetAdapter(peer) + assert.NoError(t, err) + assert.NotNil(t, aa) + + params := adapter.LookupParams{ + ServiceType: "Loan", + Identifier: "(DE-602)almafu_BV010733623", + } + holdings, _, err := aa.Lookup(params) + assert.NoError(t, err) + assert.NotNil(t, holdings) + assert.Len(t, holdings, 85) +} From b8f05f4eda8ce6937783b36b1f6f4e7232832f67 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 16:03:46 +0200 Subject: [PATCH 11/35] gvi holdings test mock --- broker/test/adapter/gvi_holdings_test.go | 390 ++++++++++++++++++++++- 1 file changed, 386 insertions(+), 4 deletions(-) diff --git a/broker/test/adapter/gvi_holdings_test.go b/broker/test/adapter/gvi_holdings_test.go index 4f5b82e2..75441766 100644 --- a/broker/test/adapter/gvi_holdings_test.go +++ b/broker/test/adapter/gvi_holdings_test.go @@ -1,6 +1,8 @@ package adapter import ( + "net/http" + "net/http/httptest" "testing" "github.com/indexdata/crosslink/broker/adapter" @@ -10,15 +12,395 @@ import ( "github.com/stretchr/testify/assert" ) +const GviResponse = ` + + 1.1 + 1 + + + marcxml + xml + + + 06947cam a2200961 c 4500 + 1795329181 + DE-627 + 20251226201436.0 + cr uuu---uuuuu + 220311s2022 gw |||||o 00| ||fre c + + 9783428585014 + 978-3-428-58501-4 + + + 10.3790/978-3-428-58501-4 + doi + + + (DE-627)1795329181 + + + (DE-599)KEP076913368 + + + (OCoLC)1304799781 + + + (DUH)9783428585014 + + + (DE-627-1)076913368 + + + DE-627 + ger + DE-627 + rda + + + fre + ger + + + XA-DE-BE + + + BF531 + + + BF + lcco + + + B + lcco + + + 128.37 + SEPA + + + 152.4 + SEPA + + + CV 2500 + DE-Ofb1/22 + rvk + (DE-625)rvk/19153: + + + CP 3200 + DE-Ofb1/22 + rvk + (DE-625)rvk/18977: + + + Les émotions créatives + sous la direction de Damien Ehrhardt, Hélène Fleury, Soraya Nour Sckell + + + Berlin + Duncker & Humblot + [2022] + + + © 2022 + + + 1 Online-Ressource (225 Seiten) + + + Text + txt + rdacontent + + + Computermedien + c + rdamedia + + + Online-Ressource + cr + rdacarrier + + + Beiträge zur politischen Wissenschaft + Band 199 + + + Online resource; title from title screen (viewed March 9, 2022) + + + L’importance du rôle des émotions dans la connaissance conduit à voir en elles bien davantage qu’un facteur perturbateur. Leur pertinence cognitive, de plus en plus reconnue par les sciences (naturelles, sociales, humaines…), consacre l’importance d’un tournant émotionnel (emotional turn). Les émotions constituent aussi de puissants moteurs de créativité et d’innovation, cruciaux dans la construction des formations socioculturelles. Les textes rassemblés dans le présent volume, dans une perspective résolument interdisciplinaire, traitent d’émotions puissamment agissantes dans l’existence, à la convergence des échelles individuelle et collective. Les deux premières parties s’interrogent sur la spécificité des émotions humainement vécues dans leurs interactions expérimentées via le corps et la raison. Les deux dernières parties abordent les émotions à une plus large échelle : celle des champs culturel et politique. / »Creative Emotions«: The scientific recognition of the cognitive significance of emotions confirms the importance of the emotional turn. Beyond this cognitive dimension, emotions are also motors of creativity, and crucial in the construction of socio-cultural configurations. The interdisciplinary texts gathered in this volume analyse how emotions act in the existence between individual and collective scales. They question the emotions in their interactions via the body and the reason, as well as in the cultural and political fields. + + + Emotions + Congresses + DLC + + + Emotions (Philosophy) + Congresses + DLC + + + Emotions and cognition + Congresses + DLC + + + Creation (Literary, artistic, etc.) + Congresses + DLC + + + Émotions (Philosophie) - Congrès + + + Émotions et cognition - Congrès + + + Creation (Literary, artistic, etc.) + + + Emotions + + + Emotions and cognition + + + Emotions (Philosophy) + + + Conference papers and proceedings + + + Emotionen + + + Kreativität + + + Wissensbildung + + + s + (DE-588)4138031-9 + (DE-627)105645575 + (DE-576)209682647 + Ideengeschichte + gnd + + + s + (DE-588)4019702-5 + (DE-627)106320602 + (DE-576)208930418 + Gefühl + gnd + + + s + (DE-588)4032903-3 + (DE-627)106259733 + (DE-576)208999248 + Kreativität + gnd + + + s + (DE-588)4073586-2 + (DE-627)106092022 + (DE-576)209190922 + Kognitive Psychologie + gnd + + + (DE-627) + + + Ehrhardt, Damien + 1969- + HerausgeberIn + (DE-588)136878849 + (DE-627)588319880 + (DE-576)301326533 + edt + + + Fleury, Hélène + HerausgeberIn + (DE-588)1252713592 + (DE-627)1794174060 + edt + + + Nour, Soraya + HerausgeberIn + (DE-588)1018682007 + (DE-627)682873136 + (DE-576)356209547 + edt + + + 9783428185016 + + + Erscheint auch als + Druck-Ausgabe + Les émotions créatives + Berlin : Duncker & Humblot, 2022 + 225 Seiten + (DE-627)1795172681 + 9783428185016 + 3428185013 + + + Beiträge zur politischen Wissenschaft + Band 199 + 199 + (DE-627)670631469 + (DE-576)47762409X + (DE-600)2633572-4 + am + + + https://elibrary.duncker-humblot.com/9783428585014 + X:DUH + Verlag + lizenzpflichtig + 1 + + + https://doi.org/10.3790/978-3-428-58501-4 + X:DUH + Resolving-System + lizenzpflichtig + 1 + + + ZDB-54-DHE + 2022 + + + ZDB-54-DHP + 2022 + + + ZDB-54-DKPW + + + CV 2500 + Soziale Kognition + Psychologie + Sozialpsychologie + Soziale Kognition + (DE-627)1437673430 + (DE-625)rvk/19153: + (DE-576)367673436 + + + CP 3200 + Gefühl + Psychologie + Allgemeine Psychologie + Gefühl + (DE-627)1271512165 + (DE-625)rvk/18977: + (DE-576)201512165 + + + BO + + + 4088716612 + DE-705 + 705 + GBV + b + p + https://doi.org/10.3790/978-3-428-58501-4 + + + 4087786013 + DE-21 + 21 + BSZ + b + p + https://doi.org/10.3790/978-3-428-58501-4 + Zugang für die Universität Tübingen + + + 4142515608 + DE-24 + 24 + BSZ + c + http://han.wlb-stuttgart.de/han/dunckerhumblot-eB/elibrary.duncker-humblot.com/9783428585014/U1 + ZDB-54-DHP + + + 4252867134 + DE-180 + 180 + BSZ + b + http://primo-49man.hosted.exlibrisgroup.com/openurl/MAN/MAN_UB_service_page?u.ignore_date_coverage=true&rft.mms_id=9919356436502561 + BSO + + + 4117933825 + DE-Ofb1 + Ofb 1 + BSZ + b + E-Book Duncker & Humblot + https://doi.org/10.3790/978-3-428-58501-4 + Zum Online-Dokument + Zugang im Hochschulnetz der HS Offenburg / extern via VPN oder Shibboleth (Login über Institution) + + + + + 1 + + + + 1.1 + rec.id="(DE-627)1795329181" + 1 + 1 + xml + marcxml + + +` + func TestGviHoldings(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/xml") + w.WriteHeader(http.StatusOK) + output := []byte(GviResponse) + _, err := w.Write(output) + assert.NoError(t, err) + }) + server := httptest.NewServer(handler) + defer server.Close() + creator := availability.NewAvailabilityCreator(availability.AvailabilityAdapterZoom, "") - // TODO: should use mock rather than external service peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ Zoom: &directory.ZoomConfig{ - Address: "https://sru.kobv.de/holdingsgvi", + Address: server.URL, Options: &map[string]string{ "sru": "get", "sru_version": "1.1", @@ -41,10 +423,10 @@ func TestGviHoldings(t *testing.T) { params := adapter.LookupParams{ ServiceType: "Loan", - Identifier: "(DE-602)almafu_BV010733623", + Identifier: "(DE-627)1795329181", } holdings, _, err := aa.Lookup(params) assert.NoError(t, err) assert.NotNil(t, holdings) - assert.Len(t, holdings, 85) + assert.Len(t, holdings, 1) } From f96cb6ef853a3dc249ec4bb2234f6690d3ec92ed Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 17:03:00 +0200 Subject: [PATCH 12/35] availability, adapter -> holdings package --- broker/adapter/mock_holdings.go | 51 ---------- broker/app/app.go | 14 +-- broker/availability/availability_creator.go | 10 -- broker/availability/availability_metaproxy.go | 23 ----- broker/availability/availability_sru.go | 30 ------ broker/holdings/adapter_metaproxy.go | 22 +++++ .../adapter_mock.go} | 11 +-- broker/holdings/adapter_sri.go | 29 ++++++ .../adapter_sru.go} | 2 +- .../adapter_zoom.go} | 15 ++- .../adapter_zoom_nocgo.go} | 5 +- .../adapter_zoom_test.go} | 33 ++++--- broker/holdings/creator.go | 9 ++ .../creator_impl.go} | 19 ++-- .../creator_shared.go} | 8 +- .../creator_test.go} | 9 +- .../adapter => holdings}/gvi_holdings_test.go | 31 ++++--- broker/{adapter => holdings}/holdings.go | 2 +- .../parser_marc.go} | 2 +- .../parser_marc21_plus.go} | 2 +- .../parser_marc21_plus_test.go} | 2 +- .../parser_opac.go} | 2 +- .../parser_reservoir.go} | 2 +- .../query_builder_isxn.go | 2 +- .../query_builder_pqf.go | 2 +- broker/service/supplierlocator.go | 12 +-- broker/service/supplierlocator_test.go | 30 +++--- broker/test/adapter/create_holdings_test.go | 42 ++++----- broker/test/adapter/sru_holdings_test.go | 92 +++++++++---------- broker/test/service/e2e_test.go | 4 +- 30 files changed, 230 insertions(+), 287 deletions(-) delete mode 100644 broker/adapter/mock_holdings.go delete mode 100644 broker/availability/availability_creator.go delete mode 100644 broker/availability/availability_metaproxy.go delete mode 100644 broker/availability/availability_sru.go create mode 100644 broker/holdings/adapter_metaproxy.go rename broker/{availability/availability_mock.go => holdings/adapter_mock.go} (79%) create mode 100644 broker/holdings/adapter_sri.go rename broker/{adapter/sru_holdings.go => holdings/adapter_sru.go} (99%) rename broker/{availability/availability_zoom.go => holdings/adapter_zoom.go} (82%) rename broker/{availability/availability_zoom_nocgo.go => holdings/adapter_zoom_nocgo.go} (50%) rename broker/{availability/availability_zoom_test.go => holdings/adapter_zoom_test.go} (79%) create mode 100644 broker/holdings/creator.go rename broker/{availability/availability_creator_impl.go => holdings/creator_impl.go} (78%) rename broker/{adapter/create_holdings.go => holdings/creator_shared.go} (90%) rename broker/{availability/availability_creator_test.go => holdings/creator_test.go} (95%) rename broker/{test/adapter => holdings}/gvi_holdings_test.go (97%) rename broker/{adapter => holdings}/holdings.go (97%) rename broker/{adapter/marc_holdings_parser.go => holdings/parser_marc.go} (99%) rename broker/{adapter/marc21_plus_holdings_parser.go => holdings/parser_marc21_plus.go} (99%) rename broker/{adapter/marc21_plus_holdings_parser_test.go => holdings/parser_marc21_plus_test.go} (99%) rename broker/{adapter/opac_holdings_parser.go => holdings/parser_opac.go} (98%) rename broker/{adapter/reservoir_holdings_parser.go => holdings/parser_reservoir.go} (98%) rename broker/{adapter => holdings}/query_builder_isxn.go (98%) rename broker/{adapter => holdings}/query_builder_pqf.go (99%) diff --git a/broker/adapter/mock_holdings.go b/broker/adapter/mock_holdings.go deleted file mode 100644 index ad95f34e..00000000 --- a/broker/adapter/mock_holdings.go +++ /dev/null @@ -1,51 +0,0 @@ -package adapter - -import ( - "errors" - "fmt" - "strconv" - "strings" -) - -type MockHoldingsLookupAdapter struct { -} - -func (m *MockHoldingsLookupAdapter) Lookup(params LookupParams) ([]Holding, string, error) { - ids := strings.Split(params.Identifier, ";") - i := 1 - var holdings []Holding - for _, id := range ids { - if id == "error" { - return []Holding{}, id, errors.New("there is error") - } - if id == "not-found" { // we could also just not append? - return []Holding{}, id, nil - } - if strings.Index(id, "return-") == 0 { - val := strings.SplitN(strings.TrimPrefix(id, "return-"), "::", 2) - if len(val) < 1 || len(val[0]) < 1 { - return nil, id, fmt.Errorf("invalid return- value") - } - var s, l string - if len(val) == 1 { - s = val[0] - l = val[0] - } - if len(val) == 2 { - s = val[0] - l = val[1] - } - holdings = append(holdings, Holding{ - Symbol: s, - LocalIdentifier: l, - }) - } else { - holdings = append(holdings, Holding{ - Symbol: "ISIL:SUP" + strconv.Itoa(i), - LocalIdentifier: id, - }) - } - i++ - } - return holdings, params.Identifier, nil -} diff --git a/broker/app/app.go b/broker/app/app.go index 419ab7eb..fd1aed61 100644 --- a/broker/app/app.go +++ b/broker/app/app.go @@ -13,7 +13,7 @@ import ( "syscall" "time" - "github.com/indexdata/crosslink/broker/availability" + "github.com/indexdata/crosslink/broker/holdings" prapi "github.com/indexdata/crosslink/broker/patron_request/api" pr_db "github.com/indexdata/crosslink/broker/patron_request/db" "github.com/indexdata/crosslink/broker/patron_request/proapi" @@ -133,11 +133,11 @@ func configLog() slog.Handler { func Init(ctx context.Context) (Context, error) { appCtx.Logger().Info("starting " + vcs.GetSignature()) - holdingsAdapter, err := adapter.CreateHoldingsLookupAdapter(map[string]any{ - adapter.HoldingsAdapter: HOLDINGS_ADAPTER, - adapter.HoldingsSruURL: HOLDINGS_SRU_URL, - adapter.HoldingsIsxnLookup: HOLDINGS_ISXN_LOOKUP, - adapter.HoldingsFormat: HOLDINGS_FORMAT, + holdingsAdapter, err := holdings.CreateHoldingsLookupShared(map[string]any{ + holdings.HoldingsAdapter: HOLDINGS_ADAPTER, + holdings.HoldingsSruURL: HOLDINGS_SRU_URL, + holdings.HoldingsIsxnLookup: HOLDINGS_ISXN_LOOKUP, + holdings.HoldingsFormat: HOLDINGS_FORMAT, }) if err != nil { return Context{}, err @@ -176,7 +176,7 @@ func Init(ctx context.Context) (Context, error) { prMessageHandler := prservice.CreatePatronRequestMessageHandler(prRepo, eventRepo, illRepo, eventBus) iso18626Handler := handler.CreateIso18626Handler(eventBus, eventRepo, illRepo, dirAdapter) lmsCreator := lms.NewLmsCreator(illRepo, dirAdapter) - availabilityCreator := availability.NewAvailabilityCreator(AVAILABILITY_ADAPTER, METAPROXY_URL) + availabilityCreator := holdings.NewAvailabilityCreator(AVAILABILITY_ADAPTER, METAPROXY_URL) prActionService := prservice.CreatePatronRequestActionService(prRepo, eventBus, &iso18626Handler, lmsCreator) prMessageHandler.SetAutoActionRunner(prActionService) iso18626Client := client.CreateIso18626Client(eventBus, illRepo, prMessageHandler, MAX_MESSAGE_SIZE, delay) diff --git a/broker/availability/availability_creator.go b/broker/availability/availability_creator.go deleted file mode 100644 index a557f478..00000000 --- a/broker/availability/availability_creator.go +++ /dev/null @@ -1,10 +0,0 @@ -package availability - -import ( - "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/ill_db" -) - -type AvailabilityCreator interface { - GetAdapter(peer ill_db.Peer) (adapter.LookupAdapter, error) -} diff --git a/broker/availability/availability_metaproxy.go b/broker/availability/availability_metaproxy.go deleted file mode 100644 index a9e4c2f5..00000000 --- a/broker/availability/availability_metaproxy.go +++ /dev/null @@ -1,23 +0,0 @@ -package availability - -import ( - "net/http" - - "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/directory" -) - -type MetaproxyAvailabilityAdapter struct { - holdingsLookupAdapter adapter.LookupAdapter -} - -func NewMetaproxyAvailabilityAdapter(config directory.ZoomConfig, metaproxyUrl string, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { - a := &MetaproxyAvailabilityAdapter{ - holdingsLookupAdapter: adapter.CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{metaproxyUrl}, config.Address, queryBuilder, holdingsParser, "marcxml"), - } - return a, nil -} - -func (a *MetaproxyAvailabilityAdapter) Lookup(params adapter.LookupParams) ([]adapter.Holding, string, error) { - return a.holdingsLookupAdapter.Lookup(params) -} diff --git a/broker/availability/availability_sru.go b/broker/availability/availability_sru.go deleted file mode 100644 index 6cfb0b1f..00000000 --- a/broker/availability/availability_sru.go +++ /dev/null @@ -1,30 +0,0 @@ -package availability - -import ( - "net/http" - - "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/directory" -) - -type SruAvailabilityAdapter struct { - holdingsLookupAdapter adapter.LookupAdapter -} - -func NewSruAvailabilityAdapter(config directory.SruConfig, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { - var recordSchema string - if config.RecordSchema != nil { - recordSchema = *config.RecordSchema - } - if recordSchema == "" { - recordSchema = "marcxml" // default to marcxml if not specified - } - a := &SruAvailabilityAdapter{ - holdingsLookupAdapter: adapter.CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{config.Address}, "", queryBuilder, holdingsParser, recordSchema), - } - return a, nil -} - -func (a *SruAvailabilityAdapter) Lookup(params adapter.LookupParams) ([]adapter.Holding, string, error) { - return a.holdingsLookupAdapter.Lookup(params) -} diff --git a/broker/holdings/adapter_metaproxy.go b/broker/holdings/adapter_metaproxy.go new file mode 100644 index 00000000..e856c835 --- /dev/null +++ b/broker/holdings/adapter_metaproxy.go @@ -0,0 +1,22 @@ +package holdings + +import ( + "net/http" + + "github.com/indexdata/crosslink/directory" +) + +type MetaproxyAvailabilityAdapter struct { + holdingsLookupAdapter LookupAdapter +} + +func NewMetaproxyAvailabilityAdapter(config directory.ZoomConfig, metaproxyUrl string, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser) (LookupAdapter, error) { + a := &MetaproxyAvailabilityAdapter{ + holdingsLookupAdapter: CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{metaproxyUrl}, config.Address, queryBuilder, holdingsParser, "marcxml"), + } + return a, nil +} + +func (a *MetaproxyAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string, error) { + return a.holdingsLookupAdapter.Lookup(params) +} diff --git a/broker/availability/availability_mock.go b/broker/holdings/adapter_mock.go similarity index 79% rename from broker/availability/availability_mock.go rename to broker/holdings/adapter_mock.go index 9546c0eb..ce30f11a 100644 --- a/broker/availability/availability_mock.go +++ b/broker/holdings/adapter_mock.go @@ -1,19 +1,18 @@ -package availability +package holdings import ( "fmt" "strings" - "github.com/indexdata/crosslink/broker/adapter" "github.com/indexdata/crosslink/directory" ) type MockAvailabilityAdapter struct { Err error - Holdings []adapter.Holding + Holdings []Holding } -func NewMockAvailabilityAdapter(config directory.AvailabilityConfig) (adapter.LookupAdapter, error) { +func NewMockAvailabilityAdapter(config directory.AvailabilityConfig) (LookupAdapter, error) { if config.Zoom != nil && config.Zoom.Options != nil { options := *config.Zoom.Options // For testing purposes, we can use the presence of "adapter-error" in options to trigger an error response @@ -28,7 +27,7 @@ func NewMockAvailabilityAdapter(config directory.AvailabilityConfig) (adapter.Lo } if val, ok := options["location"]; ok { return &MockAvailabilityAdapter{ - Holdings: []adapter.Holding{ + Holdings: []Holding{ { Location: val, }, @@ -39,7 +38,7 @@ func NewMockAvailabilityAdapter(config directory.AvailabilityConfig) (adapter.Lo return &MockAvailabilityAdapter{}, nil } -func (a *MockAvailabilityAdapter) Lookup(params adapter.LookupParams) ([]adapter.Holding, string, error) { +func (a *MockAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string, error) { if a.Err != nil { return nil, "", a.Err } diff --git a/broker/holdings/adapter_sri.go b/broker/holdings/adapter_sri.go new file mode 100644 index 00000000..510f0cae --- /dev/null +++ b/broker/holdings/adapter_sri.go @@ -0,0 +1,29 @@ +package holdings + +import ( + "net/http" + + "github.com/indexdata/crosslink/directory" +) + +type SruAvailabilityAdapter struct { + holdingsLookupAdapter LookupAdapter +} + +func NewSruAvailabilityAdapter(config directory.SruConfig, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser) (LookupAdapter, error) { + var recordSchema string + if config.RecordSchema != nil { + recordSchema = *config.RecordSchema + } + if recordSchema == "" { + recordSchema = "marcxml" // default to marcxml if not specified + } + a := &SruAvailabilityAdapter{ + holdingsLookupAdapter: CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{config.Address}, "", queryBuilder, holdingsParser, recordSchema), + } + return a, nil +} + +func (a *SruAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string, error) { + return a.holdingsLookupAdapter.Lookup(params) +} diff --git a/broker/adapter/sru_holdings.go b/broker/holdings/adapter_sru.go similarity index 99% rename from broker/adapter/sru_holdings.go rename to broker/holdings/adapter_sru.go index 5f092d92..d6cd5588 100644 --- a/broker/adapter/sru_holdings.go +++ b/broker/holdings/adapter_sru.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "encoding/xml" diff --git a/broker/availability/availability_zoom.go b/broker/holdings/adapter_zoom.go similarity index 82% rename from broker/availability/availability_zoom.go rename to broker/holdings/adapter_zoom.go index cdd59a2f..29c84b56 100644 --- a/broker/availability/availability_zoom.go +++ b/broker/holdings/adapter_zoom.go @@ -1,11 +1,10 @@ //go:build cgo -package availability +package holdings import ( "fmt" - "github.com/indexdata/crosslink/broker/adapter" "github.com/indexdata/crosslink/directory" "github.com/indexdata/crosslink/zoom" ) @@ -15,11 +14,11 @@ func cgoEnabled() bool { return true } type ZoomAvailabilityAdapter struct { zurl string options zoom.Options - holdingsParser adapter.HoldingsParser - queryBuilder adapter.LookupQueryBuilder + holdingsParser HoldingsParser + queryBuilder LookupQueryBuilder } -func NewZoomAvailabilityAdapter(config directory.ZoomConfig, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { +func NewZoomAvailabilityAdapter(config directory.ZoomConfig, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser) (LookupAdapter, error) { a := &ZoomAvailabilityAdapter{ // default options, can be overridden by config.Options options: zoom.Options{ @@ -39,13 +38,13 @@ func NewZoomAvailabilityAdapter(config directory.ZoomConfig, queryBuilder adapte return a, nil } -func (a *ZoomAvailabilityAdapter) searchRetrieve(params adapter.LookupParams, conn *zoom.Connection, query *zoom.Query) ([]adapter.Holding, error) { +func (a *ZoomAvailabilityAdapter) searchRetrieve(params LookupParams, conn *zoom.Connection, query *zoom.Query) ([]Holding, error) { set, err := conn.Search(query) if err != nil { return nil, err } defer set.Close() - var avail []adapter.Holding + var avail []Holding limit := min(set.Count(), 100) // safety limit to avoid processing too many records for i := 0; i < limit; i++ { rec, err := set.GetRecord(i) @@ -69,7 +68,7 @@ func (a *ZoomAvailabilityAdapter) searchRetrieve(params adapter.LookupParams, co return avail, nil } -func (a *ZoomAvailabilityAdapter) Lookup(params adapter.LookupParams) ([]adapter.Holding, string, error) { +func (a *ZoomAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string, error) { conn := zoom.NewConnection(a.options) defer conn.Close() err := conn.Connect(a.zurl) diff --git a/broker/availability/availability_zoom_nocgo.go b/broker/holdings/adapter_zoom_nocgo.go similarity index 50% rename from broker/availability/availability_zoom_nocgo.go rename to broker/holdings/adapter_zoom_nocgo.go index 591c31b0..476eec14 100644 --- a/broker/availability/availability_zoom_nocgo.go +++ b/broker/holdings/adapter_zoom_nocgo.go @@ -1,11 +1,10 @@ //go:build !cgo -package availability +package holdings import ( "fmt" - "github.com/indexdata/crosslink/broker/adapter" "github.com/indexdata/crosslink/directory" ) @@ -13,6 +12,6 @@ func cgoEnabled() bool { return false } type ZoomAvailabilityAdapter struct{} -func NewZoomAvailabilityAdapter(config directory.Z3950Config, queryBuilder adapter.LookupQueryBuilder, holdingsParser adapter.HoldingsParser) (adapter.LookupAdapter, error) { +func NewZoomAvailabilityAdapter(config directory.ZoomConfig, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser) (LookupAdapter, error) { return nil, fmt.Errorf("ZOOM availability adapter requires cgo, but cgo is not enabled") } diff --git a/broker/availability/availability_zoom_test.go b/broker/holdings/adapter_zoom_test.go similarity index 79% rename from broker/availability/availability_zoom_test.go rename to broker/holdings/adapter_zoom_test.go index e79eb8cc..1eccd8bb 100644 --- a/broker/availability/availability_zoom_test.go +++ b/broker/holdings/adapter_zoom_test.go @@ -1,13 +1,12 @@ //go:build cgo -package availability +package holdings import ( "context" "os" "testing" - "github.com/indexdata/crosslink/broker/adapter" "github.com/indexdata/crosslink/directory" zoomtestutil "github.com/indexdata/crosslink/testutil" "github.com/stretchr/testify/assert" @@ -37,14 +36,14 @@ func TestMain(m *testing.M) { func TestLookupFoundMarc(t *testing.T) { // target does not return holdings, we just use 010$a as fake location to verify that the record was parsed correctly config := directory.MarcParserConfig{ - MainField: adapter.NewString("010"), - LocationSubField: adapter.NewString("a"), + MainField: NewString("010"), + LocationSubField: NewString("a"), } - queryBuilder, err := adapter.NewQueryBuilder(&directory.QueryConfig{ - Title: adapter.NewString("@attr 1=1016 {term}"), + queryBuilder, err := NewQueryBuilder(&directory.QueryConfig{ + Title: NewString("@attr 1=1016 {term}"), }) assert.NoError(t, err) - holdingsParser := adapter.NewMarcHoldingsParser(config) + holdingsParser := NewMarcHoldingsParser(config) aa, err := NewZoomAvailabilityAdapter( directory.ZoomConfig{ Address: containerHost + ":" + mappedPort + "/marc", @@ -60,7 +59,7 @@ func TestLookupFoundMarc(t *testing.T) { assert.Equal(t, containerHost+":"+mappedPort+"/marc", aa.(*ZoomAvailabilityAdapter).zurl) assert.Equal(t, "20", aa.(*ZoomAvailabilityAdapter).options["count"]) - params := adapter.LookupParams{ + params := LookupParams{ Title: "Computer", } results, pqf, err := aa.Lookup(params) @@ -71,9 +70,9 @@ func TestLookupFoundMarc(t *testing.T) { } func TestLookupFoundOpac(t *testing.T) { - queryBuilder, err := adapter.NewQueryBuilder(nil) + queryBuilder, err := NewQueryBuilder(nil) assert.NoError(t, err) - holdingsParser := adapter.NewOpacHoldingsParser(directory.OpacParserConfig{}) + holdingsParser := NewOpacHoldingsParser(directory.OpacParserConfig{}) aa, err := NewZoomAvailabilityAdapter( directory.ZoomConfig{ Address: containerHost + ":" + mappedPort + "/marc", @@ -88,7 +87,7 @@ func TestLookupFoundOpac(t *testing.T) { assert.Equal(t, containerHost+":"+mappedPort+"/marc", aa.(*ZoomAvailabilityAdapter).zurl) assert.Equal(t, "10", aa.(*ZoomAvailabilityAdapter).options["count"]) - params := adapter.LookupParams{ + params := LookupParams{ Title: "Computer", } results, pqf, err := aa.Lookup(params) @@ -100,9 +99,9 @@ func TestLookupFoundOpac(t *testing.T) { } func TestLookupDiagnostics(t *testing.T) { - queryBuilder, err := adapter.NewQueryBuilder(nil) + queryBuilder, err := NewQueryBuilder(nil) assert.NoError(t, err) - holdingsParser := adapter.NewMarcHoldingsParser(directory.MarcParserConfig{}) + holdingsParser := NewMarcHoldingsParser(directory.MarcParserConfig{}) aa, err := NewZoomAvailabilityAdapter( directory.ZoomConfig{ Address: containerHost + ":" + mappedPort + "/marc", @@ -117,7 +116,7 @@ func TestLookupDiagnostics(t *testing.T) { assert.Equal(t, "localhost:"+mappedPort+"/marc", aa.(*ZoomAvailabilityAdapter).zurl) assert.Equal(t, "danmarc", aa.(*ZoomAvailabilityAdapter).options["preferredRecordSyntax"]) - params := adapter.LookupParams{Identifier: "1234"} + params := LookupParams{Identifier: "1234"} _, pqf, err := aa.Lookup(params) assert.Error(t, err) assert.Contains(t, err.Error(), "Record syntax not supported") @@ -125,9 +124,9 @@ func TestLookupDiagnostics(t *testing.T) { } func TestConnectFailure(t *testing.T) { - queryBuilder, err := adapter.NewQueryBuilder(nil) + queryBuilder, err := NewQueryBuilder(nil) assert.NoError(t, err) - holdingsParser := adapter.NewMarcHoldingsParser(directory.MarcParserConfig{}) + holdingsParser := NewMarcHoldingsParser(directory.MarcParserConfig{}) aa, err := NewZoomAvailabilityAdapter( directory.ZoomConfig{ Address: "", @@ -136,7 +135,7 @@ func TestConnectFailure(t *testing.T) { holdingsParser, ) assert.NoError(t, err) - params := adapter.LookupParams{} + params := LookupParams{} _, _, err = aa.Lookup(params) assert.Error(t, err) assert.Contains(t, err.Error(), "failed to connect to Z39.50 server") diff --git a/broker/holdings/creator.go b/broker/holdings/creator.go new file mode 100644 index 00000000..4f857412 --- /dev/null +++ b/broker/holdings/creator.go @@ -0,0 +1,9 @@ +package holdings + +import ( + "github.com/indexdata/crosslink/broker/ill_db" +) + +type AvailabilityCreator interface { + GetAdapter(peer ill_db.Peer) (LookupAdapter, error) +} diff --git a/broker/availability/availability_creator_impl.go b/broker/holdings/creator_impl.go similarity index 78% rename from broker/availability/availability_creator_impl.go rename to broker/holdings/creator_impl.go index 56f8eaa9..d648aad8 100644 --- a/broker/availability/availability_creator_impl.go +++ b/broker/holdings/creator_impl.go @@ -1,9 +1,8 @@ -package availability +package holdings import ( "fmt" - "github.com/indexdata/crosslink/broker/adapter" "github.com/indexdata/crosslink/broker/ill_db" "github.com/indexdata/crosslink/directory" ) @@ -26,26 +25,26 @@ func NewAvailabilityCreator(mode string, metaproxyUrl string) AvailabilityCreato } } -func getParser(config *directory.ParserConfig) (adapter.HoldingsParser, error) { +func getParser(config *directory.ParserConfig) (HoldingsParser, error) { if config == nil { - return adapter.NewMarcHoldingsParser(directory.MarcParserConfig{}), nil // default to marc parser + return NewMarcHoldingsParser(directory.MarcParserConfig{}), nil // default to marc parser } if config.Marc != nil { - return adapter.NewMarcHoldingsParser(*config.Marc), nil + return NewMarcHoldingsParser(*config.Marc), nil } if config.Opac != nil { - return adapter.NewOpacHoldingsParser(*config.Opac), nil + return NewOpacHoldingsParser(*config.Opac), nil } if config.Reservoir != nil { - return adapter.NewReservoirHoldingsParser(), nil + return NewReservoirHoldingsParser(), nil } if config.Marc21plus1 != nil { - return adapter.NewMarc21Plus1HoldingsParser(), nil + return NewMarc21Plus1HoldingsParser(), nil } return nil, fmt.Errorf("availabilityConfig.parserConfig must set marc, opac, reservoir, or marc21plus1 properties") } -func (c *AvailabilityCreatorImpl) GetAdapter(peer ill_db.Peer) (adapter.LookupAdapter, error) { +func (c *AvailabilityCreatorImpl) GetAdapter(peer ill_db.Peer) (LookupAdapter, error) { entry := peer.CustomData config := entry.AvailabilityConfig if config == nil { @@ -58,7 +57,7 @@ func (c *AvailabilityCreatorImpl) GetAdapter(peer ill_db.Peer) (adapter.LookupAd if err != nil { return nil, err } - queryBuilder, err := adapter.NewQueryBuilder(config.QueryConfig) + queryBuilder, err := NewQueryBuilder(config.QueryConfig) if err != nil { return nil, err } diff --git a/broker/adapter/create_holdings.go b/broker/holdings/creator_shared.go similarity index 90% rename from broker/adapter/create_holdings.go rename to broker/holdings/creator_shared.go index d2375093..e1e855ef 100644 --- a/broker/adapter/create_holdings.go +++ b/broker/holdings/creator_shared.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "fmt" @@ -15,7 +15,7 @@ const ( HoldingsFormatMarc21Plus1 string = "MARC-21plus-1" ) -func getParser(format string) (HoldingsParser, error) { +func getParserFormat(format string) (HoldingsParser, error) { switch format { case HoldingsFormatReservoir: return &ReservoirHoldingsParser{}, nil @@ -26,7 +26,7 @@ func getParser(format string) (HoldingsParser, error) { } } -func CreateHoldingsLookupAdapter(cfg map[string]any) (LookupAdapter, error) { +func CreateHoldingsLookupShared(cfg map[string]any) (LookupAdapter, error) { holdingsAdapterVal, ok := cfg[HoldingsAdapter].(string) if !ok { return nil, fmt.Errorf("missing value for %s", HoldingsAdapter) @@ -54,7 +54,7 @@ func CreateHoldingsLookupAdapter(cfg map[string]any) (LookupAdapter, error) { if !ok { return nil, fmt.Errorf("missing value for %s", HoldingsFormat) } - parser, err := getParser(format) + parser, err := getParserFormat(format) if err != nil { return nil, err } diff --git a/broker/availability/availability_creator_test.go b/broker/holdings/creator_test.go similarity index 95% rename from broker/availability/availability_creator_test.go rename to broker/holdings/creator_test.go index d3e95dc2..d0566c5a 100644 --- a/broker/availability/availability_creator_test.go +++ b/broker/holdings/creator_test.go @@ -1,9 +1,8 @@ -package availability +package holdings import ( "testing" - "github.com/indexdata/crosslink/broker/adapter" "github.com/indexdata/crosslink/broker/ill_db" "github.com/indexdata/crosslink/directory" "github.com/stretchr/testify/assert" @@ -28,7 +27,7 @@ func TestGetAdapterOtherNoConfig(t *testing.T) { func TestParserNil(t *testing.T) { parser, err := getParser(nil) assert.NoError(t, err) - assert.IsType(t, &adapter.MarcHoldingsParser{}, parser) + assert.IsType(t, &MarcHoldingsParser{}, parser) } func TestParserMissing(t *testing.T) { @@ -44,7 +43,7 @@ func TestParserMarc(t *testing.T) { } parser, err := getParser(parserConfig) assert.NoError(t, err) - assert.IsType(t, &adapter.MarcHoldingsParser{}, parser) + assert.IsType(t, &MarcHoldingsParser{}, parser) } func TestParserOpac(t *testing.T) { @@ -53,7 +52,7 @@ func TestParserOpac(t *testing.T) { } parser, err := getParser(parserConfig) assert.NoError(t, err) - assert.IsType(t, &adapter.OpacHoldingsParser{}, parser) + assert.IsType(t, &OpacHoldingsParser{}, parser) } func TestGetAdapterBadParser(t *testing.T) { diff --git a/broker/test/adapter/gvi_holdings_test.go b/broker/holdings/gvi_holdings_test.go similarity index 97% rename from broker/test/adapter/gvi_holdings_test.go rename to broker/holdings/gvi_holdings_test.go index 75441766..c81344c3 100644 --- a/broker/test/adapter/gvi_holdings_test.go +++ b/broker/holdings/gvi_holdings_test.go @@ -1,12 +1,10 @@ -package adapter +package holdings import ( "net/http" "net/http/httptest" "testing" - "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/availability" "github.com/indexdata/crosslink/broker/ill_db" "github.com/indexdata/crosslink/directory" "github.com/stretchr/testify/assert" @@ -394,7 +392,7 @@ func TestGviHoldings(t *testing.T) { server := httptest.NewServer(handler) defer server.Close() - creator := availability.NewAvailabilityCreator(availability.AvailabilityAdapterZoom, "") + creator := NewAvailabilityCreator(AvailabilityAdapterZoom, "") peer := ill_db.Peer{ CustomData: directory.Entry{ @@ -408,7 +406,7 @@ func TestGviHoldings(t *testing.T) { }, QueryConfig: &directory.QueryConfig{ Type: new(directory.Cql), - Identifier: adapter.NewString("rec.id = {term}"), + Identifier: NewString("rec.id = {term}"), }, ParserConfig: &directory.ParserConfig{ Marc21plus1: &map[string]interface{}{}, @@ -418,15 +416,20 @@ func TestGviHoldings(t *testing.T) { } aa, err := creator.GetAdapter(peer) - assert.NoError(t, err) - assert.NotNil(t, aa) + if cgoEnabled() { + assert.NoError(t, err) + assert.NotNil(t, aa) - params := adapter.LookupParams{ - ServiceType: "Loan", - Identifier: "(DE-627)1795329181", + params := LookupParams{ + ServiceType: "Loan", + Identifier: "(DE-627)1795329181", + } + holdingsList, _, err := aa.Lookup(params) + assert.NoError(t, err) + assert.NotNil(t, holdingsList) + assert.Len(t, holdingsList, 1) + } else { + assert.Error(t, err) + assert.Contains(t, err.Error(), "cgo is not enabled") } - holdings, _, err := aa.Lookup(params) - assert.NoError(t, err) - assert.NotNil(t, holdings) - assert.Len(t, holdings, 1) } diff --git a/broker/adapter/holdings.go b/broker/holdings/holdings.go similarity index 97% rename from broker/adapter/holdings.go rename to broker/holdings/holdings.go index d5518c36..839b4383 100644 --- a/broker/adapter/holdings.go +++ b/broker/holdings/holdings.go @@ -1,4 +1,4 @@ -package adapter +package holdings type LookupAdapter interface { Lookup(params LookupParams) ([]Holding, string, error) diff --git a/broker/adapter/marc_holdings_parser.go b/broker/holdings/parser_marc.go similarity index 99% rename from broker/adapter/marc_holdings_parser.go rename to broker/holdings/parser_marc.go index c8dd6857..5e0958aa 100644 --- a/broker/adapter/marc_holdings_parser.go +++ b/broker/holdings/parser_marc.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "encoding/xml" diff --git a/broker/adapter/marc21_plus_holdings_parser.go b/broker/holdings/parser_marc21_plus.go similarity index 99% rename from broker/adapter/marc21_plus_holdings_parser.go rename to broker/holdings/parser_marc21_plus.go index f04c6e98..4e8653d7 100644 --- a/broker/adapter/marc21_plus_holdings_parser.go +++ b/broker/holdings/parser_marc21_plus.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "encoding/xml" diff --git a/broker/adapter/marc21_plus_holdings_parser_test.go b/broker/holdings/parser_marc21_plus_test.go similarity index 99% rename from broker/adapter/marc21_plus_holdings_parser_test.go rename to broker/holdings/parser_marc21_plus_test.go index e66c6542..ef0759d4 100644 --- a/broker/adapter/marc21_plus_holdings_parser_test.go +++ b/broker/holdings/parser_marc21_plus_test.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "testing" diff --git a/broker/adapter/opac_holdings_parser.go b/broker/holdings/parser_opac.go similarity index 98% rename from broker/adapter/opac_holdings_parser.go rename to broker/holdings/parser_opac.go index fe4f46c7..52ba6ae4 100644 --- a/broker/adapter/opac_holdings_parser.go +++ b/broker/holdings/parser_opac.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "encoding/xml" diff --git a/broker/adapter/reservoir_holdings_parser.go b/broker/holdings/parser_reservoir.go similarity index 98% rename from broker/adapter/reservoir_holdings_parser.go rename to broker/holdings/parser_reservoir.go index 1c62b7c4..fa2ea5e0 100644 --- a/broker/adapter/reservoir_holdings_parser.go +++ b/broker/holdings/parser_reservoir.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "encoding/xml" diff --git a/broker/adapter/query_builder_isxn.go b/broker/holdings/query_builder_isxn.go similarity index 98% rename from broker/adapter/query_builder_isxn.go rename to broker/holdings/query_builder_isxn.go index ded077bd..f72f50bf 100644 --- a/broker/adapter/query_builder_isxn.go +++ b/broker/holdings/query_builder_isxn.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "errors" diff --git a/broker/adapter/query_builder_pqf.go b/broker/holdings/query_builder_pqf.go similarity index 99% rename from broker/adapter/query_builder_pqf.go rename to broker/holdings/query_builder_pqf.go index 8f080436..e5c070c3 100644 --- a/broker/adapter/query_builder_pqf.go +++ b/broker/holdings/query_builder_pqf.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "errors" diff --git a/broker/service/supplierlocator.go b/broker/service/supplierlocator.go index 03950805..5945ca27 100644 --- a/broker/service/supplierlocator.go +++ b/broker/service/supplierlocator.go @@ -9,9 +9,9 @@ import ( "github.com/google/uuid" "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/availability" "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/broker/events" + "github.com/indexdata/crosslink/broker/holdings" "github.com/indexdata/crosslink/broker/ill_db" "github.com/jackc/pgx/v5/pgtype" ) @@ -25,11 +25,11 @@ type SupplierLocator struct { eventBus events.EventBus illRepo ill_db.IllRepo dirAdapter adapter.DirectoryLookupAdapter - holdingsAdapter adapter.LookupAdapter - availabilityCreator availability.AvailabilityCreator + holdingsAdapter holdings.LookupAdapter + availabilityCreator holdings.AvailabilityCreator } -func CreateSupplierLocator(eventBus events.EventBus, illRepo ill_db.IllRepo, dirAdapter adapter.DirectoryLookupAdapter, holdingsAdapter adapter.LookupAdapter, availabilityCreator availability.AvailabilityCreator) SupplierLocator { +func CreateSupplierLocator(eventBus events.EventBus, illRepo ill_db.IllRepo, dirAdapter adapter.DirectoryLookupAdapter, holdingsAdapter holdings.LookupAdapter, availabilityCreator holdings.AvailabilityCreator) SupplierLocator { return SupplierLocator{ eventBus: eventBus, illRepo: illRepo, @@ -54,8 +54,8 @@ func (s *SupplierLocator) CheckAvailability(ctx common.ExtendedContext, event ev _, _ = s.eventBus.ProcessTask(ctx, event, events.SignalConsumers, s.checkAvailability) } -func createHoldingsParams(illTransactionData ill_db.IllTransactionData) adapter.LookupParams { - var holdingsParams adapter.LookupParams +func createHoldingsParams(illTransactionData ill_db.IllTransactionData) holdings.LookupParams { + var holdingsParams holdings.LookupParams bibliographicInfo := illTransactionData.BibliographicInfo holdingsParams.Identifier = bibliographicInfo.SupplierUniqueRecordId holdingsParams.Title = bibliographicInfo.Title diff --git a/broker/service/supplierlocator_test.go b/broker/service/supplierlocator_test.go index 32f2f4b0..4157ac41 100644 --- a/broker/service/supplierlocator_test.go +++ b/broker/service/supplierlocator_test.go @@ -9,9 +9,9 @@ import ( "time" "github.com/indexdata/crosslink/broker/adapter" - "github.com/indexdata/crosslink/broker/availability" "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/broker/events" + "github.com/indexdata/crosslink/broker/holdings" "github.com/indexdata/crosslink/broker/ill_db" "github.com/indexdata/crosslink/broker/test/mocks" "github.com/indexdata/crosslink/directory" @@ -65,7 +65,7 @@ func TestGetNextSupplierEmptyMap(t *testing.T) { peerId := "p1" mockIllRepo := new(MockIllRepoRequester) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -91,7 +91,7 @@ func TestGetNextSupplierClosed(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId, SupplierSymbol: "ISIL:SUP"}}) assert.NoError(t, err) @@ -105,7 +105,7 @@ func TestGetNextSupplierFailToLoadPeer(t *testing.T) { peerId := "p1" mockIllRepo := new(MockIllRepoRequester) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{}, errors.New("db error")) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.Equal(t, "db error", err.Error()) @@ -123,7 +123,7 @@ func TestGetNextSupplierNoClosures(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -147,7 +147,7 @@ func TestGetNextSupplierNoStartDate(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -171,7 +171,7 @@ func TestGetNextSupplierNoEndDate(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -197,7 +197,7 @@ func TestGetNextSupplierBothInPast(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -223,7 +223,7 @@ func TestGetNextSupplierBothInFuture(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -248,7 +248,7 @@ func TestGetNextSupplierCannotParseDate(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -273,7 +273,7 @@ func TestGetNextSupplierCannotParseEndDate(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -307,7 +307,7 @@ func TestGetNextSupplierBetweenHolidays(t *testing.T) { err = json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "l1", SupplierID: peerId}}) assert.NoError(t, err) @@ -333,7 +333,7 @@ func TestGetNextSupplierClosedEventFailed(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(adapter.SruHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) status, result := locator.selectSupplier(appCtx, events.Event{IllTransactionID: "1"}) assert.Equal(t, events.EventStatusProblem, status) @@ -367,7 +367,7 @@ func TestLocateSuppliersDeduplicatesHoldingSymbolsForDirectoryLookup(t *testing. }, } - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.MockDirectoryLookupAdapter), new(adapter.MockHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.MockDirectoryLookupAdapter), new(holdings.MockHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) status, _ := locator.locateSuppliers(appCtx, events.Event{IllTransactionID: "ill-1"}) assert.Equal(t, events.EventStatusSuccess, status) @@ -394,7 +394,7 @@ func TestLocateSuppliersUsesFirstHoldingLocalIdentifierForDuplicateSymbol(t *tes }, } - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.MockDirectoryLookupAdapter), new(adapter.MockHoldingsLookupAdapter), new(availability.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.MockDirectoryLookupAdapter), new(holdings.MockHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) status, _ := locator.locateSuppliers(appCtx, events.Event{IllTransactionID: "ill-1"}) assert.Equal(t, events.EventStatusSuccess, status) diff --git a/broker/test/adapter/create_holdings_test.go b/broker/test/adapter/create_holdings_test.go index afb996a9..90cf6885 100644 --- a/broker/test/adapter/create_holdings_test.go +++ b/broker/test/adapter/create_holdings_test.go @@ -3,54 +3,54 @@ package adapter import ( "testing" - "github.com/indexdata/crosslink/broker/adapter" + "github.com/indexdata/crosslink/broker/holdings" "github.com/stretchr/testify/assert" ) func TestCreateHoldings(t *testing.T) { m := make(map[string]any) - _, err := adapter.CreateHoldingsLookupAdapter(m) + _, err := holdings.CreateHoldingsLookupShared(m) assert.Error(t, err) assert.ErrorContains(t, err, "missing value for HOLDINGS_ADAPTER") - m[adapter.HoldingsAdapter] = "sru" + m[holdings.HoldingsAdapter] = "sru" - _, err = adapter.CreateHoldingsLookupAdapter(m) + _, err = holdings.CreateHoldingsLookupShared(m) assert.ErrorContains(t, err, "missing value for HOLDINGS_SRU_URL") - m[adapter.HoldingsSruURL] = "http://example.com" - _, err = adapter.CreateHoldingsLookupAdapter(m) + m[holdings.HoldingsSruURL] = "http://example.com" + _, err = holdings.CreateHoldingsLookupShared(m) assert.Error(t, err) assert.ErrorContains(t, err, "missing value for HOLDINGS_ISXN_LOOKUP") - m[adapter.HoldingsIsxnLookup] = "fake" - _, err = adapter.CreateHoldingsLookupAdapter(m) + m[holdings.HoldingsIsxnLookup] = "fake" + _, err = holdings.CreateHoldingsLookupShared(m) assert.Error(t, err) assert.ErrorContains(t, err, "invalid value for HOLDINGS_ISXN_LOOKUP") - m[adapter.HoldingsIsxnLookup] = true - m[adapter.HoldingsFormat] = "reservoir" - _, err = adapter.CreateHoldingsLookupAdapter(m) + m[holdings.HoldingsIsxnLookup] = true + m[holdings.HoldingsFormat] = "reservoir" + _, err = holdings.CreateHoldingsLookupShared(m) assert.NoError(t, err) - m[adapter.HoldingsFormat] = "MARC-21plus-1" - _, err = adapter.CreateHoldingsLookupAdapter(m) + m[holdings.HoldingsFormat] = "MARC-21plus-1" + _, err = holdings.CreateHoldingsLookupShared(m) assert.NoError(t, err) - m[adapter.HoldingsFormat] = "other" - _, err = adapter.CreateHoldingsLookupAdapter(m) + m[holdings.HoldingsFormat] = "other" + _, err = holdings.CreateHoldingsLookupShared(m) assert.ErrorContains(t, err, "bad value for HOLDINGS_FORMAT: other") - m[adapter.HoldingsFormat] = true - _, err = adapter.CreateHoldingsLookupAdapter(m) + m[holdings.HoldingsFormat] = true + _, err = holdings.CreateHoldingsLookupShared(m) assert.ErrorContains(t, err, "missing value for HOLDINGS_FORMAT") - m["HOLDINGS_ADAPTER"] = "mock" - _, err = adapter.CreateHoldingsLookupAdapter(m) + m[holdings.HoldingsAdapter] = "mock" + _, err = holdings.CreateHoldingsLookupShared(m) assert.NoError(t, err) - m["HOLDINGS_ADAPTER"] = "other" - _, err = adapter.CreateHoldingsLookupAdapter(m) + m[holdings.HoldingsAdapter] = "other" + _, err = holdings.CreateHoldingsLookupShared(m) assert.ErrorContains(t, err, "bad value for HOLDINGS_ADAPTER") } diff --git a/broker/test/adapter/sru_holdings_test.go b/broker/test/adapter/sru_holdings_test.go index eeedc87a..fff64b74 100644 --- a/broker/test/adapter/sru_holdings_test.go +++ b/broker/test/adapter/sru_holdings_test.go @@ -6,17 +6,17 @@ import ( "net/http/httptest" "testing" - "github.com/indexdata/crosslink/broker/adapter" + "github.com/indexdata/crosslink/broker/holdings" "github.com/indexdata/crosslink/marcxml" "github.com/indexdata/crosslink/sru" "github.com/indexdata/crosslink/sru/diag" "github.com/stretchr/testify/assert" ) -func createSruAdapter(t *testing.T, isxn bool, url ...string) adapter.LookupAdapter { - parser := &adapter.ReservoirHoldingsParser{} - queryBuilder := adapter.NewQueryBuilderIsxn(isxn) - ad := adapter.CreateSruHoldingsLookupAdapter(http.DefaultClient, url, "", queryBuilder, parser, "marcxml") +func createSruAdapter(t *testing.T, isxn bool, url ...string) holdings.LookupAdapter { + parser := &holdings.ReservoirHoldingsParser{} + queryBuilder := holdings.NewQueryBuilderIsxn(isxn) + ad := holdings.CreateSruHoldingsLookupAdapter(http.DefaultClient, url, "", queryBuilder, parser, "marcxml") assert.NotNil(t, ad) return ad } @@ -30,7 +30,7 @@ func TestSru500(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -47,7 +47,7 @@ func TestSruBadXml(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -81,7 +81,7 @@ func TestSruBadDiagnostics(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -106,13 +106,13 @@ func TestSruMarcxmlNoHits(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } - holdings, query, err := ad.Lookup(p) + holdingsList, query, err := ad.Lookup(p) assert.NotEmpty(t, query) assert.NoError(t, err) - assert.Len(t, holdings, 0) + assert.Len(t, holdingsList, 0) } func TestSruMarcxmlStringEncoding(t *testing.T) { @@ -144,7 +144,7 @@ func TestSruMarcxmlStringEncoding(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -181,7 +181,7 @@ func TestSruMarcxmlUnsupportedSchema(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -218,7 +218,7 @@ func TestSruMarcxmlBadSurrogateDiagnostic(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -261,7 +261,7 @@ func TestSruMarcxmlOkSurrogateDiagnostic(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -300,7 +300,7 @@ func TestSruMarcxmlBadMarc(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -371,7 +371,7 @@ func TestSruMarcxmlWithFallbackHoldings(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } holdings, query, err := ad.Lookup(p) @@ -449,7 +449,7 @@ func TestSruMarcxmlWithHoldingsDoesNotUseFallback(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } holdings, query, err := ad.Lookup(p) @@ -512,7 +512,7 @@ func TestSruMarcxmlLeavesSchemedSymbolUnchanged(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } holdings, query, err := ad.Lookup(p) @@ -586,7 +586,7 @@ func TestSruMarcxmlUsesFallbackWhenPrimaryFieldHasNoUsableHolding(t *testing.T) defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } holdings, query, err := ad.Lookup(p) @@ -692,48 +692,48 @@ func TestSruMarcxmlWithHoldings(t *testing.T) { defer server.Close() ad := createSruAdapter(t, true, server.URL) - p := adapter.LookupParams{ + p := holdings.LookupParams{ Identifier: "123", } - holdings, query, err := ad.Lookup(p) + holdingsList, query, err := ad.Lookup(p) assert.NoError(t, err) assert.NotEmpty(t, query) assert.Equal(t, "rec.id = 123", receivedQuery) - assert.Len(t, holdings, 3) - assert.Equal(t, "l1", holdings[0].LocalIdentifier) - assert.Equal(t, "ISIL:s1", holdings[0].Symbol) - assert.Equal(t, "l2", holdings[1].LocalIdentifier) - assert.Equal(t, "ISIL:s2", holdings[1].Symbol) - assert.Equal(t, "l3", holdings[2].LocalIdentifier) - assert.Equal(t, "ISIL:s3", holdings[2].Symbol) + assert.Len(t, holdingsList, 3) + assert.Equal(t, "l1", holdingsList[0].LocalIdentifier) + assert.Equal(t, "ISIL:s1", holdingsList[0].Symbol) + assert.Equal(t, "l2", holdingsList[1].LocalIdentifier) + assert.Equal(t, "ISIL:s2", holdingsList[1].Symbol) + assert.Equal(t, "l3", holdingsList[2].LocalIdentifier) + assert.Equal(t, "ISIL:s3", holdingsList[2].Symbol) ad = createSruAdapter(t, true, server.URL, server.URL) - p = adapter.LookupParams{ + p = holdings.LookupParams{ Identifier: "123", Isbn: "99-222", Issn: "99-333", } - holdings, query, err = ad.Lookup(p) + holdingsList, query, err = ad.Lookup(p) assert.NoError(t, err) assert.NotEmpty(t, query) assert.Equal(t, "rec.id = 123 or isbn = 99-222 or issn = 99-333", receivedQuery) - assert.Len(t, holdings, 6) - assert.Equal(t, "l1", holdings[0].LocalIdentifier) - assert.Equal(t, "ISIL:s1", holdings[0].Symbol) - assert.Equal(t, "l2", holdings[1].LocalIdentifier) - assert.Equal(t, "ISIL:s2", holdings[1].Symbol) - assert.Equal(t, "l3", holdings[2].LocalIdentifier) - assert.Equal(t, "ISIL:s3", holdings[2].Symbol) - - assert.Equal(t, "l1", holdings[3].LocalIdentifier) - assert.Equal(t, "ISIL:s1", holdings[3].Symbol) - assert.Equal(t, "l2", holdings[4].LocalIdentifier) - assert.Equal(t, "ISIL:s2", holdings[4].Symbol) - assert.Equal(t, "l3", holdings[5].LocalIdentifier) - assert.Equal(t, "ISIL:s3", holdings[5].Symbol) + assert.Len(t, holdingsList, 6) + assert.Equal(t, "l1", holdingsList[0].LocalIdentifier) + assert.Equal(t, "ISIL:s1", holdingsList[0].Symbol) + assert.Equal(t, "l2", holdingsList[1].LocalIdentifier) + assert.Equal(t, "ISIL:s2", holdingsList[1].Symbol) + assert.Equal(t, "l3", holdingsList[2].LocalIdentifier) + assert.Equal(t, "ISIL:s3", holdingsList[2].Symbol) + + assert.Equal(t, "l1", holdingsList[3].LocalIdentifier) + assert.Equal(t, "ISIL:s1", holdingsList[3].Symbol) + assert.Equal(t, "l2", holdingsList[4].LocalIdentifier) + assert.Equal(t, "ISIL:s2", holdingsList[4].Symbol) + assert.Equal(t, "l3", holdingsList[5].LocalIdentifier) + assert.Equal(t, "ISIL:s3", holdingsList[5].Symbol) ad = createSruAdapter(t, false, server.URL) - p = adapter.LookupParams{ + p = holdings.LookupParams{ Isbn: "99-222", } _, _, err = ad.Lookup(p) diff --git a/broker/test/service/e2e_test.go b/broker/test/service/e2e_test.go index 12b2943d..7a05690b 100644 --- a/broker/test/service/e2e_test.go +++ b/broker/test/service/e2e_test.go @@ -13,8 +13,8 @@ import ( "testing" "time" - "github.com/indexdata/crosslink/broker/availability" "github.com/indexdata/crosslink/broker/events" + "github.com/indexdata/crosslink/broker/holdings" "github.com/indexdata/crosslink/broker/adapter" "github.com/indexdata/crosslink/broker/app" @@ -52,7 +52,7 @@ func TestMain(m *testing.M) { mockPort := utils.Must(test.GetFreePort()) app.HTTP_PORT = utils.Must(test.GetFreePort()) test.Expect(os.Setenv("PEER_URL", "http://localhost:"+strconv.Itoa(app.HTTP_PORT)+"/iso18626"), "failed to set peer URL") - app.AVAILABILITY_ADAPTER = availability.AvailabilityAdapterMock + app.AVAILABILITY_ADAPTER = holdings.AvailabilityAdapterMock apptest.StartMockApp(mockPort) app.ConnectionString = connStr From 79bafd0bf61142db1fb0a5cd25d018b87c5c16f7 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 17:16:29 +0200 Subject: [PATCH 13/35] Ensure prefix ISIL: for symbols in marc21plus1 parser --- broker/holdings/adapter_sru.go | 2 -- broker/holdings/parser_marc21_plus.go | 4 ++++ broker/holdings/parser_marc21_plus_test.go | 18 +++++++++--------- broker/holdings/parser_reservoir.go | 2 ++ 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/broker/holdings/adapter_sru.go b/broker/holdings/adapter_sru.go index d6cd5588..f7d9a764 100644 --- a/broker/holdings/adapter_sru.go +++ b/broker/holdings/adapter_sru.go @@ -22,8 +22,6 @@ type SruHoldingsLookupAdapter struct { recordSchema string } -const isilPrefix = "ISIL:" - func CreateSruHoldingsLookupAdapter(client *http.Client, sruUrl []string, xTarget string, queryBuilder LookupQueryBuilder, parser HoldingsParser, recordSchema string) LookupAdapter { return &SruHoldingsLookupAdapter{client: client, sruUrl: sruUrl, queryBuilder: queryBuilder, holdingsParser: parser, xTarget: xTarget, recordSchema: recordSchema} } diff --git a/broker/holdings/parser_marc21_plus.go b/broker/holdings/parser_marc21_plus.go index 4e8653d7..7cf72cac 100644 --- a/broker/holdings/parser_marc21_plus.go +++ b/broker/holdings/parser_marc21_plus.go @@ -71,6 +71,10 @@ func (p *Marc21Plus1HoldingsParser) Parse(record []byte, params LookupParams) ([ } if subfield.Code == "b" { symbol = strings.TrimSpace(string(subfield.Text)) + scheme, _, found := strings.Cut(symbol, ":") + if !found || strings.TrimSpace(scheme) == "" { + symbol = isilPrefix + symbol + } } if subfield.Code == "d" { // loan indicator indicator := strings.TrimSpace(string(subfield.Text)) diff --git a/broker/holdings/parser_marc21_plus_test.go b/broker/holdings/parser_marc21_plus_test.go index ef0759d4..8ac335e0 100644 --- a/broker/holdings/parser_marc21_plus_test.go +++ b/broker/holdings/parser_marc21_plus_test.go @@ -52,7 +52,7 @@ func TestMarc21Plus1HoldingsParser_no_namespace(t *testing.T) { assert.Len(t, holdings, 1) holding := holdings[0] assert.Equal(t, "LocalID123", holding.LocalIdentifier) - assert.Equal(t, "ISIL123", holding.Symbol) + assert.Equal(t, "ISIL:ISIL123", holding.Symbol) } func TestMarc21Plus1HoldingsParser_Parse_da(t *testing.T) { @@ -78,7 +78,7 @@ func TestMarc21Plus1HoldingsParser_Parse_da(t *testing.T) { assert.Len(t, holdings, 1) holding := holdings[0] assert.Equal(t, "LocalID123", holding.LocalIdentifier) - assert.Equal(t, "ISIL123", holding.Symbol) + assert.Equal(t, "ISIL:ISIL123", holding.Symbol) params = LookupParams{ ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), @@ -125,7 +125,7 @@ func TestMarc21Plus1HoldingsParser_Parse_db(t *testing.T) { assert.Len(t, holdings, 1) holding := holdings[0] assert.Equal(t, "LocalID123", holding.LocalIdentifier) - assert.Equal(t, "ISIL123", holding.Symbol) + assert.Equal(t, "ISIL:ISIL123", holding.Symbol) params = LookupParams{ ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), @@ -172,7 +172,7 @@ func TestMarc21Plus1HoldingsParser_Parse_dc(t *testing.T) { assert.Len(t, holdings, 1) holding := holdings[0] assert.Equal(t, "LocalID123", holding.LocalIdentifier) - assert.Equal(t, "ISIL123", holding.Symbol) + assert.Equal(t, "ISIL:ISIL123", holding.Symbol) params = LookupParams{ ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), @@ -263,7 +263,7 @@ func TestMarc21Plus1HoldingsParser_Parse_de(t *testing.T) { assert.Len(t, holdings, 1) holding := holdings[0] assert.Equal(t, "LocalID123", holding.LocalIdentifier) - assert.Equal(t, "ISIL123", holding.Symbol) + assert.Equal(t, "ISIL:ISIL123", holding.Symbol) params = LookupParams{ ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), @@ -294,7 +294,7 @@ func TestMarc21Plus1HoldingsParser_Parse_multiple(t *testing.T) { LocalID123 - ISIL123 + 123 Region1 e http://example.com/holding1 @@ -302,7 +302,7 @@ func TestMarc21Plus1HoldingsParser_Parse_multiple(t *testing.T) { LocalID124 - ISIL124 + 124 Region1 e http://example.com/holding2 @@ -318,10 +318,10 @@ func TestMarc21Plus1HoldingsParser_Parse_multiple(t *testing.T) { assert.Len(t, holdings, 2) holding := holdings[0] assert.Equal(t, "LocalID123", holding.LocalIdentifier) - assert.Equal(t, "ISIL123", holding.Symbol) + assert.Equal(t, "ISIL:123", holding.Symbol) holding = holdings[1] assert.Equal(t, "LocalID124", holding.LocalIdentifier) - assert.Equal(t, "ISIL124", holding.Symbol) + assert.Equal(t, "ISIL:124", holding.Symbol) params = LookupParams{ ServiceType: string(iso18626.TypeServiceTypeCopyOrLoan), diff --git a/broker/holdings/parser_reservoir.go b/broker/holdings/parser_reservoir.go index fa2ea5e0..6a775b98 100644 --- a/broker/holdings/parser_reservoir.go +++ b/broker/holdings/parser_reservoir.go @@ -8,6 +8,8 @@ import ( "github.com/indexdata/crosslink/marcxml" ) +const isilPrefix = "ISIL:" + type ReservoirHoldingsParser struct{} func NewReservoirHoldingsParser() HoldingsParser { From 884f620b73ccc671e51e5b5996a419e0e8e02b8b Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 17:41:22 +0200 Subject: [PATCH 14/35] CONSORTIA_SYMBOL --- broker/README.md | 2 ++ broker/app/app.go | 3 +- broker/service/supplierlocator.go | 40 ++++++++++++++++++-------- broker/service/supplierlocator_test.go | 28 +++++++++--------- 4 files changed, 46 insertions(+), 27 deletions(-) diff --git a/broker/README.md b/broker/README.md index 116749bc..8ecba2e9 100644 --- a/broker/README.md +++ b/broker/README.md @@ -105,6 +105,8 @@ Configuration is provided via environment variables: | `HOLDINGS_SRU_URL` | Comma separated list of URLs when `HOLDINGS_ADAPTER` is `sru` | `http://localhost:8081/sru` | | `HOLDINGS_ISXN_LOOKUP` | Whether to use ISBN/ISSN lookup for `sru` method | `false` | | `HOLDINGS_FORMAT` | Parser for SRU holdings: `reservoir` or `MARC-21plus-1` | `reservoir` | +| `CONSORTIA_SYMBOL` | Designates peer for which configuration is used for consortia. At this time, it is | (empty value) | +| | used when `HOLDINGS_ADAPTER` = `consortia`. | | | `DIRECTORY_ADAPTER` | Directory lookup method: `mock` or `api` | `mock` | | `DIRECTORY_API_URL` | Comma separated list of URLs when `DIRECTORY_ADAPTER` is `api` | `http://localhost:8081/directory/entries` | | `AVAILABILITY_ADAPTER` | Availability adapter: `mock` , `zoom`, `metaproxy`. | `zoom` | diff --git a/broker/app/app.go b/broker/app/app.go index fd1aed61..b454e4d8 100644 --- a/broker/app/app.go +++ b/broker/app/app.go @@ -65,6 +65,7 @@ var HOLDINGS_ADAPTER = utils.GetEnv("HOLDINGS_ADAPTER", "mock") var HOLDINGS_SRU_URL = common.GetEnvWithDeprecated("HOLDINGS_SRU_URL", "SRU_URL", "http://localhost:8081/sru") var HOLDINGS_ISXN_LOOKUP, _ = utils.GetEnvBool("HOLDINGS_ISXN_LOOKUP", false) var HOLDINGS_FORMAT = utils.GetEnv("HOLDINGS_FORMAT", "reservoir") +var CONSORTIA_SYMBOL = utils.GetEnv("CONSORTIA_SYMBOL", "") var DIRECTORY_ADAPTER = utils.GetEnv("DIRECTORY_ADAPTER", "mock") var AVAILABILITY_ADAPTER = utils.GetEnv("AVAILABILITY_ADAPTER", "zoom") var DIRECTORY_API_URL = utils.GetEnv("DIRECTORY_API_URL", "http://localhost:8081/directory/entries") @@ -180,7 +181,7 @@ func Init(ctx context.Context) (Context, error) { prActionService := prservice.CreatePatronRequestActionService(prRepo, eventBus, &iso18626Handler, lmsCreator) prMessageHandler.SetAutoActionRunner(prActionService) iso18626Client := client.CreateIso18626Client(eventBus, illRepo, prMessageHandler, MAX_MESSAGE_SIZE, delay) - supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, holdingsAdapter, availabilityCreator) + supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, holdingsAdapter, availabilityCreator, CONSORTIA_SYMBOL) workflowManager := service.CreateWorkflowManager(eventBus, illRepo, service.WorkflowConfig{}) tenantResolver := tenant.NewResolver().WithIllRepo(illRepo).WithLookupAdapter(dirAdapter).WithTenantToSymbol(TENANT_TO_SYMBOL) apiHandler := api.NewApiHandler(eventRepo, illRepo, *tenantResolver, API_PAGE_SIZE) diff --git a/broker/service/supplierlocator.go b/broker/service/supplierlocator.go index 5945ca27..e6a94558 100644 --- a/broker/service/supplierlocator.go +++ b/broker/service/supplierlocator.go @@ -27,15 +27,17 @@ type SupplierLocator struct { dirAdapter adapter.DirectoryLookupAdapter holdingsAdapter holdings.LookupAdapter availabilityCreator holdings.AvailabilityCreator + consortiaSymbol string } -func CreateSupplierLocator(eventBus events.EventBus, illRepo ill_db.IllRepo, dirAdapter adapter.DirectoryLookupAdapter, holdingsAdapter holdings.LookupAdapter, availabilityCreator holdings.AvailabilityCreator) SupplierLocator { +func CreateSupplierLocator(eventBus events.EventBus, illRepo ill_db.IllRepo, dirAdapter adapter.DirectoryLookupAdapter, holdingsAdapter holdings.LookupAdapter, availabilityCreator holdings.AvailabilityCreator, consortiaSymbol string) SupplierLocator { return SupplierLocator{ eventBus: eventBus, illRepo: illRepo, dirAdapter: dirAdapter, holdingsAdapter: holdingsAdapter, availabilityCreator: availabilityCreator, + consortiaSymbol: consortiaSymbol, } } @@ -74,6 +76,25 @@ func createHoldingsParams(illTransactionData ill_db.IllTransactionData) holdings return holdingsParams } +// 3 cases to consider for getting the adapter: +// 1. If holdingsAdapter is set from the start (for example for testing), use it directly +// 2. If consortiaSymbol is set, lookup the peer for the consortia and use its availability adapter +// 3. Otherwise, use the availability adapter for the requesting peer (if any) +func (s *SupplierLocator) getAdapterForConsortia(ctx common.ExtendedContext, requestPeer ill_db.Peer) (holdings.LookupAdapter, error) { + lookupAdapter := s.holdingsAdapter + if lookupAdapter != nil { + return lookupAdapter, nil + } + if s.consortiaSymbol == "" { + return s.availabilityCreator.GetAdapter(requestPeer) + } + peer, err := s.illRepo.GetPeerBySymbol(ctx, s.consortiaSymbol) + if err != nil { + return nil, err + } + return s.availabilityCreator.GetAdapter(peer) +} + func (s *SupplierLocator) locateSuppliers(ctx common.ExtendedContext, event events.Event) (events.EventStatus, *events.EventResult) { illTrans, err := s.illRepo.GetIllTransactionById(ctx, event.IllTransactionID) if err != nil { @@ -89,17 +110,12 @@ func (s *SupplierLocator) locateSuppliers(ctx common.ExtendedContext, event even if err != nil { return events.LogErrorAndReturnResult(ctx, "failed to read requester peer", err) } - lookupAdapter := s.holdingsAdapter - if lookupAdapter == nil { // null if consortia adapter is configured, in which case we need to determine adapter by directory - // TODO: use CONSORTIA_SYMBOL or determine parent from peer (if not already consirtia symbol) instead of requester symbol - lookupAdapter, err = s.availabilityCreator.GetAdapter(requester) - if err != nil { - return events.LogErrorAndReturnResult(ctx, "could not create availability adapter for requester", err) - } - if lookupAdapter == nil { - err := fmt.Errorf("GetAdapter for requester returned nil") - return events.LogErrorAndReturnResult(ctx, "no availability adapter for requester", err) - } + lookupAdapter, err := s.getAdapterForConsortia(ctx, requester) + if err != nil { + return events.LogErrorAndReturnResult(ctx, "failed to get adapter for consortia", err) + } + if lookupAdapter == nil { + return events.LogErrorAndReturnResult(ctx, "no holdings adapter available for consortia", fmt.Errorf("no adapter found")) } holdings, query, err := lookupAdapter.Lookup(holdingsParams) if err != nil { diff --git a/broker/service/supplierlocator_test.go b/broker/service/supplierlocator_test.go index 4157ac41..9faa542c 100644 --- a/broker/service/supplierlocator_test.go +++ b/broker/service/supplierlocator_test.go @@ -65,7 +65,7 @@ func TestGetNextSupplierEmptyMap(t *testing.T) { peerId := "p1" mockIllRepo := new(MockIllRepoRequester) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -91,7 +91,7 @@ func TestGetNextSupplierClosed(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId, SupplierSymbol: "ISIL:SUP"}}) assert.NoError(t, err) @@ -105,7 +105,7 @@ func TestGetNextSupplierFailToLoadPeer(t *testing.T) { peerId := "p1" mockIllRepo := new(MockIllRepoRequester) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{}, errors.New("db error")) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.Equal(t, "db error", err.Error()) @@ -123,7 +123,7 @@ func TestGetNextSupplierNoClosures(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -147,7 +147,7 @@ func TestGetNextSupplierNoStartDate(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -171,7 +171,7 @@ func TestGetNextSupplierNoEndDate(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -197,7 +197,7 @@ func TestGetNextSupplierBothInPast(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -223,7 +223,7 @@ func TestGetNextSupplierBothInFuture(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -248,7 +248,7 @@ func TestGetNextSupplierCannotParseDate(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -273,7 +273,7 @@ func TestGetNextSupplierCannotParseEndDate(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "1", SupplierID: peerId}}) assert.NoError(t, err) @@ -307,7 +307,7 @@ func TestGetNextSupplierBetweenHolidays(t *testing.T) { err = json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") locSup, skipped, err := locator.getNextSupplier(appCtx, []ill_db.LocatedSupplier{{ID: "l1", SupplierID: peerId}}) assert.NoError(t, err) @@ -333,7 +333,7 @@ func TestGetNextSupplierClosedEventFailed(t *testing.T) { err := json.Unmarshal([]byte(jsonData), &data) assert.NoError(t, err) mockIllRepo.On("GetPeerById", peerId).Return(ill_db.Peer{CustomData: data}, nil) - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.ApiDirectory), new(holdings.SruHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") status, result := locator.selectSupplier(appCtx, events.Event{IllTransactionID: "1"}) assert.Equal(t, events.EventStatusProblem, status) @@ -367,7 +367,7 @@ func TestLocateSuppliersDeduplicatesHoldingSymbolsForDirectoryLookup(t *testing. }, } - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.MockDirectoryLookupAdapter), new(holdings.MockHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.MockDirectoryLookupAdapter), new(holdings.MockHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") status, _ := locator.locateSuppliers(appCtx, events.Event{IllTransactionID: "ill-1"}) assert.Equal(t, events.EventStatusSuccess, status) @@ -394,7 +394,7 @@ func TestLocateSuppliersUsesFirstHoldingLocalIdentifierForDuplicateSymbol(t *tes }, } - locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.MockDirectoryLookupAdapter), new(holdings.MockHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl)) + locator := CreateSupplierLocator(new(events.PostgresEventBus), mockIllRepo, new(adapter.MockDirectoryLookupAdapter), new(holdings.MockHoldingsLookupAdapter), new(holdings.AvailabilityCreatorImpl), "") status, _ := locator.locateSuppliers(appCtx, events.Event{IllTransactionID: "ill-1"}) assert.Equal(t, events.EventStatusSuccess, status) From d74784f85f4fc006bd70a40d3dd0593ec8874c0a Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 17:41:59 +0200 Subject: [PATCH 15/35] Missing file --- broker/holdings/adapter_mock_shared.go | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 broker/holdings/adapter_mock_shared.go diff --git a/broker/holdings/adapter_mock_shared.go b/broker/holdings/adapter_mock_shared.go new file mode 100644 index 00000000..ab1d28ea --- /dev/null +++ b/broker/holdings/adapter_mock_shared.go @@ -0,0 +1,52 @@ +package holdings + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +type MockHoldingsLookupAdapter struct { +} + +// the original mock holdings adapter that we used for shared index testing +func (m *MockHoldingsLookupAdapter) Lookup(params LookupParams) ([]Holding, string, error) { + ids := strings.Split(params.Identifier, ";") + i := 1 + var holdings []Holding + for _, id := range ids { + if id == "error" { + return []Holding{}, id, errors.New("there is error") + } + if id == "not-found" { // we could also just not append? + return []Holding{}, id, nil + } + if strings.Index(id, "return-") == 0 { + val := strings.SplitN(strings.TrimPrefix(id, "return-"), "::", 2) + if len(val) < 1 || len(val[0]) < 1 { + return nil, id, fmt.Errorf("invalid return- value") + } + var s, l string + if len(val) == 1 { + s = val[0] + l = val[0] + } + if len(val) == 2 { + s = val[0] + l = val[1] + } + holdings = append(holdings, Holding{ + Symbol: s, + LocalIdentifier: l, + }) + } else { + holdings = append(holdings, Holding{ + Symbol: "ISIL:SUP" + strconv.Itoa(i), + LocalIdentifier: id, + }) + } + i++ + } + return holdings, params.Identifier, nil +} From dcc0f9b8faf1da975d8e1615f3e4624cbeec36be Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 17:46:32 +0200 Subject: [PATCH 16/35] directory.Cql --- broker/holdings/creator_shared.go | 2 +- broker/holdings/gvi_holdings_test.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/broker/holdings/creator_shared.go b/broker/holdings/creator_shared.go index e1e855ef..9827b928 100644 --- a/broker/holdings/creator_shared.go +++ b/broker/holdings/creator_shared.go @@ -47,7 +47,7 @@ func CreateHoldingsLookupShared(cfg map[string]any) (LookupAdapter, error) { // ideally this should be per-SRU server and not for all isxnLookup, ok := cfg[HoldingsIsxnLookup].(bool) if !ok { - return nil, fmt.Errorf("invalid value for %s: %v", HoldingsIsxnLookup, isxnLookup) + return nil, fmt.Errorf("invalid value for %s: %v", HoldingsIsxnLookup, cfg[HoldingsIsxnLookup]) } queryBuilder := QueryBuilderIsxn{isxn: isxnLookup} format, ok := cfg[HoldingsFormat].(string) diff --git a/broker/holdings/gvi_holdings_test.go b/broker/holdings/gvi_holdings_test.go index c81344c3..f347a4db 100644 --- a/broker/holdings/gvi_holdings_test.go +++ b/broker/holdings/gvi_holdings_test.go @@ -394,6 +394,7 @@ func TestGviHoldings(t *testing.T) { creator := NewAvailabilityCreator(AvailabilityAdapterZoom, "") + qtype := directory.Cql peer := ill_db.Peer{ CustomData: directory.Entry{ AvailabilityConfig: &directory.AvailabilityConfig{ @@ -405,7 +406,7 @@ func TestGviHoldings(t *testing.T) { }, }, QueryConfig: &directory.QueryConfig{ - Type: new(directory.Cql), + Type: &qtype, Identifier: NewString("rec.id = {term}"), }, ParserConfig: &directory.ParserConfig{ From 033f25fb50316115a7ede930dbed3e62ab1cfd7a Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 17:48:56 +0200 Subject: [PATCH 17/35] Guard against nil --- broker/holdings/query_builder_pqf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/broker/holdings/query_builder_pqf.go b/broker/holdings/query_builder_pqf.go index e5c070c3..93cef43c 100644 --- a/broker/holdings/query_builder_pqf.go +++ b/broker/holdings/query_builder_pqf.go @@ -88,7 +88,7 @@ func (s *QueryBuilderPqf) Build(params LookupParams) (cql []string, pqf []string var pqfList []string var cqlList []string for _, pm := range paramMappings { - if pm.value != "" { + if pm.value != "" && pm.mapping != nil { if s.config.Type != nil && *s.config.Type == directory.Cql { cql := strings.ReplaceAll(*pm.mapping, "{term}", cqlEncode(pm.value)) cqlList = append(cqlList, cql) From 2c1a8a78db0f56e0f8b31504e7e7df5de213a04a Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 17:54:36 +0200 Subject: [PATCH 18/35] Guard against nil Query --- zoom/zoom.go | 3 +++ zoom/zoom_test.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/zoom/zoom.go b/zoom/zoom.go index 61257ac8..fab69e9d 100644 --- a/zoom/zoom.go +++ b/zoom/zoom.go @@ -136,6 +136,9 @@ func (c *Connection) Search(query *Query) (*ResultSet, error) { if c.conn == nil { return nil, &ZoomError{Code: 0, Message: "connection is not established"} } + if query == nil { + return nil, &ZoomError{Code: 0, Message: "query is nil"} + } if query.zquery == nil { return nil, &ZoomError{Code: 0, Message: "query is not initialized"} } diff --git a/zoom/zoom_test.go b/zoom/zoom_test.go index e5bdaecb..b9a0ea29 100644 --- a/zoom/zoom_test.go +++ b/zoom/zoom_test.go @@ -90,6 +90,11 @@ func TestSearch(t *testing.T) { assert.NoError(t, err) query.Close() + + _, err = conn.Search(nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "query is nil") + _, err = conn.Search(query) assert.Error(t, err) assert.Contains(t, err.Error(), "query is not initialized") From 7a1ab27acade1d6b166fc5f3d1b6231062951e86 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 17:56:33 +0200 Subject: [PATCH 19/35] default is pqf --- directory/directory_api.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/directory/directory_api.yaml b/directory/directory_api.yaml index 1b020811..aabd3e02 100644 --- a/directory/directory_api.yaml +++ b/directory/directory_api.yaml @@ -306,6 +306,7 @@ components: enum: - cql - pqf + default: pqf description: whether mapping is CQL or PQF. Default is PQF. title: type: string From 25ffa528729791a384e4901c79006f96e302881e Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 18:06:37 +0200 Subject: [PATCH 20/35] Remove useless source --- broker/holdings/adapter_sri.go | 29 ----------------------------- broker/holdings/adapter_sru.go | 12 ++++++++++++ broker/holdings/creator_test.go | 2 +- 3 files changed, 13 insertions(+), 30 deletions(-) delete mode 100644 broker/holdings/adapter_sri.go diff --git a/broker/holdings/adapter_sri.go b/broker/holdings/adapter_sri.go deleted file mode 100644 index 510f0cae..00000000 --- a/broker/holdings/adapter_sri.go +++ /dev/null @@ -1,29 +0,0 @@ -package holdings - -import ( - "net/http" - - "github.com/indexdata/crosslink/directory" -) - -type SruAvailabilityAdapter struct { - holdingsLookupAdapter LookupAdapter -} - -func NewSruAvailabilityAdapter(config directory.SruConfig, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser) (LookupAdapter, error) { - var recordSchema string - if config.RecordSchema != nil { - recordSchema = *config.RecordSchema - } - if recordSchema == "" { - recordSchema = "marcxml" // default to marcxml if not specified - } - a := &SruAvailabilityAdapter{ - holdingsLookupAdapter: CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{config.Address}, "", queryBuilder, holdingsParser, recordSchema), - } - return a, nil -} - -func (a *SruAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string, error) { - return a.holdingsLookupAdapter.Lookup(params) -} diff --git a/broker/holdings/adapter_sru.go b/broker/holdings/adapter_sru.go index f7d9a764..3a29de20 100644 --- a/broker/holdings/adapter_sru.go +++ b/broker/holdings/adapter_sru.go @@ -8,6 +8,7 @@ import ( "net/url" "github.com/indexdata/cql-go/cqlbuilder" + "github.com/indexdata/crosslink/directory" "github.com/indexdata/crosslink/httpclient" "github.com/indexdata/crosslink/sru" "github.com/indexdata/crosslink/sru/diag" @@ -26,6 +27,17 @@ func CreateSruHoldingsLookupAdapter(client *http.Client, sruUrl []string, xTarge return &SruHoldingsLookupAdapter{client: client, sruUrl: sruUrl, queryBuilder: queryBuilder, holdingsParser: parser, xTarget: xTarget, recordSchema: recordSchema} } +func NewSruAvailabilityAdapter(config directory.SruConfig, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser) (LookupAdapter, error) { + var recordSchema string + if config.RecordSchema != nil { + recordSchema = *config.RecordSchema + } + if recordSchema == "" { + recordSchema = "marcxml" // default to marcxml if not specified + } + return CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{config.Address}, "", queryBuilder, holdingsParser, recordSchema), nil +} + func (s *SruHoldingsLookupAdapter) parseRecord(record *sru.RecordDefinition, params LookupParams, holdings *[]Holding) error { if record.RecordXMLEscaping != nil && *record.RecordXMLEscaping != sru.RecordXMLEscapingDefinitionXml { return fmt.Errorf("unsupported RecordXMLEscaping: %s", *record.RecordXMLEscaping) diff --git a/broker/holdings/creator_test.go b/broker/holdings/creator_test.go index d0566c5a..aeb40b1a 100644 --- a/broker/holdings/creator_test.go +++ b/broker/holdings/creator_test.go @@ -183,5 +183,5 @@ func TestGetAdapterSRU(t *testing.T) { creator := NewAvailabilityCreator(AvailabilityAdapterZoom, "") aa, err := creator.GetAdapter(peer) assert.NoError(t, err) - assert.IsType(t, &SruAvailabilityAdapter{}, aa) + assert.IsType(t, &SruHoldingsLookupAdapter{}, aa) } From 984e7defcb542955648449bf8332b711942aa1ae Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 18:11:54 +0200 Subject: [PATCH 21/35] query.Close() --- zoom/zoom_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zoom/zoom_test.go b/zoom/zoom_test.go index b9a0ea29..1c0e553e 100644 --- a/zoom/zoom_test.go +++ b/zoom/zoom_test.go @@ -102,6 +102,7 @@ func TestSearch(t *testing.T) { query, err = NewPqfQuery("@attr 1=4 computer") assert.NoError(t, err) assert.NotNil(t, query) + defer query.Close() rs, err := conn.Search(query) assert.NoError(t, err) @@ -155,6 +156,7 @@ func TestSearchUnsupportedSyntaxOnSearch(t *testing.T) { query, err := NewPqfQuery("@attr 1=4 computer") assert.NoError(t, err) assert.NotNil(t, query) + defer query.Close() // getting non-surrogate diagnostic for unsupported record syntax @@ -179,6 +181,7 @@ func TestSearchUnsupportedSyntaxOnPresent(t *testing.T) { query, err := NewPqfQuery("@attr 1=4 computer") assert.NoError(t, err) assert.NotNil(t, query) + defer query.Close() rs, err := conn.Search(query) assert.NoError(t, err) @@ -205,6 +208,7 @@ func TestSearchSurrogateDiagnostic(t *testing.T) { query, err := NewPqfQuery("@attr 1=4 computer") assert.NoError(t, err) assert.NotNil(t, query) + defer query.Close() rs, err := conn.Search(query) assert.NoError(t, err) From c0074b707216860f2ad2da1fbbc422d372b3e1f3 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 May 2026 18:15:04 +0200 Subject: [PATCH 22/35] Returned non-hitting cql/pqf query --- broker/holdings/adapter_zoom.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/broker/holdings/adapter_zoom.go b/broker/holdings/adapter_zoom.go index 29c84b56..3ed30607 100644 --- a/broker/holdings/adapter_zoom.go +++ b/broker/holdings/adapter_zoom.go @@ -110,5 +110,8 @@ func (a *ZoomAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string return avail, cql, nil } } - return nil, pqfList[0], nil + if len(pqfList) > 0 { + return nil, pqfList[0], nil + } + return nil, cqlList[0], nil } From 12adceb96bb2df2c88d858840c85741dcf1cbc63 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 13:10:59 +0200 Subject: [PATCH 23/35] Rename --- broker/holdings/adapter_zoom_test.go | 8 ++++---- broker/holdings/creator_impl.go | 2 +- ...builder_pqf.go => query_builder_general.go} | 10 +++++----- go.work.sum | 18 +++++++++++++----- 4 files changed, 23 insertions(+), 15 deletions(-) rename broker/holdings/{query_builder_pqf.go => query_builder_general.go} (90%) diff --git a/broker/holdings/adapter_zoom_test.go b/broker/holdings/adapter_zoom_test.go index 1eccd8bb..f547be17 100644 --- a/broker/holdings/adapter_zoom_test.go +++ b/broker/holdings/adapter_zoom_test.go @@ -39,7 +39,7 @@ func TestLookupFoundMarc(t *testing.T) { MainField: NewString("010"), LocationSubField: NewString("a"), } - queryBuilder, err := NewQueryBuilder(&directory.QueryConfig{ + queryBuilder, err := NewQueryBuilderGen(&directory.QueryConfig{ Title: NewString("@attr 1=1016 {term}"), }) assert.NoError(t, err) @@ -70,7 +70,7 @@ func TestLookupFoundMarc(t *testing.T) { } func TestLookupFoundOpac(t *testing.T) { - queryBuilder, err := NewQueryBuilder(nil) + queryBuilder, err := NewQueryBuilderGen(nil) assert.NoError(t, err) holdingsParser := NewOpacHoldingsParser(directory.OpacParserConfig{}) aa, err := NewZoomAvailabilityAdapter( @@ -99,7 +99,7 @@ func TestLookupFoundOpac(t *testing.T) { } func TestLookupDiagnostics(t *testing.T) { - queryBuilder, err := NewQueryBuilder(nil) + queryBuilder, err := NewQueryBuilderGen(nil) assert.NoError(t, err) holdingsParser := NewMarcHoldingsParser(directory.MarcParserConfig{}) aa, err := NewZoomAvailabilityAdapter( @@ -124,7 +124,7 @@ func TestLookupDiagnostics(t *testing.T) { } func TestConnectFailure(t *testing.T) { - queryBuilder, err := NewQueryBuilder(nil) + queryBuilder, err := NewQueryBuilderGen(nil) assert.NoError(t, err) holdingsParser := NewMarcHoldingsParser(directory.MarcParserConfig{}) aa, err := NewZoomAvailabilityAdapter( diff --git a/broker/holdings/creator_impl.go b/broker/holdings/creator_impl.go index d648aad8..e28c7987 100644 --- a/broker/holdings/creator_impl.go +++ b/broker/holdings/creator_impl.go @@ -57,7 +57,7 @@ func (c *AvailabilityCreatorImpl) GetAdapter(peer ill_db.Peer) (LookupAdapter, e if err != nil { return nil, err } - queryBuilder, err := NewQueryBuilder(config.QueryConfig) + queryBuilder, err := NewQueryBuilderGen(config.QueryConfig) if err != nil { return nil, err } diff --git a/broker/holdings/query_builder_pqf.go b/broker/holdings/query_builder_general.go similarity index 90% rename from broker/holdings/query_builder_pqf.go rename to broker/holdings/query_builder_general.go index 93cef43c..716099b1 100644 --- a/broker/holdings/query_builder_pqf.go +++ b/broker/holdings/query_builder_general.go @@ -8,7 +8,7 @@ import ( "github.com/indexdata/crosslink/directory" ) -type QueryBuilderPqf struct { +type QueryBuilderGen struct { config directory.QueryConfig } @@ -19,7 +19,7 @@ func NewString(s string) *string { return nil } -func NewQueryBuilder(queryConfig *directory.QueryConfig) (*QueryBuilderPqf, error) { +func NewQueryBuilderGen(queryConfig *directory.QueryConfig) (LookupQueryBuilder, error) { var config directory.QueryConfig if queryConfig != nil { config = *queryConfig @@ -32,7 +32,7 @@ func NewQueryBuilder(queryConfig *directory.QueryConfig) (*QueryBuilderPqf, erro config.Issn = NewString("@attr 1=8 {term}") config.Title = NewString("@attr 1=4 {term}") } - return &QueryBuilderPqf{config: config}, nil + return &QueryBuilderGen{config: config}, nil } if *config.Type == directory.Cql { if config.Identifier == nil && config.Isbn == nil && config.Issn == nil && config.Title == nil { @@ -42,7 +42,7 @@ func NewQueryBuilder(queryConfig *directory.QueryConfig) (*QueryBuilderPqf, erro config.Issn = NewString("issn = {term}") config.Title = NewString("title = {term}") } - return &QueryBuilderPqf{config: config}, nil + return &QueryBuilderGen{config: config}, nil } return nil, fmt.Errorf("unsupported query builder type: %s", *config.Type) } @@ -73,7 +73,7 @@ func cqlEncode(value string) string { return escaped } -func (s *QueryBuilderPqf) Build(params LookupParams) (cql []string, pqf []string, err error) { +func (s *QueryBuilderGen) Build(params LookupParams) (cql []string, pqf []string, err error) { type paramMapping struct { value string mapping *string diff --git a/go.work.sum b/go.work.sum index 14e7901b..934506a9 100644 --- a/go.work.sum +++ b/go.work.sum @@ -303,6 +303,7 @@ github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3/go.mod h1:dppbR7CwXD4p github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= @@ -498,6 +499,7 @@ github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuA github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 h1:OGNva6WhsKst5OZf7eZOklDztV3hwtTHovdrLHV+MsA= @@ -899,6 +901,8 @@ github.com/nats-io/jwt/v2 v2.5.0/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+v github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk= +github.com/ncruces/wbt v1.0.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c= github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba h1:fhFP5RliM2HW/8XdcO5QngSfFli9GcRIpMXvypTQt6E= github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -943,11 +947,13 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -1182,7 +1188,6 @@ golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1202,7 +1207,6 @@ golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -1260,7 +1264,6 @@ golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ= @@ -1298,7 +1301,6 @@ golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= @@ -1320,7 +1322,6 @@ golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1401,6 +1402,7 @@ kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 h1:sPQ9qlSNR26fToTKbxe/HDW kernel.org/pub/linux/libs/security/libcap/cap v1.2.67/go.mod h1:GkntoBuwffz19qtdFVB+k2NtWNN+yCKnC/Ykv/hMiTU= kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 h1:NxbXJ7pDVq0FKBsqjieT92QDXI2XaqH2HAi4QcCOHt8= kernel.org/pub/linux/libs/security/libcap/psx v1.2.67/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= +lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/b v1.0.0 h1:vpvqeyp17ddcQWF29Czawql4lDdABCDRbXRAS4+aF2o= @@ -1417,6 +1419,7 @@ modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w= modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= modernc.org/golex v1.0.0 h1:wWpDlbK8ejRfSyi0frMyhilD3JBvtcx2AdGDnU+JtsE= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/golex v1.1.0/go.mod h1:2pVlfqApurXhR1m0N+WDYu6Twnc4QuvO4+U8HnwoiRA= modernc.org/internal v1.0.0 h1:XMDsFDcBDsibbBnHB2xzljZ+B1yrOVLEFkKL2u15Glw= modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= modernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI= @@ -1425,20 +1428,25 @@ modernc.org/lldb v1.0.0 h1:6vjDJxQEfhlOLwl4bhpwIz00uyFK4EmSYcbwqwbynsc= modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/parser v1.1.0/go.mod h1:CXl3OTJRZij8FeMpzI3Id/bjupHf0u9HSrCUP4Z9pbA= modernc.org/ql v1.0.0 h1:bIQ/trWNVjQPlinI6jdOQsi195SIturGo3mp5hsDqVU= modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= modernc.org/sortutil v1.1.0 h1:oP3U4uM+NT/qBQcbg/K2iqAX0Nx7B1b6YZtq3Gk/PjM= modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/y v1.1.0/go.mod h1:Iz3BmyIS4OwAbwGaUS7cqRrLsSsfp2sFWtpzX+P4CsE= modernc.org/zappy v1.0.0 h1:dPVaP+3ueIUv4guk8PuZ2wiUGcJ1WUVvIheeSSTD0yk= modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= From f2cf1347e8bcd58db743d23076423c5be7ff9ed5 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 13:12:50 +0200 Subject: [PATCH 24/35] Returns LookupQueryBuilder --- broker/holdings/query_builder_isxn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/broker/holdings/query_builder_isxn.go b/broker/holdings/query_builder_isxn.go index f72f50bf..eb58a0bf 100644 --- a/broker/holdings/query_builder_isxn.go +++ b/broker/holdings/query_builder_isxn.go @@ -9,7 +9,7 @@ type QueryBuilderIsxn struct { isxn bool } -func NewQueryBuilderIsxn(isxn bool) *QueryBuilderIsxn { +func NewQueryBuilderIsxn(isxn bool) LookupQueryBuilder { return &QueryBuilderIsxn{isxn: isxn} } From fbe224b7223f1c7967a5eeede19cc6bcbb07caec Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 13:13:44 +0200 Subject: [PATCH 25/35] Revert go.work.sum change --- go.work.sum | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/go.work.sum b/go.work.sum index 934506a9..14e7901b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -303,7 +303,6 @@ github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3/go.mod h1:dppbR7CwXD4p github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= @@ -499,7 +498,6 @@ github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuA github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= -github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 h1:OGNva6WhsKst5OZf7eZOklDztV3hwtTHovdrLHV+MsA= @@ -901,8 +899,6 @@ github.com/nats-io/jwt/v2 v2.5.0/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+v github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk= -github.com/ncruces/wbt v1.0.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c= github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba h1:fhFP5RliM2HW/8XdcO5QngSfFli9GcRIpMXvypTQt6E= github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -947,13 +943,11 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -1188,6 +1182,7 @@ golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1207,6 +1202,7 @@ golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -1264,6 +1260,7 @@ golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ= @@ -1301,6 +1298,7 @@ golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= @@ -1322,6 +1320,7 @@ golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1402,7 +1401,6 @@ kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 h1:sPQ9qlSNR26fToTKbxe/HDW kernel.org/pub/linux/libs/security/libcap/cap v1.2.67/go.mod h1:GkntoBuwffz19qtdFVB+k2NtWNN+yCKnC/Ykv/hMiTU= kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 h1:NxbXJ7pDVq0FKBsqjieT92QDXI2XaqH2HAi4QcCOHt8= kernel.org/pub/linux/libs/security/libcap/psx v1.2.67/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= -lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/b v1.0.0 h1:vpvqeyp17ddcQWF29Czawql4lDdABCDRbXRAS4+aF2o= @@ -1419,7 +1417,6 @@ modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w= modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= modernc.org/golex v1.0.0 h1:wWpDlbK8ejRfSyi0frMyhilD3JBvtcx2AdGDnU+JtsE= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/golex v1.1.0/go.mod h1:2pVlfqApurXhR1m0N+WDYu6Twnc4QuvO4+U8HnwoiRA= modernc.org/internal v1.0.0 h1:XMDsFDcBDsibbBnHB2xzljZ+B1yrOVLEFkKL2u15Glw= modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= modernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI= @@ -1428,25 +1425,20 @@ modernc.org/lldb v1.0.0 h1:6vjDJxQEfhlOLwl4bhpwIz00uyFK4EmSYcbwqwbynsc= modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/parser v1.1.0/go.mod h1:CXl3OTJRZij8FeMpzI3Id/bjupHf0u9HSrCUP4Z9pbA= modernc.org/ql v1.0.0 h1:bIQ/trWNVjQPlinI6jdOQsi195SIturGo3mp5hsDqVU= modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= modernc.org/sortutil v1.1.0 h1:oP3U4uM+NT/qBQcbg/K2iqAX0Nx7B1b6YZtq3Gk/PjM= modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/y v1.1.0/go.mod h1:Iz3BmyIS4OwAbwGaUS7cqRrLsSsfp2sFWtpzX+P4CsE= modernc.org/zappy v1.0.0 h1:dPVaP+3ueIUv4guk8PuZ2wiUGcJ1WUVvIheeSSTD0yk= modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= From b09ac589fe6b091ece54abbc9115aea609f65133 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 13:34:49 +0200 Subject: [PATCH 26/35] Test NewCqlQuery --- zoom/zoom.go | 17 ++++++----------- zoom/zoom_test.go | 7 ++++++- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/zoom/zoom.go b/zoom/zoom.go index fab69e9d..9b76c201 100644 --- a/zoom/zoom.go +++ b/zoom/zoom.go @@ -66,14 +66,7 @@ func NewPqfQuery(pqf string) (*Query, error) { defer C.free(unsafe.Pointer(cPqf)) query := &Query{zquery: C.ZOOM_query_create()} - runtime.SetFinalizer(query, (*Query).finalize) - - ret := C.ZOOM_query_prefix(query.zquery, cPqf) - if ret != 0 { - query.finalize() - return nil, &ZoomError{Code: int(ret), Message: "failed to create PQF query"} - } - return query, nil + return checkQuery(C.ZOOM_query_prefix(query.zquery, cPqf), query) } func NewCqlQuery(cql string) (*Query, error) { @@ -81,13 +74,15 @@ func NewCqlQuery(cql string) (*Query, error) { defer C.free(unsafe.Pointer(cCql)) query := &Query{zquery: C.ZOOM_query_create()} - runtime.SetFinalizer(query, (*Query).finalize) + return checkQuery(C.ZOOM_query_cql(query.zquery, cCql), query) +} - ret := C.ZOOM_query_cql(query.zquery, cCql) +func checkQuery(ret C.int, query *Query) (*Query, error) { if ret != 0 { query.finalize() - return nil, &ZoomError{Code: int(ret), Message: "failed to create CQL query"} + return nil, &ZoomError{Code: 0, Message: "failed to create query"} } + runtime.SetFinalizer(query, (*Query).finalize) return query, nil } diff --git a/zoom/zoom_test.go b/zoom/zoom_test.go index 1c0e553e..b7b39fc3 100644 --- a/zoom/zoom_test.go +++ b/zoom/zoom_test.go @@ -65,7 +65,7 @@ func TestPqfQuery(t *testing.T) { _, err = NewPqfQuery("@attr 1=4") assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to create PQF query") + assert.Contains(t, err.Error(), "failed to create query") } func TestSearch(t *testing.T) { @@ -135,6 +135,11 @@ func TestSearch(t *testing.T) { _, err = rs.GetRecord(0) assert.Error(t, err) assert.Contains(t, err.Error(), "result set is not available") + + query, err = NewCqlQuery("computer") + assert.NoError(t, err) + assert.NotNil(t, query) + defer query.Close() } func TestRecordData(t *testing.T) { From 5dea81a48fa9b7ed93290a23477c67f8ed9a5e67 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 14:00:52 +0200 Subject: [PATCH 27/35] QueryBuilderGen testing --- broker/holdings/query_builder_general.go | 12 +-- broker/holdings/query_builder_general_test.go | 87 +++++++++++++++++++ 2 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 broker/holdings/query_builder_general_test.go diff --git a/broker/holdings/query_builder_general.go b/broker/holdings/query_builder_general.go index 716099b1..2689fa01 100644 --- a/broker/holdings/query_builder_general.go +++ b/broker/holdings/query_builder_general.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/indexdata/cql-go/cqlbuilder" "github.com/indexdata/crosslink/directory" ) @@ -61,16 +62,7 @@ func pqfEncode(value string) string { } func cqlEncode(value string) string { - // escape backslashes and double quotes - escaped := "\"" - for _, r := range value { - if r == '\\' || r == '"' { - escaped += "\\" - } - escaped += string(r) - } - escaped += "\"" - return escaped + return "\"" + cqlbuilder.EscapeMaskingChars(cqlbuilder.EscapeSpecialChars(value)) + "\"" } func (s *QueryBuilderGen) Build(params LookupParams) (cql []string, pqf []string, err error) { diff --git a/broker/holdings/query_builder_general_test.go b/broker/holdings/query_builder_general_test.go new file mode 100644 index 00000000..b8fe8a80 --- /dev/null +++ b/broker/holdings/query_builder_general_test.go @@ -0,0 +1,87 @@ +package holdings + +import ( + "testing" + + "github.com/indexdata/crosslink/directory" + "github.com/stretchr/testify/assert" +) + +func TestNewQueryBuilderGen(t *testing.T) { + // Test with nil config (should use default PQF mappings) + qb, err := NewQueryBuilderGen(nil) + assert.NoError(t, err) + assert.NotNil(t, qb) + + gg := (qb).(*QueryBuilderGen) + assert.Equal(t, "@attr 1=12 {term}", *gg.config.Identifier) + assert.Equal(t, "@attr 1=7 {term}", *gg.config.Isbn) + assert.Equal(t, "@attr 1=8 {term}", *gg.config.Issn) + assert.Equal(t, "@attr 1=4 {term}", *gg.config.Title) + + // Test with empty config (should use default PQF mappings) + qb, err = NewQueryBuilderGen(&directory.QueryConfig{}) + assert.NoError(t, err) + assert.NotNil(t, qb) + gg = (qb).(*QueryBuilderGen) + assert.Equal(t, "@attr 1=12 {term}", *gg.config.Identifier) + assert.Equal(t, "@attr 1=7 {term}", *gg.config.Isbn) + assert.Equal(t, "@attr 1=8 {term}", *gg.config.Issn) + assert.Equal(t, "@attr 1=4 {term}", *gg.config.Title) + + // Test with CQL type and no mappings (should use default CQL mappings) + cqlType := directory.Cql + qb, err = NewQueryBuilderGen(&directory.QueryConfig{Type: &cqlType}) + assert.NoError(t, err) + assert.NotNil(t, qb) + gg = (qb).(*QueryBuilderGen) + assert.Equal(t, "rec.id = {term}", *gg.config.Identifier) + assert.Equal(t, "isbn = {term}", *gg.config.Isbn) + assert.Equal(t, "issn = {term}", *gg.config.Issn) + assert.Equal(t, "title = {term}", *gg.config.Title) + + cql, pqf, err := qb.Build(LookupParams{Identifier: "12345", Title: "Test Title"}) + assert.NoError(t, err) + assert.Len(t, pqf, 0) + assert.Equal(t, []string{"rec.id = \"12345\"", "title = \"Test Title\""}, cql) + + // Test with CQL type and one mapping + qb, err = NewQueryBuilderGen(&directory.QueryConfig{ + Type: &cqlType, + Identifier: NewString("id == {term}"), + }) + assert.NoError(t, err) + assert.NotNil(t, qb) + gg = (qb).(*QueryBuilderGen) + assert.Equal(t, "id == {term}", *gg.config.Identifier) + assert.Nil(t, gg.config.Isbn) + assert.Nil(t, gg.config.Issn) + assert.Nil(t, gg.config.Title) + cql, pqf, err = qb.Build(LookupParams{Identifier: "12345", Title: "Test Title"}) + assert.NoError(t, err) + assert.Len(t, pqf, 0) + assert.Equal(t, []string{"id == \"12345\""}, cql) + + // Test with unsupported type + unsupportedType := directory.QueryConfigType("unsupported") + qb, err = NewQueryBuilderGen(&directory.QueryConfig{Type: &unsupportedType}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unsupported query builder type") + assert.Nil(t, qb) +} + +func TestPqfEncode(t *testing.T) { + assert.Equal(t, "\"computer\"", pqfEncode("computer")) + assert.Equal(t, "\"co?puter*\"", pqfEncode("co?puter*")) + assert.Equal(t, "\"comp\\\"uter\"", pqfEncode("comp\"uter")) + assert.Equal(t, "\"comp\\\\uter\"", pqfEncode("comp\\uter")) + assert.Equal(t, "\"comp\\\\\\\"uter\"", pqfEncode("comp\\\"uter")) +} + +func TestCqlEncode(t *testing.T) { + assert.Equal(t, "\"computer\"", cqlEncode("computer")) + assert.Equal(t, "\"co\\?puter\\*\"", cqlEncode("co?puter*")) + assert.Equal(t, "\"comp\\\"uter\"", cqlEncode("comp\"uter")) + assert.Equal(t, "\"comp\\\\uter\"", cqlEncode("comp\\uter")) + assert.Equal(t, "\"comp\\\\\\\"uter\"", cqlEncode("comp\\\"uter")) +} From 15213dd86f8e51921777dd926d2e31e683a152a7 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 14:19:56 +0200 Subject: [PATCH 28/35] Tweak error messages --- broker/service/supplierlocator.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/broker/service/supplierlocator.go b/broker/service/supplierlocator.go index e6a94558..80342f85 100644 --- a/broker/service/supplierlocator.go +++ b/broker/service/supplierlocator.go @@ -79,7 +79,7 @@ func createHoldingsParams(illTransactionData ill_db.IllTransactionData) holdings // 3 cases to consider for getting the adapter: // 1. If holdingsAdapter is set from the start (for example for testing), use it directly // 2. If consortiaSymbol is set, lookup the peer for the consortia and use its availability adapter -// 3. Otherwise, use the availability adapter for the requesting peer (if any) +// 3. Otherwise, use the availability adapter for the requesting peer func (s *SupplierLocator) getAdapterForConsortia(ctx common.ExtendedContext, requestPeer ill_db.Peer) (holdings.LookupAdapter, error) { lookupAdapter := s.holdingsAdapter if lookupAdapter != nil { @@ -112,10 +112,10 @@ func (s *SupplierLocator) locateSuppliers(ctx common.ExtendedContext, event even } lookupAdapter, err := s.getAdapterForConsortia(ctx, requester) if err != nil { - return events.LogErrorAndReturnResult(ctx, "failed to get adapter for consortia", err) + return events.LogErrorAndReturnResult(ctx, "failed to get holdings adapter for locating suppliers", err) } if lookupAdapter == nil { - return events.LogErrorAndReturnResult(ctx, "no holdings adapter available for consortia", fmt.Errorf("no adapter found")) + return events.LogErrorAndReturnResult(ctx, "no holdings adapter available for locating suppliers", fmt.Errorf("no adapter found")) } holdings, query, err := lookupAdapter.Lookup(holdingsParams) if err != nil { From 7428e29fbee3635a9c532895f5b0d715c566a221 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 14:40:58 +0200 Subject: [PATCH 29/35] Default per field --- broker/holdings/query_builder_general.go | 20 ++++++++++++++----- broker/holdings/query_builder_general_test.go | 8 +++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/broker/holdings/query_builder_general.go b/broker/holdings/query_builder_general.go index 2689fa01..b60e8014 100644 --- a/broker/holdings/query_builder_general.go +++ b/broker/holdings/query_builder_general.go @@ -26,21 +26,31 @@ func NewQueryBuilderGen(queryConfig *directory.QueryConfig) (LookupQueryBuilder, config = *queryConfig } if config.Type == nil || *config.Type == directory.Pqf { - if config.Identifier == nil && config.Isbn == nil && config.Issn == nil && config.Title == nil { - // if no specific mappings are provided, we set default PQF mappings + if config.Identifier == nil { config.Identifier = NewString("@attr 1=12 {term}") + } + if config.Isbn == nil { config.Isbn = NewString("@attr 1=7 {term}") + } + if config.Issn == nil { config.Issn = NewString("@attr 1=8 {term}") + } + if config.Title == nil { config.Title = NewString("@attr 1=4 {term}") } return &QueryBuilderGen{config: config}, nil } if *config.Type == directory.Cql { - if config.Identifier == nil && config.Isbn == nil && config.Issn == nil && config.Title == nil { - // if no specific mappings are provided, we set default CQL mappings + if config.Identifier == nil { config.Identifier = NewString("rec.id = {term}") + } + if config.Isbn == nil { config.Isbn = NewString("isbn = {term}") + } + if config.Issn == nil { config.Issn = NewString("issn = {term}") + } + if config.Title == nil { config.Title = NewString("title = {term}") } return &QueryBuilderGen{config: config}, nil @@ -80,7 +90,7 @@ func (s *QueryBuilderGen) Build(params LookupParams) (cql []string, pqf []string var pqfList []string var cqlList []string for _, pm := range paramMappings { - if pm.value != "" && pm.mapping != nil { + if pm.value != "" && pm.mapping != nil && *pm.mapping != "" { if s.config.Type != nil && *s.config.Type == directory.Cql { cql := strings.ReplaceAll(*pm.mapping, "{term}", cqlEncode(pm.value)) cqlList = append(cqlList, cql) diff --git a/broker/holdings/query_builder_general_test.go b/broker/holdings/query_builder_general_test.go index b8fe8a80..6dca3d62 100644 --- a/broker/holdings/query_builder_general_test.go +++ b/broker/holdings/query_builder_general_test.go @@ -45,18 +45,20 @@ func TestNewQueryBuilderGen(t *testing.T) { assert.Len(t, pqf, 0) assert.Equal(t, []string{"rec.id = \"12345\"", "title = \"Test Title\""}, cql) + empty := "" // Test with CQL type and one mapping qb, err = NewQueryBuilderGen(&directory.QueryConfig{ Type: &cqlType, Identifier: NewString("id == {term}"), + Title: &empty, }) assert.NoError(t, err) assert.NotNil(t, qb) gg = (qb).(*QueryBuilderGen) assert.Equal(t, "id == {term}", *gg.config.Identifier) - assert.Nil(t, gg.config.Isbn) - assert.Nil(t, gg.config.Issn) - assert.Nil(t, gg.config.Title) + assert.Equal(t, "isbn = {term}", *gg.config.Isbn) + assert.Equal(t, "issn = {term}", *gg.config.Issn) + assert.Equal(t, "", *gg.config.Title) cql, pqf, err = qb.Build(LookupParams{Identifier: "12345", Title: "Test Title"}) assert.NoError(t, err) assert.Len(t, pqf, 0) From 22ae998887918b07cd4f51130c29d3089b9b4d72 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 15:49:05 +0200 Subject: [PATCH 30/35] HOLDINGS_FORMAT --- broker/README.md | 2 +- .../{creator_shared.go => create_holdings.go} | 12 +++- broker/holdings/create_holdings_test.go | 63 +++++++++++++++++++ broker/test/adapter/create_holdings_test.go | 56 ----------------- 4 files changed, 74 insertions(+), 59 deletions(-) rename broker/holdings/{creator_shared.go => create_holdings.go} (83%) create mode 100644 broker/holdings/create_holdings_test.go delete mode 100644 broker/test/adapter/create_holdings_test.go diff --git a/broker/README.md b/broker/README.md index 8ecba2e9..16d954bc 100644 --- a/broker/README.md +++ b/broker/README.md @@ -104,7 +104,7 @@ Configuration is provided via environment variables: | `HOLDINGS_ADAPTER` | Holdings lookup method: `mock`, `sru` or `consortia` | `mock` | | `HOLDINGS_SRU_URL` | Comma separated list of URLs when `HOLDINGS_ADAPTER` is `sru` | `http://localhost:8081/sru` | | `HOLDINGS_ISXN_LOOKUP` | Whether to use ISBN/ISSN lookup for `sru` method | `false` | -| `HOLDINGS_FORMAT` | Parser for SRU holdings: `reservoir` or `MARC-21plus-1` | `reservoir` | +| `HOLDINGS_FORMAT` | Parser for SRU holdings: `reservoir`, `marc`, `opac` or `MARC-21plus-1` | `reservoir` | | `CONSORTIA_SYMBOL` | Designates peer for which configuration is used for consortia. At this time, it is | (empty value) | | | used when `HOLDINGS_ADAPTER` = `consortia`. | | | `DIRECTORY_ADAPTER` | Directory lookup method: `mock` or `api` | `mock` | diff --git a/broker/holdings/creator_shared.go b/broker/holdings/create_holdings.go similarity index 83% rename from broker/holdings/creator_shared.go rename to broker/holdings/create_holdings.go index 9827b928..a25f0c2d 100644 --- a/broker/holdings/creator_shared.go +++ b/broker/holdings/create_holdings.go @@ -4,6 +4,8 @@ import ( "fmt" "net/http" "strings" + + "github.com/indexdata/crosslink/directory" ) const ( @@ -13,14 +15,20 @@ const ( HoldingsFormat string = "HOLDINGS_FORMAT" HoldingsFormatReservoir string = "reservoir" HoldingsFormatMarc21Plus1 string = "MARC-21plus-1" + HoldingsFormatMarc string = "marc" + HoldingsFormatOpac string = "opac" ) func getParserFormat(format string) (HoldingsParser, error) { switch format { case HoldingsFormatReservoir: - return &ReservoirHoldingsParser{}, nil + return NewReservoirHoldingsParser(), nil case HoldingsFormatMarc21Plus1: - return &Marc21Plus1HoldingsParser{}, nil + return NewMarc21Plus1HoldingsParser(), nil + case HoldingsFormatMarc: + return NewMarcHoldingsParser(directory.MarcParserConfig{}), nil + case HoldingsFormatOpac: + return NewOpacHoldingsParser(directory.OpacParserConfig{}), nil default: return nil, fmt.Errorf("bad value for %s: %s", HoldingsFormat, format) } diff --git a/broker/holdings/create_holdings_test.go b/broker/holdings/create_holdings_test.go new file mode 100644 index 00000000..9c074d20 --- /dev/null +++ b/broker/holdings/create_holdings_test.go @@ -0,0 +1,63 @@ +package holdings + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateHoldings(t *testing.T) { + m := make(map[string]any) + + _, err := CreateHoldingsLookupShared(m) + assert.Error(t, err) + assert.ErrorContains(t, err, "missing value for HOLDINGS_ADAPTER") + + m[HoldingsAdapter] = "sru" + + _, err = CreateHoldingsLookupShared(m) + assert.ErrorContains(t, err, "missing value for HOLDINGS_SRU_URL") + + m[HoldingsSruURL] = "http://example.com" + _, err = CreateHoldingsLookupShared(m) + assert.Error(t, err) + assert.ErrorContains(t, err, "missing value for HOLDINGS_ISXN_LOOKUP") + + m[HoldingsIsxnLookup] = "fake" + _, err = CreateHoldingsLookupShared(m) + assert.Error(t, err) + assert.ErrorContains(t, err, "invalid value for HOLDINGS_ISXN_LOOKUP") + + m[HoldingsIsxnLookup] = true + m[HoldingsFormat] = "reservoir" + _, err = CreateHoldingsLookupShared(m) + assert.NoError(t, err) + + m[HoldingsFormat] = "MARC-21plus-1" + _, err = CreateHoldingsLookupShared(m) + assert.NoError(t, err) + + m[HoldingsFormat] = "marc" + _, err = CreateHoldingsLookupShared(m) + assert.NoError(t, err) + + m[HoldingsFormat] = "opac" + _, err = CreateHoldingsLookupShared(m) + assert.NoError(t, err) + + m[HoldingsFormat] = "other" + _, err = CreateHoldingsLookupShared(m) + assert.ErrorContains(t, err, "bad value for HOLDINGS_FORMAT: other") + + m[HoldingsFormat] = true + _, err = CreateHoldingsLookupShared(m) + assert.ErrorContains(t, err, "missing value for HOLDINGS_FORMAT") + + m[HoldingsAdapter] = "mock" + _, err = CreateHoldingsLookupShared(m) + assert.NoError(t, err) + + m[HoldingsAdapter] = "other" + _, err = CreateHoldingsLookupShared(m) + assert.ErrorContains(t, err, "bad value for HOLDINGS_ADAPTER") +} diff --git a/broker/test/adapter/create_holdings_test.go b/broker/test/adapter/create_holdings_test.go deleted file mode 100644 index 90cf6885..00000000 --- a/broker/test/adapter/create_holdings_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package adapter - -import ( - "testing" - - "github.com/indexdata/crosslink/broker/holdings" - "github.com/stretchr/testify/assert" -) - -func TestCreateHoldings(t *testing.T) { - m := make(map[string]any) - - _, err := holdings.CreateHoldingsLookupShared(m) - assert.Error(t, err) - assert.ErrorContains(t, err, "missing value for HOLDINGS_ADAPTER") - - m[holdings.HoldingsAdapter] = "sru" - - _, err = holdings.CreateHoldingsLookupShared(m) - assert.ErrorContains(t, err, "missing value for HOLDINGS_SRU_URL") - - m[holdings.HoldingsSruURL] = "http://example.com" - _, err = holdings.CreateHoldingsLookupShared(m) - assert.Error(t, err) - assert.ErrorContains(t, err, "missing value for HOLDINGS_ISXN_LOOKUP") - - m[holdings.HoldingsIsxnLookup] = "fake" - _, err = holdings.CreateHoldingsLookupShared(m) - assert.Error(t, err) - assert.ErrorContains(t, err, "invalid value for HOLDINGS_ISXN_LOOKUP") - - m[holdings.HoldingsIsxnLookup] = true - m[holdings.HoldingsFormat] = "reservoir" - _, err = holdings.CreateHoldingsLookupShared(m) - assert.NoError(t, err) - - m[holdings.HoldingsFormat] = "MARC-21plus-1" - _, err = holdings.CreateHoldingsLookupShared(m) - assert.NoError(t, err) - - m[holdings.HoldingsFormat] = "other" - _, err = holdings.CreateHoldingsLookupShared(m) - assert.ErrorContains(t, err, "bad value for HOLDINGS_FORMAT: other") - - m[holdings.HoldingsFormat] = true - _, err = holdings.CreateHoldingsLookupShared(m) - assert.ErrorContains(t, err, "missing value for HOLDINGS_FORMAT") - - m[holdings.HoldingsAdapter] = "mock" - _, err = holdings.CreateHoldingsLookupShared(m) - assert.NoError(t, err) - - m[holdings.HoldingsAdapter] = "other" - _, err = holdings.CreateHoldingsLookupShared(m) - assert.ErrorContains(t, err, "bad value for HOLDINGS_ADAPTER") -} From 56ad913055fccbe506476c1d7c12fe8266da90ca Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 15:53:59 +0200 Subject: [PATCH 31/35] move test --- .../adapter => holdings}/sru_holdings_test.go | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) rename broker/{test/adapter => holdings}/sru_holdings_test.go (95%) diff --git a/broker/test/adapter/sru_holdings_test.go b/broker/holdings/sru_holdings_test.go similarity index 95% rename from broker/test/adapter/sru_holdings_test.go rename to broker/holdings/sru_holdings_test.go index fff64b74..6dc33898 100644 --- a/broker/test/adapter/sru_holdings_test.go +++ b/broker/holdings/sru_holdings_test.go @@ -1,4 +1,4 @@ -package adapter +package holdings import ( "encoding/xml" @@ -6,17 +6,16 @@ import ( "net/http/httptest" "testing" - "github.com/indexdata/crosslink/broker/holdings" "github.com/indexdata/crosslink/marcxml" "github.com/indexdata/crosslink/sru" "github.com/indexdata/crosslink/sru/diag" "github.com/stretchr/testify/assert" ) -func createSruAdapter(t *testing.T, isxn bool, url ...string) holdings.LookupAdapter { - parser := &holdings.ReservoirHoldingsParser{} - queryBuilder := holdings.NewQueryBuilderIsxn(isxn) - ad := holdings.CreateSruHoldingsLookupAdapter(http.DefaultClient, url, "", queryBuilder, parser, "marcxml") +func createSruAdapter(t *testing.T, isxn bool, url ...string) LookupAdapter { + parser := &ReservoirHoldingsParser{} + queryBuilder := NewQueryBuilderIsxn(isxn) + ad := CreateSruHoldingsLookupAdapter(http.DefaultClient, url, "", queryBuilder, parser, "marcxml") assert.NotNil(t, ad) return ad } @@ -30,7 +29,7 @@ func TestSru500(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -47,7 +46,7 @@ func TestSruBadXml(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -81,7 +80,7 @@ func TestSruBadDiagnostics(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -106,7 +105,7 @@ func TestSruMarcxmlNoHits(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } holdingsList, query, err := ad.Lookup(p) @@ -144,7 +143,7 @@ func TestSruMarcxmlStringEncoding(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -181,7 +180,7 @@ func TestSruMarcxmlUnsupportedSchema(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -218,7 +217,7 @@ func TestSruMarcxmlBadSurrogateDiagnostic(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -261,7 +260,7 @@ func TestSruMarcxmlOkSurrogateDiagnostic(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -300,7 +299,7 @@ func TestSruMarcxmlBadMarc(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } _, query, err := ad.Lookup(p) @@ -371,7 +370,7 @@ func TestSruMarcxmlWithFallbackHoldings(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } holdings, query, err := ad.Lookup(p) @@ -449,7 +448,7 @@ func TestSruMarcxmlWithHoldingsDoesNotUseFallback(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } holdings, query, err := ad.Lookup(p) @@ -512,7 +511,7 @@ func TestSruMarcxmlLeavesSchemedSymbolUnchanged(t *testing.T) { defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } holdings, query, err := ad.Lookup(p) @@ -586,7 +585,7 @@ func TestSruMarcxmlUsesFallbackWhenPrimaryFieldHasNoUsableHolding(t *testing.T) defer server.Close() ad := createSruAdapter(t, false, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } holdings, query, err := ad.Lookup(p) @@ -692,7 +691,7 @@ func TestSruMarcxmlWithHoldings(t *testing.T) { defer server.Close() ad := createSruAdapter(t, true, server.URL) - p := holdings.LookupParams{ + p := LookupParams{ Identifier: "123", } holdingsList, query, err := ad.Lookup(p) @@ -708,7 +707,7 @@ func TestSruMarcxmlWithHoldings(t *testing.T) { assert.Equal(t, "ISIL:s3", holdingsList[2].Symbol) ad = createSruAdapter(t, true, server.URL, server.URL) - p = holdings.LookupParams{ + p = LookupParams{ Identifier: "123", Isbn: "99-222", Issn: "99-333", @@ -733,7 +732,7 @@ func TestSruMarcxmlWithHoldings(t *testing.T) { assert.Equal(t, "ISIL:s3", holdingsList[5].Symbol) ad = createSruAdapter(t, false, server.URL) - p = holdings.LookupParams{ + p = LookupParams{ Isbn: "99-222", } _, _, err = ad.Lookup(p) From 4aae3d7cde45ea2c7168c095d3a63cfd9bcfc318 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 17:18:58 +0200 Subject: [PATCH 32/35] Include query type in error message --- zoom/zoom.go | 8 ++++---- zoom/zoom_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zoom/zoom.go b/zoom/zoom.go index 9b76c201..ce1281ac 100644 --- a/zoom/zoom.go +++ b/zoom/zoom.go @@ -66,7 +66,7 @@ func NewPqfQuery(pqf string) (*Query, error) { defer C.free(unsafe.Pointer(cPqf)) query := &Query{zquery: C.ZOOM_query_create()} - return checkQuery(C.ZOOM_query_prefix(query.zquery, cPqf), query) + return checkQuery(C.ZOOM_query_prefix(query.zquery, cPqf), query, "PQF") } func NewCqlQuery(cql string) (*Query, error) { @@ -74,13 +74,13 @@ func NewCqlQuery(cql string) (*Query, error) { defer C.free(unsafe.Pointer(cCql)) query := &Query{zquery: C.ZOOM_query_create()} - return checkQuery(C.ZOOM_query_cql(query.zquery, cCql), query) + return checkQuery(C.ZOOM_query_cql(query.zquery, cCql), query, "CQL") } -func checkQuery(ret C.int, query *Query) (*Query, error) { +func checkQuery(ret C.int, query *Query, kind string) (*Query, error) { if ret != 0 { query.finalize() - return nil, &ZoomError{Code: 0, Message: "failed to create query"} + return nil, &ZoomError{Code: 0, Message: "failed to create " + kind + " query"} } runtime.SetFinalizer(query, (*Query).finalize) return query, nil diff --git a/zoom/zoom_test.go b/zoom/zoom_test.go index b7b39fc3..6db009e9 100644 --- a/zoom/zoom_test.go +++ b/zoom/zoom_test.go @@ -65,7 +65,7 @@ func TestPqfQuery(t *testing.T) { _, err = NewPqfQuery("@attr 1=4") assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to create query") + assert.Contains(t, err.Error(), "failed to create PQF query") } func TestSearch(t *testing.T) { From 142d043d13fdc6545d5f51a73526782cb8581bd9 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 17:30:59 +0200 Subject: [PATCH 33/35] Tune error messages --- broker/holdings/adapter_zoom.go | 4 ++-- broker/holdings/adapter_zoom_test.go | 32 +++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/broker/holdings/adapter_zoom.go b/broker/holdings/adapter_zoom.go index 3ed30607..c55c71cc 100644 --- a/broker/holdings/adapter_zoom.go +++ b/broker/holdings/adapter_zoom.go @@ -90,7 +90,7 @@ func (a *ZoomAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string avail, err := a.searchRetrieve(params, conn, query) query.Close() if err != nil { - return nil, pqf, fmt.Errorf("failed to search Z39.50 server query: %s err %w", pqf, err) + return nil, pqf, fmt.Errorf("failed to search server with PQF: %s err %w", pqf, err) } if len(avail) > 0 { return avail, pqf, nil @@ -104,7 +104,7 @@ func (a *ZoomAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string avail, err := a.searchRetrieve(params, conn, query) query.Close() if err != nil { - return nil, cql, fmt.Errorf("failed to search SRU server query: %s err %w", cql, err) + return nil, cql, fmt.Errorf("failed to search server with CQL: %s err %w", cql, err) } if len(avail) > 0 { return avail, cql, nil diff --git a/broker/holdings/adapter_zoom_test.go b/broker/holdings/adapter_zoom_test.go index f547be17..30e8b15f 100644 --- a/broker/holdings/adapter_zoom_test.go +++ b/broker/holdings/adapter_zoom_test.go @@ -98,7 +98,7 @@ func TestLookupFoundOpac(t *testing.T) { assert.Equal(t, "@attr 1=4 \"Computer\"", pqf) } -func TestLookupDiagnostics(t *testing.T) { +func TestLookupDiagnosticPQF(t *testing.T) { queryBuilder, err := NewQueryBuilderGen(nil) assert.NoError(t, err) holdingsParser := NewMarcHoldingsParser(directory.MarcParserConfig{}) @@ -119,10 +119,40 @@ func TestLookupDiagnostics(t *testing.T) { params := LookupParams{Identifier: "1234"} _, pqf, err := aa.Lookup(params) assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to search server with PQF") assert.Contains(t, err.Error(), "Record syntax not supported") assert.Equal(t, "@attr 1=12 \"1234\"", pqf) } +func TestLookupDiagnosticCql(t *testing.T) { + cqlType := directory.Cql + queryBuilder, err := NewQueryBuilderGen(&directory.QueryConfig{ + Type: &cqlType, + }) + assert.NoError(t, err) + holdingsParser := NewMarcHoldingsParser(directory.MarcParserConfig{}) + aa, err := NewZoomAvailabilityAdapter( + directory.ZoomConfig{ + Address: containerHost + ":" + mappedPort + "/marc", + Options: &map[string]string{ + "preferredRecordSyntax": "danmarc", + }, + }, + queryBuilder, + holdingsParser, + ) + assert.NoError(t, err) + assert.Equal(t, "localhost:"+mappedPort+"/marc", aa.(*ZoomAvailabilityAdapter).zurl) + assert.Equal(t, "danmarc", aa.(*ZoomAvailabilityAdapter).options["preferredRecordSyntax"]) + + params := LookupParams{Identifier: "1234"} + _, cql, err := aa.Lookup(params) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to search server with CQL") + assert.Contains(t, err.Error(), "Record syntax not supported") + assert.Equal(t, "rec.id = \"1234\"", cql) +} + func TestConnectFailure(t *testing.T) { queryBuilder, err := NewQueryBuilderGen(nil) assert.NoError(t, err) From f9234d3f13f9190761b874303b213d532f285523 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 17:33:14 +0200 Subject: [PATCH 34/35] Holdings when CQL query used --- broker/holdings/adapter_zoom_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/broker/holdings/adapter_zoom_test.go b/broker/holdings/adapter_zoom_test.go index 30e8b15f..5eab7d4a 100644 --- a/broker/holdings/adapter_zoom_test.go +++ b/broker/holdings/adapter_zoom_test.go @@ -39,8 +39,10 @@ func TestLookupFoundMarc(t *testing.T) { MainField: NewString("010"), LocationSubField: NewString("a"), } + pqfType := directory.Pqf queryBuilder, err := NewQueryBuilderGen(&directory.QueryConfig{ Title: NewString("@attr 1=1016 {term}"), + Type: &pqfType, }) assert.NoError(t, err) holdingsParser := NewMarcHoldingsParser(config) @@ -70,7 +72,10 @@ func TestLookupFoundMarc(t *testing.T) { } func TestLookupFoundOpac(t *testing.T) { - queryBuilder, err := NewQueryBuilderGen(nil) + cqlType := directory.Cql + queryBuilder, err := NewQueryBuilderGen(&directory.QueryConfig{ + Type: &cqlType, + }) assert.NoError(t, err) holdingsParser := NewOpacHoldingsParser(directory.OpacParserConfig{}) aa, err := NewZoomAvailabilityAdapter( @@ -95,7 +100,7 @@ func TestLookupFoundOpac(t *testing.T) { assert.Len(t, results, 42) assert.Contains(t, results[0].ItemId, "test__000000001_") assert.Contains(t, results[1].ItemId, "test__000000002_") - assert.Equal(t, "@attr 1=4 \"Computer\"", pqf) + assert.Equal(t, "title = \"Computer\"", pqf) } func TestLookupDiagnosticPQF(t *testing.T) { From 6c901840dad98a4e07bc1d7f2fc180fbb3af55f5 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 13 May 2026 17:33:39 +0200 Subject: [PATCH 35/35] Rename --- broker/holdings/adapter_zoom_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/broker/holdings/adapter_zoom_test.go b/broker/holdings/adapter_zoom_test.go index 5eab7d4a..46534cba 100644 --- a/broker/holdings/adapter_zoom_test.go +++ b/broker/holdings/adapter_zoom_test.go @@ -95,12 +95,12 @@ func TestLookupFoundOpac(t *testing.T) { params := LookupParams{ Title: "Computer", } - results, pqf, err := aa.Lookup(params) + results, cql, err := aa.Lookup(params) assert.NoError(t, err) assert.Len(t, results, 42) assert.Contains(t, results[0].ItemId, "test__000000001_") assert.Contains(t, results[1].ItemId, "test__000000002_") - assert.Equal(t, "title = \"Computer\"", pqf) + assert.Equal(t, "title = \"Computer\"", cql) } func TestLookupDiagnosticPQF(t *testing.T) {