MINOR: cfgparse/proxy: also support spelling fixes on options
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.
diff --git a/include/haproxy/proxy.h b/include/haproxy/proxy.h
index 69cde1d..b81783a 100644
--- a/include/haproxy/proxy.h
+++ b/include/haproxy/proxy.h
@@ -49,6 +49,7 @@
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);
diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c
index ac50025..0787833 100644
--- a/src/cfgparse-listen.c
+++ b/src/cfgparse-listen.c
@@ -54,6 +54,15 @@
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 @@
} /* 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;
}
diff --git a/src/proxy.c b/src/proxy.c
index a9a4943..843941e 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -152,6 +152,55 @@
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