diff --git a/gradle/projects.main.properties b/gradle/projects.main.properties
index 88fbcd0fa4..d2a7cf1947 100644
--- a/gradle/projects.main.properties
+++ b/gradle/projects.main.properties
@@ -36,6 +36,7 @@ aggregated-license-report=aggregated-license-report
polaris-immutables=tools/immutables
polaris-container-spec-helper=tools/container-spec-helper
polaris-version=tools/version
+polaris-misc-types=tools/misc-types
polaris-config-docs-annotations=tools/config-docs/annotations
polaris-config-docs-generator=tools/config-docs/generator
diff --git a/tools/misc-types/build.gradle.kts b/tools/misc-types/build.gradle.kts
new file mode 100644
index 0000000000..17203d74ea
--- /dev/null
+++ b/tools/misc-types/build.gradle.kts
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.
+ */
+
+plugins {
+ alias(libs.plugins.jandex)
+ id("polaris-client")
+}
+
+description =
+ "Misc types used in configurations and converters for microprofile-config & Jackson, exposes no runtime dependencies"
+
+dependencies {
+ compileOnly(libs.smallrye.config.core)
+ compileOnly(platform(libs.quarkus.bom))
+ compileOnly("io.quarkus:quarkus-core")
+
+ compileOnly(platform(libs.jackson.bom))
+ compileOnly("com.fasterxml.jackson.core:jackson-databind")
+
+ testImplementation(libs.smallrye.config.core)
+
+ testImplementation(platform(libs.jackson.bom))
+ testImplementation("com.fasterxml.jackson.core:jackson-databind")
+ testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")
+
+ testCompileOnly(project(":polaris-immutables"))
+ testAnnotationProcessor(project(":polaris-immutables", configuration = "processor"))
+}
diff --git a/tools/misc-types/src/main/java/org/apache/polaris/misc/types/memorysize/MemorySize.java b/tools/misc-types/src/main/java/org/apache/polaris/misc/types/memorysize/MemorySize.java
new file mode 100644
index 0000000000..7548498884
--- /dev/null
+++ b/tools/misc-types/src/main/java/org/apache/polaris/misc/types/memorysize/MemorySize.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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 org.apache.polaris.misc.types.memorysize;
+
+import static com.fasterxml.jackson.annotation.JsonFormat.*;
+import static java.lang.String.format;
+import static java.util.Locale.ROOT;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.Nonnull;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+import org.eclipse.microprofile.config.spi.Converter;
+
+/**
+ * Type representing a memory size in bytes, using 1024 as the multiplier for kilo, mega, etc.
+ *
+ *
String representations, for both {@link #valueOf(String) parsing} and {@link #toString()
+ * generating}, support memory size suffixes like {@code K} for "kilo", {@code M} for "mega".
+ *
+ *
(De)serialization support for Eclipse Microprofile Config / smallrye-config provided via a
+ * {@link Converter} implementation, let smallrye-config discover converters automatically (default
+ * in Quarkus).
+ *
+ *
(De)serialization support for Jackson provided via a Jackson module, provided via the Java
+ * service loader mechanism. Use {@link ObjectMapper#findAndRegisterModules()} for manually created
+ * object mappers.
+ *
+ *
Jackson serialization supports both {@link Shape#STRING string} (default) and {@link
+ * Shape#NUMBER integer} representations via {@link JsonFormat @JsonFormat}{@code (shape =
+ * JsonFormat.}{@link Shape Shape}{@code .NUMBER)}. Number/int serialization always represents the
+ * number of bytes.
+ *
+ *
Note that, although unlikely in practice, memory sizes may exceed {@link Long#MAX_VALUE} and
+ * calls to {@link #asLong()} the result in an {@link ArithmeticException}.
+ */
+public abstract class MemorySize {
+ private static final Pattern MEMORY_SIZE_PATTERN =
+ Pattern.compile("^(\\d+)([BbKkMmGgTtPpEeZzYy]?)$");
+ private static final BigInteger KILO_BYTES = BigInteger.valueOf(1024);
+ private static final Map MEMORY_SIZE_MULTIPLIERS;
+ private static final char[] SUFFIXES = new char[] {'B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};
+
+ static {
+ MEMORY_SIZE_MULTIPLIERS = new HashMap<>();
+ MEMORY_SIZE_MULTIPLIERS.put("K", KILO_BYTES);
+ MEMORY_SIZE_MULTIPLIERS.put("M", KILO_BYTES.pow(2));
+ MEMORY_SIZE_MULTIPLIERS.put("G", KILO_BYTES.pow(3));
+ MEMORY_SIZE_MULTIPLIERS.put("T", KILO_BYTES.pow(4));
+ MEMORY_SIZE_MULTIPLIERS.put("P", KILO_BYTES.pow(5));
+ MEMORY_SIZE_MULTIPLIERS.put("E", KILO_BYTES.pow(6));
+ MEMORY_SIZE_MULTIPLIERS.put("Z", KILO_BYTES.pow(7));
+ MEMORY_SIZE_MULTIPLIERS.put("Y", KILO_BYTES.pow(8));
+ }
+
+ static final class MemorySizeLong extends MemorySize {
+ private final long bytes;
+
+ MemorySizeLong(long bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public long asLong() {
+ return bytes;
+ }
+
+ @Nonnull
+ @Override
+ public BigInteger asBigInteger() {
+ return BigInteger.valueOf(bytes);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof MemorySize)) {
+ return false;
+ }
+
+ if (o instanceof MemorySizeLong) {
+ var l = (MemorySizeLong) o;
+ return bytes == l.bytes;
+ }
+
+ var that = (MemorySize) o;
+ return asBigInteger().equals(that.asBigInteger());
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(bytes);
+ }
+
+ @Override
+ public String toString() {
+ var mask = 1024 - 1;
+ var s = 0;
+ var v = bytes;
+
+ while (v > 0 && (v & mask) == 0L) {
+ v >>= 10;
+ s++;
+ }
+
+ return Long.toString(v) + SUFFIXES[s];
+ }
+ }
+
+ static final class MemorySizeBig extends MemorySize {
+ private final BigInteger bytes;
+
+ MemorySizeBig(@Nonnull BigInteger bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public long asLong() {
+ return bytes.longValueExact();
+ }
+
+ @Nonnull
+ @Override
+ public BigInteger asBigInteger() {
+ return bytes;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof MemorySize)) {
+ return false;
+ }
+
+ MemorySize that = (MemorySize) o;
+ return bytes.equals(that.asBigInteger());
+ }
+
+ @Override
+ public int hashCode() {
+ return bytes.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ var s = 0;
+ var v = bytes;
+
+ while (v.signum() > 0 && v.remainder(KILO_BYTES).signum() == 0) {
+ v = v.divide(KILO_BYTES);
+ s++;
+ }
+
+ return v.toString() + SUFFIXES[s];
+ }
+ }
+
+ public static MemorySize ofBytes(long bytes) {
+ return new MemorySizeLong(bytes);
+ }
+
+ public static MemorySize ofKilo(int kb) {
+ return new MemorySizeLong(1024L * kb);
+ }
+
+ public static MemorySize ofMega(int mb) {
+ return new MemorySizeLong(1024L * 1024L * mb);
+ }
+
+ public static MemorySize ofGiga(int gb) {
+ return new MemorySizeLong(1024L * 1024L * 1024L * gb);
+ }
+
+ /**
+ * Convert data size configuration value respecting the following format (shown in regular
+ * expression) "[0-9]+[BbKkMmGgTtPpEeZzYy]?" If the value contain no suffix, the size is treated
+ * as bytes.
+ *
+ * @param value - value to convert.
+ * @return {@link MemorySize} - a memory size represented by the given value
+ */
+ public static MemorySize valueOf(String value) {
+ value = value.trim();
+ if (value.isEmpty()) {
+ return null;
+ }
+ var matcher = MEMORY_SIZE_PATTERN.matcher(value);
+ if (matcher.find()) {
+ var number = new BigInteger(matcher.group(1));
+ var scale = matcher.group(2).toUpperCase(ROOT);
+ var multiplier = MEMORY_SIZE_MULTIPLIERS.get(scale);
+ if (multiplier != null) {
+ number = number.multiply(multiplier);
+ }
+ try {
+ return new MemorySizeLong(number.longValueExact());
+ } catch (ArithmeticException e) {
+ return new MemorySizeBig(number);
+ }
+ }
+
+ throw new IllegalArgumentException(
+ format(
+ "value %s not in correct format (regular expression): [0-9]+[BbKkMmGgTtPpEeZzYy]?",
+ value));
+ }
+
+ @Nonnull
+ public abstract BigInteger asBigInteger();
+
+ /**
+ * Memory size as a {@code long} value. May throw an {@link ArithmeticException} if the value is
+ * bigger than {@link Long#MAX_VALUE}.
+ */
+ public abstract long asLong();
+}
diff --git a/tools/misc-types/src/main/java/org/apache/polaris/misc/types/memorysize/MemorySizeConfigConverter.java b/tools/misc-types/src/main/java/org/apache/polaris/misc/types/memorysize/MemorySizeConfigConverter.java
new file mode 100644
index 0000000000..975214e9cb
--- /dev/null
+++ b/tools/misc-types/src/main/java/org/apache/polaris/misc/types/memorysize/MemorySizeConfigConverter.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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 org.apache.polaris.misc.types.memorysize;
+
+import org.eclipse.microprofile.config.spi.Converter;
+
+public class MemorySizeConfigConverter implements Converter {
+
+ @Override
+ public MemorySize convert(String value) {
+ return MemorySize.valueOf(value);
+ }
+}
diff --git a/tools/misc-types/src/main/java/org/apache/polaris/misc/types/memorysize/MemorySizeJackson.java b/tools/misc-types/src/main/java/org/apache/polaris/misc/types/memorysize/MemorySizeJackson.java
new file mode 100644
index 0000000000..bc8158501c
--- /dev/null
+++ b/tools/misc-types/src/main/java/org/apache/polaris/misc/types/memorysize/MemorySizeJackson.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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 org.apache.polaris.misc.types.memorysize;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import java.io.IOException;
+
+public class MemorySizeJackson extends SimpleModule {
+ public MemorySizeJackson() {
+ addDeserializer(MemorySize.class, new MemorySizeDeserializer());
+ addSerializer(MemorySize.class, MemorySizeSerializer.AS_STRING);
+ }
+
+ private static class MemorySizeDeserializer extends JsonDeserializer {
+ @Override
+ public MemorySize deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ switch (p.currentToken()) {
+ case VALUE_NUMBER_INT:
+ var bigInt = p.getBigIntegerValue();
+ try {
+ return new MemorySize.MemorySizeLong(bigInt.longValueExact());
+ } catch (ArithmeticException e) {
+ return new MemorySize.MemorySizeBig(bigInt);
+ }
+ case VALUE_STRING:
+ return MemorySize.valueOf(p.getText());
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported token " + p.currentToken() + " for " + MemorySize.class.getName());
+ }
+ }
+ }
+
+ private static class MemorySizeSerializer extends JsonSerializer
+ implements ContextualSerializer {
+ final boolean asInt;
+
+ static final MemorySizeSerializer AS_STRING = new MemorySizeSerializer(false);
+ static final MemorySizeSerializer AS_INT = new MemorySizeSerializer(true);
+
+ private MemorySizeSerializer(boolean asInt) {
+ this.asInt = asInt;
+ }
+
+ @Override
+ public void serialize(MemorySize value, JsonGenerator generator, SerializerProvider serializers)
+ throws IOException {
+ if (asInt) {
+ if (value instanceof MemorySize.MemorySizeBig) {
+ generator.writeNumber(value.asBigInteger());
+ } else {
+ generator.writeNumber(value.asLong());
+ }
+ } else {
+ generator.writeString(value.toString());
+ }
+ }
+
+ @Override
+ public JsonSerializer> createContextual(SerializerProvider provider, BeanProperty property) {
+
+ if (property != null) {
+ var propertyFormat = property.findPropertyFormat(provider.getConfig(), handledType());
+ if (propertyFormat != null) {
+ var shape = propertyFormat.getShape();
+ switch (shape) {
+ case NUMBER:
+ case NUMBER_INT:
+ return AS_INT;
+ case STRING:
+ case ANY:
+ case NATURAL:
+ return AS_STRING;
+ default:
+ throw new IllegalStateException(
+ "Shape "
+ + shape
+ + " not supported for "
+ + MemorySize.class.getName()
+ + " serialization");
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/tools/misc-types/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module b/tools/misc-types/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module
new file mode 100644
index 0000000000..b29379a03b
--- /dev/null
+++ b/tools/misc-types/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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
+#
+# http://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.
+#
+
+org.apache.polaris.misc.types.memorysize.MemorySizeJackson
diff --git a/tools/misc-types/src/test/java/org/apache/polaris/misc/types/memorysize/TestMemorySize.java b/tools/misc-types/src/test/java/org/apache/polaris/misc/types/memorysize/TestMemorySize.java
new file mode 100644
index 0000000000..fac2dc4851
--- /dev/null
+++ b/tools/misc-types/src/test/java/org/apache/polaris/misc/types/memorysize/TestMemorySize.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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 org.apache.polaris.misc.types.memorysize;
+
+import static java.util.Locale.ROOT;
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.PropertiesConfigSource;
+import io.smallrye.config.SmallRyeConfigBuilder;
+import jakarta.annotation.Nullable;
+import java.math.BigInteger;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+import org.apache.polaris.immutables.PolarisImmutable;
+import org.assertj.core.api.SoftAssertions;
+import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+@ExtendWith(SoftAssertionsExtension.class)
+public class TestMemorySize {
+ @InjectSoftAssertions SoftAssertions soft;
+
+ @ParameterizedTest
+ @MethodSource
+ public void parseString(String in, String expected) {
+ soft.assertThat(requireNonNull(MemorySize.valueOf(in)).toString()).isEqualTo(expected);
+ }
+
+ static Stream parseString() {
+ return Stream.of(
+ arguments("0G", "0B"),
+ arguments("1024M", "1G"),
+ arguments(String.valueOf(1024 * 1024), "1M"),
+ arguments(String.valueOf(4 * 1024 * 1024), "4M"),
+ arguments(String.valueOf(1024 * 1024 * 1024), "1G"));
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ public void parse(String s, BigInteger expected) {
+ var parsed = requireNonNull(MemorySize.valueOf(s));
+ soft.assertThat(parsed.asBigInteger()).isEqualTo(expected);
+ if (Character.isDigit(s.charAt(s.length() - 1))) {
+ soft.assertThat(parsed.toString()).isEqualTo(s.toUpperCase(ROOT) + 'B');
+ } else {
+ soft.assertThat(parsed.toString()).isEqualTo(s.toUpperCase(ROOT));
+ }
+ try {
+ var l = expected.longValueExact();
+ soft.assertThat(parsed.asLong()).isEqualTo(l);
+ soft.assertThat(parsed).isInstanceOf(MemorySize.MemorySizeLong.class);
+ } catch (ArithmeticException e) {
+ soft.assertThat(parsed).isInstanceOf(MemorySize.MemorySizeBig.class);
+ }
+ }
+
+ static Stream parse() {
+ return Stream.of(
+ tuple("", BigInteger.ONE),
+ tuple("B", BigInteger.ONE),
+ tuple("K", BigInteger.valueOf(1024)),
+ tuple("M", BigInteger.valueOf(1024).pow(2)),
+ tuple("G", BigInteger.valueOf(1024).pow(3)),
+ tuple("T", BigInteger.valueOf(1024).pow(4)),
+ tuple("P", BigInteger.valueOf(1024).pow(5)),
+ tuple("E", BigInteger.valueOf(1024).pow(6)),
+ tuple("Z", BigInteger.valueOf(1024).pow(7)),
+ tuple("Y", BigInteger.valueOf(1024).pow(8)))
+ .flatMap(
+ t ->
+ Stream.of(
+ tuple(t.toList().get(0).toString().toLowerCase(ROOT), t.toList().get(1)), t))
+ .flatMap(
+ t -> {
+ var suffix = t.toList().get(0).toString();
+ var mult = (BigInteger) t.toList().get(1);
+ return Stream.of(
+ arguments("1" + suffix, mult),
+ arguments("5" + suffix, mult.multiply(BigInteger.valueOf(5))),
+ arguments("32" + suffix, mult.multiply(BigInteger.valueOf(32))),
+ arguments("1023" + suffix, mult.multiply(BigInteger.valueOf(1023))));
+ });
+ }
+
+ @ParameterizedTest
+ @MethodSource("parse")
+ public void serdeConfig(String input, BigInteger expected) {
+ var configMap =
+ Map.of(
+ "memory-size.implicit", input,
+ "memory-size.optional-present", input);
+ var config =
+ new SmallRyeConfigBuilder()
+ .withMapping(MemorySizeConfig.class)
+ .addDiscoveredConverters()
+ .withSources(new PropertiesConfigSource(configMap, "configMap"))
+ .build()
+ .getConfigMapping(MemorySizeConfig.class);
+ var value = new MemorySize.MemorySizeBig(expected);
+ soft.assertThat(config)
+ .extracting(
+ MemorySizeConfig::implicit,
+ MemorySizeConfig::optionalEmpty,
+ MemorySizeConfig::optionalPresent)
+ .containsExactly(value, Optional.empty(), Optional.of(value));
+ }
+
+ @ConfigMapping(prefix = "memory-size")
+ interface MemorySizeConfig {
+ MemorySize implicit();
+
+ Optional optionalEmpty();
+
+ Optional optionalPresent();
+ }
+
+ @ParameterizedTest
+ @MethodSource("parse")
+ public void serdeJackson(@SuppressWarnings("unused") String input, BigInteger expected)
+ throws Exception {
+ var value = new MemorySize.MemorySizeBig(expected);
+
+ var immutable =
+ ImmutableMemorySizeJson.builder()
+ .implicit(value)
+ .implicitInt(value)
+ .optionalPresent(value)
+ .optionalPresentInt(value)
+ .build();
+
+ var mapper = new ObjectMapper().findAndRegisterModules();
+ var json = mapper.writeValueAsString(immutable);
+ var nodes = mapper.readValue(json, JsonNode.class);
+ var deser = mapper.readValue(json, MemorySizeJson.class);
+
+ soft.assertThat(deser).isEqualTo(immutable);
+
+ soft.assertThat(nodes.get("implicit").asText()).isEqualTo(value.toString());
+ soft.assertThat(nodes.get("implicitInt").bigIntegerValue()).isEqualTo(value.asBigInteger());
+ soft.assertThat(nodes.get("optionalPresent").asText()).isEqualTo(value.toString());
+ soft.assertThat(nodes.get("optionalPresentInt").bigIntegerValue())
+ .isEqualTo(value.asBigInteger());
+ }
+
+ @PolarisImmutable
+ @JsonSerialize(as = ImmutableMemorySizeJson.class)
+ @JsonDeserialize(as = ImmutableMemorySizeJson.class)
+ interface MemorySizeJson {
+ MemorySize implicit();
+
+ @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT)
+ MemorySize implicitInt();
+
+ @Nullable
+ MemorySize implicitNull();
+
+ @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT)
+ @Nullable
+ MemorySize implicitIntNull();
+
+ Optional optionalEmpty();
+
+ Optional optionalPresent();
+
+ @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT)
+ Optional optionalPresentInt();
+ }
+}
diff --git a/tools/misc-types/src/test/resources/logback-test.xml b/tools/misc-types/src/test/resources/logback-test.xml
new file mode 100644
index 0000000000..4a4d9a629d
--- /dev/null
+++ b/tools/misc-types/src/test/resources/logback-test.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ %date{ISO8601} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+