Skip to content

Commit

Permalink
feat(message): differentiate content and pubsub topic namespacing
Browse files Browse the repository at this point in the history
  • Loading branch information
Lorenzo Delgado authored Mar 7, 2023
1 parent d5f93e3 commit 67db35e
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 122 deletions.
8 changes: 4 additions & 4 deletions apps/wakubridge/message_compat.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ proc toV1Topic*(contentTopic: ContentTopic): waku_protocol.Topic {.raises: [Valu
## Extracts the 4-byte array v1 topic from a content topic
## with format `/waku/1/<v1-topic-bytes-as-hex>/rfc26`

let ns = NamespacedTopic.parse(contentTopic)
let ns = NsContentTopic.parse(contentTopic)
if ns.isErr():
let err = ns.tryError()
raise newException(ValueError, $err)
Expand All @@ -33,19 +33,19 @@ proc toV2ContentTopic*(v1Topic: waku_protocol.Topic): ContentTopic =
## with format `/waku/1/<v1-topic-bytes-as-hex>/rfc26`
##
## <v1-topic-bytes-as-hex> should be prefixed with `0x`
var namespacedTopic = NamespacedTopic()
var namespacedTopic = NsContentTopic()

namespacedTopic.application = ContentTopicApplication
namespacedTopic.version = ContentTopicAppVersion
namespacedTopic.name = "0x" & v1Topic.toHex()
namespacedTopic.name = v1Topic.to0xHex()
namespacedTopic.encoding = "rfc26"

return ContentTopic($namespacedTopic)


proc isBridgeable*(msg: WakuMessage): bool =
## Determines if a Waku v2 msg is on a bridgeable content topic
let ns = NamespacedTopic.parse(msg.contentTopic)
let ns = NsContentTopic.parse(msg.contentTopic)
if ns.isErr():
return false

Expand Down
146 changes: 120 additions & 26 deletions tests/v2/test_waku_message_topics.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,94 +6,188 @@ import
import
../../waku/v2/protocol/waku_message/topics

suite "Waku Message - Topics namespacing":
suite "Waku Message - Content topics namespacing":

test "Stringify namespaced topic":
test "Stringify namespaced content topic":
## Given
var ns = NamespacedTopic()
ns.application = "waku"
var ns = NsContentTopic()
ns.application = "toychat"
ns.version = "2"
ns.name = "default-waku"
ns.name = "huilong"
ns.encoding = "proto"

## When
let topic = $ns

## Then
check:
topic == "/waku/2/default-waku/proto"
topic == "/toychat/2/huilong/proto"

test "Parse topic string - Valid string":
test "Parse content topic string - Valid string":
## Given
let topic = "/waku/2/default-waku/proto"
let topic = "/toychat/2/huilong/proto"

## When
let nsRes = NamespacedTopic.parse(topic)
let nsRes = NsContentTopic.parse(topic)

## Then
check nsRes.isOk()

let ns = nsRes.get()
check:
ns.application == "waku"
ns.application == "toychat"
ns.version == "2"
ns.name == "default-waku"
ns.name == "huilong"
ns.encoding == "proto"

test "Parse topic string - Invalid string: doesn't start with slash":
test "Parse content topic string - Invalid string: missing leading slash":
## Given
let topic = "waku/2/default-waku/proto"
let topic = "toychat/2/huilong/proto"

## When
let ns = NamespacedTopic.parse(topic)
let ns = NsContentTopic.parse(topic)

## Then
check ns.isErr()
let err = ns.tryError()
check:
err.kind == NamespacingErrorKind.InvalidFormat
err.kind == ParsingErrorKind.InvalidFormat
err.cause == "topic must start with slash"

test "Parse topic string - Invalid string: not namespaced":
test "Parse content topic string - Invalid string: not namespaced":
## Given
let topic = "/this-is-not-namespaced"

## When
let ns = NamespacedTopic.parse(topic)
let ns = NsContentTopic.parse(topic)

## Then
check ns.isErr()
let err = ns.tryError()
check:
err.kind == NamespacingErrorKind.InvalidFormat
err.kind == ParsingErrorKind.InvalidFormat
err.cause == "invalid topic structure"


test "Parse topic string - Invalid string: missing encoding part":
test "Parse content topic string - Invalid string: missing encoding part":
## Given
let topic = "/waku/2/default-waku"
let topic = "/toychat/2/huilong"

## When
let ns = NamespacedTopic.parse(topic)
let ns = NsContentTopic.parse(topic)

## Then
check ns.isErr()
let err = ns.tryError()
check:
err.kind == NamespacingErrorKind.InvalidFormat
err.kind == ParsingErrorKind.InvalidFormat
err.cause == "invalid topic structure"

test "Parse topic string - Invalid string: too many parts":
test "Parse content topic string - Invalid string: too many parts":
## Given
let topic = "/waku/2/default-waku/proto/33"
let topic = "/toychat/2/huilong/proto/33"

## When
let ns = NamespacedTopic.parse(topic)
let ns = NsContentTopic.parse(topic)

## Then
check ns.isErr()
let err = ns.tryError()
check:
err.kind == NamespacingErrorKind.InvalidFormat
err.kind == ParsingErrorKind.InvalidFormat
err.cause == "invalid topic structure"


suite "Waku Message - Pub-sub topics namespacing":

test "Stringify named sharding pub-sub topic":
## Given
var ns = NsPubsubTopic.named("waku-dev")

## When
let topic = $ns

## Then
check:
topic == "/waku/2/waku-dev"

test "Stringify static sharding pub-sub topic":
## Given
var ns = NsPubsubTopic.staticSharding(cluster=0, shard=2)

## When
let topic = $ns

## Then
check:
topic == "/waku/2/rs/0/2"

test "Parse named pub-sub topic string - Valid string":
## Given
let topic = "/waku/2/waku-dev"

## When
let nsRes = NsPubsubTopic.parse(topic)

## Then
check nsRes.isOk()

let ns = nsRes.get()
check:
ns.name == "waku-dev"

test "Parse static sharding pub-sub topic string - Valid string":
## Given
let topic = "/waku/2/rs/16/42"

## When
let nsRes = NsPubsubTopic.parse(topic)

## Then
check nsRes.isOk()

let ns = nsRes.get()
check:
ns.cluster == 16
ns.shard == 42

test "Parse pub-sub topic string - Invalid string: invalid protocol version":
## Given
let topic = "/waku/1/rs/16/42"

## When
let ns = NsPubsubTopic.parse(topic)

## Then
check ns.isErr()
let err = ns.tryError()
check:
err.kind == ParsingErrorKind.InvalidFormat

test "Parse static sharding pub-sub topic string - Invalid string: empty shard value":
## Given
let topic = "/waku/2/rs//02"

## When
let ns = NsPubsubTopic.parse(topic)

## Then
check ns.isErr()
let err = ns.tryError()
check:
err.kind == ParsingErrorKind.MissingPart
err.part == "shard_cluster_index"


test "Parse static sharding pub-sub topic string - Invalid string: cluster value":
## Given
let topic = "/waku/2/rs/xx/77"

## When
let ns = NsPubsubTopic.parse(topic)

## Then
check ns.isErr()
let err = ns.tryError()
check:
err.kind == ParsingErrorKind.InvalidFormat
97 changes: 5 additions & 92 deletions waku/v2/protocol/waku_message/topics.nim
Original file line number Diff line number Diff line change
@@ -1,94 +1,7 @@
## Waku topics definition and namespacing utils
##
## See 14/WAKU2-MESSAGE RFC: https://rfc.vac.dev/spec/14/
## See 23/WAKU2-TOPICS RFC: https://rfc.vac.dev/spec/23/

when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}


import
std/strutils,
stew/results


## Topics

type
PubsubTopic* = string
ContentTopic* = string

const
DefaultPubsubTopic* = PubsubTopic("/waku/2/default-waku/proto")
DefaultContentTopic* = ContentTopic("/waku/2/default-content/proto")


## Namespacing

type
NamespacedTopic* = object
application*: string
version*: string
name*: string
encoding*: string

type
NamespacingErrorKind* {.pure.} = enum
InvalidFormat

NamespacingError* = object
case kind*: NamespacingErrorKind
of InvalidFormat:
cause*: string

NamespacingResult*[T] = Result[T, NamespacingError]


proc invalidFormat(T: type NamespacingError, cause = "invalid format"): T =
NamespacingError(kind: NamespacingErrorKind.InvalidFormat, cause: cause)

proc `$`*(err: NamespacingError): string =
case err.kind:
of NamespacingErrorKind.InvalidFormat:
return "invalid format: " & err.cause


proc parse*(T: type NamespacedTopic, topic: PubsubTopic|ContentTopic|string): NamespacingResult[NamespacedTopic] =
## Splits a namespaced topic string into its constituent parts.
## The topic string has to be in the format `/<application>/<version>/<topic-name>/<encoding>`

if not topic.startsWith("/"):
return err(NamespacingError.invalidFormat("topic must start with slash"))

let parts = topic.split('/')

# Check that we have an expected number of substrings
if parts.len != 5:
return err(NamespacingError.invalidFormat("invalid topic structure"))

let namespaced = NamespacedTopic(
application: parts[1],
version: parts[2],
name: parts[3],
encoding: parts[4]
)

return ok(namespaced)

proc `$`*(namespacedTopic: NamespacedTopic): string =
## Returns a string representation of a namespaced topic
## in the format `/<application>/<version>/<topic-name>/<encoding>`
var str = newString(0)

str.add("/")
str.add(namespacedTopic.application)
str.add("/")
str.add(namespacedTopic.version)
str.add("/")
str.add(namespacedTopic.name)
str.add("/")
str.add(namespacedTopic.encoding)
./topics/content_topic,
./topics/pubsub_topic

return str
export
content_topic,
pubsub_topic
Loading

0 comments on commit 67db35e

Please sign in to comment.