Skip to content

Commit 80fc834

Browse files
committed
Added proto-to-proto mapping support
1 parent d22d4d6 commit 80fc834

File tree

17 files changed

+593
-85
lines changed

17 files changed

+593
-85
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*.ear
55
*.class
66
.DS_Store
7+
.java-version
78

89
# eclipse specific git ignore
910
.project

README.md

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# Mapstruct SPI implementation for protocol buffers mapping
22

33
This project provides a SPI implementation for [Mapstruct](http://mapstruct.org/) to generate mapping code from protocol
4-
buffers to the following targets:
4+
buffer messages (in the form of protobuf-java objects) to the following targets:
55

66
- Plain Old Java Objects (POJOs)
77
- [Immutables](https://immutables.github.io/) value objects
88
- Java records
9+
- other protobuf messages in the form of other protobuf-java objects
910

10-
Unit tests exist to validate all of these mappings. The SPI implementation generally requires Mapstruct 1.5.5 and Java
11-
1.8+ (of course if you want to map to records, Java 14+ is required).
11+
Unit tests exist to validate all of these mappings. The SPI implementation requires **Mapstruct 1.6.0**
12+
and Java 1.8+ (of course if you want to map to records, Java 14+ is required).
1213

1314
The enum mapping strategy assumes that Google's enum value naming scheme is used, as described
1415
here: https://developers.google.com/protocol-buffers/docs/style#enum
@@ -51,7 +52,7 @@ Include the mapstruct dependency and the annotation processor in your Maven proj
5152
<path>
5253
<groupId>de.firehead</groupId>
5354
<artifactId>mapstruct-spi-protobuf</artifactId>
54-
<version>1.0.0</version>
55+
<version>1.1.0</version>
5556
</path>
5657
</annotationProcessorPaths>
5758
</configuration>
@@ -67,7 +68,49 @@ Or for Gradle:
6768

6869
implementation"org.mapstruct:mapstruct:${mapstructVersion}"
6970
annotationProcessor"org.mapstruct:mapstruct-processor:${mapstructVersion}"
70-
annotationProcessor"de.firehead:mapstruct-spi-protobuf:1.0.0"
71+
annotationProcessor"de.firehead:mapstruct-spi-protobuf:1.1.0"
7172

7273
```
7374

75+
## Protobuf-to-Protobuf mapping
76+
77+
**There is an important caveat with regard to Protobuf-to-Protobuf mapping when it comes to enumerations!**
78+
79+
Protobuf enums have two "hard-coded" values: `UNRECOGNIZED` and `_UNSPECIFIED`, with the latter typically prefixed by
80+
the enum name in snake-case. These are used to handle unknown enum values and the default "unspecified" value,
81+
respectively. By default, this SPI implementation will map both of these to `null` in the target enum, which is usually
82+
fine if you have a "normal" Java enum on the other side, as that will not have a value like `UNRECOGNIZED`. However, if
83+
you are mapping to another Protobuf enum, then there is an `UNRECOGNIZED` value that the source value can be mapped to,
84+
and due to the SPI implementation mapping both of these values to `null`, the mapping of these special values won't work
85+
as only one of them will map to the correct other value.
86+
87+
Due to limitations of the SPI interface, the implementation cannot discern whether the mapping target is a Protobuf enum
88+
or a Java enum, so it cannot automatically take this special case into account. However, there is an SPI-specific
89+
configuration option available to disable the automatic mapping of `UNRECOGNIZED` to `null`:
90+
`protobuf.enum.mapUnrecognizedToNull`.
91+
92+
If you are mapping to another Protobuf enum, you should set this option to `false`, which works just like any of
93+
Mapstructs' own configuration options by adding a compiler parameter. In Maven, this would look for example like this:
94+
95+
```xml
96+
97+
<plugin>
98+
<groupId>org.apache.maven.plugins</groupId>
99+
<artifactId>maven-compiler-plugin</artifactId>
100+
<configuration>
101+
<annotationProcessorPaths>
102+
<path>
103+
<groupId>de.firehead</groupId>
104+
<artifactId>mapstruct-spi-protobuf</artifactId>
105+
<version>1.1.0</version>
106+
</path>
107+
</annotationProcessorPaths>
108+
<compilerArgs>
109+
<arg>-Aprotobuf.enum.mapUnrecognizedToNull=false</arg>
110+
</compilerArgs>
111+
</configuration>
112+
</plugin>
113+
```
114+
115+
If you need an example, see the mapstruct-spi-protobuf-test-proto2proto test module where this particular situation is
116+
unit-tested!

mapstruct-spi-protobuf-test-immutables/pom.xml

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
<parent>
77
<groupId>de.firehead</groupId>
88
<artifactId>mapstruct-spi-protobuf-parent</artifactId>
9-
<version>1.0.2-SNAPSHOT</version>
9+
<version>1.1.0-SNAPSHOT</version>
1010
</parent>
1111

12+
<name>Mapstruct SPI for Protobuf Mapping [Test: Protos to Immutables]</name>
1213
<artifactId>mapstruct-spi-protobuf-test-immutables</artifactId>
1314

1415
<properties>
@@ -56,30 +57,6 @@
5657
</extensions>
5758

5859
<plugins>
59-
<!-- Generate protobuf Java classes during compile phase -->
60-
<plugin>
61-
<groupId>dev.cookiecode</groupId>
62-
<artifactId>another-protobuf-maven-plugin</artifactId>
63-
<version>2.1.0</version>
64-
<executions>
65-
<execution>
66-
<goals>
67-
<goal>compile</goal>
68-
<goal>compile-custom</goal>
69-
</goals>
70-
<phase>generate-sources</phase>
71-
<configuration>
72-
<protocArtifact>
73-
com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
74-
</protocArtifact>
75-
<pluginId>grpc-java</pluginId>
76-
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
77-
</pluginArtifact>
78-
</configuration>
79-
</execution>
80-
</executions>
81-
</plugin>
82-
8360
<plugin>
8461
<groupId>org.apache.maven.plugins</groupId>
8562
<artifactId>maven-compiler-plugin</artifactId>

mapstruct-spi-protobuf-test-pojo/pom.xml

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
<parent>
77
<groupId>de.firehead</groupId>
88
<artifactId>mapstruct-spi-protobuf-parent</artifactId>
9-
<version>1.0.2-SNAPSHOT</version>
9+
<version>1.1.0-SNAPSHOT</version>
1010
</parent>
1111

12+
<name>Mapstruct SPI for Protobuf Mapping [Test: Protos to POJOs]</name>
1213
<artifactId>mapstruct-spi-protobuf-test-pojo</artifactId>
1314

1415
<properties>
@@ -51,30 +52,6 @@
5152
</extensions>
5253

5354
<plugins>
54-
<!-- Generate protobuf Java classes during compile phase -->
55-
<plugin>
56-
<groupId>dev.cookiecode</groupId>
57-
<artifactId>another-protobuf-maven-plugin</artifactId>
58-
<version>2.1.0</version>
59-
<executions>
60-
<execution>
61-
<goals>
62-
<goal>compile</goal>
63-
<goal>compile-custom</goal>
64-
</goals>
65-
<phase>generate-sources</phase>
66-
<configuration>
67-
<protocArtifact>
68-
com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
69-
</protocArtifact>
70-
<pluginId>grpc-java</pluginId>
71-
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
72-
</pluginArtifact>
73-
</configuration>
74-
</execution>
75-
</executions>
76-
</plugin>
77-
7855
<plugin>
7956
<groupId>org.apache.maven.plugins</groupId>
8057
<artifactId>maven-compiler-plugin</artifactId>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>de.firehead</groupId>
8+
<artifactId>mapstruct-spi-protobuf-parent</artifactId>
9+
<version>1.1.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<name>Mapstruct SPI for Protobuf Mapping [Test: Protos to Protos]</name>
13+
<artifactId>mapstruct-spi-protobuf-test-proto2proto</artifactId>
14+
15+
<properties>
16+
<maven.compiler.source>1.8</maven.compiler.source>
17+
<maven.compiler.target>1.8</maven.compiler.target>
18+
<maven.javadoc.skip>true</maven.javadoc.skip>
19+
<maven.deploy.skip>true</maven.deploy.skip>
20+
</properties>
21+
22+
<dependencies>
23+
<dependency>
24+
<groupId>de.firehead</groupId>
25+
<artifactId>mapstruct-spi-protobuf-test-protos</artifactId>
26+
</dependency>
27+
28+
<dependency>
29+
<groupId>org.mapstruct</groupId>
30+
<artifactId>mapstruct</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>com.google.protobuf</groupId>
34+
<artifactId>protobuf-java</artifactId>
35+
</dependency>
36+
37+
<!-- Test dependencies -->
38+
<dependency>
39+
<groupId>org.junit.jupiter</groupId>
40+
<artifactId>junit-jupiter-engine</artifactId>
41+
<scope>test</scope>
42+
</dependency>
43+
</dependencies>
44+
45+
<build>
46+
<extensions>
47+
<extension>
48+
<groupId>kr.motd.maven</groupId>
49+
<artifactId>os-maven-plugin</artifactId>
50+
<version>1.7.1</version>
51+
</extension>
52+
</extensions>
53+
54+
<plugins>
55+
<!-- Generate protobuf Java classes during compile phase -->
56+
<plugin>
57+
<groupId>dev.cookiecode</groupId>
58+
<artifactId>another-protobuf-maven-plugin</artifactId>
59+
</plugin>
60+
61+
<plugin>
62+
<groupId>org.apache.maven.plugins</groupId>
63+
<artifactId>maven-compiler-plugin</artifactId>
64+
<configuration>
65+
<source>1.8</source>
66+
<target>1.8</target>
67+
<annotationProcessorPaths>
68+
<path>
69+
<groupId>org.immutables</groupId>
70+
<artifactId>value-processor</artifactId>
71+
<version>${immutables.version}</version>
72+
</path>
73+
<path>
74+
<groupId>de.firehead</groupId>
75+
<artifactId>mapstruct-spi-protobuf</artifactId>
76+
<version>${project.version}</version>
77+
</path>
78+
</annotationProcessorPaths>
79+
<compilerArgs>
80+
<arg>-Aprotobuf.enum.mapUnrecognizedToNull=false</arg>
81+
</compilerArgs>
82+
</configuration>
83+
</plugin>
84+
</plugins>
85+
</build>
86+
87+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* mapstruct-spi-protobuf
2+
*
3+
* Copyright (C) 2024
4+
*
5+
* This software may be modified and distributed under the terms
6+
* of the MIT license. See the LICENSE file for details.
7+
*/
8+
package de.firehead.mapstruct.spi.protobuf.test.proto2proto.mapper;
9+
10+
import com.google.protobuf.ByteString;
11+
import de.firehead.mapstruct.spi.protobuf.test.proto2proto.Proto2ProtoTestProtos;
12+
import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos;
13+
import org.mapstruct.CollectionMappingStrategy;
14+
import org.mapstruct.Mapper;
15+
import org.mapstruct.factory.Mappers;
16+
17+
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
18+
public abstract class TestMapper {
19+
20+
public static final TestMapper INSTANCE = Mappers.getMapper(TestMapper.class);
21+
22+
public abstract Proto2ProtoTestProtos.OtherTestProtoMessage mapTestProtoToOtherProto(TestProtos.TestProtoMessage testProtoMessage);
23+
24+
public abstract TestProtos.TestProtoMessage mapOtherProtoToProto(Proto2ProtoTestProtos.OtherTestProtoMessage testObject);
25+
26+
public abstract Proto2ProtoTestProtos.OtherDeepTestProtoMessage mapDeepTestProtoToOtherProto(TestProtos.DeepTestProtoMessage deepTestProtoMessage);
27+
28+
public abstract TestProtos.DeepTestProtoMessage mapDeepTestOtherProtoToProto(Proto2ProtoTestProtos.OtherDeepTestProtoMessage testImmutableObject);
29+
30+
public abstract TestProtos.TestEnum mapOtherEnumToEnum(Proto2ProtoTestProtos.OtherTestEnum otherTestEnum);
31+
32+
public abstract Proto2ProtoTestProtos.OtherTestEnum mapEnumToOtherEnum(TestProtos.TestEnum testEnum);
33+
34+
protected byte[] mapByteStringToByteArray(com.google.protobuf.ByteString byteString) {
35+
return byteString.toByteArray();
36+
}
37+
38+
protected ByteString mapByteArrayToByteString(byte[] byteArray) {
39+
return ByteString.copyFrom(byteArray);
40+
}
41+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
syntax = "proto3";
2+
3+
option java_package = "de.firehead.mapstruct.spi.protobuf.test.proto2proto";
4+
option java_outer_classname = "Proto2ProtoTestProtos";
5+
6+
message OtherTestProtoMessage {
7+
string stringField = 1;
8+
int32 intField = 2;
9+
int64 longField = 3;
10+
float floatField = 4;
11+
double doubleField = 5;
12+
bool booleanField = 6;
13+
bytes bytesField = 7;
14+
OtherTestEnum enumField = 8;
15+
map<string, string> stringMapField = 9;
16+
map<string, int32> intMapField = 10;
17+
map<string, int64> longMapField = 11;
18+
map<string, float> floatMapField = 12;
19+
map<string, double> doubleMapField = 13;
20+
map<string, bool> boolMapField = 14;
21+
map<string, bytes> bytesMapField = 15;
22+
map<string, OtherTestEnum> enumMapField = 16;
23+
}
24+
25+
message OtherDeepTestProtoMessage {
26+
OtherTestProtoMessage testProtoMessagePlain = 1;
27+
repeated OtherTestProtoMessage testProtoMessageList = 2;
28+
map<string, OtherTestProtoMessage> testProtoMessageMap = 3;
29+
}
30+
31+
enum OtherTestEnum {
32+
OTHER_TEST_ENUM_UNSPECIFIED = 0;
33+
OTHER_TEST_ENUM_VALUE = 1;
34+
}

0 commit comments

Comments
 (0)