Record Class ChannelScopedId

java.lang.Object
java.lang.Record
com.tnsai.channels.api.ChannelScopedId
Record Components:
channelId - adapter id that produced the inbound message (e.g. "telegram", "cli"); never null, blank, or containing the ':' separator
senderId - platform-specific user identifier; never null or blank, but may contain colons

public record ChannelScopedId(String channelId, String senderId) extends Record
Strongly-typed identity for "this user, on this channel" — a value type replacing the channelId + ":" + senderId string concat that used to live in every adapter and every consumer.

Why not just a string?

The string form has three independent failure modes:

  1. Type confusion. A method taking String scopedId accepts any string — including a bare senderId, a conversationId, or an unrelated key. The compiler can't tell which is intended.
  2. Separator collision. If senderId ever contains the separator character (a colon, on platforms that use user:fragment ids), naive string-split parses the wrong way. ChannelScopedId pins the contract: the channelId side may not contain the separator (validated in the compact constructor), and parsing always splits on the first colon.
  3. No equals/hashCode contract. Two records with the same channel + sender are equal by record default; two raw strings depend on what the producer happened to write.

Wire format

toKey() renders the canonical disk / log / map-key form as channelId + ":" + senderId. parse(String) is the inverse. Round-tripping is total when the channelId obeys the "no colon" rule the constructor enforces.

 ChannelScopedId id = ChannelScopedId.of(message);   // typed
 String key = id.toKey();                            // "telegram:12345"
 ChannelScopedId parsed = ChannelScopedId.parse(key); // back to typed
 assert id.equals(parsed);
 

Senders with colons

A senderId may contain colons — some platforms issue identifiers like workspace:user:fragment. parse(java.lang.String) splits on the first colon only, so "telegram:workspace:user" round-trips to channel "telegram" and sender "workspace:user". The channel side never contains a colon because the compact constructor rejects it.

Since:
0.8.4
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final char
    Canonical separator between channelId and senderId.
  • Constructor Summary

    Constructors
    Constructor
    Description
    ChannelScopedId(String channelId, String senderId)
    Creates an instance of a ChannelScopedId record class.
  • Method Summary

    Modifier and Type
    Method
    Description
    Returns the value of the channelId record component.
    final boolean
    Indicates whether some other object is "equal to" this one.
    final int
    Returns a hash code value for this object.
    of(UnifiedMessage message)
    Convenience factory: pull channelId + senderId out of an inbound UnifiedMessage.
    Parse a string previously produced by toKey().
    Returns the value of the senderId record component.
    Canonical wire / log / map-key form: channelId + ":" + senderId.
    Same string as toKey() — convenient for log lines (log.info("user {}", scopedId)) and string concat without forcing every call site to remember to call toKey().

    Methods inherited from class java.lang.Object

    getClass, notify, notifyAll, wait, wait, wait
  • Field Details

    • SEPARATOR

      public static final char SEPARATOR
      Canonical separator between channelId and senderId.
      See Also:
  • Constructor Details

    • ChannelScopedId

      public ChannelScopedId(String channelId, String senderId)
      Creates an instance of a ChannelScopedId record class.
      Parameters:
      channelId - the value for the channelId record component
      senderId - the value for the senderId record component
  • Method Details

    • of

      public static ChannelScopedId of(UnifiedMessage message)
      Convenience factory: pull channelId + senderId out of an inbound UnifiedMessage. Equivalent to new ChannelScopedId(msg.channelId(), msg.senderId()) but reads at the call site like the intent ("scoped id of this message").
    • parse

      public static ChannelScopedId parse(String key)
      Parse a string previously produced by toKey(). Splits on the first colon so a senderId with internal colons survives the round trip.
      Throws:
      IllegalArgumentException - if key is null, blank, contains no colon, or has a blank channel/sender side
    • toKey

      public String toKey()
      Canonical wire / log / map-key form: channelId + ":" + senderId. Inverse of parse(String).
    • toString

      public String toString()
      Same string as toKey() — convenient for log lines (log.info("user {}", scopedId)) and string concat without forcing every call site to remember to call toKey().
      Specified by:
      toString in class Record
    • hashCode

      public final int hashCode()
      Returns a hash code value for this object. The value is derived from the hash code of each of the record components.
      Specified by:
      hashCode in class Record
      Returns:
      a hash code value for this object
    • equals

      public final boolean equals(Object o)
      Indicates whether some other object is "equal to" this one. The objects are equal if the other object is of the same class and if all the record components are equal. All components in this record class are compared with Objects::equals(Object,Object).
      Specified by:
      equals in class Record
      Parameters:
      o - the object with which to compare
      Returns:
      true if this object is the same as the o argument; false otherwise.
    • channelId

      public String channelId()
      Returns the value of the channelId record component.
      Returns:
      the value of the channelId record component
    • senderId

      public String senderId()
      Returns the value of the senderId record component.
      Returns:
      the value of the senderId record component