Files
openserp/core/response.go

162 lines
4.9 KiB
Go

package core
import "time"
// QueryEcho echoes the interpreted query parameters back to the client.
type QueryEcho struct {
Text string `json:"text"`
Lang string `json:"lang,omitempty"`
Region string `json:"region,omitempty"`
EnginesRequested []string `json:"engines_requested"`
}
// ResponseMeta carries request-level metadata for observability and debugging.
type ResponseMeta struct {
RequestID string `json:"request_id"`
RequestedAt string `json:"requested_at"`
TookMs int64 `json:"took_ms"`
EnginesResponded []string `json:"engines_responded,omitempty"`
EnginesFailed []string `json:"engines_failed"`
EngineErrors []EngineErrorDetail `json:"engine_errors,omitempty"`
Version string `json:"version"`
}
// EngineErrorDetail is a client-facing, sanitized per-engine failure summary.
type EngineErrorDetail struct {
Engine string `json:"engine"`
Error string `json:"error"`
Message string `json:"message,omitempty"`
}
// Pagination carries cursor information for client-side loop termination.
type Pagination struct {
Page int `json:"page"`
HasMore bool `json:"has_more"`
NextStart int `json:"next_start"`
}
// Envelope is the top-level v2 response wrapper for all search endpoints.
type Envelope struct {
Query QueryEcho `json:"query"`
Meta ResponseMeta `json:"meta"`
Results []Result `json:"results"`
SerpFeatures []SerpFeature `json:"serp_features"`
Pagination Pagination `json:"pagination"`
// Clusters is only populated by /mega/search (see clusters.go).
Clusters *[]Cluster `json:"clusters,omitempty"`
}
// Cluster groups results that refer to the same canonical URL across engines.
// Populated only by /mega/search. Full type defined in clusters.go.
type Cluster struct {
ID string `json:"id"`
CanonicalURL string `json:"canonical_url"`
Domain string `json:"domain"`
Title string `json:"title"`
Occurrences []ClusterOccurrence `json:"occurrences"`
EnginesCount int `json:"engines_count"`
BestRank int `json:"best_rank"`
Score float64 `json:"score"`
}
// ClusterOccurrence links one engine result back into the flat results list.
type ClusterOccurrence struct {
Engine string `json:"engine"`
Rank int `json:"rank"`
ResultID string `json:"result_id"`
}
// ImageEnvelope is the top-level v2 response wrapper for image search endpoints.
type ImageEnvelope struct {
Query QueryEcho `json:"query"`
Meta ResponseMeta `json:"meta"`
Results []ImageResult `json:"results"`
Pagination Pagination `json:"pagination"`
}
const apiVersion = "2.1"
// NewEnvelope builds a fresh Envelope pre-filled with query echo and an open
// meta block. Call Finalize before serializing.
func NewEnvelope(q Query, requestID string, startedAt time.Time, engines []string) *Envelope {
return &Envelope{
Query: QueryEcho{
Text: q.Text,
Lang: q.LangCode,
Region: q.Region,
EnginesRequested: engines,
},
Meta: ResponseMeta{
RequestID: requestID,
RequestedAt: startedAt.UTC().Format(time.RFC3339),
EnginesFailed: []string{},
Version: apiVersion,
},
Results: []Result{},
SerpFeatures: []SerpFeature{},
Pagination: Pagination{},
}
}
// NewImageEnvelope builds a fresh ImageEnvelope.
func NewImageEnvelope(q Query, requestID string, startedAt time.Time, engines []string) *ImageEnvelope {
return &ImageEnvelope{
Query: QueryEcho{
Text: q.Text,
Lang: q.LangCode,
Region: q.Region,
EnginesRequested: engines,
},
Meta: ResponseMeta{
RequestID: requestID,
RequestedAt: startedAt.UTC().Format(time.RFC3339),
EnginesFailed: []string{},
Version: apiVersion,
},
Results: []ImageResult{},
Pagination: Pagination{},
}
}
// Finalize stamps the elapsed time and computes pagination fields.
func (e *Envelope) Finalize(startedAt time.Time, q Query) {
e.Meta.TookMs = time.Since(startedAt).Milliseconds()
limit := q.Limit
if limit <= 0 {
limit = defaultQueryLimit
}
page := q.Start/limit + 1
e.Pagination = Pagination{
Page: page,
HasMore: countNonAdResults(e.Results) >= limit,
NextStart: q.Start + limit,
}
}
func countNonAdResults(results []Result) int {
count := 0
for _, result := range results {
if result.Type != ResultTypeAd {
count++
}
}
return count
}
// Finalize stamps the elapsed time and computes pagination fields.
func (e *ImageEnvelope) Finalize(startedAt time.Time, q Query) {
e.Meta.TookMs = time.Since(startedAt).Milliseconds()
limit := q.Limit
if limit <= 0 {
limit = defaultQueryLimit
}
page := q.Start/limit + 1
e.Pagination = Pagination{
Page: page,
HasMore: len(e.Results) >= limit,
NextStart: q.Start + limit,
}
}