Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c0a1f1b
feat: enhance identifier handling with new generated model
Sotatek-DucPhung2 Dec 1, 2025
fb8454a
chore: remove inline import
Sotatek-DucPhung2 Dec 1, 2025
ef91559
refactor: streamline JSON parsing in Identifier class
Sotatek-DucPhung2 Dec 2, 2025
5a5ef57
refactor: simplify group state handling in IdentifierModelConverter
Sotatek-DucPhung2 Dec 2, 2025
4fdd2c8
fix: improve KeyStateRecordDeserializer to handle numeric nodes corre…
Sotatek-DucPhung2 Dec 2, 2025
268f096
refactor: use direct method calls for transferable and windexes
Sotatek-DucPhung2 Dec 2, 2025
49d4cca
refactor: simplify IdentifierModelConverter by removing default metho…
Sotatek-DucPhung2 Dec 2, 2025
44f39c6
refactor: enforce non-null parameters in IdentifierPayloadMapper
Sotatek-DucPhung2 Dec 2, 2025
2dadad1
refactor: add nullability annotations to pars
Sotatek-DucPhung2 Dec 3, 2025
053e50b
refactor: update KeyStateRecordDeserializer to fail fast on unexpecte…
Sotatek-DucPhung2 Dec 3, 2025
c717e32
refactor: remove unused buildExchangePayload
Sotatek-DucPhung2 Dec 3, 2025
3828da3
refactor: replace HabState with Identifier in various classes and met…
Sotatek-DucPhung2 Dec 3, 2025
ac9ba10
refactor: replace States with Tier in multiple classes for improved c…
Sotatek-DucPhung2 Dec 4, 2025
04ce885
refactor: update CreateIdentifierArgs to use KeyStateRecord for state…
Sotatek-DucPhung2 Dec 4, 2025
3fc494a
chore: remove Payload mapper
Sotatek-DucPhung2 Dec 10, 2025
cd8846a
chore: use inline builder for Identifier
Sotatek-DucPhung2 Dec 10, 2025
5c44687
chore: correct getTier()
Sotatek-DucPhung2 Dec 10, 2025
80dbf62
chore: remove unused file
Sotatek-DucPhung2 Dec 10, 2025
823bc23
chore: use EndrolesAidPostRequest for addEndRole api
Sotatek-DucPhung2 Dec 14, 2025
059b9fc
feat: Update KeyStates to user KeyStateRecord
Sotatek-DucPhung2 Dec 14, 2025
52fa4a2
feat: update KeyStateRecordDeserializer
Sotatek-DucPhung2 Dec 14, 2025
477366d
feat: handle logic KeyStates get method return an array
Sotatek-DucPhung2 Dec 14, 2025
97944f9
feat: add TODO for using generated model request IdentifiersPostRequest
Sotatek-DucPhung2 Dec 16, 2025
e2bb2eb
feat: simplify KeyStates response handling to always return first Key…
Sotatek-DucPhung2 Dec 23, 2025
c2508aa
update new models, using HabState instead of Identifier
Sotatek-Patrick-Vu Mar 4, 2026
41ccbd1
fix E2E tests
Sotatek-Patrick-Vu Mar 5, 2026
77c558c
revert generated model KeyStateRecordKt.java
Sotatek-Patrick-Vu Mar 5, 2026
dc9fe3d
re-generate models and remove unused file
Sotatek-Patrick-Vu Mar 6, 2026
28d2b6c
add UnweightedThreshold and WeightedThreshold for kt, nt of key state
Sotatek-Patrick-Vu Mar 6, 2026
023c705
use new models and fix unit tests
Sotatek-Patrick-Vu Mar 9, 2026
1284e7a
update/fix CredentialsTest E2E
Sotatek-Patrick-Vu Mar 11, 2026
4d2020c
improve helpers
Sotatek-Patrick-Vu Mar 11, 2026
ea8a4d7
Merge remote-tracking branch 'origin/main' into feat/GeneratedCredent…
iFergal Mar 11, 2026
b9a56fa
fix: merge conflict changes re key state record
iFergal Mar 11, 2026
83a6d05
fix: merge conflict changes re key state record 2
iFergal Mar 11, 2026
194ca50
update MultisigHolderTest and MultisigVleiIssuanaceTest
Sotatek-Patrick-Vu Mar 12, 2026
82f9a87
fix: single sig vlei tests with new credential types
iFergal Mar 12, 2026
674f5ff
cache and use raw canonical payload (sad/iss/anc) for grant/admit
Sotatek-Patrick-Vu Mar 17, 2026
a5d1d00
Merge pull request #84 from cardano-foundation/test/singlesigvlei
Sotatek-Patrick-Vu Mar 17, 2026
ea5b3a2
fix: re-generate acdc with correct e, r fields and remove unnecessary…
iFergal Mar 23, 2026
9e90e29
chore: stop auto-initializing collections in generated modals (messes…
iFergal Mar 23, 2026
4cce55d
chore: re-generate with correct top-level ACDC order
iFergal Mar 24, 2026
adc5a36
fix: keep insertion order for fields with additionalProperties to kee…
iFergal Mar 24, 2026
35318b6
refactor: cleanup remaining grant casts to linkedhashmap
iFergal Mar 24, 2026
68085a8
refactor: remove unused grantembedmaps
iFergal Mar 24, 2026
16d2011
improve CredentialState (#90)
Sotatek-Patrick-Vu Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ tasks.openApiGenerate {
sourceFolder : "src/main/java",
hideGenerationTimestamp : "true",
openApiNullable : "false",
containerDefaultToNull : "true"
]

nameMappings.put("A", "AUpper")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.cardanofoundation.signify.app.config;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class CredentialStateMixin {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.cardanofoundation.signify.generated.keria.model.CredentialState;
import org.cardanofoundation.signify.generated.keria.model.KeyStateRecordKt;
import org.openapitools.jackson.nullable.JsonNullableModule;

Expand All @@ -15,40 +15,18 @@ public final class GeneratedModelConfig {
private GeneratedModelConfig() {
}

public static ObjectMapper mapper() {
ObjectMapper mapper = baseMapper();
mapper.registerModule(generatedModule());
return mapper;
}

/**
* Apply the generated-model settings to an existing mapper.
*/
public static void configure(ObjectMapper mapper) {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.addMixIn(CredentialState.class, CredentialStateMixin.class);
mapper.registerModule(new JsonNullableModule());
mapper.registerModule(generatedModule());
}

/**
* Base mapper with nullable module but without the custom generated module.
* Useful for internal delegates to avoid recursive deserializer lookups.
*/
public static ObjectMapper baseMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.registerModule(new JsonNullableModule());
return mapper;
}
mapper.registerModule(new InsertionOrderAdditionalPropertiesModule());

private static Module generatedModule() {
SimpleModule module = new SimpleModule("GeneratedModelModule");
module.addDeserializer(KeyStateRecordKt.class, new KeyStateRecordKtDeserializer());
// TODO: register deserializer for ICPV1Kt.class (same pattern) when any schema that uses
// it (ICPV1/V2, ROTV1/V2, DIPV1/V2, DRTV1/V2, CredentialAnc, ControllerEe) needs kt/nt
// accessed in non-generated code.
return module;
mapper.registerModule(module);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package org.cardanofoundation.signify.app.config;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Jackson module that preserves JSON insertion order for generated models with
* {@code additionalProperties: true}.
*
* <p>The generator emits {@code @JsonAnySetter}/{@code @JsonAnyGetter} for extra fields, but
* backs them with a {@code HashMap}, losing order. It also serialises typed fields first
* (per {@code @JsonPropertyOrder}), so the round-trip no longer reflects the original order.
*
* <p>This module intercepts any bean that has an {@code additionalProperties} field written by
* {@code @JsonAnySetter}, and:
* <ul>
* <li><b>Deserialization</b>: stores <em>all</em> JSON fields (typed + extra) in a
* {@code LinkedHashMap} as {@code additionalProperties}, preserving insertion order.
* Typed setters are still called so typed getters continue to work.</li>
* <li><b>Serialization</b>: if {@code additionalProperties} is the full ordered map, writes
* from it directly, bypassing {@code @JsonPropertyOrder}, so the output order matches
* the original JSON and SAID computation is correct.</li>
* </ul>
*/
public class InsertionOrderAdditionalPropertiesModule extends SimpleModule {

public InsertionOrderAdditionalPropertiesModule() {
super("InsertionOrderAdditionalPropertiesModule");
}

@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanDeserializerModifier(new DeserializerModifier());
context.addBeanSerializerModifier(new SerializerModifier());
}

/**
* Returns the additionalProperties field if the class has @JsonAnySetter,
* null otherwise.
*/
private static Field findApField(Class<?> clazz) {
try {
Field f = clazz.getDeclaredField("additionalProperties");
if (!Map.class.isAssignableFrom(f.getType())) return null;
f.setAccessible(true);
return f;
} catch (NoSuchFieldException e) {
return null;
}
}

// -------------------------------------------------------------------------
// Deserialization
// -------------------------------------------------------------------------

private static class DeserializerModifier extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
Field apField = findApField(beanDesc.getBeanClass());
if (apField == null) return deserializer;
return new OrderPreservingDeserializer<>(deserializer, apField);
}
}

@SuppressWarnings("unchecked")
private static class OrderPreservingDeserializer<T> extends StdDeserializer<T>
implements ResolvableDeserializer {

private final JsonDeserializer<T> delegate;
private final Field apField;

OrderPreservingDeserializer(JsonDeserializer<?> delegate, Field apField) {
super(delegate.handledType());
this.delegate = (JsonDeserializer<T>) delegate;
this.apField = apField;
}

@Override
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
if (delegate instanceof ResolvableDeserializer rd) rd.resolve(ctxt);
}

@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = (ObjectMapper) p.getCodec();
ObjectNode tree = (ObjectNode) ctxt.readTree(p);

// Capture all fields in wire order
LinkedHashMap<String, Object> ordered = new LinkedHashMap<>();
tree.fields().forEachRemaining(e ->
ordered.put(e.getKey(), mapper.convertValue(e.getValue(), Object.class)));

// Delegate to standard bean deserializer for typed field access
JsonParser treeParser = tree.traverse(mapper);
treeParser.nextToken();
T bean = delegate.deserialize(treeParser, ctxt);

// Replace with ordered map containing all fields
try {
apField.set(bean, ordered);
} catch (IllegalAccessException e) {
throw new IOException("Cannot set additionalProperties on "
+ bean.getClass().getSimpleName(), e);
}

return bean;
}
}

// -------------------------------------------------------------------------
// Serialization
// -------------------------------------------------------------------------

private static class SerializerModifier extends BeanSerializerModifier {
@Override
public JsonSerializer<?> modifySerializer(SerializationConfig config,
BeanDescription beanDesc,
JsonSerializer<?> serializer) {
Field apField = findApField(beanDesc.getBeanClass());
if (apField == null) return serializer;
return new OrderPreservingSerializer<>(serializer, apField);
}
}

@SuppressWarnings("unchecked")
private static class OrderPreservingSerializer<T> extends JsonSerializer<T> {

private final JsonSerializer<T> delegate;
private final Field apField;

OrderPreservingSerializer(JsonSerializer<?> delegate, Field apField) {
this.delegate = (JsonSerializer<T>) delegate;
this.apField = apField;
}

@Override
public void serialize(T value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
Map<String, Object> ordered;
try {
ordered = (Map<String, Object>) apField.get(value);
} catch (IllegalAccessException e) {
delegate.serialize(value, gen, provider);
return;
}

if (ordered == null || ordered.isEmpty()) {
delegate.serialize(value, gen, provider);
return;
}

gen.writeStartObject();
for (Map.Entry<String, Object> entry : ordered.entrySet()) {
gen.writeFieldName(entry.getKey());
provider.defaultSerializeValue(entry.getValue(), gen);
}
gen.writeEndObject();
}
}
}

This file was deleted.

Loading
Loading