Error Handling
Relevant source files
The following files were used as context for generating this wiki page:
Error handling in MauQNotes follows standard Go conventions, where functions that can fail return an error as their last return value. The system employs a layered approach, where errors are propagated up the call stack from low-level clients (like HTTP clients for Qdrant and embedding services) to the service layer, and finally to the application's entrypoint.
At the highest level, in main.go, any error returned from the core search.IngestNotes or search.Query functions is treated as a fatal event, causing the application to terminate immediately with a logged message. This ensures that the application does not continue in a partially failed or indeterminate state. Lower-level modules are responsible for creating descriptive errors related to configuration, network issues, or API responses.
Error Propagation Flow
Errors are consistently checked and returned up the call stack. A failure in a dependency like the Qdrant client or an embedding service will bubble up through the search service to the main application function.
The diagram below illustrates the general error propagation path from a dependency to the application's entrypoint.
Application Entrypoint (main.go)
The main function in cmd/app/main.go serves as the final checkpoint for errors. If either the ingestion or query process fails, the program is terminated using log.Fatal. This function prints the error message to the standard logger and then calls os.Exit(1).
// Ingest
if err := search.IngestNotes(notes); err != nil {
log.Fatal("ingest failed: ", err)
}
//Query
query := "cara mulai project AI kecil dengan semantic search"
results, err := search.Query(3, query)
if err != nil {
log.Fatal("search failed:", err)
}
Sources: cmd/app/main.go:16-18, cmd/app/main.go:23-25
Service Layer (search/service.go)
The search service acts as an orchestrator, and its primary role in error handling is to propagate errors from its dependencies. Functions like IngestNotes and Query call the embedding and Qdrant client modules. If any of these calls return an error, the service immediately returns the error to its caller (main.go).
The following sequence diagram shows how an error during the IngestNotes process is handled. An error from either embeddings.FromOllama or client.Upsert will halt the process and return the error up the stack.
Sources: internal/search/service.go:37-59
Qdrant Client (qdrant/client.go)
The Qdrant client is a significant source of potential errors, including configuration issues, network problems, and API-specific failures.
Configuration Validation
Before any request is made, the validate() method is called to check for required environment variables. This prevents runtime errors due to a missing configuration.
| Variable | Check | Error Message |
|---|---|---|
QDRANT_URL | Must not be empty. | QDRANT_URL is empty |
QDRANT_URL | Must start with http:// or https://. | QDRANT_URL must start with http:// or https:// |
QDRANT_COLLECTION | Must not be empty. | QDRANT_COLLECTION is empty |
Sources: internal/qdrant/client.go:88-99
The flow of the validation logic is as follows:
Sources: internal/qdrant/client.go:88-99
HTTP and API Errors
The client handles several types of errors during HTTP requests:
- Request Creation/Execution Failure: If
http.DefaultClient.Do(req)fails due to network issues (e.g., DNS lookup failure, connection refused), the underlying error is returned.
Sources: internal/qdrant/client.go:121-124, internal/qdrant/client.go:150-153 - Non-Successful Status Codes: For operations like
Upsert, any status code outside the 2xx range is considered an error. The response body is read to provide a more detailed error message.
Sources: internal/qdrant/client.go:155-161if resp.StatusCode/100 != 2 { // Read response body for more details var respBody bytes.Buffer respBody.ReadFrom(resp.Body) return fmt.Errorf("upsert failed: %s - %s", resp.Status, respBody.String()) } - Specific Status Codes: For
CreateCollectionIfNotExists, a409 Conflictstatus code is explicitly ignored, as it indicates the collection already exists, which is the desired state. Any other non-200 status code is an error.
Sources: internal/qdrant/client.go:126-130 - API Logic Errors: For search operations, even with a 200 OK response, the API might indicate a problem in the response body. The client checks if
out.Status != "ok"and returns an error if it's not.
Sources: internal/qdrant/client.go:189-191
Embedding Services (embeddings/)
Both Ollama and OpenAI embedding clients handle errors from their respective APIs.
Ollama (ollama.go)
The FromOllama function handles several failure modes:
- Configuration: If
OLLAMA_URLis not set, it defaults tohttp://localhost:11434. No error is thrown for missing configuration, but the request will fail if Ollama is not running there. - HTTP Request Failure: Standard network errors from
http.DefaultClient.Doare propagated. - API Error: The Ollama API response JSON contains an
errorfield. If this field is not empty, its content is used to create a new error. - Empty Response: If the API returns a successful response but the
embeddingarray is empty, a specific errorempty embeddings responseis returned.
var out ollamaResponse
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return nil, err
}
if out.Error != "" {
return nil, errors.New(out.Error)
}
if len(out.Embedding) == 0 {
return nil, errors.New("empty embeddings response")
}
Sources: internal/embeddings/ollama.go:57-67
OpenAI (openai.go)
The FromOpenAI function has a similar error handling pattern:
- Configuration: It explicitly checks for
OPENAI_API_KEYandEMBEDDING_MODEL. If either is missing, it returns an error immediately. - HTTP Request Failure: Propagates network errors from the HTTP client.
- API Error: The OpenAI API response contains an
errorobject. If this object is notnil, theMessagefield is extracted and returned as an error. - Empty Response: If the
dataarray in the response is empty, it returns anempty embeddings responseerror.
if apiKey == "" || model == "" {
return nil, errors.New("missing OpenAI key or embedding model")
}
// ...
if out.Error != nil {
return nil, errors.New(out.Error.Message)
}
if len(out.Data) == 0 {
return nil, errors.New("empty embeddings response")
}
Sources: internal/embeddings/openai.go:29-31, internal/embeddings/openai.go:51-56
Summary
The error handling strategy in MauQNotes is robust and follows Go best practices. Errors are explicitly checked and propagated from dependencies up to the main application, where critical failures lead to a controlled shutdown. This ensures that issues with external services (Qdrant, Ollama, OpenAI) or configuration are caught and reported, preventing the application from running in an invalid state.