Skip to content

Commit 4d04677

Browse files
add FromFieldPath, ToFieldPath and MapFieldPaths functions
Change-Id: Id0b1b4f340e82744ba27274625b5192bb4ce8e5f
1 parent 4d9d57d commit 4d04677

File tree

2 files changed

+55
-19
lines changed

2 files changed

+55
-19
lines changed

compose/field_mapping.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,12 @@ func (m *FieldMapping) String() string {
6060
return sb.String()
6161
}
6262

63-
const pathSeparator = "."
63+
const pathSeparator = "\x1F"
6464

6565
// FromField creates a FieldMapping that maps a single predecessor field to the entire successor input.
6666
// This is an exclusive mapping - once set, no other field mappings can be added since the successor input
6767
// has already been fully mapped.
6868
// Field: either the field of a struct, or the key of a map.
69-
// For nested fields, use the path separator (.) to separate the field names.
7069
func FromField(from string) *FieldMapping {
7170
return &FieldMapping{
7271
from: from,
@@ -75,7 +74,6 @@ func FromField(from string) *FieldMapping {
7574

7675
// ToField creates a FieldMapping that maps the entire predecessor output to a single successor field.
7776
// Field: either the field of a struct, or the key of a map.
78-
// For nested fields, use the path separator (.) to separate the field names.
7977
func ToField(to string) *FieldMapping {
8078
return &FieldMapping{
8179
to: to,
@@ -84,14 +82,34 @@ func ToField(to string) *FieldMapping {
8482

8583
// MapFields creates a FieldMapping that maps a single predecessor field to a single successor field.
8684
// Field: either the field of a struct, or the key of a map.
87-
// For nested fields, use the path separator (.) to separate the field names.
8885
func MapFields(from, to string) *FieldMapping {
8986
return &FieldMapping{
9087
from: from,
9188
to: to,
9289
}
9390
}
9491

92+
type FieldPath []string
93+
94+
func FromFieldPath(fromFieldPath FieldPath) *FieldMapping {
95+
return &FieldMapping{
96+
from: strings.Join(fromFieldPath, pathSeparator),
97+
}
98+
}
99+
100+
func ToFieldPath(toFieldPath FieldPath) *FieldMapping {
101+
return &FieldMapping{
102+
to: strings.Join(toFieldPath, pathSeparator),
103+
}
104+
}
105+
106+
func MapFieldPaths(fromFieldPath, toFieldPath FieldPath) *FieldMapping {
107+
return &FieldMapping{
108+
from: strings.Join(fromFieldPath, pathSeparator),
109+
to: strings.Join(toFieldPath, pathSeparator),
110+
}
111+
}
112+
95113
func buildFieldMappingConverter[I any]() func(input any) (any, error) {
96114
return func(input any) (any, error) {
97115
in, ok := input.(map[string]any)

compose/workflow_test.go

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
281281

282282
t.Run("from struct.struct.field", func(t *testing.T) {
283283
wf := NewWorkflow[*structB, string]()
284-
wf.AddEnd(START, FromField("F1.F1"))
284+
wf.AddEnd(START, FromFieldPath([]string{"F1", "F1"}))
285285
r, err := wf.Compile(ctx)
286286
assert.NoError(t, err)
287287
out, err := r.Invoke(ctx, &structB{
@@ -293,14 +293,14 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
293293
assert.Equal(t, "hello", out)
294294

295295
wf = NewWorkflow[*structB, string]()
296-
wf.AddEnd(START, FromField("F1.F2"))
296+
wf.AddEnd(START, FromFieldPath([]string{"F1", "F2"}))
297297
_, err = wf.Compile(ctx)
298298
assert.ErrorContains(t, err, "type[compose.structA] has no field[F2]")
299299
})
300300

301301
t.Run("from map.map.field", func(t *testing.T) {
302302
wf := NewWorkflow[map[string]map[string]string, string]()
303-
wf.AddEnd(START, FromField("F1.F1"))
303+
wf.AddEnd(START, FromFieldPath([]string{"F1", "F1"}))
304304
r, err := wf.Compile(ctx)
305305
assert.NoError(t, err)
306306
out, err := r.Invoke(ctx, map[string]map[string]string{
@@ -322,7 +322,7 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
322322

323323
t.Run("from struct.map.field", func(t *testing.T) {
324324
wf := NewWorkflow[*structB, string]()
325-
wf.AddEnd(START, FromField("F2.F1"))
325+
wf.AddEnd(START, FromFieldPath([]string{"F2", "F1"}))
326326
r, err := wf.Compile(ctx)
327327
assert.NoError(t, err)
328328
out, err := r.Invoke(ctx, &structB{
@@ -344,7 +344,7 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
344344

345345
t.Run("from map.struct.field", func(t *testing.T) {
346346
wf := NewWorkflow[map[string]*structA, string]()
347-
wf.AddEnd(START, FromField("F1.F1"))
347+
wf.AddEnd(START, FromFieldPath([]string{"F1", "F1"}))
348348
r, err := wf.Compile(ctx)
349349
assert.NoError(t, err)
350350
out, err := r.Invoke(ctx, map[string]*structA{
@@ -356,14 +356,14 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
356356
assert.Equal(t, "hello", out)
357357

358358
wf = NewWorkflow[map[string]*structA, string]()
359-
wf.AddEnd(START, FromField("F1.F2"))
359+
wf.AddEnd(START, FromFieldPath([]string{"F1", "F2"}))
360360
_, err = wf.Compile(ctx)
361361
assert.ErrorContains(t, err, "type[compose.structA] has no field[F2]")
362362
})
363363

364364
t.Run("from map[string]any.field", func(t *testing.T) {
365365
wf := NewWorkflow[map[string]any, string]()
366-
wf.AddEnd(START, FromField("F1.F1"))
366+
wf.AddEnd(START, FromFieldPath([]string{"F1", "F1"}))
367367
r, err := wf.Compile(ctx)
368368
assert.NoError(t, err)
369369
out, err := r.Invoke(ctx, map[string]any{
@@ -391,7 +391,7 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
391391

392392
t.Run("to struct.struct.field", func(t *testing.T) {
393393
wf := NewWorkflow[string, *structB]()
394-
wf.AddEnd(START, ToField("F1.F1"))
394+
wf.AddEnd(START, ToFieldPath([]string{"F1", "F1"}))
395395
r, err := wf.Compile(ctx)
396396
assert.NoError(t, err)
397397
out, err := r.Invoke(ctx, "hello")
@@ -403,14 +403,14 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
403403
}, out)
404404

405405
wf = NewWorkflow[string, *structB]()
406-
wf.AddEnd(START, ToField("F1.F2"))
406+
wf.AddEnd(START, ToFieldPath([]string{"F1", "F2"}))
407407
_, err = wf.Compile(ctx)
408408
assert.ErrorContains(t, err, "type[compose.structA] has no field[F2]")
409409
})
410410

411411
t.Run("to map.map.field", func(t *testing.T) {
412412
wf := NewWorkflow[string, map[string]map[string]string]()
413-
wf.AddEnd(START, ToField("F1.F1"))
413+
wf.AddEnd(START, ToFieldPath([]string{"F1", "F1"}))
414414
r, err := wf.Compile(ctx)
415415
assert.NoError(t, err)
416416
out, err := r.Invoke(ctx, "hello")
@@ -422,14 +422,14 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
422422
}, out)
423423

424424
wf1 := NewWorkflow[string, map[string]map[string]int]()
425-
wf1.AddEnd(START, ToField("F1.F1"))
425+
wf1.AddEnd(START, ToFieldPath([]string{"F1", "F1"}))
426426
_, err = wf1.Compile(ctx)
427427
assert.ErrorContains(t, err, "field[string]-[int] must not be assignable")
428428
})
429429

430430
t.Run("to struct.map.field", func(t *testing.T) {
431431
wf := NewWorkflow[string, *structB]()
432-
wf.AddEnd(START, ToField("F2.F1"))
432+
wf.AddEnd(START, ToFieldPath([]string{"F2", "F1"}))
433433
r, err := wf.Compile(ctx)
434434
assert.NoError(t, err)
435435
out, err := r.Invoke(ctx, "hello")
@@ -443,7 +443,7 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
443443

444444
t.Run("to map.struct.struct.field", func(t *testing.T) {
445445
wf := NewWorkflow[string, map[string]*structB]()
446-
wf.AddEnd(START, ToField("F1.F1.F1"))
446+
wf.AddEnd(START, ToFieldPath([]string{"F1", "F1", "F1"}))
447447
r, err := wf.Compile(ctx)
448448
assert.NoError(t, err)
449449
out, err := r.Invoke(ctx, "hello")
@@ -459,17 +459,35 @@ func TestWorkflowWithNestedFieldMappings(t *testing.T) {
459459

460460
t.Run("to struct.int.field", func(t *testing.T) {
461461
wf := NewWorkflow[string, *structB]()
462-
wf.AddEnd(START, ToField("F3.F1.F1"))
462+
wf.AddEnd(START, ToFieldPath([]string{"F3", "F1", "F1"}))
463463
_, err := wf.Compile(ctx)
464464
assert.ErrorContains(t, err, "type[int] is not valid")
465465
})
466466

467467
t.Run("to struct.any.field", func(t *testing.T) {
468468
wf := NewWorkflow[string, *structB]()
469-
wf.AddEnd(START, ToField("F4.F1.F1"))
469+
wf.AddEnd(START, ToFieldPath([]string{"F4", "F1", "F1"}))
470470
_, err := wf.Compile(ctx)
471471
assert.ErrorContains(t, err, "the successor has intermediate interface type interface {}")
472472
})
473+
474+
t.Run("from nested to nested", func(t *testing.T) {
475+
wf := NewWorkflow[map[string]any, *structB]()
476+
wf.AddEnd(START, MapFieldPaths([]string{"key1", "key2"}, []string{"F1", "F1"}))
477+
r, err := wf.Compile(ctx)
478+
assert.NoError(t, err)
479+
out, err := r.Invoke(ctx, map[string]any{
480+
"key1": map[string]any{
481+
"key2": "hello",
482+
},
483+
})
484+
assert.NoError(t, err)
485+
assert.Equal(t, &structB{
486+
F1: &structA{
487+
F1: "hello",
488+
},
489+
}, out)
490+
})
473491
}
474492

475493
func TestWorkflowCompile(t *testing.T) {

0 commit comments

Comments
 (0)