Add configurable Kalamar integration
Change-Id: Ic07423dd7cc605509a364154bf4f37e4c13dc0d1
diff --git a/config/config.go b/config/config.go
index 6bafaad..128c063 100644
--- a/config/config.go
+++ b/config/config.go
@@ -9,6 +9,11 @@
"gopkg.in/yaml.v3"
)
+const (
+ defaultServer = "https://korap.ids-mannheim.de/"
+ defaultSDK = "https://korap.ids-mannheim.de/js/korap-plugin-latest.js"
+)
+
// MappingRule represents a single mapping rule in the configuration
type MappingRule string
@@ -22,13 +27,15 @@
Mappings []MappingRule `yaml:"mappings"`
}
-// MappingLists represents the root configuration containing multiple mapping lists
-type MappingLists struct {
- Lists []MappingList
+// MappingConfig represents the root configuration containing multiple mapping lists
+type MappingConfig struct {
+ SDK string `yaml:"sdk,omitempty"`
+ Server string `yaml:"server,omitempty"`
+ Lists []MappingList `yaml:"lists,omitempty"`
}
// LoadConfig loads a YAML configuration file and returns a Config object
-func LoadConfig(filename string) (*MappingLists, error) {
+func LoadConfig(filename string) (*MappingConfig, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
@@ -39,37 +46,71 @@
return nil, fmt.Errorf("EOF: config file is empty")
}
+ // Try to unmarshal as new format first (object with optional sdk/server and lists)
+ var config MappingConfig
+ if err := yaml.Unmarshal(data, &config); err == nil && len(config.Lists) > 0 {
+ // Successfully parsed as new format with lists field
+ if err := validateMappingLists(config.Lists); err != nil {
+ return nil, err
+ }
+ // Apply defaults if not specified
+ applyDefaults(&config)
+ return &config, nil
+ }
+
+ // Fall back to old format (direct list)
var lists []MappingList
if err := yaml.Unmarshal(data, &lists); err != nil {
return nil, fmt.Errorf("failed to parse YAML: %w", err)
}
+ if err := validateMappingLists(lists); err != nil {
+ return nil, err
+ }
+
+ config = MappingConfig{Lists: lists}
+ // Apply defaults if not specified
+ applyDefaults(&config)
+ return &config, nil
+}
+
+// applyDefaults sets default values for SDK and Server if they are empty
+func applyDefaults(config *MappingConfig) {
+ if config.SDK == "" {
+ config.SDK = defaultSDK
+ }
+ if config.Server == "" {
+ config.Server = defaultServer
+ }
+}
+
+// validateMappingLists validates a slice of mapping lists
+func validateMappingLists(lists []MappingList) error {
// Validate the configuration
seenIDs := make(map[string]bool)
for i, list := range lists {
if list.ID == "" {
- return nil, fmt.Errorf("mapping list at index %d is missing an ID", i)
+ return fmt.Errorf("mapping list at index %d is missing an ID", i)
}
// Check for duplicate IDs
if seenIDs[list.ID] {
- return nil, fmt.Errorf("duplicate mapping list ID found: %s", list.ID)
+ return fmt.Errorf("duplicate mapping list ID found: %s", list.ID)
}
seenIDs[list.ID] = true
if len(list.Mappings) == 0 {
- return nil, fmt.Errorf("mapping list '%s' has no mapping rules", list.ID)
+ return fmt.Errorf("mapping list '%s' has no mapping rules", list.ID)
}
// Validate each mapping rule
for j, rule := range list.Mappings {
if rule == "" {
- return nil, fmt.Errorf("mapping list '%s' rule at index %d is empty", list.ID, j)
+ return fmt.Errorf("mapping list '%s' rule at index %d is empty", list.ID, j)
}
}
}
-
- return &MappingLists{Lists: lists}, nil
+ return nil
}
// ParseMappings parses all mapping rules in a list and returns a slice of parsed rules
diff --git a/config/config_test.go b/config/config_test.go
index 9dc64ca..6519f82 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -574,3 +574,120 @@
assert.Equal(t, expectedValue, pronTypeTerm.Key)
}
}
+
+func TestConfigWithSdkAndServer(t *testing.T) {
+ tests := []struct {
+ name string
+ content string
+ expectedSDK string
+ expectedServer string
+ wantErr bool
+ }{
+ {
+ name: "Configuration with SDK and Server values",
+ content: `
+sdk: "https://custom.example.com/sdk.js"
+server: "https://custom.example.com/"
+lists:
+- id: test-mapper
+ foundryA: opennlp
+ layerA: p
+ foundryB: upos
+ layerB: p
+ mappings:
+ - "[A] <> [B]"
+`,
+ expectedSDK: "https://custom.example.com/sdk.js",
+ expectedServer: "https://custom.example.com/",
+ wantErr: false,
+ },
+ {
+ name: "Configuration with only SDK value",
+ content: `
+sdk: "https://custom.example.com/sdk.js"
+lists:
+- id: test-mapper
+ mappings:
+ - "[A] <> [B]"
+`,
+ expectedSDK: "https://custom.example.com/sdk.js",
+ expectedServer: "https://korap.ids-mannheim.de/", // default applied
+ wantErr: false,
+ },
+ {
+ name: "Configuration with only Server value",
+ content: `
+server: "https://custom.example.com/"
+lists:
+- id: test-mapper
+ mappings:
+ - "[A] <> [B]"
+`,
+ expectedSDK: "https://korap.ids-mannheim.de/js/korap-plugin-latest.js", // default applied
+ expectedServer: "https://custom.example.com/",
+ wantErr: false,
+ },
+ {
+ name: "Configuration without SDK and Server (old format with defaults applied)",
+ content: `
+- id: test-mapper
+ mappings:
+ - "[A] <> [B]"
+`,
+ expectedSDK: "https://korap.ids-mannheim.de/js/korap-plugin-latest.js", // default applied
+ expectedServer: "https://korap.ids-mannheim.de/", // default applied
+ wantErr: false,
+ },
+ {
+ name: "Configuration with lists field explicitly",
+ content: `
+sdk: "https://custom.example.com/sdk.js"
+server: "https://custom.example.com/"
+lists:
+- id: test-mapper-1
+ mappings:
+ - "[A] <> [B]"
+- id: test-mapper-2
+ mappings:
+ - "[C] <> [D]"
+`,
+ expectedSDK: "https://custom.example.com/sdk.js",
+ expectedServer: "https://custom.example.com/",
+ wantErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tmpfile, err := os.CreateTemp("", "config-*.yaml")
+ require.NoError(t, err)
+ defer os.Remove(tmpfile.Name())
+
+ _, err = tmpfile.WriteString(tt.content)
+ require.NoError(t, err)
+ err = tmpfile.Close()
+ require.NoError(t, err)
+
+ config, err := LoadConfig(tmpfile.Name())
+ if tt.wantErr {
+ require.Error(t, err)
+ return
+ }
+
+ require.NoError(t, err)
+ require.NotNil(t, config)
+
+ // Check SDK and Server values
+ assert.Equal(t, tt.expectedSDK, config.SDK)
+ assert.Equal(t, tt.expectedServer, config.Server)
+
+ // Ensure lists are still loaded correctly
+ require.Greater(t, len(config.Lists), 0)
+
+ // Verify first mapping list
+ firstList := config.Lists[0]
+ assert.NotEmpty(t, firstList.ID)
+ assert.Greater(t, len(firstList.Mappings), 0)
+ })
+ }
+}