Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
106a83e
feat: enhance identifier handling with new generated model
Sotatek-DucPhung Dec 1, 2025
446f364
chore: remove inline import
Sotatek-DucPhung Dec 1, 2025
3810e9b
refactor: streamline JSON parsing in Identifier class
Sotatek-DucPhung Dec 2, 2025
7e9a244
refactor: simplify group state handling in IdentifierModelConverter
Sotatek-DucPhung Dec 2, 2025
babb9ea
fix: improve KeyStateRecordDeserializer to handle numeric nodes corre…
Sotatek-DucPhung Dec 2, 2025
678cc85
refactor: use direct method calls for transferable and windexes
Sotatek-DucPhung Dec 2, 2025
5e5f848
refactor: simplify IdentifierModelConverter by removing default metho…
Sotatek-DucPhung Dec 2, 2025
0ab4dd0
refactor: enforce non-null parameters in IdentifierPayloadMapper
Sotatek-DucPhung Dec 2, 2025
4cca638
refactor: add nullability annotations to pars
Sotatek-DucPhung Dec 3, 2025
0126fbc
refactor: update KeyStateRecordDeserializer to fail fast on unexpecte…
Sotatek-DucPhung Dec 3, 2025
2b4451d
refactor: remove unused buildExchangePayload
Sotatek-DucPhung Dec 3, 2025
c099e48
refactor: replace HabState with Identifier in various classes and met…
Sotatek-DucPhung Dec 3, 2025
68dd696
refactor: replace States with Tier in multiple classes for improved c…
Sotatek-DucPhung Dec 4, 2025
1f5c65f
Merge pull request #65 from cardano-foundation/fix/import-generated-t…
Sotatek-DucPhung Dec 4, 2025
91ee571
refactor: update CreateIdentifierArgs to use KeyStateRecord for state…
Sotatek-DucPhung Dec 4, 2025
45f4e58
chore: remove Payload mapper
Sotatek-DucPhung Dec 10, 2025
305e1ae
chore: use inline builder for Identifier
Sotatek-DucPhung Dec 10, 2025
cbf3016
chore: correct getTier()
Sotatek-DucPhung Dec 10, 2025
d4243dd
chore: remove unused file
Sotatek-DucPhung Dec 10, 2025
50ed3aa
chore: use EndrolesAidPostRequest for addEndRole api
Sotatek-DucPhung Dec 14, 2025
e604625
feat: Update KeyStates to user KeyStateRecord
Sotatek-DucPhung Dec 14, 2025
b00ea03
feat: update KeyStateRecordDeserializer
Sotatek-DucPhung Dec 14, 2025
3970072
feat: handle logic KeyStates get method return an array
Sotatek-DucPhung Dec 14, 2025
bcffb6a
feat: add TODO for using generated model request IdentifiersPostRequest
Sotatek-DucPhung Dec 16, 2025
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointer to https://github.com/cardano-foundation/cf-signify-java/pull/59/files#r2604004041

Should be resolved before this PR is merged!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sotatek-DucPhung Please check my comment there: #59 (comment)

Those changes MUST be fixed in this PR before merging.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.cardanofoundation.signify.app.clienting.SignifyClient;
import org.cardanofoundation.signify.cesr.exceptions.LibsodiumException;
import org.cardanofoundation.signify.cesr.util.Utils;
import org.cardanofoundation.signify.core.States;

import java.io.IOException;
import java.net.HttpURLConnection;
Expand All @@ -19,6 +18,7 @@
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import java.util.concurrent.ExecutionException;
import org.cardanofoundation.signify.generated.keria.model.Identifier;

public class Contacting {

Expand Down Expand Up @@ -66,7 +66,7 @@ public Challenge generate() throws LibsodiumException, IOException, InterruptedE
* @throws Exception if the fetch operation fails
*/
public Object respond(String name, String recipient, List<String> words) throws IOException, InterruptedException, DigestException, ExecutionException, LibsodiumException {
States.HabState hab = this.client.identifiers().get(name)
Identifier hab = this.client.identifiers().get(name)
.orElseThrow(() -> new IllegalArgumentException("Identifier not found: " + name));
Exchanging.Exchanges exchanges = this.client.exchanges();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
import org.cardanofoundation.signify.cesr.params.KeeperParams;
import org.cardanofoundation.signify.cesr.util.CoreUtil;
import org.cardanofoundation.signify.cesr.util.Utils;
import org.cardanofoundation.signify.core.States.HabState;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.http.HttpResponse;
import java.security.DigestException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import org.cardanofoundation.signify.generated.keria.model.Identifier;

public class Exchanging {
@Getter
Expand Down Expand Up @@ -46,7 +46,7 @@ public Exchanges(SignifyClient client) {
* @return array containing [Serder, signatures, attachment]
*/
public ExchangeMessageResult createExchangeMessage(
HabState sender,
Identifier sender,
String route,
Map<String, Object> payload,
Map<String, List<Object>> embeds,
Expand Down Expand Up @@ -89,7 +89,7 @@ public ExchangeMessageResult createExchangeMessage(
public Object send(
String name,
String topic,
HabState sender,
Identifier sender,
String route,
Map<String, Object> payload,
Map<String, List<Object>> embeds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import java.util.List;

import lombok.*;
import org.cardanofoundation.signify.cesr.Salter.Tier;
import org.cardanofoundation.signify.core.Manager.Algos;
import org.cardanofoundation.signify.core.States.HabState;
import org.cardanofoundation.signify.generated.keria.model.Identifier;
import org.cardanofoundation.signify.generated.keria.model.KeyStateRecord;
import org.cardanofoundation.signify.generated.keria.model.Tier;

@Getter
@Setter
Expand All @@ -24,11 +25,11 @@ public class CreateIdentifierArgs {
private Object data;
private Algos algo;
private String pre;
private List<Object> states;
private List<Object> rstates;
private List<KeyStateRecord> states;
private List<KeyStateRecord> rstates;
private List<Object> prxs;
private List<Object> nxts;
private HabState mhab;
private Identifier mhab;
private List<String> keys;
private List<String> ndigs;
private String bran;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.cardanofoundation.signify.cesr.util.CoreUtil.Serials;
import org.cardanofoundation.signify.core.Eventing;
import org.cardanofoundation.signify.core.Httping;
import org.cardanofoundation.signify.core.States;
import org.cardanofoundation.signify.core.Manager.Algos;

import java.io.IOException;
Expand All @@ -27,19 +26,22 @@
import java.security.DigestException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import org.cardanofoundation.signify.generated.keria.model.EndrolesAidPostRequest;
import org.cardanofoundation.signify.generated.keria.model.Identifier;
import org.cardanofoundation.signify.generated.keria.model.KeyStateRecord;

import static org.cardanofoundation.signify.cesr.util.CoreUtil.Versionage;
import static org.cardanofoundation.signify.core.Httping.parseRangeHeaders;

public class Identifier {
public class IdentifierController {
public final IdentifierDeps client;

/**
* Identifier
*
* @param client the client dependencies
*/
public Identifier(IdentifierDeps client) {
public IdentifierController(IdentifierDeps client) {
this.client = client;
}

Expand Down Expand Up @@ -68,7 +70,7 @@ public IdentifierListResponse list(Integer start, Integer end) throws Interrupte
range.start(),
range.end(),
range.total(),
response.body()
Arrays.asList(Utils.fromJson(response.body(), Identifier[].class))
);
}

Expand All @@ -86,7 +88,7 @@ public IdentifierListResponse list(Integer start) throws IOException, Interrupte
* @param name Prefix or alias of the identifier
* @return An Optional containing the HabState if found, or empty if not found
*/
public Optional<States.HabState> get(String name) throws InterruptedException, IOException, LibsodiumException {
public Optional<Identifier> get(String name) throws InterruptedException, IOException, LibsodiumException {
final String path = "/identifiers/" + URI.create(name).toASCIIString();
final String method = "GET";

Expand All @@ -96,7 +98,7 @@ public Optional<States.HabState> get(String name) throws InterruptedException, I
return Optional.empty();
}

return Optional.of(Utils.fromJson(response.body(), States.HabState.class));
return Optional.of(Utils.fromJson(response.body(), Identifier.class));
}

/**
Expand All @@ -106,12 +108,16 @@ public Optional<States.HabState> get(String name) throws InterruptedException, I
* @param info Information to update for the given identifier
* @return A HabState to the identifier information after updating
*/
public States.HabState update(String name, IdentifierInfo info) throws InterruptedException, IOException, LibsodiumException {
public Identifier update(String name, IdentifierInfo info) throws InterruptedException, IOException, LibsodiumException {
final String path = "/identifiers/" + name;
final String method = "PUT";

HttpResponse<String> response = this.client.fetch(path, method, info);
return Utils.fromJson(response.body(), States.HabState.class);
HttpResponse<String> response = this.client.fetch(
path,
method,
info
);
return Utils.fromJson(response.body(), Identifier.class);
}

/**
Expand Down Expand Up @@ -244,15 +250,16 @@ public EventResult create(String name, CreateIdentifierArgs kargs) throws Interr
List<String> rmids = null;

if (states != null) {
List<States.State> stateDeserialized = Utils.fromJson(Utils.jsonStringify(states), new TypeReference<>() {});
smids = stateDeserialized.stream().map(States.State::getI).toList();
List<KeyStateRecord> stateDeserialized = Utils.fromJson(Utils.jsonStringify(states), new TypeReference<>() {});
smids = stateDeserialized.stream().map(KeyStateRecord::getI).toList();
}

if (rstates != null) {
List<States.State> rstateDeserialized = Utils.fromJson(Utils.jsonStringify(rstates), new TypeReference<>() {});
rmids = rstateDeserialized.stream().map(States.State::getI).toList();
List<KeyStateRecord> rstateDeserialized = Utils.fromJson(Utils.jsonStringify(rstates), new TypeReference<>() {});
rmids = rstateDeserialized.stream().map(KeyStateRecord::getI).toList();
}

// TODO use generated model request IdentifiersPostRequest, when it supports dynamic fields (proxy, smids, rmids)
Map<String, Object> jsondata = new LinkedHashMap<>();
jsondata.put("name", name);
jsondata.put("icp", serder.getKed());
Expand Down Expand Up @@ -282,7 +289,7 @@ public EventResult create(String name, CreateIdentifierArgs kargs) throws Interr
* @throws LibsodiumException if there is an error in the cryptographic operations
*/
public EventResult addEndRole(String name, String role, String eid, String stamp) throws InterruptedException, DigestException, IOException, LibsodiumException {
States.HabState hab = this.get(name)
Identifier hab = this.get(name)
.orElseThrow(() -> new IllegalArgumentException("Identifier not found: " + name));
String pre = hab.getPrefix();

Expand All @@ -292,14 +299,14 @@ public EventResult addEndRole(String name, String role, String eid, String stamp
Keeping.SignResult signResult = keeper.sign(rpy.getRaw().getBytes());
List<String> sigs = signResult.signatures();

LinkedHashMap<String, Object> jsondata = new LinkedHashMap<>();
jsondata.put("rpy", rpy.getKed());
jsondata.put("sigs", sigs);
EndrolesAidPostRequest endrolesAidPostRequest = new EndrolesAidPostRequest()
.rpy(rpy.getKed())
.sigs(sigs);

HttpResponse<String> res = this.client.fetch(
"/identifiers/" + name + "/endroles",
"POST",
jsondata
endrolesAidPostRequest
);
return new EventResult(rpy, sigs, res);
}
Expand Down Expand Up @@ -337,11 +344,11 @@ public EventResult interact(String name, Object data) throws InterruptedExceptio
}

public InteractionResponse createInteract(String name, Object data) throws InterruptedException, DigestException, IOException, LibsodiumException {
States.HabState hab = this.get(name)
Identifier hab = this.get(name)
.orElseThrow(() -> new IllegalArgumentException("Identifier not found: " + name));
String pre = hab.getPrefix();

States.State state = hab.getState();
KeyStateRecord state = hab.getState();
int sn = Integer.parseInt(state.getS(), 16);
String dig = state.getD();

Expand Down Expand Up @@ -376,12 +383,12 @@ public EventResult rotate(String name, RotateIdentifierArgs kargs) throws Interr
String ncode = kargs.getNcode() != null ? kargs.getNcode() : MatterCodex.Ed25519_Seed.getValue();
int ncount = kargs.getNcount() != null ? kargs.getNcount() : 1;

States.HabState hab = this.get(name)
Identifier hab = this.get(name)
.orElseThrow(() -> new IllegalArgumentException("Identifier not found: " + name));
String pre = hab.getPrefix();
boolean delegated = !hab.getState().getDi().isEmpty();

States.State state = hab.getState();
KeyStateRecord state = hab.getState();
int count = state.getK().size();
String dig = state.getD();
int ridx = Integer.parseInt(state.getS(), 16) + 1;
Expand All @@ -407,8 +414,8 @@ public EventResult rotate(String name, RotateIdentifierArgs kargs) throws Interr
// Create new keys for next digests
List<String> ncodes = kargs.getNcodes() != null ? kargs.getNcodes() : Collections.nCopies(ncount, ncode);

List<States.State> states = kargs.getStates() == null ? new ArrayList<>() : kargs.getStates();
List<States.State> rstates = kargs.getStates() == null ? new ArrayList<>() : kargs.getRstates();
List<KeyStateRecord> states = kargs.getStates() == null ? new ArrayList<>() : kargs.getStates();
List<KeyStateRecord> rstates = kargs.getStates() == null ? new ArrayList<>() : kargs.getRstates();
KeeperResult keeperResult = keeper.rotate(
ncodes,
transferable,
Expand Down Expand Up @@ -446,8 +453,8 @@ public EventResult rotate(String name, RotateIdentifierArgs kargs) throws Interr
Map<String, Object> jsondata = new LinkedHashMap<>();
jsondata.put("rot", serder.getKed());
jsondata.put("sigs", sigs);
jsondata.put("smids", !states.isEmpty() ? states.stream().map(States.State::getI).toList() : null);
jsondata.put("rmids", !rstates.isEmpty() ? rstates.stream().map(States.State::getI).toList() : null);
jsondata.put("smids", !states.isEmpty() ? states.stream().map(KeyStateRecord::getI).toList() : null);
jsondata.put("rmids", !rstates.isEmpty() ? rstates.stream().map(KeyStateRecord::getI).toList() : null);
jsondata.put(keeper.getAlgo().toString(), keeper.getParams().toMap());

HttpResponse<String> res = this.client.fetch(
Expand All @@ -472,4 +479,4 @@ public Object members(String name) throws LibsodiumException, InterruptedExcepti
);
return Utils.fromJson(response.body(), Object.class);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
package org.cardanofoundation.signify.app.aiding;

public record IdentifierListResponse(int start, int end, int total, Object aids) {
import org.cardanofoundation.signify.generated.keria.model.Identifier;

import java.util.List;

public record IdentifierListResponse(int start, int end, int total, List<Identifier> aids) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.cardanofoundation.signify.app.aiding;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.cardanofoundation.signify.generated.keria.model.KeyStateRecord;
import org.cardanofoundation.signify.app.config.GeneratedModelConfig;

import java.io.IOException;

/**
* Allows kt/nt fields to arrive as arrays/objects and coerces them to strings for the generated model.
*/
public class KeyStateRecordDeserializer extends StdDeserializer<KeyStateRecord> {

private static final ObjectMapper delegate = GeneratedModelConfig.baseMapper();

public KeyStateRecordDeserializer() {
super(KeyStateRecord.class);
}

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

coerceToString(node, "kt");
coerceToString(node, "nt");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really ever get ints back from the API? I thought it was always strings. (I know the API for creating an identifier accepts both though, which is different)

Copy link
Collaborator Author

@Sotatek-DucPhung Sotatek-DucPhung Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We hit a parse failure when kt/nt came back as an array (rotation thresholds), so this coerces non-text (array/number) to a string before binding.
If the API is guaranteed to return strings only, I can tighten this to handle only arrays (or drop the coercion and let it fail loudly). Let me know your prefer

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, but it's an array of strings. I think @Sotatek-Patrick-Vu might be fixing this, or have fixed it. We should return the string array, not a string.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in coerceToString method

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right but is it possible to receive a numeric response from the API? I don't think it is (may be a mistake in Patrick's generated models)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sotatek-DucPhung What does the generator generate in Java types for this, when using the up to date API spec from Patrick?

Copy link
Collaborator Author

@Sotatek-DucPhung Sotatek-DucPhung Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's KeyStateRecord, this model generated default have kt and nt with data tyoe String, but it can be array String

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is this before or after getting Patrick's fixes? he needs to re-open a new PR: cardano-foundation/keria#16

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anw, i have add logic to handle the String array deserilizer, so the e2e test is passed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect, we are not meant to have strings of JSON arrays. We are meant to have arrays.

Please work with Patrick to get the OpenAPI changes needed to update the generated model and have the correct format.


return delegate.treeToValue(node, KeyStateRecord.class);
}

private void coerceToString(ObjectNode node, String field) {
JsonNode value = node.get(field);
if (value == null) {
return;
}
// If already a string, leave it
if (value.isTextual()) {
return;
}
// If it's an array, convert to JSON string representation
if (value.isArray()) {
node.put(field, value.toString());
return;
}
// If it's a number, convert to string
if (value.isNumber()) {
node.put(field, value.asText());
return;
}
throw new IllegalArgumentException("Unexpected type for field '" + field + "': " + value.getNodeType());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.cardanofoundation.signify.core.States;

import java.util.List;
import org.cardanofoundation.signify.generated.keria.model.KeyStateRecord;

@Builder
@Getter
Expand All @@ -20,6 +20,6 @@ public class RotateIdentifierArgs {
private String ncode;
private Integer ncount;
private List<String> ncodes;
private List<States.State> states;
private List<States.State> rstates;
private List<KeyStateRecord> states;
private List<KeyStateRecord> rstates;
}
Loading
Loading