blob: dc00da500e1eafa4f2f7851ff2b79ef955b64eca [file] [log] [blame]
Akrona379f1e2026-02-20 08:29:43 +01001package main
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/KorAP/Koral-Mapper/config"
8)
9
10// CascadeEntry represents a single mapping configuration parsed from
11// the cfg URL parameter. After parsing, empty override fields are
12// merged with the YAML defaults from the corresponding MappingList.
13type CascadeEntry struct {
14 ID string
15 Direction string
16 FoundryA string
17 LayerA string
18 FoundryB string
19 LayerB string
20}
21
22// ParseCfgParam parses the compact cfg URL parameter into a slice of
23// CascadeEntry structs. Empty override fields are merged with YAML
24// defaults from the matching MappingList.
25//
26// Format: entry (";" entry)*
27//
28// entry = id ":" dir [ ":" foundryA ":" layerA ":" foundryB ":" layerB ]
29//
30// An entry has either 2 fields (all foundry/layer use defaults) or
31// 6 fields (explicit values, empty means use default).
32func ParseCfgParam(raw string, lists []config.MappingList) ([]CascadeEntry, error) {
33 if raw == "" {
34 return nil, nil
35 }
36
37 listsByID := make(map[string]*config.MappingList, len(lists))
38 for i := range lists {
39 listsByID[lists[i].ID] = &lists[i]
40 }
41
42 parts := strings.Split(raw, ";")
43 result := make([]CascadeEntry, 0, len(parts))
44
45 for _, part := range parts {
46 fields := strings.Split(part, ":")
47 n := len(fields)
48
49 if n != 2 && n != 6 {
50 return nil, fmt.Errorf("invalid entry %q: expected 2 or 6 colon-separated fields, got %d", part, n)
51 }
52
53 id := fields[0]
54 dir := fields[1]
55
56 if dir != "atob" && dir != "btoa" {
57 return nil, fmt.Errorf("invalid direction %q in entry %q", dir, part)
58 }
59
60 list, ok := listsByID[id]
61 if !ok {
62 return nil, fmt.Errorf("unknown mapping ID %q", id)
63 }
64
65 ce := CascadeEntry{
66 ID: id,
67 Direction: dir,
68 }
69
70 if n == 6 {
71 ce.FoundryA = fields[2]
72 ce.LayerA = fields[3]
73 ce.FoundryB = fields[4]
74 ce.LayerB = fields[5]
75 }
76
77 if ce.FoundryA == "" {
78 ce.FoundryA = list.FoundryA
79 }
80 if ce.LayerA == "" {
81 ce.LayerA = list.LayerA
82 }
83 if ce.FoundryB == "" {
84 ce.FoundryB = list.FoundryB
85 }
86 if ce.LayerB == "" {
87 ce.LayerB = list.LayerB
88 }
89
90 result = append(result, ce)
91 }
92
93 return result, nil
94}
95
96// BuildCfgParam serialises a slice of CascadeEntry back to the compact
97// cfg string format. Entries with all foundry/layer fields empty use
98// the short 2-field format (id:dir). Entries with any non-empty
99// foundry/layer field use the full 6-field format.
100func BuildCfgParam(entries []CascadeEntry) string {
101 if len(entries) == 0 {
102 return ""
103 }
104
105 parts := make([]string, len(entries))
106 for i, e := range entries {
107 if e.FoundryA == "" && e.LayerA == "" && e.FoundryB == "" && e.LayerB == "" {
108 parts[i] = e.ID + ":" + e.Direction
109 } else {
110 parts[i] = e.ID + ":" + e.Direction + ":" + e.FoundryA + ":" + e.LayerA + ":" + e.FoundryB + ":" + e.LayerB
111 }
112 }
113
114 return strings.Join(parts, ";")
115}