]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: cfgparse/proxy: also support spelling fixes on options
authorWilly Tarreau <w@1wt.eu>
Mon, 15 Mar 2021 10:11:55 +0000 (11:11 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 15 Mar 2021 10:14:57 +0000 (11:14 +0100)
Some are not always easy to spot with "chk" vs "check" or hyphens at
some places and not at others. Now entering "option http-close" properly
suggests "httpclose" and "option tcp-chk" suggests "tcp-check". There's
no need to consider the proxy's capabilities, what matters is to figure
what related word the user tried to spell, and there are not that many
options anyway.

include/haproxy/proxy.h
src/cfgparse-listen.c
src/proxy.c

index 69cde1df53301d21d1109d78ac859f670004d30a..b81783a938991b0adf2558bb7baa746f158b4f28 100644 (file)
@@ -49,6 +49,7 @@ int  stream_set_backend(struct stream *s, struct proxy *be);
 
 const char *proxy_cap_str(int cap);
 const char *proxy_mode_str(int mode);
+const char *proxy_find_best_option(const char *word, const char **extra);
 void proxy_store_name(struct proxy *px);
 struct proxy *proxy_find_by_id(int id, int cap, int table);
 struct proxy *proxy_find_by_name(const char *name, int cap, int table);
index ac5002536abbe0f20c01cdedf3d402659488a9b3..07878330f67e3fb3e501e3ec8e5a5eada0ae041b 100644 (file)
@@ -54,6 +54,15 @@ static const char *common_kw_list[] = {
        NULL /* must be last */
 };
 
+static const char *common_options[] = {
+       "httpclose", "forceclose", "http-server-close", "http-keep-alive",
+       "http-tunnel", "redispatch", "httplog", "tcplog", "tcpka", "httpchk",
+       "ssl-hello-chk", "smtpchk", "pgsql-check", "redis-check",
+       "mysql-check", "ldap-check", "spop-check", "tcp-check",
+       "external-check", "forwardfor", "original-to",
+       NULL /* must be last */
+};
+
 /* Report a warning if a rule is placed after a 'tcp-request session' rule.
  * Return 1 if the warning has been emitted, otherwise 0.
  */
@@ -2238,7 +2247,13 @@ stats_error_parsing:
                        } /* end while loop */
                }
                else {
-                       ha_alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]);
+                       const char *best = proxy_find_best_option(args[1], common_options);
+
+                       if (best)
+                               ha_alert("parsing [%s:%d] : unknown option '%s'; did you mean '%s' maybe ?\n", file, linenum, args[1], best);
+                       else
+                               ha_alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]);
+
                        err_code |= ERR_ALERT | ERR_FATAL;
                        goto out;
                }
index a9a49439d6beec9dc3f6e2c9a77e1ac5472d9214..843941e5a08f54ea94fb48be24b08c5c66555292 100644 (file)
@@ -152,6 +152,55 @@ const char *proxy_mode_str(int mode) {
                return "unknown";
 }
 
+/* try to find among known options the one that looks closest to <word> by
+ * counting transitions between letters, digits and other characters. Will
+ * return the best matching word if found, otherwise NULL. An optional array
+ * of extra words to compare may be passed in <extra>, but it must then be
+ * terminated by a NULL entry. If unused it may be NULL.
+ */
+const char *proxy_find_best_option(const char *word, const char **extra)
+{
+       uint8_t word_sig[1024];
+       uint8_t list_sig[1024];
+       const char *best_ptr = NULL;
+       int dist, best_dist = INT_MAX;
+       int index;
+
+       make_word_fingerprint(word_sig, word);
+
+       for (index = 0; cfg_opts[index].name; index++) {
+               make_word_fingerprint(list_sig, cfg_opts[index].name);
+               dist = word_fingerprint_distance(word_sig, list_sig);
+               if (dist < best_dist) {
+                       best_dist = dist;
+                       best_ptr = cfg_opts[index].name;
+               }
+       }
+
+       for (index = 0; cfg_opts2[index].name; index++) {
+               make_word_fingerprint(list_sig, cfg_opts2[index].name);
+               dist = word_fingerprint_distance(word_sig, list_sig);
+               if (dist < best_dist) {
+                       best_dist = dist;
+                       best_ptr = cfg_opts2[index].name;
+               }
+       }
+
+       while (extra && *extra) {
+               make_word_fingerprint(list_sig, *extra);
+               dist = word_fingerprint_distance(word_sig, list_sig);
+               if (dist < best_dist) {
+                       best_dist = dist;
+                       best_ptr = *extra;
+               }
+               extra++;
+       }
+
+       if (best_dist > 2 * strlen(word) || (best_ptr && best_dist > 2 * strlen(best_ptr)))
+               best_ptr = NULL;
+       return best_ptr;
+}
+
 /*
  * This function scans the list of backends and servers to retrieve the first
  * backend and the first server with the given names, and sets them in both