155a6db824
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
133 lines
3.5 KiB
Go
133 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
registryDBOnce sync.Once
|
|
registryDBConn *sql.DB
|
|
registryDBErr error
|
|
)
|
|
|
|
func resolveRegistryDBPath() string {
|
|
if p := os.Getenv("FN_REGISTRY_DB"); p != "" {
|
|
return p
|
|
}
|
|
if root := os.Getenv("FN_REGISTRY_ROOT"); root != "" {
|
|
return filepath.Join(root, "registry.db")
|
|
}
|
|
return "/home/lucas/fn_registry/registry.db"
|
|
}
|
|
|
|
func openRegistryDB() (*sql.DB, error) {
|
|
registryDBOnce.Do(func() {
|
|
path := resolveRegistryDBPath()
|
|
// Read-only access — registry.db es source of truth gestionada via `fn index`.
|
|
db, err := sql.Open("sqlite3", "file:"+path+"?mode=ro&_query_only=1")
|
|
if err != nil {
|
|
registryDBErr = err
|
|
return
|
|
}
|
|
registryDBConn = db
|
|
})
|
|
return registryDBConn, registryDBErr
|
|
}
|
|
|
|
func (s *Server) handleFunctionByID(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
if id == "" {
|
|
writeError(w, http.StatusBadRequest, "id is required")
|
|
return
|
|
}
|
|
|
|
db, err := openRegistryDB()
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), queryTimeout)
|
|
defer cancel()
|
|
|
|
// Try functions table first.
|
|
const fnQuery = `SELECT id, name, kind, lang, domain, version, purity, signature,
|
|
description, tags, returns, returns_optional, error_type, file_path, code, documentation
|
|
FROM functions WHERE id = ?`
|
|
var (
|
|
fnID, name, kind, lang, domain, version, purity, signature string
|
|
description, tags, returns, errorType, filePath string
|
|
code, documentation string
|
|
returnsOptional bool
|
|
)
|
|
err = db.QueryRowContext(ctx, fnQuery, id).Scan(
|
|
&fnID, &name, &kind, &lang, &domain, &version, &purity, &signature,
|
|
&description, &tags, &returns, &returnsOptional, &errorType, &filePath,
|
|
&code, &documentation,
|
|
)
|
|
if err == nil {
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"entity": "function",
|
|
"id": fnID,
|
|
"name": name,
|
|
"kind": kind,
|
|
"lang": lang,
|
|
"domain": domain,
|
|
"version": version,
|
|
"purity": purity,
|
|
"signature": signature,
|
|
"description": description,
|
|
"tags": tags,
|
|
"returns": returns,
|
|
"returns_optional": returnsOptional,
|
|
"error_type": errorType,
|
|
"file_path": filePath,
|
|
"code": code,
|
|
"documentation": documentation,
|
|
})
|
|
return
|
|
}
|
|
if err != sql.ErrNoRows {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
// Fallback: types table.
|
|
const typeQuery = `SELECT id, name, lang, domain, version, description, tags, definition, file_path
|
|
FROM types WHERE id = ?`
|
|
var (
|
|
tID, tName, tLang, tDomain, tVersion string
|
|
tDescription, tTags, tDefinition string
|
|
tFilePath string
|
|
)
|
|
err = db.QueryRowContext(ctx, typeQuery, id).Scan(
|
|
&tID, &tName, &tLang, &tDomain, &tVersion,
|
|
&tDescription, &tTags, &tDefinition, &tFilePath,
|
|
)
|
|
if err == nil {
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"entity": "type",
|
|
"id": tID,
|
|
"name": tName,
|
|
"lang": tLang,
|
|
"domain": tDomain,
|
|
"version": tVersion,
|
|
"description": tDescription,
|
|
"tags": tTags,
|
|
"definition": tDefinition,
|
|
"file_path": tFilePath,
|
|
})
|
|
return
|
|
}
|
|
if err == sql.ErrNoRows {
|
|
writeError(w, http.StatusNotFound, "not_found")
|
|
return
|
|
}
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
}
|