blob: aa4e3e23e71107feb81acc68cf72dc05fd14582f [file] [log] [blame]
Akron49ceeb42025-05-23 17:46:01 +02001package main
2
3import (
4 "flag"
5 "fmt"
6 "os"
7 "os/signal"
8 "strings"
9 "syscall"
10
11 "github.com/KorAP/KoralPipe-TermMapper2/pkg/mapper"
12 "github.com/gofiber/fiber/v2"
13 "github.com/rs/zerolog"
14 "github.com/rs/zerolog/log"
15)
16
17type config struct {
18 port int
19 config string
20 logLevel string
21}
22
23func parseFlags() *config {
24 cfg := &config{}
25
26 flag.IntVar(&cfg.port, "port", 8080, "Port to listen on")
27 flag.IntVar(&cfg.port, "p", 8080, "Port to listen on (shorthand)")
28
29 flag.StringVar(&cfg.config, "config", "", "YAML configuration file containing mapping directives")
30 flag.StringVar(&cfg.config, "c", "", "YAML configuration file containing mapping directives (shorthand)")
31
32 flag.StringVar(&cfg.logLevel, "log-level", "info", "Log level (debug, info, warn, error)")
33 flag.StringVar(&cfg.logLevel, "l", "info", "Log level (shorthand)")
34
35 flag.Usage = func() {
36 fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
37 fmt.Fprintf(os.Stderr, "\nA web service for transforming JSON objects using term mapping rules.\n\n")
38 fmt.Fprintf(os.Stderr, "Options:\n")
39 flag.PrintDefaults()
40 }
41
42 flag.Parse()
43
44 if cfg.config == "" {
45 fmt.Fprintln(os.Stderr, "Error: config file is required")
46 flag.Usage()
47 os.Exit(1)
48 }
49
50 return cfg
51}
52
53func setupLogger(level string) {
54 // Parse log level
55 lvl, err := zerolog.ParseLevel(strings.ToLower(level))
56 if err != nil {
57 log.Error().Err(err).Str("level", level).Msg("Invalid log level, defaulting to info")
58 lvl = zerolog.InfoLevel
59 }
60
61 // Configure zerolog
62 zerolog.SetGlobalLevel(lvl)
63 log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
64}
65
66func main() {
67 // Parse command line flags
68 cfg := parseFlags()
69
70 // Set up logging
71 setupLogger(cfg.logLevel)
72
73 // Create a new mapper instance
74 m, err := mapper.NewMapper(cfg.config)
75 if err != nil {
76 log.Fatal().Err(err).Msg("Failed to create mapper")
77 }
78
79 // Create fiber app
80 app := fiber.New(fiber.Config{
81 DisableStartupMessage: true,
82 })
83
84 // Set up routes
85 setupRoutes(app, m)
86
87 // Start server
88 go func() {
89 log.Info().Int("port", cfg.port).Msg("Starting server")
90 if err := app.Listen(fmt.Sprintf(":%d", cfg.port)); err != nil {
91 log.Fatal().Err(err).Msg("Server error")
92 }
93 }()
94
95 // Wait for interrupt signal
96 sigChan := make(chan os.Signal, 1)
97 signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
98 <-sigChan
99
100 // Graceful shutdown
101 log.Info().Msg("Shutting down server")
102 if err := app.Shutdown(); err != nil {
103 log.Error().Err(err).Msg("Error during shutdown")
104 }
105}
106
107func setupRoutes(app *fiber.App, m *mapper.Mapper) {
108 // Health check endpoint
109 app.Get("/health", func(c *fiber.Ctx) error {
110 return c.SendString("OK")
111 })
112
113 // Transformation endpoint
114 app.Post("/:map/query", handleTransform(m))
115}
116
117func handleTransform(m *mapper.Mapper) fiber.Handler {
118 return func(c *fiber.Ctx) error {
119 // Get parameters
120 mapID := c.Params("map")
121 dir := c.Query("dir", "atob")
122 foundryA := c.Query("foundryA", "")
123 foundryB := c.Query("foundryB", "")
124 layerA := c.Query("layerA", "")
125 layerB := c.Query("layerB", "")
126
127 // Validate direction
128 if dir != "atob" && dir != "btoa" {
129 return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
130 "error": "invalid direction, must be 'atob' or 'btoa'",
131 })
132 }
133
134 // Parse request body
135 var jsonData interface{}
136 if err := c.BodyParser(&jsonData); err != nil {
137 return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
138 "error": "invalid JSON in request body",
139 })
140 }
141
142 // Apply mappings
143 result, err := m.ApplyMappings(mapID, mapper.MappingOptions{
144 Direction: mapper.Direction(dir),
145 FoundryA: foundryA,
146 FoundryB: foundryB,
147 LayerA: layerA,
148 LayerB: layerB,
149 }, jsonData)
150
151 if err != nil {
152 log.Error().Err(err).
153 Str("mapID", mapID).
154 Str("direction", dir).
155 Msg("Failed to apply mappings")
156
157 return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
158 "error": err.Error(),
159 })
160 }
161
162 return c.JSON(result)
163 }
164}