Files
visualizador_instanciados/backend_go/config.go

274 lines
8.4 KiB
Go

package main
import (
"fmt"
"os"
"strconv"
"strings"
"time"
)
type Config struct {
IncludeBNodes bool
CorsOrigins string
DefaultNodeLimit int
DefaultEdgeLimit int
MaxNodeLimit int
MaxEdgeLimit int
EdgeBatchSize int
FreeOSMemoryAfterSnapshot bool
LogSnapshotTimings bool
SparqlSourceMode string
SparqlHost string
SparqlEndpoint string
ExternalSparqlEndpoint string
AccessToken string
KeycloakTokenEndpoint string
KeycloakClientID string
KeycloakUsername string
KeycloakPassword string
KeycloakScope string
SparqlUser string
SparqlPass string
SparqlInsecureTLS bool
SparqlDataFile string
SparqlGraphIRI string
SparqlLoadOnStart bool
SparqlTimeout time.Duration
SparqlReadyRetries int
SparqlReadyDelay time.Duration
SparqlReadyTimeout time.Duration
HierarchyLayoutEngine string
HierarchyLayoutBridgeBin string
HierarchyLayoutBridgeWorkdir string
HierarchyLayoutTimeout time.Duration
HierarchyLayoutRootIRI string
ListenAddr string
}
func LoadConfig() (Config, error) {
cfg := Config{
IncludeBNodes: envBool("INCLUDE_BNODES", false),
CorsOrigins: envString("CORS_ORIGINS", "*"),
DefaultNodeLimit: envInt("DEFAULT_NODE_LIMIT", 800_000),
DefaultEdgeLimit: envInt("DEFAULT_EDGE_LIMIT", 2_000_000),
MaxNodeLimit: envInt("MAX_NODE_LIMIT", 10_000_000),
MaxEdgeLimit: envInt("MAX_EDGE_LIMIT", 20_000_000),
EdgeBatchSize: envInt("EDGE_BATCH_SIZE", 100_000),
FreeOSMemoryAfterSnapshot: envBool("FREE_OS_MEMORY_AFTER_SNAPSHOT", false),
LogSnapshotTimings: envBool("LOG_SNAPSHOT_TIMINGS", false),
SparqlSourceMode: envString("SPARQL_SOURCE_MODE", "local"),
SparqlHost: envString("SPARQL_HOST", "http://anzograph:8080"),
SparqlEndpoint: envString("SPARQL_ENDPOINT", ""),
ExternalSparqlEndpoint: envString("EXTERNAL_SPARQL_ENDPOINT", ""),
AccessToken: envString("ACCESS_TOKEN", ""),
KeycloakTokenEndpoint: envString("KEYCLOAK_TOKEN_ENDPOINT", ""),
KeycloakClientID: envString("KEYCLOAK_CLIENT_ID", ""),
KeycloakUsername: envString("KEYCLOAK_USERNAME", ""),
KeycloakPassword: envString("KEYCLOAK_PASSWORD", ""),
KeycloakScope: envString("KEYCLOAK_SCOPE", "openid"),
SparqlUser: envString("SPARQL_USER", ""),
SparqlPass: envString("SPARQL_PASS", ""),
SparqlInsecureTLS: envBool("SPARQL_INSECURE_TLS", false),
SparqlDataFile: envString("SPARQL_DATA_FILE", ""),
SparqlGraphIRI: envString("SPARQL_GRAPH_IRI", ""),
SparqlLoadOnStart: envBool("SPARQL_LOAD_ON_START", false),
HierarchyLayoutEngine: envString("HIERARCHY_LAYOUT_ENGINE", "go"),
HierarchyLayoutBridgeBin: envString("HIERARCHY_LAYOUT_BRIDGE_BIN", "/app/radial_sugiyama_go_bridge"),
HierarchyLayoutBridgeWorkdir: envString("HIERARCHY_LAYOUT_BRIDGE_WORKDIR", "/workspace/radial_sugiyama"),
HierarchyLayoutRootIRI: envString("HIERARCHY_LAYOUT_ROOT_IRI", "http://purl.obolibrary.org/obo/BFO_0000001"),
SparqlReadyRetries: envInt("SPARQL_READY_RETRIES", 30),
ListenAddr: envString("LISTEN_ADDR", ":8000"),
}
var err error
cfg.SparqlTimeout, err = envSeconds("SPARQL_TIMEOUT_S", 300)
if err != nil {
return Config{}, err
}
cfg.SparqlReadyDelay, err = envSeconds("SPARQL_READY_DELAY_S", 4)
if err != nil {
return Config{}, err
}
cfg.SparqlReadyTimeout, err = envSeconds("SPARQL_READY_TIMEOUT_S", 10)
if err != nil {
return Config{}, err
}
cfg.HierarchyLayoutTimeout, err = envSeconds("HIERARCHY_LAYOUT_TIMEOUT_S", 60)
if err != nil {
return Config{}, err
}
if cfg.SparqlLoadOnStart && strings.TrimSpace(cfg.SparqlDataFile) == "" {
return Config{}, fmt.Errorf("SPARQL_LOAD_ON_START=true but SPARQL_DATA_FILE is not set")
}
switch strings.ToLower(strings.TrimSpace(cfg.SparqlSourceMode)) {
case "local", "external":
cfg.SparqlSourceMode = strings.ToLower(strings.TrimSpace(cfg.SparqlSourceMode))
default:
return Config{}, fmt.Errorf("SPARQL_SOURCE_MODE must be 'local' or 'external'")
}
if cfg.UsesExternalSparql() {
if strings.TrimSpace(cfg.ExternalSparqlEndpoint) == "" {
return Config{}, fmt.Errorf("EXTERNAL_SPARQL_ENDPOINT must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakTokenEndpoint) == "" {
return Config{}, fmt.Errorf("KEYCLOAK_TOKEN_ENDPOINT must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakClientID) == "" {
return Config{}, fmt.Errorf("KEYCLOAK_CLIENT_ID must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakUsername) == "" {
return Config{}, fmt.Errorf("KEYCLOAK_USERNAME must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakPassword) == "" {
return Config{}, fmt.Errorf("KEYCLOAK_PASSWORD must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakScope) == "" {
cfg.KeycloakScope = "openid"
}
if cfg.SparqlLoadOnStart {
return Config{}, fmt.Errorf("SPARQL_LOAD_ON_START is not supported when SPARQL_SOURCE_MODE=external")
}
}
if cfg.DefaultNodeLimit < 1 {
return Config{}, fmt.Errorf("DEFAULT_NODE_LIMIT must be >= 1")
}
if cfg.DefaultEdgeLimit < 1 {
return Config{}, fmt.Errorf("DEFAULT_EDGE_LIMIT must be >= 1")
}
if cfg.MaxNodeLimit < 1 {
return Config{}, fmt.Errorf("MAX_NODE_LIMIT must be >= 1")
}
if cfg.MaxEdgeLimit < 1 {
return Config{}, fmt.Errorf("MAX_EDGE_LIMIT must be >= 1")
}
if cfg.DefaultNodeLimit > cfg.MaxNodeLimit {
return Config{}, fmt.Errorf("DEFAULT_NODE_LIMIT must be <= MAX_NODE_LIMIT")
}
if cfg.DefaultEdgeLimit > cfg.MaxEdgeLimit {
return Config{}, fmt.Errorf("DEFAULT_EDGE_LIMIT must be <= MAX_EDGE_LIMIT")
}
if cfg.EdgeBatchSize < 1 {
return Config{}, fmt.Errorf("EDGE_BATCH_SIZE must be >= 1")
}
if cfg.EdgeBatchSize > cfg.MaxEdgeLimit {
return Config{}, fmt.Errorf("EDGE_BATCH_SIZE must be <= MAX_EDGE_LIMIT")
}
switch strings.ToLower(strings.TrimSpace(cfg.HierarchyLayoutEngine)) {
case "go", "rust":
cfg.HierarchyLayoutEngine = strings.ToLower(strings.TrimSpace(cfg.HierarchyLayoutEngine))
default:
return Config{}, fmt.Errorf("HIERARCHY_LAYOUT_ENGINE must be 'go' or 'rust'")
}
if strings.TrimSpace(cfg.HierarchyLayoutBridgeBin) == "" {
return Config{}, fmt.Errorf("HIERARCHY_LAYOUT_BRIDGE_BIN must not be empty")
}
if strings.TrimSpace(cfg.HierarchyLayoutBridgeWorkdir) == "" {
return Config{}, fmt.Errorf("HIERARCHY_LAYOUT_BRIDGE_WORKDIR must not be empty")
}
if strings.TrimSpace(cfg.HierarchyLayoutRootIRI) == "" {
return Config{}, fmt.Errorf("HIERARCHY_LAYOUT_ROOT_IRI must not be empty")
}
if cfg.HierarchyLayoutTimeout <= 0 {
return Config{}, fmt.Errorf("HIERARCHY_LAYOUT_TIMEOUT_S must be > 0")
}
return cfg, nil
}
func (c Config) EffectiveSparqlEndpoint() string {
if c.UsesExternalSparql() {
return strings.TrimSpace(c.ExternalSparqlEndpoint)
}
if strings.TrimSpace(c.SparqlEndpoint) != "" {
return strings.TrimSpace(c.SparqlEndpoint)
}
return strings.TrimRight(c.SparqlHost, "/") + "/sparql"
}
func (c Config) UsesExternalSparql() bool {
return strings.EqualFold(strings.TrimSpace(c.SparqlSourceMode), "external")
}
func (c Config) corsOriginList() []string {
raw := strings.TrimSpace(c.CorsOrigins)
if raw == "" || raw == "*" {
return []string{"*"}
}
parts := strings.Split(raw, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p == "" {
continue
}
out = append(out, p)
}
if len(out) == 0 {
return []string{"*"}
}
return out
}
func envString(name, def string) string {
v := os.Getenv(name)
if strings.TrimSpace(v) == "" {
return def
}
return v
}
func envBool(name string, def bool) bool {
v := strings.TrimSpace(os.Getenv(name))
if v == "" {
return def
}
switch strings.ToLower(v) {
case "1", "true", "yes", "y", "on":
return true
case "0", "false", "no", "n", "off":
return false
default:
return def
}
}
func envInt(name string, def int) int {
v := strings.TrimSpace(os.Getenv(name))
if v == "" {
return def
}
v = strings.ReplaceAll(v, "_", "")
n, err := strconv.Atoi(v)
if err != nil {
return def
}
return n
}
func envSeconds(name string, def float64) (time.Duration, error) {
v := strings.TrimSpace(os.Getenv(name))
if v == "" {
return time.Duration(def * float64(time.Second)), nil
}
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return 0, fmt.Errorf("%s must be a number (seconds): %w", name, err)
}
return time.Duration(f * float64(time.Second)), nil
}