]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: tools: add find_word_index_in_array() to find a keyword
authorWilly Tarreau <w@1wt.eu>
Mon, 19 Oct 2026 16:22:49 +0000 (18:22 +0200)
committerWilly Tarreau <w@1wt.eu>
Wed, 21 Oct 2026 15:40:55 +0000 (17:40 +0200)
This will be used to find a word among a set in an array of
arbitrary size and layout provided that the words are always
accessible via a pointer at a fixed location in each item. The
goal here is to simplify call places which want to match a word
among a list, find the associated entry, and propose a list of
possible matches.

This will return the following values depending on the looked up
word:
  0 = not found
  1 = found a different one which looks like a candidate
  2 = found exact match

When non-zero is returned, an index pointer will designate the
associated entry in the array so that the caller can find the
context of the sought word, or at least report the mostly similar
one in the error message, like this:

    size_t idx;

    match = find_word_index_in_array(kw_array, sizeof(kw_array[0]), -1,
                                     offsetof(struct keyword, name), word, &idx);
    if (match != 2) {
            memprintf(err, "Invalid keyword '%s'.", word);
            if (match == 1)
                    memprintf(err, "%s Maybe you meant '%s' ?", *err, kw_array[idx].name);
            goto fail;
    }

include/haproxy/tools.h
src/tools.c

index da95ba79e08e35cfcb3fca8b19828b73640d61a6..40e655dbdff1181ad153615e9fe997487c85ff09 100644 (file)
@@ -1020,6 +1020,8 @@ void update_word_fingerprint(uint8_t *fp, const char *word);
 void make_word_fingerprint_with_len(uint8_t *fp, struct ist word);
 void make_word_fingerprint(uint8_t *fp, const char *word);
 int word_fingerprint_distance(const uint8_t *fp1, const uint8_t *fp2);
+int find_word_index_in_array(const void *array, size_t item_sz, size_t nbelem,
+                             size_t ofs, const char *word, size_t *idx);
 
 /* debugging macro to emit messages using write() on fd #-1 so that strace sees
  * them. It relies on variadic macros with optional arguments so that any
index 40c3a22a785dd99a92b78a65794330dedf852894..16936985b28c72ca99d9d243196786b43b47c8da 100644 (file)
@@ -7011,6 +7011,62 @@ struct ist env_suggest(struct ist word)
        return best_name;
 }
 
+/* Look up word <word> among array of words <array>, made of elements of size
+ * <item_sz>. The pointer to the word to find in the array must be at offset
+ * <ofs> in the element. If <nbelem> is >= 0, no word will be looked up past
+ * this number of elements. In any case, a NULL pointer to a word marks the
+ * end of the list. On return, if <idx> is not NULL, it will be set to the
+ * index of the element found, the closest one, or won't be touched if no
+ * element is found. The return value is:
+ *   0 : no element found
+ *   1 : approximate match at position <idx>
+ *   2 : exact match at position <idx>
+ */
+int find_word_index_in_array(const void *array, size_t item_sz, size_t nbelem,
+                             size_t ofs, const char *word, size_t *idx)
+{
+       uint8_t word_sig[1024];
+       uint8_t list_sig[1024];
+       const char *best_ptr = NULL;
+       int dist, best_dist = INT_MAX;
+       const char **match;
+       size_t elem, best_elem = 0;
+
+       make_word_fingerprint(word_sig, word);
+
+       for (elem = 0; elem < nbelem; elem++) {
+               match = (const char **)((const char *)array + ofs + elem * item_sz);
+               if (!*match)
+                       break;
+
+               if (strcmp(word, *match) == 0) {
+                       /* found exact match */
+                       if (idx)
+                               *idx = elem;
+                       return 2;
+               }
+               make_word_fingerprint(list_sig, *match);
+               dist = word_fingerprint_distance(word_sig, list_sig);
+               if (dist < best_dist) {
+                       best_dist = dist;
+                       best_ptr = *match;
+                       best_elem = elem;
+               }
+       }
+
+       if (best_dist > 2 * strlen(word) || (best_ptr && best_dist > 2 * strlen(best_ptr)))
+               best_ptr = NULL;
+
+       if (best_ptr) {
+               /* found approximate match */
+               if (idx)
+                       *idx = best_elem;
+               return 1;
+       }
+
+       /* not found */
+       return 0;
+}
 
 /* This function hashes a word, scramble is the anonymizing key, returns
  * the hashed word when the key (scramble) != 0, else returns the word.