diff --git a/sdk/builders/fluent/search.go b/sdk/builders/fluent/search.go index 9553334..53162ae 100644 --- a/sdk/builders/fluent/search.go +++ b/sdk/builders/fluent/search.go @@ -2,19 +2,26 @@ package fluent import ( "context" + "encoding/json" "fmt" "github.com/nudibranches-tech/bifrost-hyperfluid-sdk-dev/sdk/utils" ) +type OriginalFilePayload struct { + OriginalFilePath string `json:"original_file_path"` + SourceDataDockID string `json:"source_data_dock_id"` + SourceFileName string `json:"source_filename"` +} + type DocumentRecord struct { - Name string `json:"name"` - Content string `json:"content"` - Summary string `json:"summary"` - HfContext string `json:"hf_context"` - OriginalFile string `json:"original_file"` - Categories string `json:"categories"` - RlsLabels string `json:"rls_labels"` + Name string `json:"name"` + Content string `json:"content"` + Summary string `json:"summary"` + HfContext string `json:"hf_context"` + OriginalFile OriginalFilePayload `json:"original_file"` + Categories string `json:"categories"` + RlsLabels string `json:"rls_labels"` } type DocumentResult struct { @@ -22,6 +29,36 @@ type DocumentResult struct { Score float64 `json:"score"` } +func (dr *DocumentRecord) UnmarshalJSON(data []byte) error { + type alias DocumentRecord + aux := &struct { + OriginalFile json.RawMessage `json:"original_file"` + *alias + }{ + alias: (*alias)(dr), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + if len(aux.OriginalFile) == 0 { + return nil + } + if aux.OriginalFile[0] == '"' { + var inner string + if err := json.Unmarshal(aux.OriginalFile, &inner); err != nil { + return fmt.Errorf("unquote failed: %w", err) + } + if err := json.Unmarshal([]byte(inner), &dr.OriginalFile); err != nil { + return fmt.Errorf("inner decode failed: %w", err) + } + } else { + if err := json.Unmarshal(aux.OriginalFile, &dr.OriginalFile); err != nil { + return fmt.Errorf("direct decode failed: %w", err) + } + } + return nil +} + type SearchResults struct { Results []DocumentResult `json:"results"` Total int `json:"total"` diff --git a/usage_examples/fluent_api_examples.go b/usage_examples/fluent_api_examples.go index 8533f00..5659a46 100644 --- a/usage_examples/fluent_api_examples.go +++ b/usage_examples/fluent_api_examples.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "log" "os" "time" @@ -13,46 +14,74 @@ import ( // This file demonstrates the new fluent API for the Bifrost SDK. // The fluent API provides a more intuitive and user-friendly way to interact with the SDK. -/* - func runSearchExample() { - fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") - fmt.Println("🎯 Search Example: Full-Text Search") - fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") - - config := getConfig() - client := sdk.NewClient(config) - - projectID := getEnv("BIFROST_DATADOCK_ID", "") - catalog := getEnv("BIFROST_TEST_CATALOG", "iceberg") - schema := getEnv("BIFROST_TEST_SCHEMA", "public") - table := getEnv("BIFROST_TEST_TABLE", "text_files") - - if projectID == "" { - fmt.Println("⚠️ Skipping: BIFROST_DATADOCK_ID not set") - fmt.Println() - return - } +func runSearchExample() { + fmt.Println("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + fmt.Println("🎯 Search Example: Full‑Text Search") + fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") - fmt.Println("📝 Full-text search query:") - fmt.Printf(" DataDock: %s\n", projectID) - fmt.Printf(" Searching in: %s.%s.%s\n", catalog, schema, table) - fmt.Println() + // ---- 1️⃣ Build config & client (must be non‑nil) ---- + cfg := getConfig() + if cfg.BaseURL == "" { + log.Fatal("❌ Base URL not set – cannot create SDK client") + } + client := sdk.NewClient(cfg) + if client == nil { + log.Fatal("❌ sdk.NewClient returned nil – check configuration") + } + + // ---- 2️⃣ Pull required identifiers from env ---- + projectID := getEnv("BIFROST_DATADOCK_ID", "") + catalog := getEnv("BIFROST_TEST_CATALOG", "iceberg") + schema := getEnv("BIFROST_TEST_SCHEMA", "public") + table := getEnv("BIFROST_TEST_TABLE", "text_files") - // Search for content - resp, _ := client.Search(). - Query("rapport ventes"). - DataDock(projectID). // ✅ Use the actual UUID variable - Catalog(catalog). // ✅ Use actual catalog - Schema(schema). // ✅ Use actual schema - Table(table). // ✅ Use actual table - Columns("content", "summary"). // Adjust columns based on your table - Limit(10). - Execute(context.Background()) - - fmt.Println(resp.Results) + if projectID == "" { + fmt.Println("⚠️ Skipping: BIFROST_DATADOCK_ID not set") fmt.Println() + return } -*/ + + // ---- 3️⃣ Show what we are about to do ---- + fmt.Println("📝 Full‑text search query:") + fmt.Printf(" DataDock: %s\n", projectID) + fmt.Printf(" Searching in: %s.%s.%s\n\n", catalog, schema, table) + + // ---- 4️⃣ Build the fluent request ---- + builder := client.Search(). + Query("rapport ventes"). + DataDock(projectID). + Catalog(catalog). + Schema(schema). + Table(table). + Columns("content", "summary", "original_file"). // <-- include original_file + Limit(10) + + // ---- 5️⃣ Execute and **handle the error** ---- + results, err := builder.Execute(context.Background()) + if err != nil { + // The builder already validates required fields, so any error here + // is a network/API problem or a JSON‑unmarshal issue. + log.Fatalf("❌ Search execution failed: %v", err) + } + + // ---- 6️⃣ Print a concise summary ---- + fmt.Printf("✅ Got %d result(s) (took %d ms)\n", results.Total, results.TimeTakenMs) + + // ---- 7️⃣ Walk the results and display the S3 URL ---- + for i, r := range results.Results { + fmt.Printf("\n#%d – Score: %.3f\n", i+1, r.Score) + fmt.Printf(" Name : %s\n", r.Record.Name) + fmt.Printf(" Summary: %s\n", r.Record.Summary) + + s3URL := r.Record.OriginalFile.OriginalFilePath + if s3URL != "" { + fmt.Printf(" S3 URL: %s\n", s3URL) + } else { + fmt.Println(" S3 URL: ") + } + } + fmt.Println() +} /* func runFluentAPISimpleExample() { @@ -91,7 +120,6 @@ func runFluentAPISimpleExample() { } */ -/* func runFluentAPIWithSelectExample() { fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") fmt.Println("🎯 Fluent API Example 2: Query with SELECT") @@ -133,7 +161,6 @@ func runFluentAPIWithSelectExample() { handleResponse(resp, err) fmt.Println() } -*/ /* func runFluentAPIComplexExample() { @@ -178,7 +205,7 @@ func runFluentAPIComplexExample() { } */ // Helper functions -/* + func handleResponse(resp *utils.Response, err error) { if err != nil { fmt.Printf("❌ Error: %s\n", err.Error()) @@ -205,7 +232,6 @@ func handleResponse(resp *utils.Response, err error) { fmt.Printf("📦 Data: %v\n", dataMap) } } -*/ func runS3Example() { fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") @@ -304,7 +330,6 @@ func getEnv(key, fallback string) string { return fallback } -/* func splitColumns(cols string) []string { if cols == "" { return []string{} @@ -327,4 +352,3 @@ func splitColumns(cols string) []string { } return result } -*/ diff --git a/usage_examples/main.go b/usage_examples/main.go index 90528cd..5bba6f5 100644 --- a/usage_examples/main.go +++ b/usage_examples/main.go @@ -27,6 +27,8 @@ func main() { fmt.Println() runS3Example() + runFluentAPIWithSelectExample() + runSearchExample() fmt.Println() fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━")