blob: 4869c7bba71802a3164dbf85a587b260e5c3cd41 [file] [log] [blame]
Akron32d53de2025-05-22 13:45:32 +02001package mapper
2
3import (
Akron32d53de2025-05-22 13:45:32 +02004 "fmt"
5
Akron2ef703c2025-07-03 15:57:42 +02006 "github.com/KorAP/Koral-Mapper/config"
7 "github.com/KorAP/Koral-Mapper/parser"
Akron32d53de2025-05-22 13:45:32 +02008)
9
10// Direction represents the mapping direction (A to B or B to A)
Akrona1a183f2025-05-26 17:47:33 +020011type Direction bool
Akron32d53de2025-05-22 13:45:32 +020012
13const (
Akrona1a183f2025-05-26 17:47:33 +020014 AtoB Direction = true
15 BtoA Direction = false
Akron2f93c582026-02-19 16:49:13 +010016
17 RewriteEditor = "Koral-Mapper"
Akron32d53de2025-05-22 13:45:32 +020018)
19
Akron2f93c582026-02-19 16:49:13 +010020// newRewriteEntry creates a koral:rewrite annotation entry.
21func newRewriteEntry(scope string, original any) map[string]any {
22 r := map[string]any{
23 "@type": "koral:rewrite",
24 "editor": RewriteEditor,
25 }
26 if scope != "" {
27 r["scope"] = scope
28 }
29 if original != nil {
30 r["original"] = original
31 }
32 return r
33}
34
Akrona1a183f2025-05-26 17:47:33 +020035// String converts the Direction to its string representation
36func (d Direction) String() string {
37 if d {
38 return "atob"
39 }
40 return "btoa"
41}
42
43// ParseDirection converts a string direction to Direction type
44func ParseDirection(dir string) (Direction, error) {
45 switch dir {
46 case "atob":
47 return AtoB, nil
48 case "btoa":
49 return BtoA, nil
50 default:
51 return false, fmt.Errorf("invalid direction: %s", dir)
52 }
53}
54
Akron32d53de2025-05-22 13:45:32 +020055// Mapper handles the application of mapping rules to JSON objects
56type Mapper struct {
Akron2f93c582026-02-19 16:49:13 +010057 mappingLists map[string]*config.MappingList
58 parsedQueryRules map[string][]*parser.MappingResult
59 parsedCorpusRules map[string][]*parser.CorpusMappingResult
Akron32d53de2025-05-22 13:45:32 +020060}
61
Akrona00d4752025-05-26 17:34:36 +020062// NewMapper creates a new Mapper instance from a list of MappingLists
63func NewMapper(lists []config.MappingList) (*Mapper, error) {
Akron32d53de2025-05-22 13:45:32 +020064 m := &Mapper{
Akron2f93c582026-02-19 16:49:13 +010065 mappingLists: make(map[string]*config.MappingList),
66 parsedQueryRules: make(map[string][]*parser.MappingResult),
67 parsedCorpusRules: make(map[string][]*parser.CorpusMappingResult),
Akron32d53de2025-05-22 13:45:32 +020068 }
69
Akrona00d4752025-05-26 17:34:36 +020070 // Store mapping lists by ID
71 for _, list := range lists {
72 if _, exists := m.mappingLists[list.ID]; exists {
73 return nil, fmt.Errorf("duplicate mapping list ID found: %s", list.ID)
74 }
75
Akrona00d4752025-05-26 17:34:36 +020076 listCopy := list
77 m.mappingLists[list.ID] = &listCopy
78
Akron2f93c582026-02-19 16:49:13 +010079 if list.IsCorpus() {
80 corpusRules, err := list.ParseCorpusMappings()
81 if err != nil {
82 return nil, fmt.Errorf("failed to parse corpus mappings for list %s: %w", list.ID, err)
83 }
84 m.parsedCorpusRules[list.ID] = corpusRules
85 } else {
86 queryRules, err := list.ParseMappings()
87 if err != nil {
88 return nil, fmt.Errorf("failed to parse mappings for list %s: %w", list.ID, err)
89 }
90 m.parsedQueryRules[list.ID] = queryRules
Akron32d53de2025-05-22 13:45:32 +020091 }
Akron32d53de2025-05-22 13:45:32 +020092 }
93
94 return m, nil
95}
96
97// MappingOptions contains the options for applying mappings
98type MappingOptions struct {
Akron0d9117c2025-05-27 15:20:21 +020099 FoundryA string
100 LayerA string
101 FoundryB string
102 LayerB string
103 Direction Direction
104 AddRewrites bool
Akron32d53de2025-05-22 13:45:32 +0200105}
Akrone4f570d2026-02-20 08:18:06 +0100106
107// CascadeQueryMappings applies multiple mapping lists sequentially,
108// feeding the output of each into the next. orderedIDs and
109// perMappingOpts must have the same length. An empty list returns
110// jsonData unchanged.
111func (m *Mapper) CascadeQueryMappings(orderedIDs []string, perMappingOpts []MappingOptions, jsonData any) (any, error) {
112 if len(orderedIDs) != len(perMappingOpts) {
113 return nil, fmt.Errorf("orderedIDs length (%d) must match perMappingOpts length (%d)", len(orderedIDs), len(perMappingOpts))
114 }
115
116 result := jsonData
117 for i, id := range orderedIDs {
118 var err error
119 result, err = m.ApplyQueryMappings(id, perMappingOpts[i], result)
120 if err != nil {
121 return nil, fmt.Errorf("cascade step %d (mapping %q): %w", i, id, err)
122 }
123 }
124 return result, nil
125}
126
127// CascadeResponseMappings applies multiple mapping lists sequentially
128// to a response object, feeding the output of each into the next.
129// orderedIDs and perMappingOpts must have the same length. An empty
130// list returns jsonData unchanged.
131func (m *Mapper) CascadeResponseMappings(orderedIDs []string, perMappingOpts []MappingOptions, jsonData any) (any, error) {
132 if len(orderedIDs) != len(perMappingOpts) {
133 return nil, fmt.Errorf("orderedIDs length (%d) must match perMappingOpts length (%d)", len(orderedIDs), len(perMappingOpts))
134 }
135
136 result := jsonData
137 for i, id := range orderedIDs {
138 var err error
139 result, err = m.ApplyResponseMappings(id, perMappingOpts[i], result)
140 if err != nil {
141 return nil, fmt.Errorf("cascade step %d (mapping %q): %w", i, id, err)
142 }
143 }
144 return result, nil
145}