Support automatic response pipe addition in Kalamar endpoint
Change-Id: Ic76b53edaaa8c3ad7cee311900e301c723688796
diff --git a/cmd/termmapper/main.go b/cmd/termmapper/main.go
index 37de2da..90f6242 100644
--- a/cmd/termmapper/main.go
+++ b/cmd/termmapper/main.go
@@ -49,6 +49,14 @@
Mappings []TemplateMapping
}
+type QueryParams struct {
+ Dir string
+ FoundryA string
+ FoundryB string
+ LayerA string
+ LayerB string
+}
+
func parseConfig() *appConfig {
cfg := &appConfig{}
@@ -336,6 +344,27 @@
return func(c *fiber.Ctx) error {
mapID := c.Params("map")
+ // Get query parameters
+ dir := c.Query("dir", "atob")
+ foundryA := c.Query("foundryA", "")
+ foundryB := c.Query("foundryB", "")
+ layerA := c.Query("layerA", "")
+ layerB := c.Query("layerB", "")
+
+ // Validate input parameters (reuse existing validation)
+ if err := validateInput(mapID, dir, foundryA, foundryB, layerA, layerB, []byte{}); err != nil {
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
+ "error": err.Error(),
+ })
+ }
+
+ // Validate direction
+ if dir != "atob" && dir != "btoa" {
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
+ "error": "invalid direction, must be 'atob' or 'btoa'",
+ })
+ }
+
// Get list of available mappings
var mappings []TemplateMapping
for _, list := range yamlConfig.Lists {
@@ -363,8 +392,17 @@
Mappings: mappings,
}
+ // Add query parameters to template data
+ queryParams := QueryParams{
+ Dir: dir,
+ FoundryA: foundryA,
+ FoundryB: foundryB,
+ LayerA: layerA,
+ LayerB: layerB,
+ }
+
// Generate HTML
- html := generateKalamarPluginHTML(data)
+ html := generateKalamarPluginHTML(data, queryParams)
c.Set("Content-Type", "text/html")
return c.SendString(html)
@@ -373,7 +411,7 @@
// generateKalamarPluginHTML creates the HTML template for the Kalamar plugin page
// This function can be easily modified to change the appearance and content
-func generateKalamarPluginHTML(data TemplateData) string {
+func generateKalamarPluginHTML(data TemplateData, queryParams QueryParams) string {
html := `<!DOCTYPE html>
<html lang="en">
<head>
@@ -419,7 +457,7 @@
}
html += `
- </dl>`
+ </dl></div>`
if data.MapID != "" {
@@ -430,7 +468,10 @@
// Use path.Join to normalize the path part
queryServiceURL.Path = path.Join(queryServiceURL.Path, data.MapID+"/query")
- queryServiceURL.RawQuery = "dir=atob"
+
+ // Build query parameters for query URL
+ queryParamString := buildQueryParams(queryParams.Dir, queryParams.FoundryA, queryParams.FoundryB, queryParams.LayerA, queryParams.LayerB)
+ queryServiceURL.RawQuery = queryParamString
responseServiceURL, err := url.Parse(data.ServiceURL)
if err != nil {
@@ -440,7 +481,16 @@
// Use path.Join to normalize the path part
responseServiceURL.Path = path.Join(responseServiceURL.Path, data.MapID+"/response")
- html += ` <script>
+ reversedDir := "btoa"
+ if queryParams.Dir == "btoa" {
+ reversedDir = "atob"
+ }
+
+ // Build query parameters for response URL (with reversed direction)
+ responseParamString := buildQueryParams(reversedDir, queryParams.FoundryA, queryParams.FoundryB, queryParams.LayerA, queryParams.LayerB)
+ responseServiceURL.RawQuery = responseParamString
+
+ html += `<script>
<!-- activates/deactivates Mapper. -->
let qdata = {
@@ -483,6 +533,27 @@
return html
}
+// buildQueryParams builds a query string from the provided parameters
+func buildQueryParams(dir, foundryA, foundryB, layerA, layerB string) string {
+ params := url.Values{}
+ if dir != "" {
+ params.Add("dir", dir)
+ }
+ if foundryA != "" {
+ params.Add("foundryA", foundryA)
+ }
+ if foundryB != "" {
+ params.Add("foundryB", foundryB)
+ }
+ if layerA != "" {
+ params.Add("layerA", layerA)
+ }
+ if layerB != "" {
+ params.Add("layerB", layerB)
+ }
+ return params.Encode()
+}
+
// expandGlobs expands glob patterns in the slice of file paths
// Returns the expanded list of files or an error if glob expansion fails
func expandGlobs(patterns []string) ([]string, error) {
diff --git a/cmd/termmapper/main_test.go b/cmd/termmapper/main_test.go
index 530ab68..80c7f4b 100644
--- a/cmd/termmapper/main_test.go
+++ b/cmd/termmapper/main_test.go
@@ -9,6 +9,7 @@
"os"
"path/filepath"
"sort"
+ "strings"
"testing"
tmconfig "github.com/KorAP/KoralPipe-TermMapper/config"
@@ -1345,8 +1346,205 @@
Mappings: []TemplateMapping{},
}
- html := generateKalamarPluginHTML(data)
+ // Use default query parameters for this test
+ queryParams := QueryParams{
+ Dir: "atob",
+ FoundryA: "",
+ FoundryB: "",
+ LayerA: "",
+ LayerB: "",
+ }
+
+ html := generateKalamarPluginHTML(data, queryParams)
assert.Contains(t, html, tt.expected)
})
}
}
+
+func TestKalamarPluginWithQueryParameters(t *testing.T) {
+ // Create test mapping list
+ mappingList := tmconfig.MappingList{
+ ID: "test-mapper",
+ Mappings: []tmconfig.MappingRule{
+ "[A] <> [B]",
+ },
+ }
+
+ // Create mapper
+ m, err := mapper.NewMapper([]tmconfig.MappingList{mappingList})
+ require.NoError(t, err)
+
+ // Create mock config
+ mockConfig := &tmconfig.MappingConfig{
+ ServiceURL: "https://example.com/plugin/termmapper",
+ Lists: []tmconfig.MappingList{mappingList},
+ }
+
+ // Apply defaults
+ tmconfig.ApplyDefaults(mockConfig)
+
+ // Create fiber app
+ app := fiber.New()
+ setupRoutes(app, m, mockConfig)
+
+ tests := []struct {
+ name string
+ url string
+ expectedQueryURL string
+ expectedRespURL string
+ expectedStatus int
+ expectedError string
+ }{
+ {
+ name: "Default parameters (no query params)",
+ url: "/test-mapper",
+ expectedQueryURL: "https://example.com/plugin/termmapper/test-mapper/query?dir=atob",
+ expectedRespURL: "https://example.com/plugin/termmapper/test-mapper/response?dir=btoa",
+ expectedStatus: http.StatusOK,
+ },
+ {
+ name: "Explicit dir=atob",
+ url: "/test-mapper?dir=atob",
+ expectedQueryURL: "https://example.com/plugin/termmapper/test-mapper/query?dir=atob",
+ expectedRespURL: "https://example.com/plugin/termmapper/test-mapper/response?dir=btoa",
+ expectedStatus: http.StatusOK,
+ },
+ {
+ name: "Explicit dir=btoa",
+ url: "/test-mapper?dir=btoa",
+ expectedQueryURL: "https://example.com/plugin/termmapper/test-mapper/query?dir=btoa",
+ expectedRespURL: "https://example.com/plugin/termmapper/test-mapper/response?dir=atob",
+ expectedStatus: http.StatusOK,
+ },
+ {
+ name: "With foundry parameters",
+ url: "/test-mapper?dir=atob&foundryA=opennlp&foundryB=upos",
+ expectedQueryURL: "https://example.com/plugin/termmapper/test-mapper/query?dir=atob&foundryA=opennlp&foundryB=upos",
+ expectedRespURL: "https://example.com/plugin/termmapper/test-mapper/response?dir=btoa&foundryA=opennlp&foundryB=upos",
+ expectedStatus: http.StatusOK,
+ },
+ {
+ name: "With layer parameters",
+ url: "/test-mapper?dir=btoa&layerA=pos&layerB=upos",
+ expectedQueryURL: "https://example.com/plugin/termmapper/test-mapper/query?dir=btoa&layerA=pos&layerB=upos",
+ expectedRespURL: "https://example.com/plugin/termmapper/test-mapper/response?dir=atob&layerA=pos&layerB=upos",
+ expectedStatus: http.StatusOK,
+ },
+ {
+ name: "All parameters",
+ url: "/test-mapper?dir=atob&foundryA=opennlp&foundryB=upos&layerA=pos&layerB=upos",
+ expectedQueryURL: "https://example.com/plugin/termmapper/test-mapper/query?dir=atob&foundryA=opennlp&foundryB=upos&layerA=pos&layerB=upos",
+ expectedRespURL: "https://example.com/plugin/termmapper/test-mapper/response?dir=btoa&foundryA=opennlp&foundryB=upos&layerA=pos&layerB=upos",
+ expectedStatus: http.StatusOK,
+ },
+ {
+ name: "Invalid direction",
+ url: "/test-mapper?dir=invalid",
+ expectedStatus: http.StatusBadRequest,
+ expectedError: "invalid direction, must be 'atob' or 'btoa'",
+ },
+ {
+ name: "Parameter too long",
+ url: "/test-mapper?foundryA=" + strings.Repeat("a", 1025),
+ expectedStatus: http.StatusBadRequest,
+ expectedError: "foundryA too long (max 1024 bytes)",
+ },
+ {
+ name: "Invalid characters in parameter",
+ url: "/test-mapper?foundryA=invalid<>chars",
+ expectedStatus: http.StatusBadRequest,
+ expectedError: "foundryA contains invalid characters",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ req := httptest.NewRequest(http.MethodGet, tt.url, nil)
+ resp, err := app.Test(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, tt.expectedStatus, resp.StatusCode)
+
+ body, err := io.ReadAll(resp.Body)
+ require.NoError(t, err)
+
+ if tt.expectedError != "" {
+ // Check error message
+ var errResp fiber.Map
+ err = json.Unmarshal(body, &errResp)
+ require.NoError(t, err)
+ assert.Equal(t, tt.expectedError, errResp["error"])
+ } else {
+ htmlContent := string(body)
+
+ // Check that both query and response URLs are present with correct parameters
+ assert.Contains(t, htmlContent, "'service' : '"+tt.expectedQueryURL+"'")
+ assert.Contains(t, htmlContent, "'service' : '"+tt.expectedRespURL+"'")
+
+ // Ensure it's still a valid HTML page
+ assert.Contains(t, htmlContent, "KoralPipe-TermMapper")
+ assert.Contains(t, htmlContent, "<!DOCTYPE html>")
+ }
+ })
+ }
+}
+
+func TestBuildQueryParams(t *testing.T) {
+ tests := []struct {
+ name string
+ dir string
+ foundryA string
+ foundryB string
+ layerA string
+ layerB string
+ expected string
+ }{
+ {
+ name: "Only direction parameter",
+ dir: "atob",
+ expected: "dir=atob",
+ },
+ {
+ name: "All parameters",
+ dir: "btoa",
+ foundryA: "opennlp",
+ foundryB: "upos",
+ layerA: "pos",
+ layerB: "upos",
+ expected: "dir=btoa&foundryA=opennlp&foundryB=upos&layerA=pos&layerB=upos",
+ },
+ {
+ name: "Some parameters empty",
+ dir: "atob",
+ foundryA: "opennlp",
+ foundryB: "",
+ layerA: "pos",
+ layerB: "",
+ expected: "dir=atob&foundryA=opennlp&layerA=pos",
+ },
+ {
+ name: "All parameters empty",
+ dir: "",
+ foundryA: "",
+ foundryB: "",
+ layerA: "",
+ layerB: "",
+ expected: "",
+ },
+ {
+ name: "URL encoding needed",
+ dir: "atob",
+ foundryA: "test space",
+ foundryB: "test&special",
+ expected: "dir=atob&foundryA=test+space&foundryB=test%26special",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := buildQueryParams(tt.dir, tt.foundryA, tt.foundryB, tt.layerA, tt.layerB)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}