diff --git a/handler/v12/base.go b/handler/v12/base.go index c35b55a..70af9a6 100644 --- a/handler/v12/base.go +++ b/handler/v12/base.go @@ -57,7 +57,8 @@ func (a *FCSSubHandlerV12) produceXMLResponse(ctx *gin.Context, code int, xslt s ctx.Writer.Header().Set("Content-Type", "application/xml") } -func (a *FCSSubHandlerV12) produceErrorResponse(ctx *gin.Context, code int, xslt string, fcsErrors []general.FCSError) { +func (a *FCSSubHandlerV12) produceExplainErrorResponse( + ctx *gin.Context, code int, xslt string, fcsErrors []general.FCSError) { ans := schema.XMLExplainResponse{ XMLNSSRU: "http://www.loc.gov/zing/srw/", Version: "1.2", @@ -69,18 +70,32 @@ func (a *FCSSubHandlerV12) produceErrorResponse(ctx *gin.Context, code int, xslt a.produceXMLResponse(ctx, code, xslt, ans) } +func (a *FCSSubHandlerV12) produceSRErrorResponse( + ctx *gin.Context, code int, xslt string, fcsErrors []general.FCSError) { + ans := schema.XMLSRResponse{ + XMLNSSRUResponse: "http://www.loc.gov/zing/srw/", + Version: "1.2", + Diagnostics: schema.NewXMLDiagnostics(), + } + for _, fcsErr := range fcsErrors { + ans.Diagnostics.AddDiagnostic(fcsErr.Code, fcsErr.Type, fcsErr.Ident, fcsErr.Message) + } + a.produceXMLResponse(ctx, code, xslt, ans) +} + func (a *FCSSubHandlerV12) Handle( ctx *gin.Context, fcsGeneralRequest general.FCSGeneralRequest, xslt map[string]string, ) { fcsResponse := &FCSRequest{ - General: fcsGeneralRequest, + General: &fcsGeneralRequest, RecordPacking: RecordPackingXML, Operation: OperationExplain, } if fcsResponse.General.HasFatalError() { - a.produceErrorResponse(ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + a.produceExplainErrorResponse( + ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) return } @@ -100,7 +115,8 @@ func (a *FCSSubHandlerV12) Handle( Ident: "operation", Message: fmt.Sprintf("Unsupported operation: %s", operation), }) - a.produceErrorResponse(ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + a.produceExplainErrorResponse( + ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) return } fcsResponse.Operation = operation @@ -114,7 +130,14 @@ func (a *FCSSubHandlerV12) Handle( Ident: "recordPacking", Message: err.Error(), }) - a.produceErrorResponse(ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + if operation == OperationSearchRetrive { + a.produceSRErrorResponse( + ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + + } else { + a.produceExplainErrorResponse( + ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + } return } fcsResponse.RecordPacking = recordPacking diff --git a/handler/v12/request.go b/handler/v12/request.go index c8f9e4b..71a0b3c 100644 --- a/handler/v12/request.go +++ b/handler/v12/request.go @@ -23,7 +23,7 @@ import ( ) type FCSRequest struct { - General general.FCSGeneralRequest + General *general.FCSGeneralRequest RecordPacking RecordPacking Operation Operation } diff --git a/handler/v12/scan.go b/handler/v12/scan.go index c6da57d..a4d8e98 100644 --- a/handler/v12/scan.go +++ b/handler/v12/scan.go @@ -40,7 +40,8 @@ func (a *FCSSubHandlerV12) scan(ctx *gin.Context, fcsResponse *FCSRequest) (sche _, err := strconv.Atoi(xMaxTerms) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameter, 0, ScanArgMaximumTerms.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, ScanArgMaximumTerms.String()) return ans, general.ConformantUnprocessableEntity } @@ -48,18 +49,21 @@ func (a *FCSSubHandlerV12) scan(ctx *gin.Context, fcsResponse *FCSRequest) (sche _, err = strconv.Atoi(xResponsePos) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, ScanArgResponsePosition.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, ScanArgResponsePosition.String()) return ans, general.ConformantUnprocessableEntity } scanClause := ctx.Query(ScanArgScanClause.String()) if scanClause == "" { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCMandatoryParameterNotSupplied, 0, ScanArgScanClause.String(), general.DCMandatoryParameterNotSupplied.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCMandatoryParameterNotSupplied, 0, ScanArgScanClause.String()) return ans, general.ConformantUnprocessableEntity } ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedIndex, 0, ScanArgScanClause.String(), general.DCUnsupportedIndex.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedIndex, 0, ScanArgScanClause.String()) return ans, general.ConformantUnprocessableEntity } diff --git a/handler/v12/schema/diagnostics.go b/handler/v12/schema/diagnostics.go index b9c71aa..c7a011d 100644 --- a/handler/v12/schema/diagnostics.go +++ b/handler/v12/schema/diagnostics.go @@ -36,7 +36,23 @@ type XMLDiagnostics struct { Diagnostics []XMLDiagnostic `xml:"diag:diagnostic"` } -func (d *XMLDiagnostics) AddDiagnostic(code general.DiagnosticCode, typ general.DiagnosticType, ident string, message string) { +// AddDfltMsgDiagnostics adds a diagnostics code along with +// its attached default message. For custom message, +// use AddDiagnostic. +func (d *XMLDiagnostics) AddDfltMsgDiagnostic( + code general.DiagnosticCode, + typ general.DiagnosticType, + ident string, +) { + d.AddDiagnostic(code, typ, ident, code.AsMessage()) +} + +func (d *XMLDiagnostics) AddDiagnostic( + code general.DiagnosticCode, + typ general.DiagnosticType, + ident string, + message string, +) { uri := []string{} if code > 0 { uri = append(uri, fmt.Sprintf("info:srw/diagnostic/1/%d", code)) diff --git a/handler/v12/schema/searchRetrieve.go b/handler/v12/schema/searchRetrieve.go index ef4dd56..dd33a34 100644 --- a/handler/v12/schema/searchRetrieve.go +++ b/handler/v12/schema/searchRetrieve.go @@ -25,10 +25,14 @@ type XMLSRResponse struct { XMLNSSRUResponse string `xml:"xmlns:sru,attr"` Version string `xml:"sru:version"` - NumberOfRecords int `xml:"sru:numberOfRecords"` - Records *[]XMLSRRecord `xml:"sru:records>sru:record,omitempty"` - EchoedRequest XMLSREchoedRequest `xml:"sru:echoedSearchRetrieveRequest"` - Diagnostics *XMLDiagnostics `xml:"sru:diagnostics,omitempty"` + NumberOfRecords int `xml:"sru:numberOfRecords"` + + // Records + // note: we need a pointer here to allow the marshaler skip the 'records' parent + // in case there are no 'record' children + Records *[]XMLSRRecord `xml:"sru:records>sru:record,omitempty"` + EchoedRequest XMLSREchoedRequest `xml:"sru:echoedSearchRetrieveRequest"` + Diagnostics *XMLDiagnostics `xml:"sru:diagnostics,omitempty"` } func NewXMLSRResponse() XMLSRResponse { diff --git a/handler/v12/searchrt.go b/handler/v12/searchrt.go index bd3cd8b..fa99a63 100644 --- a/handler/v12/searchrt.go +++ b/handler/v12/searchrt.go @@ -90,7 +90,8 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ fcsQuery := ctx.Query(SearchRetrArgQuery.String()) if len(fcsQuery) == 0 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCMandatoryParameterNotSupplied, 0, "fcs_query", general.DCMandatoryParameterNotSupplied.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCMandatoryParameterNotSupplied, 0, "fcs_query") return ans, general.ConformantStatusBadRequest } ans.EchoedRequest.Query = fcsQuery @@ -101,12 +102,14 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ startRecord, err := strconv.Atoi(xStartRecord) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, SearchRetrStartRecord.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, SearchRetrStartRecord.String()) return ans, general.ConformantUnprocessableEntity } if startRecord < 1 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, SearchRetrStartRecord.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, SearchRetrStartRecord.String()) return ans, general.ConformantUnprocessableEntity } ans.EchoedRequest.StartRecord = startRecord @@ -116,7 +119,8 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ recordSchema := ctx.DefaultQuery(SearchRetrArgRecordSchema.String(), general.RecordSchema) if recordSchema != general.RecordSchema { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnknownSchemaForRetrieval, 0, SearchMaximumRecords.String(), general.DCUnknownSchemaForRetrieval.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnknownSchemaForRetrieval, 0, SearchMaximumRecords.String()) return ans, general.ConformantUnprocessableEntity } @@ -126,13 +130,15 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ maximumRecords, err = strconv.Atoi(xMaximumRecords) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, SearchMaximumRecords.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, SearchMaximumRecords.String()) return ans, general.ConformantUnprocessableEntity } } if maximumRecords < 1 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, SearchMaximumRecords.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, SearchMaximumRecords.String()) return ans, general.ConformantUnprocessableEntity } @@ -141,7 +147,8 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ // as the actual result can be very small. But we still // have to limit max. number of records... ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCTooManyMatchingRecords, 0, fmt.Sprintf("%d", mango.MaxRecordsInternalLimit), general.DCTooManyMatchingRecords.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCTooManyMatchingRecords, 0, fmt.Sprintf("%d", mango.MaxRecordsInternalLimit)) return ans, general.ConformantUnprocessableEntity } logArgs[SearchMaximumRecords.String()] = maximumRecords @@ -153,7 +160,7 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ for _, pid := range corporaPids { res, err := a.corporaConf.Resources.GetResourceByPID(pid) if err == corpus.ErrResourceNotFound { - ans.Records = &[]schema.XMLSRRecord{} + ans.Records = nil return ans, http.StatusOK } corpora = append(corpora, res.ID) @@ -166,13 +173,15 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ // get searchable corpora and attrs if len(corpora) == 0 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedContextSet, 0, SearchRetrArgFCSContext.String(), general.DCUnsupportedContextSet.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedContextSet, 0, SearchRetrArgFCSContext.String()) return ans, general.ConformantStatusBadRequest } retrieveAttrs, err := a.corporaConf.Resources.GetCommonPosAttrNames(corpora...) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } @@ -198,13 +207,15 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ query := ast.Generate() if len(ast.Errors()) > 0 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCQueryCannotProcess, 0, SearchRetrArgQuery.String(), ast.Errors()[0].Error()) + ans.Diagnostics.AddDiagnostic( + general.DCQueryCannotProcess, 0, SearchRetrArgQuery.String(), ast.Errors()[0].Error()) return ans, general.ConformantUnprocessableEntity } rscConf, err := a.corporaConf.Resources.GetResource(rng.Rsc) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, general.ConformandGeneralServerError } args, err := sonic.Marshal(rdb.ConcExampleArgs{ @@ -218,7 +229,8 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ }) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } wait, err := a.radapter.PublishQuery(rdb.Query{ @@ -227,7 +239,8 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ }) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } waits[i] = wait @@ -241,7 +254,8 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ result, err := rdb.DeserializeConcExampleResult(rawResult) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } if err := result.Err(); err != nil { @@ -250,7 +264,8 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ } else { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCQueryCannotProcess, 0, err.Error(), general.DCQueryCannotProcess.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCQueryCannotProcess, 0, err.Error()) return ans, http.StatusInternalServerError } } @@ -262,12 +277,14 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ ans.NumberOfRecords = totalConcSize if fromResource.AllHasOutOfRangeError() { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCFirstRecordPosOutOfRange, 0, fromResource.GetFirstError().Error(), general.DCFirstRecordPosOutOfRange.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCFirstRecordPosOutOfRange, 0, fromResource.GetFirstError().Error()) return ans, general.ConformantUnprocessableEntity } else if fromResource.HasFatalError() { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCQueryCannotProcess, 0, fromResource.GetFirstError().Error(), general.DCQueryCannotProcess.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCQueryCannotProcess, 0, fromResource.GetFirstError().Error()) return ans, general.ConformandGeneralServerError } @@ -277,7 +294,8 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ res, err := a.corporaConf.Resources.GetResource(fromResource.CurrRscName()) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } item := fromResource.CurrLine() @@ -321,6 +339,8 @@ func (a *FCSSubHandlerV12) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ RecordPosition: len(records) + startRecord, }) } - ans.Records = &records + if len(records) > 0 { + ans.Records = &records + } return ans, http.StatusOK } diff --git a/handler/v20/base.go b/handler/v20/base.go index 1f1943c..001be03 100644 --- a/handler/v20/base.go +++ b/handler/v20/base.go @@ -57,7 +57,7 @@ func (a *FCSSubHandlerV20) produceXMLResponse(ctx *gin.Context, code int, xslt s ctx.Writer.Header().Set("Content-Type", "application/xml") } -func (a *FCSSubHandlerV20) produceErrorResponse(ctx *gin.Context, code int, xslt string, fcsErrors []general.FCSError) { +func (a *FCSSubHandlerV20) produceExplainErrorResponse(ctx *gin.Context, code int, xslt string, fcsErrors []general.FCSError) { ans := schema.XMLExplainResponse{ XMLNSSRUResponse: "http://docs.oasis-open.org/ns/search-ws/sruResponse", Version: "2.0", @@ -69,19 +69,28 @@ func (a *FCSSubHandlerV20) produceErrorResponse(ctx *gin.Context, code int, xslt a.produceXMLResponse(ctx, code, xslt, ans) } +func (a *FCSSubHandlerV20) produceSRErrorResponse(ctx *gin.Context, code int, xslt string, fcsErrors []general.FCSError) { + ans := schema.NewMinimalXMLSRResponse() + ans.Diagnostics = schema.NewXMLDiagnostics() + for _, fcsErr := range fcsErrors { + ans.Diagnostics.AddDiagnostic(fcsErr.Code, fcsErr.Type, fcsErr.Ident, fcsErr.Message) + } + a.produceXMLResponse(ctx, code, xslt, ans) +} + func (a *FCSSubHandlerV20) Handle( ctx *gin.Context, fcsGeneralRequest general.FCSGeneralRequest, xslt map[string]string, ) { fcsRequest := &FCSRequest{ - General: fcsGeneralRequest, + General: &fcsGeneralRequest, RecordXMLEscaping: RecordXMLEscapingXML, Operation: OperationExplain, } if fcsRequest.General.HasFatalError() { - a.produceErrorResponse(ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + a.produceExplainErrorResponse(ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) return } @@ -95,13 +104,15 @@ func (a *FCSSubHandlerV20) Handle( } else if ctx.Request.URL.Query().Has(ScanArgScanClause.String()) { operation = OperationScan } + if err := operation.Validate(); err != nil { fcsRequest.General.AddError(general.FCSError{ Code: general.DCUnsupportedOperation, Ident: "operation", Message: fmt.Sprintf("Unsupported operation: %s", operation), }) - a.produceErrorResponse(ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + a.produceExplainErrorResponse( + ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) return } fcsRequest.Operation = operation @@ -115,7 +126,14 @@ func (a *FCSSubHandlerV20) Handle( Ident: "recordXMLEscaping", Message: err.Error(), }) - a.produceErrorResponse(ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + if operation == OperationSearchRetrive { + a.produceSRErrorResponse( + ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + + } else { + a.produceExplainErrorResponse( + ctx, general.ConformantStatusBadRequest, fcsGeneralRequest.XSLT, fcsGeneralRequest.Errors) + } return } fcsRequest.RecordXMLEscaping = recordXMLEscaping @@ -123,6 +141,7 @@ func (a *FCSSubHandlerV20) Handle( var response any var code int + switch fcsRequest.Operation { case OperationExplain: response, code = a.explain(ctx, fcsRequest) diff --git a/handler/v20/request.go b/handler/v20/request.go index e617343..63e79ae 100644 --- a/handler/v20/request.go +++ b/handler/v20/request.go @@ -23,7 +23,7 @@ import ( ) type FCSRequest struct { - General general.FCSGeneralRequest + General *general.FCSGeneralRequest RecordXMLEscaping RecordXMLEscaping Operation Operation } diff --git a/handler/v20/scan.go b/handler/v20/scan.go index 7c4341e..8dcd5b4 100644 --- a/handler/v20/scan.go +++ b/handler/v20/scan.go @@ -26,12 +26,13 @@ import ( "github.com/gin-gonic/gin" ) -func (a *FCSSubHandlerV20) scan(ctx *gin.Context, fcsResponse *FCSRequest) (schema.XMLScanResponse, int) { +func (a *FCSSubHandlerV20) scan(ctx *gin.Context, _ *FCSRequest) (schema.XMLScanResponse, int) { ans := schema.NewXMLScanResponse() for key, _ := range ctx.Request.URL.Query() { if err := ScanArg(key).Validate(); err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameter, 0, key, err.Error()) + ans.Diagnostics.AddDiagnostic( + general.DCUnsupportedParameter, 0, key, err.Error()) return ans, general.ConformantStatusBadRequest } } @@ -40,7 +41,8 @@ func (a *FCSSubHandlerV20) scan(ctx *gin.Context, fcsResponse *FCSRequest) (sche _, err := strconv.Atoi(xMaxTerms) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameter, 0, ScanArgMaximumTerms.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, ScanArgMaximumTerms.String()) return ans, general.ConformantUnprocessableEntity } @@ -48,18 +50,21 @@ func (a *FCSSubHandlerV20) scan(ctx *gin.Context, fcsResponse *FCSRequest) (sche _, err = strconv.Atoi(xResponsePos) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, ScanArgResponsePosition.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, ScanArgResponsePosition.String()) return ans, general.ConformantUnprocessableEntity } scanClause := ctx.Query(ScanArgScanClause.String()) if scanClause == "" { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCMandatoryParameterNotSupplied, 0, ScanArgScanClause.String(), general.DCMandatoryParameterNotSupplied.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCMandatoryParameterNotSupplied, 0, ScanArgScanClause.String()) return ans, general.ConformantUnprocessableEntity } ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedIndex, 0, ScanArgScanClause.String(), general.DCUnsupportedIndex.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedIndex, 0, ScanArgScanClause.String()) return ans, general.ConformantUnprocessableEntity } diff --git a/handler/v20/schema/diagnostics.go b/handler/v20/schema/diagnostics.go index 023baab..9f32388 100644 --- a/handler/v20/schema/diagnostics.go +++ b/handler/v20/schema/diagnostics.go @@ -36,7 +36,15 @@ type XMLDiagnostics struct { Diagnostics []XMLDiagnostic `xml:"diag:diagnostic"` } -func (d *XMLDiagnostics) AddDiagnostic(code general.DiagnosticCode, typ general.DiagnosticType, ident string, message string) { +// AddDiagnostic add diagnostics output with a custom +// message. For most situations AddDfltMsgDiagnostics +// should be preferable. +func (d *XMLDiagnostics) AddDiagnostic( + code general.DiagnosticCode, + typ general.DiagnosticType, + ident string, + message string, +) { uri := []string{} if code > 0 { uri = append(uri, fmt.Sprintf("info:srw/diagnostic/1/%d", code)) @@ -51,6 +59,17 @@ func (d *XMLDiagnostics) AddDiagnostic(code general.DiagnosticCode, typ general. }) } +// AddDfltMsgDiagnostics adds a diagnostics code along with +// its attached default message. For custom message, +// use AddDiagnostic. +func (d *XMLDiagnostics) AddDfltMsgDiagnostic( + code general.DiagnosticCode, + typ general.DiagnosticType, + ident string, +) { + d.AddDiagnostic(code, typ, ident, code.AsMessage()) +} + func NewXMLDiagnostics() *XMLDiagnostics { return &XMLDiagnostics{ XMLNSDiag: "http://docs.oasis-open.org/ns/search-ws/diagnostic", diff --git a/handler/v20/schema/searchRetrieve.go b/handler/v20/schema/searchRetrieve.go index c828064..0e989e7 100644 --- a/handler/v20/schema/searchRetrieve.go +++ b/handler/v20/schema/searchRetrieve.go @@ -25,12 +25,16 @@ type XMLSRResponse struct { XMLNSSRUResponse string `xml:"xmlns:sruResponse,attr"` Version string `xml:"sruResponse:version"` - NumberOfRecords int `xml:"sruResponse:numberOfRecords"` - Records *[]XMLSRRecord `xml:"sruResponse:records>sruResponse:record,omitempty"` - NextRecordPosition int `xml:"sruResponse:nextRecordPosition,omitempty"` - EchoedRequest XMLSREchoedRequest `xml:"sruResponse:echoedSearchRetrieveRequest"` - Diagnostics *XMLDiagnostics `xml:"sruResponse:diagnostics,omitempty"` - ResultCountPrecision string `xml:"sruResponse:resultCountPrecision"` + NumberOfRecords int `xml:"sruResponse:numberOfRecords"` + + // Records + // note: we need a pointer here to allow the marshaler skip the 'records' parent + // in case there are no 'record' children + Records *[]XMLSRRecord `xml:"sruResponse:records>sruResponse:record,omitempty"` + NextRecordPosition int `xml:"sruResponse:nextRecordPosition,omitempty"` + EchoedRequest *XMLSREchoedRequest `xml:"sruResponse:echoedSearchRetrieveRequest,omitempty"` + Diagnostics *XMLDiagnostics `xml:"sruResponse:diagnostics,omitempty"` + ResultCountPrecision string `xml:"sruResponse:resultCountPrecision"` } func NewXMLSRResponse() XMLSRResponse { @@ -38,7 +42,15 @@ func NewXMLSRResponse() XMLSRResponse { XMLNSSRUResponse: "http://docs.oasis-open.org/ns/search-ws/sruResponse", Version: "2.0", ResultCountPrecision: "info:srw/vocabulary/resultCountPrecision/1/exact", - EchoedRequest: XMLSREchoedRequest{Version: "2.0"}, + EchoedRequest: &XMLSREchoedRequest{Version: "2.0"}, + } +} + +func NewMinimalXMLSRResponse() XMLSRResponse { + return XMLSRResponse{ + XMLNSSRUResponse: "http://docs.oasis-open.org/ns/search-ws/sruResponse", + ResultCountPrecision: "info:srw/vocabulary/resultCountPrecision/1/exact", + Version: "2.0", } } diff --git a/handler/v20/searchrt.go b/handler/v20/searchrt.go index d5f4133..5391542 100644 --- a/handler/v20/searchrt.go +++ b/handler/v20/searchrt.go @@ -115,9 +115,8 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ logArgs := make(map[string]interface{}) logging.AddLogEvent(ctx, "args", logArgs) ans := schema.NewXMLSRResponse() - // check if all parameters are supported - for key, _ := range ctx.Request.URL.Query() { + for key := range ctx.Request.URL.Query() { if err := SearchRetrArg(key).Validate(); err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameter, 0, key, err.Error()) @@ -129,23 +128,25 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ fcsQuery := ctx.Query(SearchRetrArgQuery.String()) if len(fcsQuery) == 0 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCMandatoryParameterNotSupplied, 0, "fcs_query", general.DCMandatoryParameterNotSupplied.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCMandatoryParameterNotSupplied, 0, "fcs_query") return ans, general.ConformantStatusBadRequest } ans.EchoedRequest.Query = fcsQuery logArgs[SearchRetrArgQuery.String()] = fcsQuery - // handle start record parameter xStartRecord := ctx.DefaultQuery(SearchRetrStartRecord.String(), "1") startRecord, err := strconv.Atoi(xStartRecord) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, SearchRetrStartRecord.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, SearchRetrStartRecord.String()) return ans, general.ConformantUnprocessableEntity } if startRecord < 1 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, SearchRetrStartRecord.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, SearchRetrStartRecord.String()) return ans, general.ConformantUnprocessableEntity } ans.EchoedRequest.StartRecord = startRecord @@ -155,7 +156,8 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ recordSchema := ctx.DefaultQuery(SearchRetrArgRecordSchema.String(), general.RecordSchema) if recordSchema != general.RecordSchema { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnknownSchemaForRetrieval, 0, SearchMaximumRecords.String(), general.DCUnknownSchemaForRetrieval.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnknownSchemaForRetrieval, 0, SearchMaximumRecords.String()) return ans, general.ConformantUnprocessableEntity } @@ -165,13 +167,15 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ maximumRecords, err = strconv.Atoi(xMaximumRecords) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, SearchMaximumRecords.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, SearchMaximumRecords.String()) return ans, general.ConformantUnprocessableEntity } } if maximumRecords < 1 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedParameterValue, 0, SearchMaximumRecords.String(), general.DCUnsupportedParameterValue.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedParameterValue, 0, SearchMaximumRecords.String()) return ans, general.ConformantUnprocessableEntity } @@ -180,7 +184,8 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ // as the actual result can be very small. But we still // have to limit max. number of records... ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCTooManyMatchingRecords, 0, fmt.Sprintf("%d", mango.MaxRecordsInternalLimit), general.DCTooManyMatchingRecords.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCTooManyMatchingRecords, 0, fmt.Sprintf("%d", mango.MaxRecordsInternalLimit)) return ans, general.ConformantUnprocessableEntity } logArgs[SearchMaximumRecords.String()] = maximumRecords @@ -192,7 +197,7 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ for _, pid := range corporaPids { res, err := a.corporaConf.Resources.GetResourceByPID(pid) if err == corpus.ErrResourceNotFound { - ans.Records = &[]schema.XMLSRRecord{} + ans.Records = nil return ans, http.StatusOK } corpora = append(corpora, res.ID) @@ -205,13 +210,15 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ // get searchable corpora and attrs if len(corpora) == 0 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCUnsupportedContextSet, 0, SearchRetrArgFCSContext.String(), general.DCUnsupportedContextSet.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCUnsupportedContextSet, 0, SearchRetrArgFCSContext.String()) return ans, general.ConformantStatusBadRequest } retrieveAttrs, err := a.corporaConf.Resources.GetCommonPosAttrNames(corpora...) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } @@ -240,13 +247,15 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ query := ast.Generate() if len(ast.Errors()) > 0 { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCQueryCannotProcess, 0, SearchRetrArgQuery.String(), ast.Errors()[0].Error()) + ans.Diagnostics.AddDiagnostic( + general.DCQueryCannotProcess, 0, SearchRetrArgQuery.String(), ast.Errors()[0].Error()) return ans, general.ConformantUnprocessableEntity } rscConf, err := a.corporaConf.Resources.GetResource(rng.Rsc) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, general.ConformandGeneralServerError } args, err := sonic.Marshal(rdb.ConcExampleArgs{ @@ -260,7 +269,8 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ }) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } wait, err := a.radapter.PublishQuery(rdb.Query{ @@ -269,7 +279,8 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ }) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } waits[i] = wait @@ -283,7 +294,8 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ result, err := rdb.DeserializeConcExampleResult(rawResult) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } if err := result.Err(); err != nil { @@ -292,7 +304,8 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ } else { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCQueryCannotProcess, 0, err.Error(), general.DCQueryCannotProcess.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCQueryCannotProcess, 0, err.Error()) return ans, http.StatusInternalServerError } } @@ -304,12 +317,14 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ ans.NumberOfRecords = totalConcSize if fromResource.AllHasOutOfRangeError() { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCFirstRecordPosOutOfRange, 0, fromResource.GetFirstError().Error(), general.DCFirstRecordPosOutOfRange.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCFirstRecordPosOutOfRange, 0, fromResource.GetFirstError().Error()) return ans, general.ConformantUnprocessableEntity } else if fromResource.HasFatalError() { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCQueryCannotProcess, 0, fromResource.GetFirstError().Error(), general.DCQueryCannotProcess.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCQueryCannotProcess, 0, fromResource.GetFirstError().Error()) return ans, general.ConformandGeneralServerError } @@ -318,7 +333,8 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ commonPosAttrs, err := a.corporaConf.Resources.GetCommonPosAttrs(corpora...) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } @@ -327,7 +343,8 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ res, err := a.corporaConf.Resources.GetResource(fromResource.CurrRscName()) if err != nil { ans.Diagnostics = schema.NewXMLDiagnostics() - ans.Diagnostics.AddDiagnostic(general.DCGeneralSystemError, 0, err.Error(), general.DCGeneralSystemError.AsMessage()) + ans.Diagnostics.AddDfltMsgDiagnostic( + general.DCGeneralSystemError, 0, err.Error()) return ans, http.StatusInternalServerError } item := fromResource.CurrLine() @@ -351,7 +368,7 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ Ref: refURL, DataViews: []*schema.XMLSRDataView{ // basic data view - &schema.XMLSRDataView{ + { Type: "application/x-clarin-fcs-hits+xml", Result: schema.XMLSRBasicDataViewResult{ XMLNSHits: "http://clarin.eu/fcs/dataview/hits", @@ -417,7 +434,9 @@ func (a *FCSSubHandlerV20) searchRetrieve(ctx *gin.Context, fcsResponse *FCSRequ RecordPosition: len(records) + startRecord, }) } - ans.Records = &records + if len(records) > 0 { + ans.Records = &records + } if len(records)+startRecord-1 < ans.NumberOfRecords { ans.NextRecordPosition = len(records) + startRecord }