canonize static method

String canonize(
  1. String keyExpr
)

Returns keyExpr rewritten into canon form.

A pure transform: no handle, no native resource, no lifecycle. On input that is already canon it returns the same bytes back, unchanged.

The rewrite is canon's, and it is these four rules:

  • a run of contiguous $* collapses to one — hello/foo$*$*/bar becomes hello/foo$*/bar;
  • contiguous ** chunks collapse to one — hello/**/** becomes hello/**;
  • a chunk that is exactly $* becomes *hello/$*/bye becomes hello/*/bye;
  • **/* reorders to */**hello/**/* becomes hello/*/**.

Throws ZenohException only on genuine invalidity — an expression canon rejects even after the rewrite, such as a//b or the empty string. It never throws on a merely non-canon expression: rewriting those is the whole point of the method, and they are exactly the inputs the strict KeyExpr constructor turns away.

KeyExpr(KeyExpr.canonize(s)) therefore constructs for every s this returns, and canonizing twice returns the same bytes as canonizing once.

A String-to-String transform — nothing is constructed and nothing is sent — so non-ASCII input is safe here, and the rewrite operates on /-delimited chunks and ASCII metacharacters, never splitting a multi-byte character. Using the result with a session is a different question: see the non-ASCII warning in this class's documentation. keyExpr is transformed as bytes, with the encode-boundary caveat described there.

Implementation

static String canonize(String keyExpr) {
  final encoded = utf8.encode(keyExpr);
  // 🔴 Canon rewrites this buffer IN PLACE through a `&mut str`. It is the
  // marshalling layer's own malloc'd process-heap block -- never
  // Dart-managed memory, never a string literal, never read-only -- which
  // is the whole of the SEGFAULT guard, and it is non-NULL at length 0.
  final buf = _copyToNative(encoded);
  // Canon writes the canonized length back here, on success only. It is the
  // ONLY truth about the result's extent: the rewrite can preserve the
  // length as easily as shorten it, so nothing below reuses `encoded`'s.
  final lenCell = calloc<Size>();
  try {
    lenCell.value = encoded.length;
    final rc = bindings.zd_keyexpr_canonize(buf.cast(), lenCell);
    if (rc != 0) {
      throw ZenohException('Invalid key expression: "$keyExpr"', rc);
    }
    return buf.cast<Utf8>().toDartString(length: lenCell.value);
  } finally {
    malloc.free(buf);
    calloc.free(lenCell);
  }
}