diff --git a/arazzo/arazzo.go b/arazzo/arazzo.go index 724415f..92f056d 100644 --- a/arazzo/arazzo.go +++ b/arazzo/arazzo.go @@ -75,7 +75,7 @@ func Unmarshal(ctx context.Context, doc io.Reader, opts ...Option[unmarshalOptio } arazzo := &Arazzo{} - if err := marshaller.PopulateModel(*c, arazzo); err != nil { + if err := marshaller.Populate(*c, arazzo); err != nil { return nil, nil, err } diff --git a/arazzo/criterion/criterion.go b/arazzo/criterion/criterion.go index 360a136..b7efaa7 100644 --- a/arazzo/criterion/criterion.go +++ b/arazzo/criterion/criterion.go @@ -136,8 +136,8 @@ func (c CriterionTypeUnion) GetVersion() CriterionTypeVersion { return c.ExpressionType.Version } -func (c *CriterionTypeUnion) FromCore(cr any) error { - coreCriterionTypeUnion, ok := cr.(core.CriterionTypeUnion) +func (c *CriterionTypeUnion) Populate(source any) error { + coreCriterionTypeUnion, ok := source.(core.CriterionTypeUnion) if !ok { return fmt.Errorf("expected core.CriterionTypeUnion, got %T", c) } @@ -147,7 +147,7 @@ func (c *CriterionTypeUnion) FromCore(cr any) error { c.Type = &typ } else if coreCriterionTypeUnion.ExpressionType != nil { c.ExpressionType = &CriterionExpressionType{} - if err := marshaller.PopulateModel(*coreCriterionTypeUnion.ExpressionType, c.ExpressionType); err != nil { + if err := marshaller.Populate(*coreCriterionTypeUnion.ExpressionType, c.ExpressionType); err != nil { return err } } diff --git a/extensions/extensions.go b/extensions/extensions.go index 1ad9d0c..11d6857 100644 --- a/extensions/extensions.go +++ b/extensions/extensions.go @@ -83,7 +83,7 @@ func UnmarshalExtensionModel[H any, L any](ctx context.Context, e *Extensions, e var mV H - if err := marshaller.PopulateModel(*c, &mV); err != nil { + if err := marshaller.Populate(*c, &mV); err != nil { return err } *m = mV diff --git a/extensions/extensions_test.go b/extensions/extensions_test.go index a6796e0..2e6c750 100644 --- a/extensions/extensions_test.go +++ b/extensions/extensions_test.go @@ -116,7 +116,7 @@ func getTestModelWithExtensions(ctx context.Context, t *testing.T, data string) require.NoError(t, err) m := &ModelWithExtensions{} - err = marshaller.PopulateModel(c, m) + err = marshaller.Populate(c, m) require.NoError(t, err) return m diff --git a/marshaller/extensions_test.go b/marshaller/extensions_test.go index 8037d47..52a5728 100644 --- a/marshaller/extensions_test.go +++ b/marshaller/extensions_test.go @@ -42,7 +42,7 @@ primitiveField: hello type TestExtensionModel struct { marshaller.CoreModel PrimitiveField marshaller.Node[string] `key:"primitiveField"` - Extensions marshaller.Extensions `key:"extensions"` + Extensions Extensions `key:"extensions"` } func Test_Extensions_SyncExtensions_Success(t *testing.T) { @@ -74,7 +74,7 @@ func Test_Extensions_SyncExtensions_Success(t *testing.T) { type TestExtensionUnmarshalError struct { marshaller.CoreModel - Extensions marshaller.Extensions `key:"extensions"` + Extensions Extensions `key:"extensions"` } func Test_Extensions_UnmarshalError_Coverage(t *testing.T) { diff --git a/marshaller/populator.go b/marshaller/populator.go index 4bbbeff..2e68c8f 100644 --- a/marshaller/populator.go +++ b/marshaller/populator.go @@ -5,11 +5,11 @@ import ( "reflect" ) -type ModelFromCore interface { - FromCore(c any) error +type Populator interface { + Populate(source any) error } -func PopulateModel(source any, target any) error { +func Populate(source any, target any) error { t := reflect.ValueOf(target) if t.Kind() == reflect.Ptr && t.IsNil() { @@ -125,12 +125,8 @@ func populateValue(source any, target reflect.Value) error { target = target.Addr() } - if value.Kind() == reflect.Ptr { - value = value.Elem() - } - - if target.Type().Implements(reflect.TypeOf((*ModelFromCore)(nil)).Elem()) { - return target.Interface().(ModelFromCore).FromCore(value.Interface()) + if target.Type().Implements(reflect.TypeOf((*Populator)(nil)).Elem()) { + return target.Interface().(Populator).Populate(value.Interface()) } // Check if target implements CoreSetter interface @@ -143,58 +139,33 @@ func populateValue(source any, target reflect.Value) error { return nil } - if target.Type().Implements(reflect.TypeOf((*SequencedMap)(nil)).Elem()) { - return populateSequencedMap(value, target) - } - target = target.Elem() - switch value.Kind() { + valueDerefed := value + if value.Kind() == reflect.Ptr { + valueDerefed = value.Elem() + } + + switch valueDerefed.Kind() { case reflect.Slice, reflect.Array: - if value.IsNil() { + if valueDerefed.IsNil() { return nil } - target.Set(reflect.MakeSlice(target.Type(), value.Len(), value.Len())) + target.Set(reflect.MakeSlice(target.Type(), valueDerefed.Len(), valueDerefed.Len())) - for i := 0; i < value.Len(); i++ { - if err := populateValue(value.Index(i).Interface(), target.Index(i)); err != nil { + for i := 0; i < valueDerefed.Len(); i++ { + if err := populateValue(valueDerefed.Index(i).Interface(), target.Index(i)); err != nil { return err } } default: - if value.Type().AssignableTo(target.Type()) { - target.Set(value) - } else if value.CanConvert(target.Type()) { - target.Set(value.Convert(target.Type())) + if valueDerefed.Type().AssignableTo(target.Type()) { + target.Set(valueDerefed) + } else if valueDerefed.CanConvert(target.Type()) { + target.Set(valueDerefed.Convert(target.Type())) } else { - return fmt.Errorf("cannot convert %v to %v", value.Type(), target.Type()) - } - } - - return nil -} - -func populateSequencedMap(source reflect.Value, target reflect.Value) error { - sm, ok := source.Addr().Interface().(SequencedMap) - if !ok { - return fmt.Errorf("expected source to be SequencedMap, got %s", source.Type()) - } - - tm, ok := target.Interface().(SequencedMap) - if !ok { - return fmt.Errorf("expected target to be SequencedMap, got %s", target.Type()) - } - - tm.Init() - - for key, value := range sm.AllUntyped() { - targetValue := reflect.New(tm.GetValueType()).Elem() - if err := populateValue(value, targetValue); err != nil { - return err - } - if err := tm.SetUntyped(key, targetValue.Interface()); err != nil { - return err + return fmt.Errorf("cannot convert %v to %v", valueDerefed.Type(), target.Type()) } } diff --git a/marshaller/populator_bare_slice_test.go b/marshaller/populator_bare_slice_test.go index 514abd9..4e61055 100644 --- a/marshaller/populator_bare_slice_test.go +++ b/marshaller/populator_bare_slice_test.go @@ -9,7 +9,7 @@ import ( ) // To trigger the slice/array case in populateValue, we need: -// 1. A target that does NOT implement ModelFromCore, CoreSetter, or SequencedMap +// 1. A target that does NOT implement Populator, CoreSetter, or SequencedMap // 2. A source that is a slice or array // 3. The target to be a pointer that after target.Elem() can accept the slice/array @@ -28,7 +28,7 @@ func Test_PopulateValue_BareSliceTarget_Success(t *testing.T) { target := &BareSliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, []string{"bare1", "bare2", "bare3"}, target.SimpleSlice) @@ -36,7 +36,7 @@ func Test_PopulateValue_BareSliceTarget_Success(t *testing.T) { } // Try with an even simpler case: directly pass slice values -// This creates a scenario where populateValue would be called recursively +// This creates a scenario where populateValue would be called recursively // with slice source and pointer target type ContainerWithSliceField struct { @@ -51,7 +51,7 @@ func Test_PopulateValue_ContainerWithSlice_Success(t *testing.T) { target := &ContainerWithSliceField{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, []string{"container1", "container2"}, target.SliceField) @@ -66,7 +66,7 @@ type PointerSliceTarget struct { func Test_PopulateValue_PointerSliceTarget_Success(t *testing.T) { sourceSlice := []string{"ptr1", "ptr2"} sourceArray := [2]int{100, 200} - + source := PointerSliceTarget{ SlicePtr: &sourceSlice, ArrayPtr: &sourceArray, @@ -74,7 +74,7 @@ func Test_PopulateValue_PointerSliceTarget_Success(t *testing.T) { target := &PointerSliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) require.NotNil(t, target.SlicePtr) @@ -101,7 +101,7 @@ func Test_PopulateValue_EmbeddedSliceStruct_Success(t *testing.T) { target := &EmbeddedSliceStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, []string{"embedded1", "embedded2"}, target.Inner.Data) @@ -119,8 +119,8 @@ func Test_PopulateValue_JustSlice_Success(t *testing.T) { target := &JustSlice{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, []string{"just1", "just2"}, target.Data) -} \ No newline at end of file +} diff --git a/marshaller/populator_comprehensive_test.go b/marshaller/populator_comprehensive_test.go index f88f469..df44927 100644 --- a/marshaller/populator_comprehensive_test.go +++ b/marshaller/populator_comprehensive_test.go @@ -27,7 +27,7 @@ func Test_PopulateModel_SimpleStructToStruct(t *testing.T) { target := &Target{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, "test", target.StringField) assert.Equal(t, 42, target.IntField) @@ -49,7 +49,7 @@ func Test_PopulateModel_WithSlices(t *testing.T) { target := &Target{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, []string{"a", "b", "c"}, target.SliceField) } @@ -70,7 +70,7 @@ func Test_PopulateModel_WithNilSlice(t *testing.T) { target := &Target{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Nil(t, target.SliceField) } @@ -91,7 +91,7 @@ func Test_PopulateModel_TypeConversion(t *testing.T) { target := &Target{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) // This might fail due to type incompatibility - that's expected if err != nil { assert.Contains(t, err.Error(), "cannot convert") @@ -117,7 +117,7 @@ func Test_PopulateModel_WithPointers(t *testing.T) { target := &Target{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) require.NotNil(t, target.PtrField) assert.Equal(t, "test-value", *target.PtrField) @@ -139,7 +139,7 @@ func Test_PopulateModel_WithNilPointer(t *testing.T) { target := &Target{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Nil(t, target.PtrField) } @@ -153,7 +153,7 @@ func Test_PopulateModel_NonStructSource_Error(t *testing.T) { source := "not-a-struct" target := &Target{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.Error(t, err) assert.Contains(t, err.Error(), "cannot convert") } @@ -174,7 +174,7 @@ func Test_PopulateModel_IncompatibleTypes_Error(t *testing.T) { target := &Target{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.Error(t, err) assert.Contains(t, err.Error(), "cannot convert") } @@ -202,9 +202,9 @@ func Test_PopulateModel_NestedSlices(t *testing.T) { target := &Target{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) require.Len(t, target.NestedSlice, 2) assert.Equal(t, "first", target.NestedSlice[0].Value) assert.Equal(t, "second", target.NestedSlice[1].Value) -} \ No newline at end of file +} diff --git a/marshaller/populator_coresetter_test.go b/marshaller/populator_coresetter_test.go index 1c29ffe..99ee668 100644 --- a/marshaller/populator_coresetter_test.go +++ b/marshaller/populator_coresetter_test.go @@ -49,12 +49,12 @@ func Test_PopulateModel_CoreSetter_With_NodeAccessor(t *testing.T) { target := &SimpleCoreSetterTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify the field was populated via populateModel using NodeAccessor.GetValue() assert.Equal(t, "test-value", target.Value) - + // Verify SetCoreValue was called (proves CoreSetter path was taken) assert.NotNil(t, target.core) } @@ -148,24 +148,24 @@ func Test_PopulateModel_Extensions_Handling_NodeAccessor(t *testing.T) { extensionNode := marshaller.Node[*yaml.Node]{ Value: &yaml.Node{Kind: yaml.ScalarNode, Value: "extension-value"}, } - + extensionMap := &MockExtensionCoreMap{} extensionMap.Init() extensionMap.Set("x-test", extensionNode) - + source := SourceWithExtensionsNodeAccessor{ - Value: TestNodeAccessor{value: "test-value"}, + Value: TestNodeAccessor{value: "test-value"}, Extensions: extensionMap, } target := &TargetWithExtensions{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify basic field assert.Equal(t, "test-value", target.Value) - + // Verify Extensions field was handled specially assert.NotNil(t, target.Extensions) assert.NotNil(t, target.Extensions.data) @@ -173,10 +173,10 @@ func Test_PopulateModel_Extensions_Handling_NodeAccessor(t *testing.T) { extensionValue := target.Extensions.data["x-test"] assert.NotNil(t, extensionValue) assert.Equal(t, "extension-value", extensionValue.Value) - + // Verify Extensions.SetCore was called assert.NotNil(t, target.Extensions.core) - + // Verify target SetCoreValue was called assert.NotNil(t, target.core) } @@ -205,15 +205,15 @@ func Test_PopulateModel_PopulatorValue_Tag_NodeAccessor(t *testing.T) { target := &TargetWithTag{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify tagged field used direct value (not NodeAccessor.GetValue) assert.Equal(t, "tagged-value", target.TaggedField) - + // Verify untagged field used NodeAccessor.GetValue assert.Equal(t, "untagged-value", target.UntaggedField) - + // Verify SetCoreValue was called assert.NotNil(t, target.core) } @@ -222,7 +222,7 @@ func Test_PopulateModel_PopulatorValue_Tag_NodeAccessor(t *testing.T) { func Test_PopulateModel_NonStruct_Error_CoreSetter(t *testing.T) { target := &SimpleCoreSetterTarget{} - err := marshaller.PopulateModel("not-a-struct", target) + err := marshaller.Populate("not-a-struct", target) require.Error(t, err) assert.Contains(t, err.Error(), "expected struct, got string") } @@ -251,7 +251,7 @@ func Test_PopulateModel_MissingTargetField_NodeAccessor(t *testing.T) { target := &TargetMissingField{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Should succeed and ignore missing field @@ -283,14 +283,14 @@ func Test_PopulateModel_PointerHandling_NodeAccessor(t *testing.T) { target := &TargetWithPointer{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify fields were populated require.NotNil(t, target.PtrField) assert.Equal(t, "ptr-value", *target.PtrField) assert.Equal(t, "can-addr-value", target.CanAddr) - + // Verify SetCoreValue was called assert.NotNil(t, target.core) } @@ -316,7 +316,7 @@ func Test_PopulateModel_Invalid_NodeAccessor_Error(t *testing.T) { target := &TargetInvalidAccessor{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.Error(t, err) assert.Contains(t, err.Error(), "expected NodeAccessor") -} \ No newline at end of file +} diff --git a/marshaller/populator_force_slice_test.go b/marshaller/populator_force_slice_test.go index 14a4b79..7cf058e 100644 --- a/marshaller/populator_force_slice_test.go +++ b/marshaller/populator_force_slice_test.go @@ -27,33 +27,33 @@ func Test_PopulateValue_RecursiveSliceCall_Success(t *testing.T) { // recursive populateValue calls with slice values and pointer targets source := RecursiveSliceStruct{ SliceOfAny: []interface{}{ - []string{"sub1", "sub2"}, // This slice should trigger the slice case in populateValue - []int{10, 20, 30}, // This slice should also trigger it + []string{"sub1", "sub2"}, // This slice should trigger the slice case in populateValue + []int{10, 20, 30}, // This slice should also trigger it [2]string{"arr1", "arr2"}, // This array should trigger the array case }, } target := &RecursiveSliceStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) require.Len(t, target.SliceOfAny, 3) - + // Check first slice if firstSlice, ok := target.SliceOfAny[0].([]string); ok { assert.Equal(t, []string{"sub1", "sub2"}, firstSlice) } else { t.Errorf("Expected []string, got %T", target.SliceOfAny[0]) } - - // Check second slice + + // Check second slice if secondSlice, ok := target.SliceOfAny[1].([]int); ok { assert.Equal(t, []int{10, 20, 30}, secondSlice) } else { t.Errorf("Expected []int, got %T", target.SliceOfAny[1]) } - + // Check array if thirdArray, ok := target.SliceOfAny[2].([2]string); ok { assert.Equal(t, [2]string{"arr1", "arr2"}, thirdArray) @@ -88,7 +88,7 @@ func Test_PopulateValue_DeeplyNestedSlices_Success(t *testing.T) { target := &DeeplyNestedWithSlices{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) require.NotNil(t, target.Level1) @@ -114,7 +114,7 @@ func Test_PopulateValue_MixedNesting_SliceInInterface_Success(t *testing.T) { target := &MixedNestingStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) if dataMap, ok := target.Data.(map[string]interface{}); ok { @@ -125,7 +125,7 @@ func Test_PopulateValue_MixedNesting_SliceInInterface_Success(t *testing.T) { t.Errorf("Expected []string for nested_slice, got %T", nestedSlice) } } - + if nestedArray, exists := dataMap["nested_array"]; exists { if array, ok := nestedArray.([3]int); ok { assert.Equal(t, [3]int{1, 2, 3}, array) @@ -159,14 +159,14 @@ func Test_PopulateValue_SliceValueInMap_Success(t *testing.T) { target := &SliceValueMapStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) require.Len(t, target.SliceMap, 2) assert.Equal(t, []string{"map1", "map2"}, target.SliceMap["key1"]) assert.Equal(t, []string{"map3", "map4"}, target.SliceMap["key2"]) - + require.Len(t, target.ArrayMap, 2) assert.Equal(t, [2]int{10, 20}, target.ArrayMap["arr1"]) assert.Equal(t, [2]int{30, 40}, target.ArrayMap["arr2"]) -} \ No newline at end of file +} diff --git a/marshaller/populator_nested_slices_test.go b/marshaller/populator_nested_slices_test.go index b445a21..9cde0e5 100644 --- a/marshaller/populator_nested_slices_test.go +++ b/marshaller/populator_nested_slices_test.go @@ -30,7 +30,7 @@ func Test_PopulateValue_SliceOfSlices_Success(t *testing.T) { target := &SliceOfSlicesStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify nested slices @@ -67,14 +67,14 @@ func Test_PopulateValue_SliceOfSlicesOfSlices_Success(t *testing.T) { target := &TripleNestedStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify 3D slice structure require.Len(t, target.ThreeDSlice, 2) require.Len(t, target.ThreeDSlice[0], 2) require.Len(t, target.ThreeDSlice[1], 2) - + assert.Equal(t, []string{"1a", "1b"}, target.ThreeDSlice[0][0]) assert.Equal(t, []string{"1c", "1d"}, target.ThreeDSlice[0][1]) assert.Equal(t, []string{"2a", "2b"}, target.ThreeDSlice[1][0]) @@ -97,7 +97,7 @@ func Test_PopulateValue_SliceOfSlices_WithNilInner_Success(t *testing.T) { target := &SliceWithNilsStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify mixed slices with nil @@ -107,7 +107,7 @@ func Test_PopulateValue_SliceOfSlices_WithNilInner_Success(t *testing.T) { assert.Equal(t, []string{"c", "d"}, target.MixedSlices[2]) } -// Test array of slices +// Test array of slices func Test_PopulateValue_ArrayOfSlices_Success(t *testing.T) { type ArrayOfSlicesStruct struct { ArrayOfSlices [3][]string @@ -123,7 +123,7 @@ func Test_PopulateValue_ArrayOfSlices_Success(t *testing.T) { target := &ArrayOfSlicesStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify array of slices @@ -148,7 +148,7 @@ func Test_PopulateValue_SliceOfArrays_Success(t *testing.T) { target := &SliceOfArraysStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify slice of arrays @@ -184,7 +184,7 @@ func Test_PopulateValue_ComplexNestedSlices_Success(t *testing.T) { target := &ComplexNestedStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify complex nested structure @@ -221,7 +221,7 @@ func Test_PopulateValue_EmptyOuterSlice_Success(t *testing.T) { target := &EmptyOuterStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify empty outer slice @@ -241,9 +241,9 @@ func Test_PopulateValue_NilOuterSlice_Success(t *testing.T) { target := &NilOuterStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify nil outer slice assert.Nil(t, target.NilOuter) -} \ No newline at end of file +} diff --git a/marshaller/populator_sequencedmap_test.go b/marshaller/populator_sequencedmap_test.go index c8121c7..256acad 100644 --- a/marshaller/populator_sequencedmap_test.go +++ b/marshaller/populator_sequencedmap_test.go @@ -6,76 +6,38 @@ import ( "testing" "github.com/speakeasy-api/openapi/marshaller" + "github.com/speakeasy-api/openapi/sequencedmap" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// Mock SequencedMap implementation for testing populateSequencedMap -type MockSequencedMap struct { - data map[any]any - keyOrder []any -} - -func (m *MockSequencedMap) Init() { - if m.data == nil { - m.data = make(map[any]any) - } - if m.keyOrder == nil { - m.keyOrder = make([]any, 0) - } -} - -func (m *MockSequencedMap) SetUntyped(key, value any) error { - if m.data == nil { - m.Init() - } - if _, exists := m.data[key]; !exists { - m.keyOrder = append(m.keyOrder, key) - } - m.data[key] = value - return nil -} - -func (m *MockSequencedMap) AllUntyped() iter.Seq2[any, any] { - return func(yield func(any, any) bool) { - if m.data == nil { - return - } - for _, key := range m.keyOrder { - if value, exists := m.data[key]; exists { - if !yield(key, value) { - return - } - } - } - } -} - -func (m *MockSequencedMap) GetValueType() reflect.Type { - return reflect.TypeOf("") -} - // Test populateSequencedMap function coverage func Test_PopulateModel_SequencedMap_Success(t *testing.T) { // Create source SequencedMap - source := &MockSequencedMap{} - source.Init() + source := sequencedmap.New[string, string]() require.NoError(t, source.SetUntyped("key1", "value1")) require.NoError(t, source.SetUntyped("key2", "value2")) require.NoError(t, source.SetUntyped("key3", "value3")) // Create target SequencedMap - target := &MockSequencedMap{} + target := sequencedmap.New[string, string]() // Test populateSequencedMap by calling PopulateModel - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify the data was copied - assert.NotNil(t, target.data) - assert.Equal(t, "value1", target.data["key1"]) - assert.Equal(t, "value2", target.data["key2"]) - assert.Equal(t, "value3", target.data["key3"]) + value1, ok := target.Get("key1") + assert.True(t, ok) + assert.Equal(t, "value1", value1) + + value2, ok := target.Get("key2") + assert.True(t, ok) + assert.Equal(t, "value2", value2) + + value3, ok := target.Get("key3") + assert.True(t, ok) + assert.Equal(t, "value3", value3) // Verify order is maintained keys := make([]any, 0) @@ -88,18 +50,17 @@ func Test_PopulateModel_SequencedMap_Success(t *testing.T) { // Test populateSequencedMap with nil source func Test_PopulateModel_SequencedMap_NilSource_Success(t *testing.T) { // Create source SequencedMap with no data (uninitialized) - source := &MockSequencedMap{} + source := sequencedmap.New[string, string]() // Create target SequencedMap - target := &MockSequencedMap{} + target := sequencedmap.New[string, string]() // Test populateSequencedMap by calling PopulateModel - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) - // Verify target was initialized but has no data - assert.NotNil(t, target.data) - assert.Empty(t, target.data) + // Verify target has no data + assert.Equal(t, 0, target.Len()) } // Test populateSequencedMap error case: source not SequencedMap @@ -112,10 +73,10 @@ func Test_PopulateModel_SequencedMap_InvalidSource_Error(t *testing.T) { source := &NotSequencedMapSource{Field: "not-a-sequenced-map"} // Create target SequencedMap - target := &MockSequencedMap{} + target := sequencedmap.New[string, string]() // Test should fail with type error when it tries to cast source to SequencedMap - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.Error(t, err) assert.Contains(t, err.Error(), "expected source to be SequencedMap") } @@ -142,8 +103,7 @@ func (i *InvalidTargetSequencedMap) GetValueType() reflect.Type { // Test populateSequencedMap error case: target not SequencedMap interface func Test_PopulateModel_SequencedMap_InvalidTarget_Error(t *testing.T) { // Create source SequencedMap - source := &MockSequencedMap{} - source.Init() + source := sequencedmap.New[string, string]() require.NoError(t, source.SetUntyped("key", "value")) // Create a mock target that looks like it implements SequencedMap but doesn't cast correctly @@ -157,108 +117,36 @@ func Test_PopulateModel_SequencedMap_InvalidTarget_Error(t *testing.T) { target := &FakeSequencedMap{} // This should fail because target doesn't actually implement SequencedMap interface - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.Error(t, err) assert.Contains(t, err.Error(), "cannot convert") } -// Mock SequencedMap that returns error on SetUntyped -type ErrorSequencedMap struct { - MockSequencedMap -} - -func (e *ErrorSequencedMap) SetUntyped(key, value any) error { - return assert.AnError -} - -// Test populateSequencedMap with SetUntyped error -func Test_PopulateModel_SequencedMap_SetError(t *testing.T) { - // Create source SequencedMap - source := &MockSequencedMap{} - source.Init() - require.NoError(t, source.SetUntyped("key", "value")) - - // Create target SequencedMap that errors on Set - target := &ErrorSequencedMap{} - - // Test should fail with the set error - err := marshaller.PopulateModel(source, target) - require.Error(t, err) - assert.Equal(t, assert.AnError, err) -} - -// Mock SequencedMap with complex value type for testing populateValue recursion +// Complex value type for testing populateValue recursion type ComplexValue struct { Field string } -type ComplexSequencedMap struct { - data map[any]any - keyOrder []any -} - -func (c *ComplexSequencedMap) Init() { - if c.data == nil { - c.data = make(map[any]any) - } - if c.keyOrder == nil { - c.keyOrder = make([]any, 0) - } -} - -func (c *ComplexSequencedMap) SetUntyped(key, value any) error { - if c.data == nil { - c.Init() - } - if _, exists := c.data[key]; !exists { - c.keyOrder = append(c.keyOrder, key) - } - c.data[key] = value - return nil -} - -func (c *ComplexSequencedMap) AllUntyped() iter.Seq2[any, any] { - return func(yield func(any, any) bool) { - if c.data == nil { - return - } - for _, key := range c.keyOrder { - if value, exists := c.data[key]; exists { - if !yield(key, value) { - return - } - } - } - } -} - -func (c *ComplexSequencedMap) GetValueType() reflect.Type { - return reflect.TypeOf(ComplexValue{}) -} - // Test populateSequencedMap with complex values that require populateValue recursion func Test_PopulateModel_SequencedMap_ComplexValues_Success(t *testing.T) { // Create source SequencedMap with complex values - source := &ComplexSequencedMap{} - source.Init() + source := sequencedmap.New[string, ComplexValue]() require.NoError(t, source.SetUntyped("item1", ComplexValue{Field: "value1"})) require.NoError(t, source.SetUntyped("item2", ComplexValue{Field: "value2"})) // Create target SequencedMap - target := &ComplexSequencedMap{} + target := sequencedmap.New[string, ComplexValue]() // Test populateSequencedMap - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify complex values were copied correctly - assert.NotNil(t, target.data) - - item1, ok := target.data["item1"].(ComplexValue) + item1, ok := target.Get("item1") require.True(t, ok) assert.Equal(t, "value1", item1.Field) - item2, ok := target.data["item2"].(ComplexValue) + item2, ok := target.Get("item2") require.True(t, ok) assert.Equal(t, "value2", item2.Field) } diff --git a/marshaller/populator_slice_array_test.go b/marshaller/populator_slice_array_test.go index 681ddbd..fd85cff 100644 --- a/marshaller/populator_slice_array_test.go +++ b/marshaller/populator_slice_array_test.go @@ -8,13 +8,13 @@ import ( "github.com/stretchr/testify/require" ) -// Test slice handling in populateValue by creating a scenario where +// Test slice handling in populateValue by creating a scenario where // the target doesn't implement special interfaces but the source has slice fields func Test_PopulateValue_Slice_DirectPopulation_Success(t *testing.T) { // Create a scenario where populateValue handles slices directly // This happens when the target is a simple type (not implementing special interfaces) // and the source contains slices - + type SimpleTarget struct { StringSlice []string IntSlice []int @@ -29,7 +29,7 @@ func Test_PopulateValue_Slice_DirectPopulation_Success(t *testing.T) { // Target that will be populated target := &SimpleTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify slice population @@ -53,7 +53,7 @@ func Test_PopulateValue_Array_DirectPopulation_Success(t *testing.T) { // Target that will be populated target := &ArrayTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify array population @@ -77,7 +77,7 @@ func Test_PopulateValue_NilSlice_DirectPopulation_Success(t *testing.T) { // Target that will be populated target := &NilSliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify nil slices remain nil @@ -101,7 +101,7 @@ func Test_PopulateValue_EmptySlice_DirectPopulation_Success(t *testing.T) { // Target that will be populated target := &EmptySliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify empty slices are created @@ -134,7 +134,7 @@ func Test_PopulateValue_NestedSlice_DirectPopulation_Success(t *testing.T) { // Target that will be populated target := &NestedSliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify nested slice population @@ -166,7 +166,7 @@ func Test_PopulateValue_SliceRecursion_Error(t *testing.T) { // Target that will cause conversion error target := &IncompatibleTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.Error(t, err) assert.Contains(t, err.Error(), "cannot convert") } @@ -189,7 +189,7 @@ func Test_PopulateValue_SliceOfPointers_DirectPopulation_Success(t *testing.T) { // Target that will be populated target := &PointerSliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify pointer slice population @@ -217,7 +217,7 @@ func Test_PopulateValue_MultiDimensionalSlice_DirectPopulation_Success(t *testin // Target that will be populated target := &MultiDimTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify 2D slice population @@ -245,7 +245,7 @@ func Test_PopulateValue_LargeSlice_DirectPopulation_Success(t *testing.T) { // Target that will be populated target := &LargeSliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify large slice population @@ -253,4 +253,4 @@ func Test_PopulateValue_LargeSlice_DirectPopulation_Success(t *testing.T) { assert.Equal(t, 0, target.Numbers[0]) assert.Equal(t, 500, target.Numbers[500]) assert.Equal(t, 999, target.Numbers[999]) -} \ No newline at end of file +} diff --git a/marshaller/populator_slice_direct_call_test.go b/marshaller/populator_slice_direct_call_test.go index 81ff544..ef75f5d 100644 --- a/marshaller/populator_slice_direct_call_test.go +++ b/marshaller/populator_slice_direct_call_test.go @@ -18,7 +18,7 @@ type DirectSlicePopulator struct { } // This is a custom implementation that will trigger the slice path -func (d *DirectSlicePopulator) FromCore(c any) error { +func (d *DirectSlicePopulator) Populate(c any) error { // We'll use reflection to directly call populateValue with slices if data, ok := c.(map[string]any); ok { // Get the slice data @@ -26,7 +26,7 @@ func (d *DirectSlicePopulator) FromCore(c any) error { // Create reflect values for direct populateValue call sourceValue := reflect.ValueOf(sliceData) targetValue := reflect.ValueOf(&d.SliceField) - + // This should trigger the slice case in populateValue // because sourceValue is a slice and targetValue is a pointer to slice if sourceValue.Kind() == reflect.Slice && targetValue.Kind() == reflect.Ptr { @@ -42,11 +42,11 @@ func (d *DirectSlicePopulator) FromCore(c any) error { } } } - + if arrayData, exists := data["array_field"]; exists { sourceValue := reflect.ValueOf(arrayData) targetValue := reflect.ValueOf(&d.ArrayField) - + if sourceValue.Kind() == reflect.Array && targetValue.Kind() == reflect.Ptr { targetArray := targetValue.Elem() for i := 0; i < sourceValue.Len(); i++ { @@ -66,7 +66,7 @@ func Test_PopulateValue_DirectSliceCall_Success(t *testing.T) { target := &DirectSlicePopulator{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, []string{"direct1", "direct2", "direct3"}, target.SliceField) @@ -93,7 +93,7 @@ func Test_PopulateValue_NestedSliceContainer_Success(t *testing.T) { target := &NestedSliceContainer{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) require.NotNil(t, target.Inner) @@ -115,7 +115,7 @@ func Test_PopulateValue_InterfaceSliceHolder_Success(t *testing.T) { target := &InterfaceSliceHolder{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) if slice, ok := target.Data.([]string); ok { @@ -136,7 +136,7 @@ func Test_PopulateValue_InterfaceNestedSlices_Success(t *testing.T) { target := &InterfaceSliceHolder{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) if nestedSlice, ok := target.Data.([][]string); ok { @@ -146,4 +146,4 @@ func Test_PopulateValue_InterfaceNestedSlices_Success(t *testing.T) { } else { t.Errorf("Expected [][]string, got %T", target.Data) } -} \ No newline at end of file +} diff --git a/marshaller/populator_slice_direct_targeted_test.go b/marshaller/populator_slice_direct_targeted_test.go index 8856a48..b52b2ae 100644 --- a/marshaller/populator_slice_direct_targeted_test.go +++ b/marshaller/populator_slice_direct_targeted_test.go @@ -8,17 +8,17 @@ import ( "github.com/stretchr/testify/require" ) -// Create a scenario where ModelFromCore.FromCore calls something that triggers slice path -type SliceFromCoreProcessor struct { +// Create a scenario where Populator.Populate calls something that triggers slice path +type SliceProcessorPopulator struct { ProcessedSlices [][]string ProcessedArrays [][3]int } -func (s *SliceFromCoreProcessor) FromCore(c any) error { - // This implements ModelFromCore interface +func (s *SliceProcessorPopulator) Populate(c any) error { + // This implements Populator interface // Inside here, we could theoretically call populateValue with slices directly // But since we don't have access to populateValue directly, we'll simulate the scenario - + if data, ok := c.(map[string]any); ok { if sliceData, exists := data["slices"]; exists { if slices, ok := sliceData.([][]string); ok { @@ -40,15 +40,15 @@ func Test_PopulateValue_SliceFromCoreProcessor_Success(t *testing.T) { "arrays": [][3]int{{1, 2, 3}, {4, 5, 6}}, } - target := &SliceFromCoreProcessor{} + target := &SliceProcessorPopulator{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Len(t, target.ProcessedSlices, 2) assert.Equal(t, []string{"a", "b"}, target.ProcessedSlices[0]) assert.Equal(t, []string{"c", "d"}, target.ProcessedSlices[1]) - + assert.Len(t, target.ProcessedArrays, 2) assert.Equal(t, [3]int{1, 2, 3}, target.ProcessedArrays[0]) assert.Equal(t, [3]int{4, 5, 6}, target.ProcessedArrays[1]) @@ -71,7 +71,7 @@ func Test_PopulateValue_UnusualSliceScenario_Success(t *testing.T) { target := &UnusualSliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, []string{"unusual1", "unusual2", "unusual3"}, target.Data) @@ -90,7 +90,7 @@ func Test_PopulateValue_ReflectiveSlice_Success(t *testing.T) { target := &ReflectiveSliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) // Verify the slice was copied @@ -113,11 +113,11 @@ func Test_PopulateValue_InterfaceSlice_Success(t *testing.T) { target := &InterfaceSliceTarget{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) require.Len(t, target.Items, 3) assert.Equal(t, "item1", target.Items[0]) assert.Equal(t, 42, target.Items[1]) assert.Equal(t, true, target.Items[2]) -} \ No newline at end of file +} diff --git a/marshaller/populator_slice_direct_test.go b/marshaller/populator_slice_direct_test.go index a94e586..07c78b6 100644 --- a/marshaller/populator_slice_direct_test.go +++ b/marshaller/populator_slice_direct_test.go @@ -8,17 +8,17 @@ import ( "github.com/stretchr/testify/require" ) -// Test to trigger the slice/array path in populateValue by using ModelFromCore interface -type SliceModelFromCore struct { +// Test to trigger the slice/array path in populateValue by using Populator interface +type SlicePopulator struct { SliceData []string ArrayData [3]int processed bool } -func (s *SliceModelFromCore) FromCore(c any) error { +func (s *SlicePopulator) Populate(c any) error { // This will trigger populateValue with the slice/array types directly s.processed = true - + // Cast the core data and populate manually to trigger slice path if data, ok := c.(map[string]any); ok { if sliceData, exists := data["slice"]; exists { @@ -32,24 +32,24 @@ func (s *SliceModelFromCore) FromCore(c any) error { } } } - + return nil } func Test_PopulateValue_SliceViaModelFromCore_Success(t *testing.T) { - // Create source data that will be passed to FromCore + // Create source data that will be passed to Populate source := map[string]any{ "slice": []string{"item1", "item2", "item3"}, "array": [3]int{10, 20, 30}, } - // Target implements ModelFromCore - target := &SliceModelFromCore{} + // Target implements Populator + target := &SlicePopulator{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) - // Verify that FromCore was called + // Verify that Populate was called assert.True(t, target.processed) assert.Equal(t, []string{"item1", "item2", "item3"}, target.SliceData) assert.Equal(t, [3]int{10, 20, 30}, target.ArrayData) @@ -70,10 +70,10 @@ func Test_PopulateValue_SimpleSliceStruct_Success(t *testing.T) { Items: []string{"simple1", "simple2"}, Nums: []int{100, 200}, } - + target := &SimpleSliceStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, []string{"simple1", "simple2"}, target.Items) @@ -86,10 +86,10 @@ func Test_PopulateValue_SimpleSliceStruct_NilSlice_Success(t *testing.T) { Items: nil, Nums: nil, } - + target := &SimpleSliceStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Nil(t, target.Items) @@ -107,12 +107,12 @@ func Test_PopulateValue_SimpleArrayStruct_Success(t *testing.T) { Items: [3]string{"array1", "array2", "array3"}, Nums: [2]int{300, 400}, } - + target := &SimpleArrayStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, [3]string{"array1", "array2", "array3"}, target.Items) assert.Equal(t, [2]int{300, 400}, target.Nums) -} \ No newline at end of file +} diff --git a/marshaller/populator_slice_source_test.go b/marshaller/populator_slice_source_test.go index 2b596f0..29d18f3 100644 --- a/marshaller/populator_slice_source_test.go +++ b/marshaller/populator_slice_source_test.go @@ -14,13 +14,13 @@ import ( func Test_PopulateValue_SliceAsSource_Success(t *testing.T) { // Use a slice as the source directly source := []string{"source1", "source2", "source3"} - + // Target must be a pointer to a slice var target []string - - err := marshaller.PopulateModel(source, &target) + + err := marshaller.Populate(source, &target) require.NoError(t, err) - + assert.Equal(t, []string{"source1", "source2", "source3"}, target) } @@ -37,26 +37,26 @@ func Test_PopulateValue_SliceAsSource_Success(t *testing.T) { func Test_PopulateValue_NilSliceAsSource_Success(t *testing.T) { // Use a nil slice as the source var source []string = nil - + // Target must be a pointer to a slice var target []string - - err := marshaller.PopulateModel(source, &target) + + err := marshaller.Populate(source, &target) require.NoError(t, err) - + assert.Nil(t, target) } func Test_PopulateValue_EmptySliceAsSource_Success(t *testing.T) { // Use an empty slice as the source source := []string{} - + // Target must be a pointer to a slice var target []string - - err := marshaller.PopulateModel(source, &target) + + err := marshaller.Populate(source, &target) require.NoError(t, err) - + assert.NotNil(t, target) assert.Len(t, target, 0) } @@ -64,10 +64,10 @@ func Test_PopulateValue_EmptySliceAsSource_Success(t *testing.T) { func Test_PopulateValue_SliceOfIntsAsSource_Success(t *testing.T) { source := []int{1, 2, 3, 4, 5} var target []int - - err := marshaller.PopulateModel(source, &target) + + err := marshaller.Populate(source, &target) require.NoError(t, err) - + assert.Equal(t, []int{1, 2, 3, 4, 5}, target) } @@ -78,10 +78,10 @@ func Test_PopulateValue_NestedSliceAsSource_Success(t *testing.T) { {"nested3", "nested4"}, } var target [][]string - - err := marshaller.PopulateModel(source, &target) + + err := marshaller.Populate(source, &target) require.NoError(t, err) - + require.Len(t, target, 2) assert.Equal(t, []string{"nested1", "nested2"}, target[0]) assert.Equal(t, []string{"nested3", "nested4"}, target[1]) @@ -97,4 +97,4 @@ func Test_PopulateValue_NestedSliceAsSource_Success(t *testing.T) { // require.Len(t, target, 2) // assert.Equal(t, [2]string{"arr1", "arr2"}, target[0]) // assert.Equal(t, [2]string{"arr3", "arr4"}, target[1]) -// } \ No newline at end of file +// } diff --git a/marshaller/populator_test.go b/marshaller/populator_test.go index 92816b7..57ed316 100644 --- a/marshaller/populator_test.go +++ b/marshaller/populator_test.go @@ -12,11 +12,11 @@ type NestedSourceStruct struct { Value string } -type ModelFromCoreTarget struct { +type PopulatorTarget struct { Value string } -func (m *ModelFromCoreTarget) FromCore(c any) error { +func (m *PopulatorTarget) Populate(c any) error { if core, ok := c.(NestedSourceStruct); ok { m.Value = core.Value } @@ -24,11 +24,11 @@ func (m *ModelFromCoreTarget) FromCore(c any) error { } func Test_PopulateModel_Success(t *testing.T) { - // Test ModelFromCore interface + // Test Populator interface source := NestedSourceStruct{Value: "from-core"} - target := &ModelFromCoreTarget{} - - err := marshaller.PopulateModel(source, target) + target := &PopulatorTarget{} + + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, "from-core", target.Value) } @@ -37,8 +37,8 @@ func Test_PopulateModel_SimpleStruct_Success(t *testing.T) { // Test basic struct population source := NestedSourceStruct{Value: "test"} target := &NestedSourceStruct{} - - err := marshaller.PopulateModel(source, target) + + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, "test", target.Value) -} \ No newline at end of file +} diff --git a/marshaller/populator_value_test.go b/marshaller/populator_value_test.go index ae35e97..1739e4f 100644 --- a/marshaller/populator_value_test.go +++ b/marshaller/populator_value_test.go @@ -8,13 +8,13 @@ import ( "github.com/stretchr/testify/require" ) -// Test ModelFromCore interface path in populateValue -type MockModelFromCore struct { - Value string +// Test Populator interface path in populateValue +type MockPopulator struct { + Value string FromCoreCalled bool } -func (m *MockModelFromCore) FromCore(c any) error { +func (m *MockPopulator) Populate(c any) error { m.FromCoreCalled = true if str, ok := c.(string); ok { m.Value = str @@ -24,27 +24,27 @@ func (m *MockModelFromCore) FromCore(c any) error { func Test_PopulateValue_ModelFromCore_Success(t *testing.T) { source := "test-value" - target := &MockModelFromCore{} + target := &MockPopulator{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.True(t, target.FromCoreCalled) assert.Equal(t, "test-value", target.Value) } -// Test ModelFromCore error path -type ErrorModelFromCore struct{} +// Test Populator error path +type ErrorPopulator struct{} -func (e *ErrorModelFromCore) FromCore(c any) error { +func (e *ErrorPopulator) Populate(c any) error { return assert.AnError } func Test_PopulateValue_ModelFromCore_Error(t *testing.T) { source := "test-value" - target := &ErrorModelFromCore{} + target := &ErrorPopulator{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.Error(t, err) assert.Equal(t, assert.AnError, err) } @@ -61,7 +61,7 @@ func Test_PopulateValue_Slice_Success(t *testing.T) { target := &SimpleStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, []string{"item1", "item2", "item3"}, target.SliceField) @@ -79,7 +79,7 @@ func Test_PopulateValue_Array_Success(t *testing.T) { target := &ArrayStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, [3]string{"item1", "item2", "item3"}, target.ArrayField) @@ -97,7 +97,7 @@ func Test_PopulateValue_NilSlice_Success(t *testing.T) { target := &SliceStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Nil(t, target.SliceField) @@ -124,7 +124,7 @@ func Test_PopulateValue_Slice_Recursive_Error(t *testing.T) { target := &TargetStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.Error(t, err) assert.Contains(t, err.Error(), "cannot convert") } @@ -141,7 +141,7 @@ func Test_PopulateValue_NilPointer_BothPointers(t *testing.T) { target := &PointerStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Nil(t, target.PtrField) @@ -156,7 +156,7 @@ func Test_PopulateValue_AssignableType_Success(t *testing.T) { } type TargetStruct struct { - StringField string // Same type, directly assignable + StringField string // Same type, directly assignable } source := SourceStruct{ @@ -165,7 +165,7 @@ func Test_PopulateValue_AssignableType_Success(t *testing.T) { target := &TargetStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) assert.Equal(t, "test-value", target.StringField) @@ -174,7 +174,7 @@ func Test_PopulateValue_AssignableType_Success(t *testing.T) { // Test conversion error path (line 171) func Test_PopulateValue_ConversionError(t *testing.T) { type SourceStruct struct { - ChanField chan int // Cannot be converted to string + ChanField chan int // Cannot be converted to string } type TargetStruct struct { @@ -187,7 +187,7 @@ func Test_PopulateValue_ConversionError(t *testing.T) { target := &TargetStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.Error(t, err) assert.Contains(t, err.Error(), "cannot convert") } @@ -212,7 +212,7 @@ func Test_PopulateValue_NestedSliceComplex_Success(t *testing.T) { target := &ContainerStruct{} - err := marshaller.PopulateModel(source, target) + err := marshaller.Populate(source, target) require.NoError(t, err) require.Len(t, target.Items, 2) @@ -220,4 +220,4 @@ func Test_PopulateValue_NestedSliceComplex_Success(t *testing.T) { assert.Equal(t, 1, target.Items[0].Count) assert.Equal(t, "second", target.Items[1].Value) assert.Equal(t, 2, target.Items[1].Count) -} \ No newline at end of file +} diff --git a/marshaller/unmarshaller.go b/marshaller/unmarshaller.go index 5508b84..86ad0e0 100644 --- a/marshaller/unmarshaller.go +++ b/marshaller/unmarshaller.go @@ -3,12 +3,10 @@ package marshaller import ( "context" "fmt" - "iter" "reflect" "strings" "github.com/speakeasy-api/openapi/errors" - "github.com/speakeasy-api/openapi/sequencedmap" "github.com/speakeasy-api/openapi/validation" "gopkg.in/yaml.v3" ) @@ -17,15 +15,6 @@ type Unmarshallable interface { Unmarshal(ctx context.Context, value *yaml.Node) error } -type SequencedMap interface { - Init() - SetUntyped(key, value any) error - AllUntyped() iter.Seq2[any, any] - GetValueType() reflect.Type -} - -var _ SequencedMap = (*sequencedmap.Map[any, any])(nil) - func Unmarshal(ctx context.Context, node *yaml.Node, out any) error { if node.Kind == yaml.DocumentNode { if len(node.Content) != 1 { @@ -39,6 +28,9 @@ func Unmarshal(ctx context.Context, node *yaml.Node, out any) error { if v.Kind() == reflect.Ptr && !v.IsNil() { v = v.Elem() } + for v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + } return unmarshal(ctx, node, v) } @@ -60,15 +52,15 @@ func UnmarshalModel(ctx context.Context, node *yaml.Node, structPtr any) error { var unmarshallable CoreModeler - // Check if struct implements UnmarshallableCoreModel - if out.Addr().Type().Implements(reflect.TypeOf((*CoreModeler)(nil)).Elem()) { + // Check if struct implements CoreModeler + if isCoreModel(out) { var ok bool unmarshallable, ok = out.Addr().Interface().(CoreModeler) if !ok { - return fmt.Errorf("expected UnmarshallableCoreModel, got %s", out.Type()) + return fmt.Errorf("expected CoreModeler, got %s", out.Type()) } } else { - return fmt.Errorf("expected struct to implement UnmarshallableCoreModel, got %s", out.Type()) + return fmt.Errorf("expected struct to implement CoreModeler, got %s", out.Type()) } unmarshallable.SetRootNode(node) @@ -80,8 +72,9 @@ func UnmarshalModel(ctx context.Context, node *yaml.Node, structPtr any) error { } // get fields by tag first - fields := sequencedmap.New[string, Field]() + fields := map[string]Field{} var extensionsField *reflect.Value + requiredFields := map[string]Field{} // Track required fields separately for i := 0; i < out.NumField(); i++ { field := out.Type().Field(i) @@ -113,14 +106,23 @@ func UnmarshalModel(ctx context.Context, node *yaml.Node, structPtr any) error { } } - fields.Set(tag, Field{ + fieldInfo := Field{ Name: field.Name, Field: out.Field(i), Required: required, - }) + } + + fields[tag] = fieldInfo + + // Track required fields for validation + if required { + requiredFields[tag] = fieldInfo + } } - foundFields := sequencedmap.New[string, bool]() + // Process YAML nodes and validate required fields in one pass + valid := true + foundRequiredFields := map[string]bool{} for i := 0; i < len(node.Content); i += 2 { keyNode := node.Content[i] @@ -128,7 +130,7 @@ func UnmarshalModel(ctx context.Context, node *yaml.Node, structPtr any) error { key := keyNode.Value - field, ok := fields.Get(key) + field, ok := fields[key] if !ok { if !strings.HasPrefix(key, "x-") { continue @@ -144,19 +146,17 @@ func UnmarshalModel(ctx context.Context, node *yaml.Node, structPtr any) error { return err } - foundFields.Set(key, true) + // Mark required field as found + if field.Required { + foundRequiredFields[key] = true + } } } - valid := true - - for key, field := range fields.All() { - if !field.Required { - continue - } - - if _, ok := foundFields.Get(key); !ok { - unmarshallable.AddValidationError(validation.NewNodeError(fmt.Sprintf("field %s is missing", key), node)) + // Check for missing required fields + for tag := range requiredFields { + if !foundRequiredFields[tag] { + unmarshallable.AddValidationError(validation.NewNodeError(fmt.Sprintf("field %s is missing", tag), node)) valid = false } } @@ -193,16 +193,6 @@ func unmarshal(ctx context.Context, node *yaml.Node, out reflect.Value) error { return unmarshallable.Unmarshal(ctx, node) } - // Auto-detect core models by checking for RootNode field - if isCoreModel(out) && node.Kind == yaml.MappingNode { - outPtr := out - if out.Kind() != reflect.Ptr { - outPtr = out.Addr() - } - - return UnmarshalModel(ctx, node, outPtr.Interface()) - } - switch node.Kind { case yaml.MappingNode: return unmarshalMapping(ctx, node, out) @@ -218,11 +208,6 @@ func unmarshal(ctx context.Context, node *yaml.Node, out reflect.Value) error { } func unmarshalMapping(ctx context.Context, node *yaml.Node, out reflect.Value) error { - _, ok := out.Interface().(SequencedMap) - if ok { - return unmarshalSequencedMap(ctx, node, out) - } - if out.Kind() == reflect.Ptr { out.Set(reflect.New(out.Type().Elem())) out = out.Elem() @@ -230,7 +215,11 @@ func unmarshalMapping(ctx context.Context, node *yaml.Node, out reflect.Value) e switch { case out.Kind() == reflect.Struct: - return UnmarshalModel(ctx, node, out.Addr().Interface()) + if isCoreModel(out) { + return UnmarshalModel(ctx, node, out.Addr().Interface()) + } else { + return unmarshalStruct(ctx, node, out.Addr().Interface()) + } case out.Kind() == reflect.Map: return fmt.Errorf("currently unsupported out kind: %v", out.Kind()) default: @@ -238,6 +227,11 @@ func unmarshalMapping(ctx context.Context, node *yaml.Node, out reflect.Value) e } } +func unmarshalStruct(_ context.Context, node *yaml.Node, structPtr any) error { + // TODO do we need a custom implementation for this? This implementation will treat any child of a normal struct as also a normal struct unless it implements a custom unmarshaller + return node.Decode(structPtr) +} + func unmarshalSequence(ctx context.Context, node *yaml.Node, out reflect.Value) error { if out.Kind() != reflect.Slice { return fmt.Errorf("expected slice, got %s", out.Kind()) @@ -290,63 +284,39 @@ func unmarshalNode(ctx context.Context, keyNode, valueNode *yaml.Node, fieldName return nil } -func unmarshalSequencedMap(ctx context.Context, node *yaml.Node, out reflect.Value) error { - if out.Kind() == reflect.Ptr && out.IsNil() { - out.Set(reflect.New(out.Type().Elem())) - } - - sm, ok := out.Interface().(SequencedMap) - if !ok { - return fmt.Errorf("expected SequencedMap, got %s", out.Type()) - } - - sm.Init() - - for i := 0; i < len(node.Content); i += 2 { - keyNode := node.Content[i] - valueNode := node.Content[i+1] - - key := keyNode.Value - - valueOut := reflect.New(sm.GetValueType()).Elem() - - if err := unmarshal(ctx, valueNode, valueOut); err != nil { - return err - } +func isUnmarshallable(out reflect.Value) bool { + // Store original value to check directly + original := out - if err := sm.SetUntyped(key, valueOut.Interface()); err != nil { - return err - } + // Unwrap interface if needed + for out.Kind() == reflect.Interface && !out.IsNil() { + out = out.Elem() } - return nil -} - -func isUnmarshallable(out reflect.Value) bool { + // Get addressable value if needed if out.Kind() != reflect.Ptr { + if !out.CanAddr() { + // Try checking the original value directly + return original.Type().Implements(reflect.TypeOf((*Unmarshallable)(nil)).Elem()) + } out = out.Addr() } return out.Type().Implements(reflect.TypeOf((*Unmarshallable)(nil)).Elem()) } -// isCoreModel checks if a value is a struct with a RootNode field of type *yaml.Node +// isCoreModel checks if a value implements the CoreModeler interface func isCoreModel(out reflect.Value) bool { if out.Kind() == reflect.Ptr { if out.IsNil() { return false } - out = out.Elem() - } - - if out.Kind() != reflect.Struct { - return false - } - - rootNodeField := out.FieldByName("RootNode") - if !rootNodeField.IsValid() { + } else if out.CanAddr() { + out = out.Addr() + } else { return false } - return rootNodeField.Type() == reflect.TypeOf((*yaml.Node)(nil)) + coreModelerType := reflect.TypeOf((*CoreModeler)(nil)).Elem() + return out.Type().Implements(coreModelerType) } diff --git a/marshaller/unmarshaller_additional_test.go b/marshaller/unmarshaller_additional_test.go index e1af4d5..06754c1 100644 --- a/marshaller/unmarshaller_additional_test.go +++ b/marshaller/unmarshaller_additional_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/speakeasy-api/openapi/marshaller" + "github.com/speakeasy-api/openapi/sequencedmap" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" @@ -44,9 +45,219 @@ func Test_UnmarshalMapping_Error(t *testing.T) { } } +// Simple model for testing +type SimpleModel struct { + Name string `yaml:"name"` + Value int `yaml:"value"` +} + +// CoreModeler-based model for testing +type CoreModel struct { + marshaller.CoreModel + Name marshaller.Node[string] `key:"name"` + Value marshaller.Node[int] `key:"value"` +} + func Test_UnmarshalMapping_SequencedMap_Success(t *testing.T) { - // Skip this test for now as it requires specific unmarshalling setup - t.Skip("Skipping sequenced map test - requires specific interface implementation") + tests := []struct { + name string + yaml string + target any + validate func(t *testing.T, target any) + }{ + { + name: "simple string values", + yaml: `key1: value1 +key2: value2 +key3: value3`, + target: sequencedmap.New[string, string](), + validate: func(t *testing.T, target any) { + sm := target.(*sequencedmap.Map[string, string]) + + assert.Equal(t, 3, sm.Len()) + + // Check values + val1, ok := sm.Get("key1") + require.True(t, ok) + assert.Equal(t, "value1", val1) + + val2, ok := sm.Get("key2") + require.True(t, ok) + assert.Equal(t, "value2", val2) + + val3, ok := sm.Get("key3") + require.True(t, ok) + assert.Equal(t, "value3", val3) + + // Verify order is maintained + keys := make([]string, 0) + for key := range sm.Keys() { + keys = append(keys, key) + } + assert.Equal(t, []string{"key1", "key2", "key3"}, keys) + }, + }, + { + name: "integer values", + yaml: `item1: 42 +item2: 84 +item3: 126`, + target: sequencedmap.New[string, int](), + validate: func(t *testing.T, target any) { + sm := target.(*sequencedmap.Map[string, int]) + + assert.Equal(t, 3, sm.Len()) + + // Check values + val1, ok := sm.Get("item1") + require.True(t, ok) + assert.Equal(t, 42, val1) + + val2, ok := sm.Get("item2") + require.True(t, ok) + assert.Equal(t, 84, val2) + + val3, ok := sm.Get("item3") + require.True(t, ok) + assert.Equal(t, 126, val3) + + // Verify order is maintained + keys := make([]string, 0) + for key := range sm.Keys() { + keys = append(keys, key) + } + assert.Equal(t, []string{"item1", "item2", "item3"}, keys) + }, + }, + { + name: "empty map", + yaml: `{}`, + target: sequencedmap.New[string, string](), + validate: func(t *testing.T, target any) { + sm := target.(*sequencedmap.Map[string, string]) + assert.Equal(t, 0, sm.Len()) + }, + }, + { + name: "single key-value pair", + yaml: `single: value`, + target: sequencedmap.New[string, string](), + validate: func(t *testing.T, target any) { + sm := target.(*sequencedmap.Map[string, string]) + assert.Equal(t, 1, sm.Len()) + + val, ok := sm.Get("single") + require.True(t, ok) + assert.Equal(t, "value", val) + }, + }, + { + name: "plain struct values", + yaml: `model1: + name: "test1" + value: 42 +model2: + name: "test2" + value: 84`, + target: sequencedmap.New[string, SimpleModel](), + validate: func(t *testing.T, target any) { + sm := target.(*sequencedmap.Map[string, SimpleModel]) + assert.Equal(t, 2, sm.Len()) + + // Check first model + model1, ok := sm.Get("model1") + require.True(t, ok) + assert.Equal(t, "test1", model1.Name) + assert.Equal(t, 42, model1.Value) + + // Check second model + model2, ok := sm.Get("model2") + require.True(t, ok) + assert.Equal(t, "test2", model2.Name) + assert.Equal(t, 84, model2.Value) + + // Verify order is maintained + keys := make([]string, 0) + for key := range sm.Keys() { + keys = append(keys, key) + } + assert.Equal(t, []string{"model1", "model2"}, keys) + }, + }, + { + name: "CoreModeler struct values", + yaml: `core1: + name: "core1" + value: 123 +core2: + name: "core2" + value: 456`, + target: sequencedmap.New[string, CoreModel](), + validate: func(t *testing.T, target any) { + sm := target.(*sequencedmap.Map[string, CoreModel]) + assert.Equal(t, 2, sm.Len()) + + // Check first core model + core1, ok := sm.Get("core1") + require.True(t, ok) + assert.Equal(t, "core1", core1.Name.GetValue()) + assert.Equal(t, 123, core1.Value.GetValue()) + assert.True(t, core1.GetValid()) + + // Check second core model + core2, ok := sm.Get("core2") + require.True(t, ok) + assert.Equal(t, "core2", core2.Name.GetValue()) + assert.Equal(t, 456, core2.Value.GetValue()) + assert.True(t, core2.GetValid()) + + // Verify order is maintained + keys := make([]string, 0) + for key := range sm.Keys() { + keys = append(keys, key) + } + assert.Equal(t, []string{"core1", "core2"}, keys) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var node yaml.Node + err := yaml.Unmarshal([]byte(tt.yaml), &node) + require.NoError(t, err) + + // Use a pointer to the target to avoid the interface detection issue + err = marshaller.Unmarshal(context.Background(), node.Content[0], &tt.target) + require.NoError(t, err) + + tt.validate(t, tt.target) + }) + } +} + +func Test_UnmarshalMapping_SequencedMap_PlainStruct_Success(t *testing.T) { + yamlData := `model1: + name: "test1" + value: 42` + + var node yaml.Node + err := yaml.Unmarshal([]byte(yamlData), &node) + require.NoError(t, err) + + target := sequencedmap.New[string, SimpleModel]() + err = marshaller.Unmarshal(context.Background(), node.Content[0], &target) + + // Should now work for plain structs + require.NoError(t, err) + + // Verify the data was unmarshaled correctly + assert.Equal(t, 1, target.Len()) + + model, ok := target.Get("model1") + require.True(t, ok) + assert.Equal(t, "test1", model.Name) + assert.Equal(t, 42, model.Value) } func Test_UnmarshalSequence_Error(t *testing.T) { @@ -96,11 +307,6 @@ type TestCoreModelStructCore struct { Value marshaller.Node[string] `key:"value"` } -func Test_UnmarshalNode_CoreModel_Success(t *testing.T) { - // Skip this test for now as it requires specific core model implementation - t.Skip("Skipping core model test - requires specific interface implementation") -} - func Test_UnmarshalNode_ScalarTypes_Success(t *testing.T) { tests := []struct { name string @@ -167,4 +373,4 @@ func Test_UnmarshalNode_InvalidNode_Error(t *testing.T) { target := new(string) err := marshaller.Unmarshal(context.Background(), node, target) require.Error(t, err) -} \ No newline at end of file +} diff --git a/marshaller/unmarshaller_test.go b/marshaller/unmarshaller_test.go index 03ae3ef..81166a7 100644 --- a/marshaller/unmarshaller_test.go +++ b/marshaller/unmarshaller_test.go @@ -1,10 +1,11 @@ -package marshaller +package marshaller_test import ( "context" "testing" "github.com/speakeasy-api/openapi/internal/testutils" + "github.com/speakeasy-api/openapi/marshaller" "github.com/speakeasy-api/openapi/pointer" "github.com/speakeasy-api/openapi/sequencedmap" "github.com/stretchr/testify/assert" @@ -12,31 +13,31 @@ import ( "gopkg.in/yaml.v3" ) -type Extensions = *sequencedmap.Map[string, Node[Extension]] +type Extensions = *sequencedmap.Map[string, marshaller.Node[marshaller.Extension]] type TestCoreModel struct { - CoreModel - - PrimitiveField Node[string] `key:"primitiveField"` - NestedModelField Node[TestNestedModel] `key:"nestedModelField"` - NestedModelOptionalField Node[*TestNestedModel] `key:"nestedModelOptionalField"` - SliceNestedModelField Node[[]TestNestedModel] `key:"sliceNestedModelField"` - MapRequiredNestedModelField Node[*sequencedmap.Map[string, TestNestedModel]] `key:"mapRequiredNestedModelField" required:"true"` - Extensions Extensions `key:"extensions"` + marshaller.CoreModel + + PrimitiveField marshaller.Node[string] `key:"primitiveField"` + NestedModelField marshaller.Node[TestNestedModel] `key:"nestedModelField"` + NestedModelOptionalField marshaller.Node[*TestNestedModel] `key:"nestedModelOptionalField"` + SliceNestedModelField marshaller.Node[[]TestNestedModel] `key:"sliceNestedModelField"` + MapRequiredNestedModelField marshaller.Node[*sequencedmap.Map[string, TestNestedModel]] `key:"mapRequiredNestedModelField" required:"true"` + Extensions Extensions `key:"extensions"` } type TestNestedModel struct { - CoreModel + marshaller.CoreModel - PrimitiveOptionalField Node[*string] `key:"primitiveOptionalField"` - SlicePrimitiveField Node[[]string] `key:"slicePrimitiveField"` - SliceRequiredPrimitiveField Node[[]string] `key:"sliceRequiredPrimitiveField" required:"true"` - MapPrimitiveField Node[*sequencedmap.Map[string, int]] `key:"mapPrimitiveField"` - Extensions Extensions `key:"extensions"` + PrimitiveOptionalField marshaller.Node[*string] `key:"primitiveOptionalField"` + SlicePrimitiveField marshaller.Node[[]string] `key:"slicePrimitiveField"` + SliceRequiredPrimitiveField marshaller.Node[[]string] `key:"sliceRequiredPrimitiveField" required:"true"` + MapPrimitiveField marshaller.Node[*sequencedmap.Map[string, int]] `key:"mapPrimitiveField"` + Extensions Extensions `key:"extensions"` } func (t *TestNestedModel) Unmarshal(ctx context.Context, node *yaml.Node) error { - return UnmarshalModel(ctx, node, t) + return marshaller.UnmarshalModel(ctx, node, t) } func Test_UnmarshalModel_Success(t *testing.T) { @@ -72,7 +73,7 @@ x-test-2: some-value-2 require.NoError(t, err) var out TestCoreModel - err = Unmarshal(context.Background(), &node, &out) + err = marshaller.Unmarshal(context.Background(), &node, &out) require.NoError(t, err) assertNodeField(t, "primitiveField", 1, "hello world", 1, out.PrimitiveField) @@ -83,7 +84,7 @@ x-test-2: some-value-2 assertNodeField(t, "sliceRequiredPrimitiveField", 5, []string{"I", "am", "here"}, 5, out.NestedModelField.Value.SliceRequiredPrimitiveField) assertNodeField(t, "mapPrimitiveField", 6, sequencedmap.New(sequencedmap.NewElem("a", 1), sequencedmap.NewElem("b", 2)), 7, out.NestedModelField.Value.MapPrimitiveField) xTestExtensionNodeNestedModelField := testutils.CreateStringYamlNode("some-value", 9, 11) - assert.Equal(t, sequencedmap.New(sequencedmap.NewElem("x-test", Node[Extension]{ + assert.Equal(t, sequencedmap.New(sequencedmap.NewElem("x-test", marshaller.Node[marshaller.Extension]{ Key: "x-test", KeyNode: testutils.CreateStringYamlNode("x-test", 9, 3), Value: xTestExtensionNodeNestedModelField, @@ -104,7 +105,7 @@ x-test-2: some-value-2 assertNodeField(t, "slicePrimitiveField", 20, []string{"p", "q", "r"}, 20, out.MapRequiredNestedModelField.Value.GetOrZero("z").SlicePrimitiveField) assertNodeField(t, "sliceRequiredPrimitiveField", 21, []string{"s", "t", "u"}, 21, out.MapRequiredNestedModelField.Value.GetOrZero("z").SliceRequiredPrimitiveField) xTestExtensionNodeMapRequiredNestedModelField := testutils.CreateStringYamlNode("some-value", 22, 13) - assert.Equal(t, sequencedmap.New(sequencedmap.NewElem("x-test", Node[Extension]{ + assert.Equal(t, sequencedmap.New(sequencedmap.NewElem("x-test", marshaller.Node[marshaller.Extension]{ Key: "x-test", KeyNode: testutils.CreateStringYamlNode("x-test", 22, 5), Value: xTestExtensionNodeMapRequiredNestedModelField, @@ -114,7 +115,7 @@ x-test-2: some-value-2 assertNodeField(t, "sliceRequiredPrimitiveField", 25, []string{"1", "2", "3"}, 25, out.MapRequiredNestedModelField.Value.GetOrZero("x").SliceRequiredPrimitiveField) xTestExtensionNode := testutils.CreateStringYamlNode("some-value-2", 26, 11) - assert.Equal(t, sequencedmap.New(sequencedmap.NewElem("x-test-2", Node[Extension]{ + assert.Equal(t, sequencedmap.New(sequencedmap.NewElem("x-test-2", marshaller.Node[marshaller.Extension]{ Key: "x-test-2", KeyNode: testutils.CreateStringYamlNode("x-test-2", 26, 1), Value: xTestExtensionNode, @@ -133,7 +134,7 @@ func Test_UnmarshalModel_NotAMappingNode_Error(t *testing.T) { var out TestCoreModel - err = UnmarshalModel(context.Background(), node.Content[0], &out) + err = marshaller.UnmarshalModel(context.Background(), node.Content[0], &out) require.Error(t, err) assert.Equal(t, "expected a mapping node, got 8", err.Error()) } @@ -146,24 +147,24 @@ func Test_UnmarshalModel_NotAStruct_Error(t *testing.T) { var out map[string]string - err = UnmarshalModel(context.Background(), node.Content[0], &out) + err = marshaller.UnmarshalModel(context.Background(), node.Content[0], &out) require.Error(t, err) assert.Equal(t, "expected a struct, got map", err.Error()) var outNil any = nil - err = UnmarshalModel(context.Background(), node.Content[0], &outNil) + err = marshaller.UnmarshalModel(context.Background(), node.Content[0], &outNil) require.Error(t, err) assert.Equal(t, "expected a struct, got interface", err.Error()) } -func assertNodeField[T any](t *testing.T, expectedKey string, expectedKeyLine int, expectedValue any, expectedValueLine int, actual Node[T]) { +func assertNodeField[T any](t *testing.T, expectedKey string, expectedKeyLine int, expectedValue any, expectedValueLine int, actual marshaller.Node[T]) { assert.Equal(t, expectedKey, actual.Key) assert.Equal(t, expectedKeyLine, actual.KeyNode.Line) assert.Equal(t, expectedValue, actual.Value) assert.Equal(t, expectedValueLine, actual.ValueNode.Line) } -func assertModelNodeField[T any](t *testing.T, expectedKey string, expectedKeyLine int, expectedValueLine int, actual Node[T]) { +func assertModelNodeField[T any](t *testing.T, expectedKey string, expectedKeyLine int, expectedValueLine int, actual marshaller.Node[T]) { assert.Equal(t, expectedKey, actual.Key) assert.Equal(t, expectedKeyLine, actual.KeyNode.Line) assert.Equal(t, expectedValueLine, actual.ValueNode.Line) diff --git a/sequencedmap/map.go b/sequencedmap/map.go index 44c5a85..439ca39 100644 --- a/sequencedmap/map.go +++ b/sequencedmap/map.go @@ -10,6 +10,7 @@ import ( "reflect" "slices" + "github.com/speakeasy-api/openapi/marshaller" "github.com/speakeasy-api/openapi/yml" "gopkg.in/yaml.v3" ) @@ -299,6 +300,33 @@ func (m *Map[K, V]) NavigateWithKey(key string) (any, error) { return v, nil } +func (m *Map[K, V]) Unmarshal(ctx context.Context, node *yaml.Node) error { + m.Init() + + for i := 0; i < len(node.Content); i += 2 { + keyNode := node.Content[i] + valueNode := node.Content[i+1] + + key := keyNode.Value + + // Create a concrete value of the value type + valueType := m.GetValueType() + concreteValue := reflect.New(valueType).Interface() + + // Unmarshal into the concrete value + if err := marshaller.Unmarshal(ctx, valueNode, concreteValue); err != nil { + return err + } + + // Extract the value from the pointer and set it in the map + if err := m.SetUntyped(key, reflect.ValueOf(concreteValue).Elem().Interface()); err != nil { + return err + } + } + + return nil +} + // MarshalJSON returns the JSON representation of the map. func (m *Map[K, V]) MarshalJSON() ([]byte, error) { if m == nil { @@ -334,6 +362,54 @@ func (m *Map[K, V]) MarshalJSON() ([]byte, error) { return buf.Bytes(), nil } +func (m *Map[K, V]) Populate(source any) error { + if source == nil { + return nil + } + + type SequencedMap interface { + Init() + SetUntyped(key, value any) error + AllUntyped() iter.Seq2[any, any] + GetValueType() reflect.Type + } + + sourceValue := reflect.ValueOf(source) + + var sm SequencedMap + var ok bool + + // Handle both pointer and non-pointer cases + if sourceValue.Kind() == reflect.Ptr { + // Source is already a pointer + sm, ok = source.(SequencedMap) + } else if sourceValue.CanAddr() { + // Source is addressable, get a pointer to it + sm, ok = sourceValue.Addr().Interface().(SequencedMap) + } else { + // Source is neither a pointer nor addressable + return fmt.Errorf("expected source to be addressable or a pointer to SequencedMap, got %s", sourceValue.Type()) + } + + if !ok { + return fmt.Errorf("expected source to be SequencedMap, got %s", sourceValue.Type()) + } + + m.Init() + + for key, value := range sm.AllUntyped() { + targetValue := reflect.New(m.GetValueType()).Interface() + if err := marshaller.Populate(value, targetValue); err != nil { + return err + } + if err := m.SetUntyped(key, reflect.ValueOf(targetValue).Elem().Interface()); err != nil { + return err + } + } + + return nil +} + type mapGetter interface { AllUntyped() iter.Seq2[any, any] }