From ca8f47274114328dd3e3a93342328ce2769f1fe3 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 27 Mar 2024 06:45:13 +0100 Subject: [PATCH] Support renaming constructor arguments and choosing a different constructor by mixins (#804) --- .../serde/jackson/JsonPropertySpec.groovy | 115 ++++++++++++++++ .../serde/jackson/SerdeImportSpec.groovy | 54 ++++++++ .../annotation/SerdeJsonPropertySpec.groovy | 25 ---- .../serdeimport/JodaDateTimeSerde.java | 47 +++++++ .../ResponseElementsEntitySerde.java | 40 ++++++ .../serdeimport/S3EventNotificationSerde.java | 41 ++++++ .../S3EventNotificationSpec.groovy | 128 ++++++++++++++++++ .../jackson/serdeimport/S3EventSerde.java | 39 ++++++ .../serdeimport/S3ObjectEntitySerde.java | 58 ++++++++ .../src/test/resources/s3-event.json | 39 ++++++ serde-jackson/src/test/resources/s3-put.json | 38 ++++++ .../processor/SerdeAnnotationVisitor.java | 25 +++- 12 files changed, 621 insertions(+), 28 deletions(-) create mode 100644 serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/JodaDateTimeSerde.java create mode 100644 serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/ResponseElementsEntitySerde.java create mode 100644 serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventNotificationSerde.java create mode 100644 serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventNotificationSpec.groovy create mode 100644 serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventSerde.java create mode 100644 serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3ObjectEntitySerde.java create mode 100644 serde-jackson/src/test/resources/s3-event.json create mode 100644 serde-jackson/src/test/resources/s3-put.json diff --git a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonPropertySpec.groovy b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonPropertySpec.groovy index abffee9f6..be1177931 100644 --- a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonPropertySpec.groovy +++ b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonPropertySpec.groovy @@ -5,6 +5,121 @@ import spock.lang.Unroll class JsonPropertySpec extends JsonCompileSpec { + void "simple JsonCreator"() { + given: + def context = buildContext('example.Test', ''' +package example; + +import com.fasterxml.jackson.annotation.*; +@io.micronaut.serde.annotation.Serdeable +class Test { + public final String foo; + + @JsonCreator + public Test(@JsonProperty("foo") String foo) { + this.foo = foo; + } +} + +''') + def deserialized = jsonMapper.readValue('{"foo": "42"}', typeUnderTest) + + expect: + deserialized.foo == "42" + + cleanup: + context.close() + } + + void "static JsonCreator"() { + given: + def context = buildContext('example.Test', ''' +package example; + +import com.fasterxml.jackson.annotation.*; +@io.micronaut.serde.annotation.Serdeable +class Test { + public final String foo; + + private Test(@JsonProperty("foo") String foo) { + this.foo = foo; + } + + @JsonCreator + public static Test creator(@JsonProperty("foo") String foo) { + return new Test(foo + "xyz"); + } +} +''') + def deserialized = jsonMapper.readValue('{"foo": "42"}', typeUnderTest) + + expect: + deserialized.foo == "42xyz" + + cleanup: + context.close() + } + + void "static JsonCreator on interface"() { + given: + def context = buildContext('example.Test', ''' +package example; + +import com.fasterxml.jackson.annotation.*; +@io.micronaut.serde.annotation.Serdeable +interface Test { + + String getFoo(); + + @JsonCreator + static Test creator(@JsonProperty("foo") String foo) { + return (example.Test) () -> foo + "abc"; + } + +} +''') + def deserialized = jsonMapper.readValue('{"foo": "42"}', typeUnderTest) + + expect: + deserialized.foo == "42abc" + + cleanup: + context.close() + } + + void "static JsonCreator(mode = JsonCreator.Mode.DELEGATING) on interface"() { + given: + def context = buildContext('example.Test', ''' +package example; + +import com.fasterxml.jackson.annotation.*; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +interface Test { + + String getFoo(); + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + static Test creator(Data data) { + return (example.Test) () -> data.foo() + "abc"; + } + +} + +@Serdeable +record Data(String foo) { +} +''') + def deserialized = jsonMapper.readValue('{"foo": "42"}', typeUnderTest) + + expect: + deserialized.foo == "42abc" + + cleanup: + context.close() + } + void "test @JsonProperty.Access.WRITE_ONLY (set only) - records"() { given: def context = buildContext(""" diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/SerdeImportSpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/SerdeImportSpec.groovy index fb81ab3f8..3e3f86c19 100644 --- a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/SerdeImportSpec.groovy +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/SerdeImportSpec.groovy @@ -108,6 +108,60 @@ abstract class TestMixin { context.close() } + void "test mixin constructor with parameter renamed"() { + def context = buildContext('mixintest.Test',''' +package mixintest; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.SerdeImport; +import io.micronaut.http.HttpStatus; +import io.micronaut.serde.annotation.Serdeable; + +import java.util.List; +@SerdeImport( + value = Test.class, + mixin = TestMixin.class +) +class TestImport {} + +public class Test { + private int code; + Test(int code) { + this.code = code; + } + public int getCode() { + return code; + } +} + +abstract class TestMixin { + + @JsonCreator + TestMixin(@JsonProperty("customWrite") int code) { + } + + @JsonProperty("customRead") + abstract int getCode(); + +} + +''') + def impl = argumentOf(context, 'mixintest.Test') + def bean = impl.type.newInstance(200) + + expect: + writeJson(jsonMapper, bean) == '{"customRead":200}' + + def read = jsonMapper.readValue('{"customWrite":200}', typeUnderTest) + read.getCode() == 200 + + cleanup: + context.close() + } + void "test import with interface"() { def context = buildContext('mixintest.HttpStatusInfo',''' package mixintest; diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonPropertySpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonPropertySpec.groovy index 70f282415..8c96f97c0 100644 --- a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonPropertySpec.groovy +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonPropertySpec.groovy @@ -169,31 +169,6 @@ class Test { context.close() } - void "JsonCreator with single parameter of same name"() { - given: - def context = buildContext('example.Test', ''' -package example; - -import com.fasterxml.jackson.annotation.*; -@io.micronaut.serde.annotation.Serdeable -class Test { - public final String foo; - - @JsonCreator - public Test(String foo) { - this.foo = foo; - } -} -''') - def deserialized = jsonMapper.readValue('{"foo": "42"}', typeUnderTest) - - expect: - deserialized.foo == "42" - - cleanup: - context.close() - } - @PendingFeature(reason = 'single-parameter json creator. Dont think we should support this, can be done with delegating mode for JsonCreator') void "JsonCreator with single parameter of different name"() { given: diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/JodaDateTimeSerde.java b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/JodaDateTimeSerde.java new file mode 100644 index 000000000..d199cd26f --- /dev/null +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/JodaDateTimeSerde.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017-2021 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.serde.jackson.serdeimport; + +import com.amazonaws.services.lambda.runtime.serialization.util.SerializeUtil; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.type.Argument; +import io.micronaut.serde.Decoder; +import io.micronaut.serde.Encoder; +import io.micronaut.serde.util.NullableSerde; +import jakarta.inject.Singleton; +import org.joda.time.DateTime; + +import java.io.IOException; + +@Singleton +@Requires(classes = DateTime.class) +public class JodaDateTimeSerde implements NullableSerde { + + @Override + public void serialize(@NonNull Encoder encoder, + @NonNull EncoderContext context, + @NonNull Argument type, + @NonNull DateTime value) throws IOException { + encoder.encodeString(SerializeUtil.serializeDateTime(value, getClass().getClassLoader())); + } + + @Override + @NonNull + public DateTime deserializeNonNull(Decoder decoder, DecoderContext decoderContext, Argument type) throws IOException { + return SerializeUtil.deserializeDateTime(DateTime.class, decoder.decodeString()); + } +} diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/ResponseElementsEntitySerde.java b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/ResponseElementsEntitySerde.java new file mode 100644 index 000000000..28ea23f49 --- /dev/null +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/ResponseElementsEntitySerde.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.serde.jackson.serdeimport; + +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonCreator; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonGetter; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonProperty; +import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; +import io.micronaut.serde.annotation.SerdeImport; + +@SerdeImport(value = S3EventNotification.ResponseElementsEntity.class, mixin = ResponseElementsEntitySerde.ResponseElementsEntityMixin.class) +public class ResponseElementsEntitySerde { + private static final String X_AMZ_ID_2 = "x-amz-id-2"; + private static final String X_AMZ_REQUEST_ID = "x-amz-request-id"; + + static abstract class ResponseElementsEntityMixin { + @JsonGetter(X_AMZ_ID_2) + abstract String getxAmzId2(); + + @JsonGetter(X_AMZ_REQUEST_ID) + abstract String getxAmzRequestId(); + + @JsonCreator + ResponseElementsEntityMixin(@JsonProperty(X_AMZ_ID_2) String xAmzId2, @JsonProperty(X_AMZ_REQUEST_ID) String xAmzRequestId) { + } + } +} diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventNotificationSerde.java b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventNotificationSerde.java new file mode 100644 index 000000000..08f1b5269 --- /dev/null +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventNotificationSerde.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.serde.jackson.serdeimport; + +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonProperty; +import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; +import io.micronaut.serde.annotation.SerdeImport; + +import java.util.List; + +@SerdeImport(value = S3EventNotification.class, mixin = S3EventNotificationSerde.S3EventNotificationMixin.class) +@SerdeImport(value = S3EventNotification.S3Entity.class) +@SerdeImport(value = S3EventNotification.RequestParametersEntity.class) +@SerdeImport(value = S3EventNotification.S3BucketEntity.class) +@SerdeImport(value = S3EventNotification.UserIdentityEntity.class) +@SerdeImport(value = S3EventNotification.S3EventNotificationRecord.class) +public class S3EventNotificationSerde { + /** + * Records Mixin. + */ + public interface S3EventNotificationMixin { + /** + * @return Records. + */ + @JsonProperty("Records") + List getRecords(); + } +} diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventNotificationSpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventNotificationSpec.groovy new file mode 100644 index 000000000..71e457c49 --- /dev/null +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventNotificationSpec.groovy @@ -0,0 +1,128 @@ +package io.micronaut.serde.jackson.serdeimport + +import com.amazonaws.services.lambda.runtime.events.S3Event +import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification +import io.micronaut.json.JsonMapper +import spock.lang.Specification + +class S3EventNotificationSpec extends Specification { + + JsonMapper objectMapper = JsonMapper.createDefault() + + void "S3EventNotification can be serialized with s3-put"() { + given: + File f = new File("src/test/resources/s3-put.json") + + expect: + f.exists() + + when: + String json = f.text + S3EventNotification event = getObjectMapper().readValue(json, S3EventNotification) + + then: + assertionsS3Put(event) + + when: + json = getObjectMapper().writeValueAsString(event) + + then: + json.contains("\"x-amz-id-2\":\"EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH\"") + json.contains("\"x-amz-request-id\":\"EXAMPLE123456789\"") + } + + void "S3EventNotification can be serialized with s3-event"() { + given: + File f = new File("src/test/resources/s3-event.json") + + expect: + f.exists() + + when: + String json = f.text + S3EventNotification event = getObjectMapper().readValue(json, S3EventNotification) + + then: + assertionsS3Event(event) + } + + void "S3Event can be deserialized with s3-put"() { + given: + File f = new File("src/test/resources/s3-put.json") + + expect: + f.exists() + + when: + String json = f.text + S3Event event = getObjectMapper().readValue(json, S3Event) + + then: + assertionsS3Put(event) + } + + void "S3Event can be serialized with s3-event"() { + given: + File f = new File("src/test/resources/s3-event.json") + + expect: + f.exists() + + when: + String json = f.text + S3Event event = getObjectMapper().readValue(json, S3Event) + + then: + assertionsS3Event(event) + } + + + void assertionsS3Event(S3EventNotification event) { + assert event + assert event.records + assert "2.0" == event.records[0].eventVersion + assert "aws:s3" == event.records[0].eventSource + assert "us-east-1" == event.records[0].awsRegion + assert "1970-01-01T00:00:00.123Z" == event.records[0].eventTime.toString() + assert "ObjectCreated:Put" == event.records[0].eventName + assert "EXAMPLE" == event.records[0].userIdentity.principalId + assert "127.0.0.1" == event.records[0].requestParameters.sourceIPAddress + assert "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" == event.records[0].responseElements.xAmzId2 + assert "C3D13FE58DE4C810" == event.records[0].responseElements.xAmzRequestId + assert "1.0" == event.records[0].s3.s3SchemaVersion + assert "testConfigRule" == event.records[0].s3.configurationId + assert "sourcebucket" == event.records[0].s3.bucket.name + assert "EXAMPLE" == event.records[0].s3.bucket.ownerIdentity.principalId + assert "arn:aws:s3:::mybucket" == event.records[0].s3.bucket.arn + assert "Happy%20Face.jpg" == event.records[0].s3.object.key + assert 1024 == event.records[0].s3.object.size + assert "version" == event.records[0].s3.object.versionId + assert "d41d8cd98f00b204e9800998ecf8427e" == event.records[0].s3.object.eTag + assert "Happy Sequencer" == event.records[0].s3.object.sequencer + } + + void assertionsS3Put(S3EventNotification event) { + assert event + assert event.records != null + assert event.records.size() > 0 + assert "2.0" == event.records[0].eventVersion + assert "aws:s3" == event.records[0].eventSource + assert "us-east-1" == event.records[0].awsRegion + assert "1970-01-01T00:00:00.000Z" == event.records[0].eventTime.toString() + assert "ObjectCreated:Put" == event.records[0].eventName + assert "EXAMPLE" == event.records[0].userIdentity.principalId + assert "127.0.0.1" == event.records[0].requestParameters.sourceIPAddress + assert "EXAMPLE123456789" == event.records[0].responseElements.xAmzRequestId + assert "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" == event.records[0].responseElements.xAmzId2 + assert "1.0" == event.records[0].s3.s3SchemaVersion + assert "testConfigRule" == event.records[0].s3.configurationId + assert "example-bucket" == event.records[0].s3.bucket.name + assert "EXAMPLE" == event.records[0].s3.bucket.ownerIdentity.principalId + assert "arn:aws:s3:::example-bucket" == event.records[0].s3.bucket.arn + assert "test%2Fkey" == event.records[0].s3.object.key + assert 1024 == event.records[0].s3.object.size + assert "0123456789abcdef0123456789abcdef" == event.records[0].s3.object.eTag + assert "0A1B2C3D4E5F678901" == event.records[0].s3.object.sequencer + } + +} diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventSerde.java b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventSerde.java new file mode 100644 index 000000000..c32d977ee --- /dev/null +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3EventSerde.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.serde.jackson.serdeimport; + +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonCreator; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonGetter; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonProperty; +import com.amazonaws.services.lambda.runtime.events.S3Event; +import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; +import io.micronaut.serde.annotation.SerdeImport; + +import java.util.List; + +@SerdeImport(value = S3Event.class, mixin = S3EventSerde.S3EventSerdeMixin.class) +public class S3EventSerde { + private static final String RECORDS = "Records"; + + public static abstract class S3EventSerdeMixin { + @JsonGetter(RECORDS) + public abstract List getRecords(); + + @JsonCreator + public S3EventSerdeMixin(@JsonProperty(RECORDS) List records) { + } + } +} diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3ObjectEntitySerde.java b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3ObjectEntitySerde.java new file mode 100644 index 000000000..02c65a9bf --- /dev/null +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/serdeimport/S3ObjectEntitySerde.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.serde.jackson.serdeimport; + +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonCreator; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonGetter; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.annotation.JsonProperty; +import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; +import io.micronaut.serde.annotation.SerdeImport; + +@SerdeImport(value = S3EventNotification.S3ObjectEntity.class, mixin = S3ObjectEntitySerde.S3ObjectEntityMixin.class) +public class S3ObjectEntitySerde { + private static final String KEY = "key"; + private static final String SIZE = "size"; + private static final String ETAG = "eTag"; + private static final String VERSION_ID = "versionId"; + private static final String SEQUENCER = "sequencer"; + + static abstract class S3ObjectEntityMixin { + @JsonGetter(KEY) + abstract String getKey(); + + @JsonGetter(SIZE) + abstract Long getSizeAsLong(); + + @JsonGetter(ETAG) + abstract String geteTag(); + + @JsonGetter(VERSION_ID) + abstract String getVersionId(); + + @JsonGetter(SEQUENCER) + abstract String getSequencer(); + + @JsonCreator + S3ObjectEntityMixin( + @JsonProperty(KEY) String key, + @JsonProperty(SIZE) Long size, + @JsonProperty(ETAG) String eTag, + @JsonProperty(VERSION_ID) String versionId, + @JsonProperty(SEQUENCER) String sequencer + ) { + } + } +} diff --git a/serde-jackson/src/test/resources/s3-event.json b/serde-jackson/src/test/resources/s3-event.json new file mode 100644 index 000000000..fab197ec7 --- /dev/null +++ b/serde-jackson/src/test/resources/s3-event.json @@ -0,0 +1,39 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "1970-01-01T00:00:00.123Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "C3D13FE58DE4C810", + "x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "sourcebucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::mybucket" + }, + "object": { + "key": "Happy%20Face.jpg", + "size": 1024, + "versionId": "version", + "eTag": "d41d8cd98f00b204e9800998ecf8427e", + "sequencer": "Happy Sequencer" + } + } + } + ] +} diff --git a/serde-jackson/src/test/resources/s3-put.json b/serde-jackson/src/test/resources/s3-put.json new file mode 100644 index 000000000..188bb7ca0 --- /dev/null +++ b/serde-jackson/src/test/resources/s3-put.json @@ -0,0 +1,38 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "1970-01-01T00:00:00.000Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "EXAMPLE123456789", + "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "example-bucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::example-bucket" + }, + "object": { + "key": "test%2Fkey", + "size": 1024, + "eTag": "0123456789abcdef0123456789abcdef", + "sequencer": "0A1B2C3D4E5F678901" + } + } + } + ] +} \ No newline at end of file diff --git a/serde-processor/src/main/java/io/micronaut/serde/processor/SerdeAnnotationVisitor.java b/serde-processor/src/main/java/io/micronaut/serde/processor/SerdeAnnotationVisitor.java index 70d2bafcf..61f25f3f8 100644 --- a/serde-processor/src/main/java/io/micronaut/serde/processor/SerdeAnnotationVisitor.java +++ b/serde-processor/src/main/java/io/micronaut/serde/processor/SerdeAnnotationVisitor.java @@ -842,9 +842,28 @@ private void visitMixin(ClassElement mixinType, ClassElement type) { )); final MethodElement mixinCtor = mixinType.getPrimaryConstructor().orElse(null); - final MethodElement targetCtor = type.getPrimaryConstructor().orElse(null); - if (mixinCtor != null && targetCtor != null && argumentsMatch(mixinCtor, targetCtor)) { - replicateAnnotations(mixinCtor, targetCtor); + MethodElement targetCtor = type.getPrimaryConstructor().orElse(null); + if (mixinCtor != null && targetCtor != null) { + if (!argumentsMatch(mixinCtor, targetCtor)) { + // The mixin constructor and the primary constructor doesn't match, + // lets try to find a matching one and mark it as a primary + MethodElement prevCtor = targetCtor; + targetCtor = type.getAccessibleConstructors().stream().filter(c -> argumentsMatch(mixinCtor, c)).findFirst().orElse(null); + if (targetCtor != null) { + targetCtor.annotate(Creator.class); + prevCtor.removeAnnotation(Creator.class); + } + } + if (targetCtor != null) { + replicateAnnotations(mixinCtor, targetCtor); + ParameterElement[] mixinCtorParameters = mixinCtor.getParameters(); + ParameterElement[] targetCtorParameters = targetCtor.getParameters(); + for (int i = 0; i < mixinCtorParameters.length; i++) { + ParameterElement mixinCtorParameter = mixinCtorParameters[i]; + ParameterElement targetCtorParameter = targetCtorParameters[i]; + replicateAnnotations(mixinCtorParameter, targetCtorParameter); + } + } } final List serdeMethods = mixinType.isRecord() ? Collections.emptyList() : new ArrayList<>(mixinType.getEnclosedElements(