Skip to content

Not all @AfterMapping methods executed when using record #3805

@anno1985

Description

@anno1985

Expected behavior

When several @AfterMapping methods are applicable, they should effectively be chained by the mapper (cf. example).

Actual behavior

Mapper backs out of calling subsequent @AfterMapping methods when any returns non-null value.

Steps to reproduce the problem

import lombok.Data;
import lombok.With;
import org.junit.jupiter.api.Test;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;

import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * <pre>
 *     <org.mapstruct.version>1.6.3</org.mapstruct.version>
 *     <org.projectlombok.version>1.18.36</org.projectlombok.version>
 * </pre>
 */
public class FooTest {

    @Test
    void test() {
        Bar bar = new Bar();
        bar.setMsg("foo");
        Foo foo = Mappers.getMapper(FooTest.FooMapper.class).toFoo(bar);
        assertTrue(foo.msg().contains("foo"), "foo missing");
        assertTrue(foo.msg().contains("bar"), "bar missing");
        assertTrue(foo.msg().contains("baz"), "baz missing");
    }

    @Mapper
    interface FooMapper {

        /**
         * <blockquote cite="https://mapstruct.org/documentation/stable/reference/html/#customizing-mappings-with-before-and-after">All before/after-mapping methods that can be applied to a mapping method will be used.</blockquote>
         * Actual: Generated code does not call all @AfterMapping methods if any returns non-null value:
         * <pre>
         *         FooTest.Foo foo1 = new FooTest.Foo( foo );
         *
         *         FooTest.Foo target = addBar( foo1, bar );
         *         if ( target != null ) {
         *             return target;
         *         }
         *         FooTest.Foo target1 = addBaz( foo1, bar );
         *         if ( target1 != null ) {
         *             return target1;
         *         }
         *
         *         return foo1;
         * </pre>
         * Expected: method calls are chained (order of calls not guaranteed):
         * <pre>
         *         FooTest.Foo foo1 = new FooTest.Foo( foo );
         *
         *         FooTest.Foo target = addBar( foo1, bar );
         *         if ( target != null ) {
         *             foo1 = target;
         *         }
         *         FooTest.Foo target1 = addBaz( foo1, bar );
         *         if ( target1 != null ) {
         *             foo1 = target1;
         *         }
         *
         *         return foo1;
         * </pre>
         */
        Foo toFoo(Bar bar);

        @AfterMapping
        default Foo addBar(@MappingTarget Foo foo, Bar bar) {
            // assume bar is used to calculate the new message
            return foo.withMsg(foo.msg() + "bar");
        }

        @AfterMapping
        default Foo addBaz(@MappingTarget Foo foo, Bar bar) {
            // assume bar is used to caculate the new message
            return foo.withMsg(foo.msg() + "baz");
        }

    }

    record Foo(@With String msg) {
    }

    @Data
    static class Bar {
        private String msg;
    }
}

MapStruct Version

1.6.3

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions