1 /* =========================================================================
2 ztrie - simple trie for tokenizable strings
3
4 Copyright (c) the Contributors as noted in the AUTHORS file.
5 This file is part of CZMQ, the high-level C binding for 0MQ:
6 http://czmq.zeromq.org.
7
8 This Source Code Form is subject to the terms of the Mozilla Public
9 License, v. 2.0. If a copy of the MPL was not distributed with this
10 file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 =========================================================================
12 */
13
14 /*
15 @header
16 This is a variant of a trie or prefix tree where all the descendants of a
17 node have a common prefix of the string associated with that node. This
18 implementation is specialized for strings that can be tokenized by a delimiter
19 like a URL, URI or URN. Routes in the tree can be matched by regular expressions
20 and by using capturing groups parts of a matched route can be easily obtained.
21 @discuss
22 Note that the performance for pure string based matching is okay but on short
23 strings zhash and zhashx are 3-4 times faster.
24 @end
25 */
26
27 #include "czmq_classes.h"
28
29 #define MODE_INSERT 0
30 #define MODE_LOOKUP 1
31 #define MODE_MATCH 2
32
33 #define NODE_TYPE_STRING 0 // Node with string token
34 #define NODE_TYPE_REGEX 1 // Node with regex token
35 #define NODE_TYPE_PARAM 2 // Node with named regex token and capturing group(s)
36 #define NODE_TYPE_ASTERISK 3 // Node with an asterisk, to match routes of variable length
37
38 #define MIN_LEN(x,y) \
39 y + ((x - y) & ((x - y) >>(sizeof(int) * CHAR_BIT - 1)))
40
41
42 // TODO: Move to a more appropriate location:
43 static char *
s_strndup(const char * s,size_t size)44 s_strndup (const char *s, size_t size) {
45 char *dup;
46 char *end = (char *) memchr (s, '\0', size);
47 if (end)
48 size = end - 1 - s; // -1 to get the last char before '\0'
49 dup = (char *) zmalloc (sizeof (char) * size + 1); // +1 for trailing '\0'
50 if (size) {
51 memcpy (dup, s, size);
52 dup [size] = '\0';
53 }
54 return dup;
55 }
56
57 // Trie node, used internally only
58
59 typedef struct _ztrie_node_t {
60 char *token; // Part of a path between to delemiters e.g. '/{token}/'
61 int token_type; // Type of the token, string, regex, asterisk
62 int token_len; // Number of characters of the token
63 size_t path_len; // Length of the path/route including this token
64 bool endpoint; // Has a path been added that routes to this node?
65 size_t parameter_count; // How many regex parameters does this token contain?
66 char **parameter_names; // Names of the regex parameters for easy access at matching time
67 char **parameter_values; // Values of the parameters
68 char *asterisk_match; // Matched asterisk route
69 zrex_t *regex; // Compiled regex
70 void *data; // Custom arbitrary data assoziated with a route
71 ztrie_destroy_data_fn *destroy_data_fn; // Callback to destroy custom data
72 zlistx_t *children; // List of all children to this route
73 struct _ztrie_node_t *parent; // Parent of this node
74 } ztrie_node_t;
75
76 // --------------------------------------------------------------------------
77 // Structure of our class
78
79 struct _ztrie_t {
80 char delimiter; // Character that seperates the tokens of a route
81 ztrie_node_t *root; // Root node of this tree
82 ztrie_node_t *match; // Last match made by ztrie_matches
83 zlistx_t *params; // List of regex parameters found during parsing
84 // The list is kept globally to optimize performance.
85 };
86
87 // Internal helper functions
88
89 static int
s_ztrie_node_compare(const void * item1,const void * item2)90 s_ztrie_node_compare (const void *item1, const void *item2) {
91 assert (item1);
92 assert (item2);
93 ztrie_node_t *node1 = (ztrie_node_t *) item1;
94 ztrie_node_t *node2 = (ztrie_node_t *) item2;
95
96 int same = node1->token_type - node2->token_type;
97 if (same == 0) {
98 if (*node1->token == *node2->token // This achieves a small performance boost
99 && node1->token_len == node2->token_len
100 && strncmp (node1->token, node2->token, MIN_LEN (node1->token_len, node2->token_len)) == 0)
101 return 0;
102 else
103 return -1;
104 }
105 else
106 return same;
107 }
108
109
110 // Create a new ztrie_node. If parent is not NULL the child will add itself
111 // to the parents children.
112
113 static ztrie_node_t *
s_ztrie_node_new(ztrie_node_t * parent,char * token,int token_len,zlistx_t * param_keys,int token_type)114 s_ztrie_node_new (ztrie_node_t *parent, char *token, int token_len, zlistx_t *param_keys, int token_type)
115 {
116 ztrie_node_t *self = (ztrie_node_t *) zmalloc (sizeof (ztrie_node_t));
117 assert (self);
118
119 // Initialize properties
120 self->token = s_strndup (token, token_len);
121 self->token_type = token_type;
122 self->token_len = token_len;
123 self->endpoint = false;
124 self->parameter_count = 0;
125 self->parameter_names = NULL;
126 self->parameter_values = NULL;
127 if (param_keys && zlistx_size (param_keys) > 0) {
128 self->parameter_count = zlistx_size (param_keys);
129 self->parameter_names = (char **) malloc (sizeof (char *) * self->parameter_count);
130 self->parameter_values = (char **) malloc (sizeof (char *) * self->parameter_count);
131 char *key = (char *) zlistx_first (param_keys);
132 size_t index;
133 for (index = 0; index < zlistx_size (param_keys); index++) {
134 self->parameter_names [index] = key;
135 self->parameter_values [index] = NULL;
136 key = (char *) zlistx_next (param_keys);
137 }
138 }
139 if (self->token_type == NODE_TYPE_REGEX || self->token_type == NODE_TYPE_PARAM)
140 self->regex = zrex_new (self->token);
141 self->data = NULL;
142 // Initialize relationships
143 self->parent = parent;
144 if (self->parent) {
145 zlistx_add_end (self->parent->children, self);
146 // Sort regexes to the end to avoid conlficts
147 zlistx_sort (self->parent->children);
148 }
149 size_t parent_path_len = self->parent? self->parent->path_len: 0;
150 self->path_len = parent_path_len + strlen (self->token) + 1; // +1 path delimiter
151 self->children = zlistx_new ();
152 zlistx_set_comparator (self->children, s_ztrie_node_compare);
153
154 return self;
155 }
156
157
158 // Destroy the ztrie_node.
159
160 static void
s_ztrie_node_destroy(ztrie_node_t ** self_p)161 s_ztrie_node_destroy (ztrie_node_t **self_p)
162 {
163 assert (self_p);
164 if (*self_p) {
165 ztrie_node_t *self = *self_p;
166
167 // Free class properties
168 zstr_free (&self->token);
169 zstr_free (&self->asterisk_match);
170 if (self->parameter_count > 0) {
171 size_t index;
172 for (index = 0; index < self->parameter_count; index++) {
173 freen (self->parameter_names [index]);
174 if (self->parameter_values [index])
175 freen (self->parameter_values [index]);
176 }
177 freen (self->parameter_names);
178 freen (self->parameter_values);
179 }
180 if (self->token_type == NODE_TYPE_REGEX || self->token_type == NODE_TYPE_PARAM)
181 zrex_destroy (&self->regex);
182 zlistx_destroy (&self->children);
183 if (self->data && self->destroy_data_fn)
184 (self->destroy_data_fn) (&self->data);
185
186 // Free object itself
187 freen (self);
188 *self_p = NULL;
189 }
190 }
191
192
193 // Update the value of a regex parameter at position.
194
195 static void
s_ztrie_node_update_param(ztrie_node_t * self,int pos,const char * value)196 s_ztrie_node_update_param (ztrie_node_t *self, int pos, const char *value)
197 {
198 assert (self);
199 zstr_free (&self->parameter_values [pos - 1]);
200 self->parameter_values [pos - 1] = strdup (value);
201 }
202
203
204 // --------------------------------------------------------------------------
205 // Creates a new ztrie.
206
207 ztrie_t *
ztrie_new(char delimiter)208 ztrie_new (char delimiter)
209 {
210 ztrie_t *self = (ztrie_t *) zmalloc (sizeof (ztrie_t));
211 assert (self);
212
213 // Initialize properties
214 if (delimiter)
215 self->delimiter = delimiter;
216 else
217 self->delimiter = '/';
218 self->root = s_ztrie_node_new (NULL, "", 0, NULL, NODE_TYPE_STRING);
219 self->match = NULL;
220 self->params = zlistx_new ();
221
222 return self;
223 }
224
225 // Destroy all children of a node
226
227 static void
s_ztrie_destroy_children(ztrie_node_t * node)228 s_ztrie_destroy_children (ztrie_node_t *node)
229 {
230 ztrie_node_t *child = (ztrie_node_t *) zlistx_first (node->children);
231 while (child) {
232 s_ztrie_destroy_children (child);
233 s_ztrie_node_destroy (&child);
234 child = (ztrie_node_t *) zlistx_next (node->children);
235 }
236 }
237
238 // --------------------------------------------------------------------------
239 // Destroy the ztrie.
240
241 void
ztrie_destroy(ztrie_t ** self_p)242 ztrie_destroy (ztrie_t **self_p)
243 {
244 assert (self_p);
245 if (*self_p) {
246 ztrie_t *self = *self_p;
247
248 // Free class properties
249 s_ztrie_destroy_children (self->root);
250 s_ztrie_node_destroy (&self->root);
251 zlistx_destroy (&self->params);
252
253 // Free object itself
254 freen (self);
255 *self_p = NULL;
256 }
257 }
258
259 // String compare token
260
261 static ztrie_node_t *
s_ztrie_compare_token(ztrie_node_t * parent,char * token,int len)262 s_ztrie_compare_token (ztrie_node_t *parent, char *token, int len)
263 {
264 ztrie_node_t *child = (ztrie_node_t *) zlistx_first (parent->children);
265 while (child) {
266 if (child->token_len == len &&
267 strncmp (child->token, token, MIN_LEN (child->token_len, len)) == 0)
268 return child;
269 child = (ztrie_node_t *) zlistx_next (parent->children);
270 }
271 return NULL;
272 }
273
274 // String compare token or evaluate regexes
275
276 static ztrie_node_t *
s_ztrie_matches_token(ztrie_node_t * parent,char * token,int len)277 s_ztrie_matches_token (ztrie_node_t *parent, char *token, int len)
278 {
279 char firstbyte = *token;
280 ztrie_node_t *child = (ztrie_node_t *) zlistx_first (parent->children);
281 while (child) {
282 if (child->token_type == NODE_TYPE_STRING) {
283 if (firstbyte == *child->token // This achieves a small performance boost
284 && child->token_len == len
285 && strncmp (child->token, token, MIN_LEN (child->token_len, len)) == 0)
286 return child;
287 }
288 else
289 if (child->token_type == NODE_TYPE_ASTERISK) {
290 child->asterisk_match = strdup (token);
291 return child;
292 }
293 else {
294 // Need to copy token as zrex_matches expects '\0' terminated string
295 char *token_term = s_strndup (token, len);
296 if (zrex_matches (child->regex, token_term)) {
297 if (child->token_type == NODE_TYPE_PARAM) {
298 // One hit means no capturing group was defined
299 // More than one hit indicates that at least on capturing group.
300 // In this case only the values of the capturing groups are considered.
301 if (zrex_hits (child->regex) == 1)
302 s_ztrie_node_update_param (child, 1, zrex_hit (child->regex, 0));
303 else
304 if (zrex_hits (child->regex) > 1) {
305 int index;
306 for (index = 1; index < zrex_hits (child->regex); index++)
307 s_ztrie_node_update_param (child, index, zrex_hit (child->regex, index));
308 }
309 }
310 freen (token_term);
311 return child;
312 }
313 freen (token_term);
314 }
315 child = (ztrie_node_t *) zlistx_next (parent->children);
316 }
317 return NULL;
318 }
319
320
321 // --------------------------------------------------------------------------
322 // Parses a path bytewise trying to find matches for path tokens. Depending
323 // on the mode there are different behaviors on,
324 // how the tokens are compared:
325 // MODE_INSERT: All tokens are compared as strings.
326 // MODE_LOOKUP: All tokens are compared as strings.
327 // MODE_MATCH: Node tokens of NODE_TYPE_STRING tokens are compared as strings,
328 // otherwise the tokens are matched against the nodes regex.
329 // how a mismatch is handled:
330 // MODE_INSERT: creates a new node and attaches it to the common prefix for
331 // the given path, repeat for the remaining path tokens.
332 // returns the node that has been attached last.
333 // MODE_LOOKUP: returns NULL if the comparison failed.
334 // MODE_MATCH: returns NULL if the comparison failed.
335
336 static ztrie_node_t *
s_ztrie_parse_path(ztrie_t * self,const char * path,int mode)337 s_ztrie_parse_path (ztrie_t *self, const char *path, int mode)
338 {
339 int state = 0;
340 char *needle, *beginToken = NULL, *beginRegex = NULL;
341 ztrie_node_t *parent = self->root;
342 if (zlistx_size (self->params) > 0)
343 zlistx_purge (self->params);
344
345 size_t len = strlen (path);
346 needle = (char *) path;
347 char *needle_stop = needle + len;
348 // Ignore trailing delimiter
349 if (needle [len-1] == self->delimiter)
350 needle_stop -= 1;
351 while (needle < needle_stop + 1) {
352 // It is valid not to have an delimiter at the end of the path
353 if (*needle == self->delimiter || needle == needle_stop) {
354 // Token starts with delimiter ignore everything that comes before
355 if (state == 0) {
356 beginToken = needle + 1;
357 state++;
358 if (mode == MODE_INSERT || mode == MODE_LOOKUP)
359 // Increment so regexes are parsed which is only relevant
360 // during INSERT or LOOKUP. Using different states gives a small
361 // performance boost for matching.
362 state++;
363 }
364 // Token ends with delimiter.
365 else
366 if (state < 3) {
367 int matchType = zlistx_size (self->params) > 0? NODE_TYPE_PARAM:
368 beginRegex? NODE_TYPE_REGEX: NODE_TYPE_STRING;
369 char *matchToken = beginRegex? beginRegex: beginToken;
370 int matchTokenLen = (int) (needle - matchToken) - (beginRegex? 1: 0);
371 // Illegal token
372 if (matchTokenLen == 0)
373 return NULL;
374 ztrie_node_t *match = NULL;
375 // Asterisk nodes are only allowed at the end of a route
376 if (needle == needle_stop && *matchToken == '*') {
377 if (zlistx_size (parent->children) == 0) {
378 matchType = NODE_TYPE_ASTERISK;
379 matchToken = needle - 1;
380 matchTokenLen = 1;
381 }
382 // Asterisk must be a leaf in the tree
383 else
384 return NULL;
385 }
386 else {
387 matchType = zlistx_size (self->params) > 0? NODE_TYPE_PARAM:
388 beginRegex? NODE_TYPE_REGEX: NODE_TYPE_STRING;
389 matchToken = beginRegex? beginRegex: beginToken;
390 matchTokenLen = (int) (needle - matchToken) - (beginRegex? 1: 0);
391 }
392
393 // In insert and lookup mode only do a string comparison
394 if (mode == MODE_INSERT || mode == MODE_LOOKUP)
395 match = s_ztrie_compare_token (parent, matchToken, matchTokenLen);
396 else
397 // Otherwise evaluate regexes
398 if (mode == MODE_MATCH)
399 match = s_ztrie_matches_token (parent, matchToken, matchTokenLen);
400
401 // Mismatch behavior depends on mode
402 if (!match) {
403 // Append to common prefix
404 if (mode == MODE_INSERT) {
405 // It's not allowed to append on asterisk
406 if (parent->token_type == NODE_TYPE_ASTERISK ||
407 (zlistx_size (parent->children) == 1 &&
408 ((ztrie_node_t *) (zlistx_first (parent->children)))->token_type == NODE_TYPE_ASTERISK))
409 return NULL;
410 parent = s_ztrie_node_new (parent, matchToken, matchTokenLen, self->params, matchType);
411 }
412 else
413 // No match for path found
414 if (mode == MODE_MATCH || mode == MODE_LOOKUP)
415 return NULL;
416 }
417 // If a match has been found it becomes the parent for next path token
418 else {
419 parent = match;
420 // In case a asterisk match has been made skip the rest of the route
421 if (parent->token_type == NODE_TYPE_ASTERISK)
422 break;
423 }
424 // Cleanup for next token
425 beginRegex = NULL;
426 if (zlistx_size (self->params) > 0)
427 zlistx_purge (self->params);
428 // Token end equals token begin
429 beginToken = needle + 1;
430 }
431 }
432 else
433 // regex starts with '{'
434 if (state == 2 && *needle == '{') {
435 beginRegex = needle + 1;
436 state++;
437 }
438 else
439 // in the middle of the regex. Found a named regex.
440 if (state == 3 && (*needle == ':')) {
441 zlistx_add_end (self->params, s_strndup (beginRegex, needle - beginRegex));
442 beginRegex = needle + 1;
443
444 }
445 else
446 // regex ends with {
447 if (state == 3 && *needle == '}') {
448 state--;
449 }
450 needle++;
451 }
452
453 // In matching mode the discovered node must be an endpoint
454 if (parent && mode == MODE_MATCH && !parent->endpoint)
455 return NULL;
456
457 return parent;
458 }
459
460
461 // --------------------------------------------------------------------------
462 // Inserts a new route into the trie and attaches the data. Returns -1
463 // if the route already exists, otherwise 0. This method takes ownership of
464 // the provided data.
465
466 int
ztrie_insert_route(ztrie_t * self,const char * path,void * data,ztrie_destroy_data_fn destroy_data_fn)467 ztrie_insert_route (ztrie_t *self, const char *path, void *data, ztrie_destroy_data_fn destroy_data_fn)
468 {
469 assert (self);
470 ztrie_node_t *node = s_ztrie_parse_path (self, path, MODE_INSERT);
471 // If the returned node has no endpoint, a new route can be assigned to it.
472 if (node && !node->endpoint) {
473 node->endpoint = true;
474 node->data = data;
475 node->destroy_data_fn = destroy_data_fn;
476 return 0;
477 }
478 // If the returned node has an endpoint, a route has already assigned to it.
479 else
480 return -1;
481 }
482
483
484 // --------------------------------------------------------------------------
485 // Removes a route from the trie and destroys its data. Returns -1 if the
486 // route does not exists, otherwise 0.
487
488 int
ztrie_remove_route(ztrie_t * self,const char * path)489 ztrie_remove_route (ztrie_t *self, const char *path)
490 {
491 assert (self);
492 ztrie_node_t *match = s_ztrie_parse_path (self, path, MODE_LOOKUP);
493 // The path did match a node which is endpoint to a route
494 if (match && match->endpoint) {
495 // This node is part of other routes, thus it cannot destroy it
496 if (zlistx_size (match->children) > 0) {
497 match->endpoint = false;
498 if (match->data && match->destroy_data_fn)
499 (match->destroy_data_fn) (&match->data);
500 }
501 // If this node is not part of other routes, destroy it
502 else {
503 // Delete match from parent's children before destroying
504 void *handle = zlistx_find (match->parent->children, match);
505 assert (handle);
506 zlistx_delete (match->parent->children, handle);
507 s_ztrie_node_destroy (&match);
508 }
509 return 0;
510 }
511 // If there is no match or the match is not endpoint to a route, fail
512 else
513 return -1;
514 }
515
516 // --------------------------------------------------------------------------
517 // Returns true if the path matches a route in the tree, otherwise false.
518
519 bool
ztrie_matches(ztrie_t * self,const char * path)520 ztrie_matches (ztrie_t *self, const char *path)
521 {
522 assert (self);
523 self->match = s_ztrie_parse_path (self, path, MODE_MATCH);
524 return self->match? true: false;
525 }
526
527
528 // --------------------------------------------------------------------------
529 // Returns the data of a matched route from last ztrie_matches. If the path
530 // did not match, returns NULL. Do not delete the data as it's owned by
531 // ztrie.
532
533 void *
ztrie_hit_data(ztrie_t * self)534 ztrie_hit_data (ztrie_t *self)
535 {
536 assert (self);
537 if (self->match)
538 return self->match->data;
539 return NULL;
540 }
541
542
543 // --------------------------------------------------------------------------
544 // Returns the count of parameters that a matched route has.
545
546 size_t
ztrie_hit_parameter_count(ztrie_t * self)547 ztrie_hit_parameter_count (ztrie_t *self)
548 {
549 size_t count = 0;
550 ztrie_node_t *node = self->match;
551 while (node) {
552 count += node->parameter_count;
553 node = node->parent;
554 }
555 return count;
556 }
557
558 // --------------------------------------------------------------------------
559 // Returns the parameters of a matched route with named regexes from last
560 // ztrie_matches. If the path did not match or the route did not contain any
561 // named regexes, returns NULL. The caller is responseable for destroy the map.
562
563 zhashx_t *
ztrie_hit_parameters(ztrie_t * self)564 ztrie_hit_parameters (ztrie_t *self)
565 {
566 assert (self);
567 if (self->match) {
568 zhashx_t *route_parameters = zhashx_new ();
569 ztrie_node_t *node = self->match;
570 while (node) {
571 size_t index;
572 for (index = 0; index < node->parameter_count; index++)
573 zhashx_insert (route_parameters,
574 node->parameter_names [index],
575 (void *) node->parameter_values [index]);
576 node = node->parent;
577 }
578 return route_parameters;
579 }
580 return NULL;
581 }
582
583
584 // --------------------------------------------------------------------------
585 // Returns the asterisk matched part of a route, if there has been no match
586 // or no asterisk match, returns NULL.
587
588 const char *
ztrie_hit_asterisk_match(ztrie_t * self)589 ztrie_hit_asterisk_match (ztrie_t *self)
590 {
591 assert (self);
592 if (self->match)
593 return self->match->asterisk_match;
594 return NULL;
595 }
596
597 // --------------------------------------------------------------------------
598 // Print properties of the ztrie object.
599 //
600
601 static void
s_ztrie_print_tree_line(ztrie_node_t * self,bool end_line)602 s_ztrie_print_tree_line (ztrie_node_t *self, bool end_line)
603 {
604 if (self->parent) {
605 s_ztrie_print_tree_line (self->parent, false);
606 if (zlistx_tail (self->parent->children) == self) {
607 if (end_line)
608 printf ("`-- ");
609 else
610 printf (" ");
611 }
612 else {
613 if (end_line)
614 printf ("+-- ");
615 else
616 printf ("| ");
617 }
618 if (end_line) {
619 char *is_endpoint = self->endpoint? "true": "false";
620 printf ("%s (params: %zu, endpoint: %s, type: %d)\n",
621 self->token, self->parameter_count, is_endpoint, self->token_type);
622 }
623 }
624 }
625
626 static void
s_ztrie_print_tree(ztrie_node_t * self)627 s_ztrie_print_tree (ztrie_node_t *self)
628 {
629 // Print tree like structure
630 s_ztrie_print_tree_line (self, true);
631 ztrie_node_t *child = (ztrie_node_t *) zlistx_first (self->children);
632 while (child) {
633 s_ztrie_print_tree (child);
634 child = (ztrie_node_t *) zlistx_next (self->children);
635 }
636 }
637
638 void
ztrie_print(ztrie_t * self)639 ztrie_print (ztrie_t *self)
640 {
641 assert (self);
642 s_ztrie_print_tree (self->root);
643 }
644
645
646 // --------------------------------------------------------------------------
647 // Self test of this class.
648
649 void
ztrie_test(bool verbose)650 ztrie_test (bool verbose)
651 {
652 printf (" * ztrie: ");
653 // @selftest
654 // Create a new trie for matching strings that can be tokenized by a slash
655 // (e.g. URLs minus the protocol, address and port).
656 ztrie_t *self = ztrie_new ('/');
657 assert (self);
658
659 int ret = 0;
660
661 // Let's start by inserting a couple of routes into the trie.
662 // This one is for the route '/foo/bar' the slash at the beginning of the
663 // route is important because everything before the first delimiter will be
664 // discarded. A slash at the end of a route is optional though. The data
665 // associated with this node is passed without destroy function which means
666 // it must be destroyed by the caller.
667 int foo_bar_data = 10;
668 ret = ztrie_insert_route (self, "/foo/bar", &foo_bar_data, NULL);
669 assert (ret == 0);
670
671 // Now suppose we like to match all routes with two tokens that start with
672 // '/foo/' but aren't '/foo/bar'. This is possible by using regular
673 // expressions which are enclosed in an opening and closing curly bracket.
674 // Tokens that contain regular expressions are always match after string
675 // based tokens.
676 // Note: There is no order in which regular expressions are sorted thus
677 // if you enter multiple expressions for a route you will have to make
678 // sure they don't have overlapping results. For example '/foo/{[^/]+}'
679 // and '/foo/{\d+} having could turn out badly.
680 int foo_other_data = 100;
681 ret = ztrie_insert_route (self, "/foo/{[^/]+}", &foo_other_data, NULL);
682 assert (ret == 0);
683
684 // Regular expression are only matched against tokens of the same level.
685 // This allows us to append to are route with a regular expression as if
686 // it were a string.
687 ret = ztrie_insert_route (self, "/foo/{[^/]+}/gulp", NULL, NULL);
688 assert (ret == 0);
689
690 // Routes are identified by their endpoint, which is the last token of the route.
691 // It is possible to insert routes for a node that already exists but isn't an
692 // endpoint yet. The delimiter at the end of a route is optional and has no effect.
693 ret = ztrie_insert_route (self, "/foo/", NULL, NULL);
694 assert (ret == 0);
695
696 // If you try to insert a route which already exists the method will return -1.
697 ret = ztrie_insert_route (self, "/foo", NULL, NULL);
698 assert (ret == -1);
699
700 // It is not allowed to insert routes with empty tokens.
701 ret = ztrie_insert_route (self, "//foo", NULL, NULL);
702 assert (ret == -1);
703
704 // Everything before the first delimiter is ignored so 'foo/bar/baz' is equivalent
705 // to '/bar/baz'.
706 ret = ztrie_insert_route (self, "foo/bar/baz", NULL, NULL);
707 assert (ret == 0);
708 ret = ztrie_insert_route (self, "/bar/baz", NULL, NULL);
709 assert (ret == -1);
710
711 // Of course you are allowed to remove routes, in case there is data associated with a
712 // route and a destroy data function has been supplied that data will be destroyed.
713 ret = ztrie_remove_route (self, "/foo");
714 assert (ret == 0);
715
716 // Removing a non existent route will as well return -1.
717 ret = ztrie_remove_route (self, "/foo");
718 assert (ret == -1);
719
720 // Removing a route with a regular expression must exactly match the entered one.
721 ret = ztrie_remove_route (self, "/foo/{[^/]+}");
722 assert (ret == 0);
723
724 // Next we like to match a path by regular expressions and also extract matched
725 // parts of a route. This can be done by naming the regular expression. The name of a
726 // regular expression is entered at the beginning of the curly brackets and separated
727 // by a colon from the regular expression. The first one in this examples is named
728 // 'name' and names the expression '[^/]'. If there is no capturing group defined in
729 // the expression the whole matched string will be associated with this parameter. In
730 // case you don't like the get the whole matched string use a capturing group, like
731 // it has been done for the 'id' parameter. This is nice but you can even match as
732 // many parameter for a token as you like. Therefore simply put the parameter names
733 // separated by colons in front of the regular expression and make sure to add a
734 // capturing group for each parameter. The first parameter will be associated with
735 // the first capturing and so on.
736 char *data = (char *) malloc (80);
737 sprintf (data, "%s", "Hello World!");
738 ret = ztrie_insert_route (self, "/baz/{name:[^/]+}/{id:--(\\d+)}/{street:nr:(\\a+)(\\d+)}", data, NULL);
739 assert (ret == 0);
740
741 // There is a lot you can do with regular expression but matching routes
742 // of arbitrary length wont work. Therefore we make use of the asterisk
743 // operator. Just place it at the end of your route, e.g. '/config/bar/*'.
744 ret = ztrie_insert_route (self, "/config/bar/*", NULL, NULL);
745 assert (ret == 0);
746
747 // Appending to an asterisk as you would to with a regular expression
748 // isn't valid.
749 ret = ztrie_insert_route (self, "/config/bar/*/bar", NULL, NULL);
750 assert (ret == -1);
751
752 // The asterisk operator will only work as a leaf in the tree. If you
753 // enter an asterisk in the middle of your route it will simply be
754 // interpreted as a string.
755 ret = ztrie_insert_route (self, "/test/*/bar", NULL, NULL);
756 assert (ret == 0);
757
758 // If a parent has an asterisk as child it is not allowed to have
759 // other siblings.
760 ret = ztrie_insert_route (self, "/config/bar/foo/glup", NULL, NULL);
761 assert (ret != 0);
762
763 // Test matches
764 bool hasMatch = false;
765
766 // The route '/bar/foo' will fail to match as this route has never been inserted.
767 hasMatch = ztrie_matches (self, "/bar/foo");
768 assert (!hasMatch);
769
770 // The route '/foo/bar' will match and we can obtain the data associated with it.
771 hasMatch = ztrie_matches (self, "/foo/bar");
772 assert (hasMatch);
773 int foo_bar_hit_data = *((int *) ztrie_hit_data (self));
774 assert (foo_bar_data == foo_bar_hit_data);
775
776 // This route is part of another but is no endpoint itself thus the matches will fail.
777 hasMatch = ztrie_matches (self, "/baz/blub");
778 assert (!hasMatch);
779
780 // This route will match our named regular expressions route. Thus we can extract data
781 // from the route by their names.
782 hasMatch = ztrie_matches (self, "/baz/blub/--11/abc23");
783 assert (hasMatch);
784 char *match_data = (char *) ztrie_hit_data (self);
785 assert (streq ("Hello World!", match_data));
786 zhashx_t *parameters = ztrie_hit_parameters (self);
787 assert (zhashx_size (parameters) == 4);
788 assert (streq ("blub", (char *) zhashx_lookup (parameters, "name")));
789 assert (streq ("11", (char *) zhashx_lookup (parameters, "id")));
790 assert (streq ("abc", (char *) zhashx_lookup (parameters, "street")));
791 assert (streq ("23", (char *) zhashx_lookup (parameters, "nr")));
792 zhashx_destroy (¶meters);
793
794 // This will match our asterisk route '/config/bar/*'. As the result we
795 // can obtain the asterisk matched part of the route.
796 hasMatch = ztrie_matches (self, "/config/bar/foo/bar");
797 assert (hasMatch);
798 assert (streq (ztrie_hit_asterisk_match (self), "foo/bar"));
799
800 zstr_free (&data);
801 ztrie_destroy (&self);
802
803 #if defined (__WINDOWS__)
804 zsys_shutdown();
805 #endif
806 // @end
807
808 printf ("OK\n");
809 }
810