Zenoh keys#
A Zenoh key expression names a set of keys rather than one, which is how a
subscription covers a fleet instead of a node. The language is small: chunks
separated by slashes, * for exactly one chunk, and ** for any number of them
including none. pamoja implements the rules and nothing else, so a gateway can
decide what a subscription covers, and whether two of them are the same, without
a Zenoh installation anywhere near it.
What the example does#
It asks what each wildcard selects, using keys under a fleet subtree as the subject, then canonizes an expression written with a repeated wildcard, and finally offers a malformed one.
canonize reduces the redundant fleet/**/**/battery to fleet/**/battery,
which is the same spelling the ** line above already used as a pattern. The
example checks what comes back against it, and asks the library separately
whether that spelling is the canonical one.
It proves:
*stands for exactly one chunk, sofleet/*/batterycoversfleet/n7/batteryand notfleet/n7/rack/battery.**stands for any number of chunks, sofleet/**coversfleet/n7/rack/battery, andfleet/**/batterycoversfleet/battery, where it stands for none at all.- A repeated wildcard is not canonical.
fleet/**/**/batterycanonizes tofleet/**/battery, so a router compares subscriptions in that form rather than as written. - An empty chunk makes
fleet//batteryinvalid, and canonizing it yields nothing rather than a repaired expression.
Rust#
From examples/tests/guides/zenoh.rs:
use pamoja_zenoh::keyexpr::{canonize, is_canon, is_valid, matches};
// A key expression names a set of keys. `*` stands for exactly one chunk, so this
// selects the battery of any node, and not a battery nested deeper.
let any_node = "fleet/*/battery";
for key in ["fleet/n7/battery", "fleet/n7/rack/battery"] {
println!("{any_node} covers {key}: {}", matches(any_node, key));
}
// `**` stands for any number of chunks, including none, which is what a subscription
// covering a whole subtree wants.
println!(
"fleet/** covers a nested key: {}",
matches("fleet/**", "fleet/n7/rack/battery")
);
println!(
"fleet/**/battery covers fleet/battery: {}",
matches("fleet/**/battery", "fleet/battery")
);
// Two expressions that select the same keys have one canonical form. Comparing or
// routing on the written form would treat these as different subscriptions.
let written = "fleet/**/**/battery";
let canonical = canonize(written).expect("a canonical form");
println!(
"{written} is canonical: {}, and canonizes to {canonical}",
is_canon(written)
);
// A malformed expression is rejected rather than canonized into something plausible.
let malformed = "fleet//battery";
let refused = canonize(malformed);
println!(
"{malformed} is valid: {}, canonizes to {}",
is_valid(malformed),
refused.as_deref().unwrap_or("nothing")
);TypeScript#
From bindings/node/guides/zenoh.ts:
import { keyexpr } from '@pamoja/zenoh'
// A key expression names a set of keys. `*` stands for exactly one chunk, so this selects
// the battery of any node, and not a battery nested deeper.
const anyNode = 'fleet/*/battery'
for (const key of ['fleet/n7/battery', 'fleet/n7/rack/battery']) {
console.log(`${anyNode} covers ${key}: ${keyexpr.matches(anyNode, key)}`)
}
// `**` stands for any number of chunks, including none, which is what a subscription
// covering a whole subtree wants.
console.log(`fleet/** covers a nested key: ${keyexpr.matches('fleet/**', 'fleet/n7/rack/battery')}`)
console.log(
`fleet/**/battery covers fleet/battery: ${keyexpr.matches('fleet/**/battery', 'fleet/battery')}`,
)
// Two expressions that select the same keys have one canonical form. Comparing or routing
// on the written form would treat these as different subscriptions.
const written = 'fleet/**/**/battery'
const canonical = keyexpr.canonize(written)
console.log(`${written} is canonical: ${keyexpr.isCanon(written)}, and canonizes to ${canonical}`)
// A malformed expression is rejected rather than canonized into something plausible.
const malformed = 'fleet//battery'
console.log(
`${malformed} is valid: ${keyexpr.isValid(malformed)},` +
` canonizes to ${keyexpr.canonize(malformed)}`,
)Python#
From bindings/python/guides/zenoh.py:
from pamoja.zenoh import canonize, is_canon, is_valid, matches
# A key expression names a set of keys. `*` stands for exactly one chunk, so this selects
# the battery of any node, and not a battery nested deeper.
any_node = "fleet/*/battery"
for key in ("fleet/n7/battery", "fleet/n7/rack/battery"):
print(f"{any_node} covers {key}: {matches(any_node, key)}")
# `**` stands for any number of chunks, including none, which is what a subscription
# covering a whole subtree wants.
print(f"fleet/** covers a nested key: {matches('fleet/**', 'fleet/n7/rack/battery')}")
print(f"fleet/**/battery covers fleet/battery: {matches('fleet/**/battery', 'fleet/battery')}")
# Two expressions that select the same keys have one canonical form. Comparing or routing
# on the written form would treat these as different subscriptions.
written = "fleet/**/**/battery"
canonical = canonize(written)
print(f"{written} is canonical: {is_canon(written)}, and canonizes to {canonical}")
# A malformed expression is rejected rather than canonized into something plausible.
malformed = "fleet//battery"
print(f"{malformed} is valid: {is_valid(malformed)}, canonizes to {canonize(malformed)}")C##
From bindings/dotnet/samples/Pamoja.Guides/ZenohGuide.cs:
// A key expression names a set of keys. `*` stands for exactly one chunk, so this
// selects the battery of any node, and not a battery nested deeper.
const string AnyNode = "fleet/*/battery";
foreach (string key in new[] { "fleet/n7/battery", "fleet/n7/rack/battery" })
{
Console.WriteLine($"{AnyNode} covers {key}: {KeyExpression.Matches(AnyNode, key)}");
}
// `**` stands for any number of chunks, including none, which is what a
// subscription covering a whole subtree wants.
Console.WriteLine(
"fleet/** covers a nested key: "
+ KeyExpression.Matches("fleet/**", "fleet/n7/rack/battery"));
Console.WriteLine(
"fleet/**/battery covers fleet/battery: "
+ KeyExpression.Matches("fleet/**/battery", "fleet/battery"));
// Two expressions that select the same keys have one canonical form. Comparing or
// routing on the written form would treat these as different subscriptions.
const string Written = "fleet/**/**/battery";
string? canonical = KeyExpression.Canonize(Written);
Console.WriteLine(
$"{Written} is canonical: {KeyExpression.IsCanon(Written)},"
+ $" and canonizes to {canonical}");
// A malformed expression is rejected rather than canonized into something
// plausible.
const string Malformed = "fleet//battery";
Console.WriteLine(
$"{Malformed} is valid: {KeyExpression.IsValid(Malformed)},"
+ $" canonizes to {KeyExpression.Canonize(Malformed) ?? "nothing"}");Reference#
- Rust:
pamoja-zenoh, install - TypeScript:
@pamoja/zenoh, install - Python:
pamoja.zenoh, install - C#:
Pamoja.Zenoh, install