Repository: danielgtaylor/huma
Pull request #44 (2022-04-22 19:41:15)

Huma now accepts naked boolean queryparams
Today in order to use a boolean with queryparams it needs to be set like this: `?param=true`, now you can set it with `?param` or `?param`


Patch:
diff --git a/resolver.go b/resolver.go
index 2cb37e6..f786049 100644
--- a/resolver.go
+++ b/resolver.go
@@ -275,6 +275,9 @@ func setFields(ctx *hcontext, req *http.Request, input reflect.Value, t reflect.
 			location = locationQuery
 			if v := req.URL.Query().Get(name); v != "" {
 				pv = v
+			} else if req.URL.Query().Has(name) && f.Type.Kind() == reflect.Bool {
+				// name has no associated value, but exists in the map of QueryParams.  This is a boolean value
+				pv = "true"
 			}
 		}
 


Test patch:
diff --git a/resolver_test.go b/resolver_test.go
index fc63d86..8f668e8 100644
--- a/resolver_test.go
+++ b/resolver_test.go
@@ -1,6 +1,7 @@
 package huma
 
 import (
+	"encoding/json"
 	"net/http"
 	"net/http/httptest"
 	"strings"
@@ -141,3 +142,188 @@ func TestInvalidJSON(t *testing.T) {
 
 	assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
 }
+
+type QueryParamTestModel struct {
+	BooleanParam bool   `query:"b"`
+	OtherParam   string `query:"s"`
+}
+
+func TestBooleanQueryParamNoVal(t *testing.T) {
+	app := newTestRouter()
+
+	app.Resource("/").Get("test", "Test",
+		NewResponse(http.StatusOK, "desc"),
+	).Run(func(ctx Context, input QueryParamTestModel) {
+		out := &QueryParamTestModel{
+			BooleanParam: input.BooleanParam,
+			OtherParam:   input.OtherParam,
+		}
+		j, err := json.Marshal(out)
+		if err == nil {
+			ctx.Write(j)
+		} else {
+			ctx.WriteError(http.StatusBadRequest, "error marshaling to json", err)
+		}
+
+	})
+
+	w := httptest.NewRecorder()
+	r, _ := http.NewRequest(http.MethodGet, "/?s=test&b", nil)
+	app.ServeHTTP(w, r)
+
+	assert.Equal(t, http.StatusOK, w.Result().StatusCode)
+	decoder := json.NewDecoder(w.Body)
+	var o QueryParamTestModel
+
+	err := decoder.Decode(&o)
+	if err != nil {
+		assert.Fail(t, "Unable to decode json response")
+	}
+
+	assert.Equal(t, o.BooleanParam, true)
+	assert.Equal(t, o.OtherParam, "test")
+}
+
+func TestBooleanQueryParamTrailingEqual(t *testing.T) {
+	app := newTestRouter()
+
+	app.Resource("/").Get("test", "Test",
+		NewResponse(http.StatusOK, "desc"),
+	).Run(func(ctx Context, input QueryParamTestModel) {
+		out := &QueryParamTestModel{
+			BooleanParam: input.BooleanParam,
+			OtherParam:   input.OtherParam,
+		}
+		j, err := json.Marshal(out)
+		if err == nil {
+			ctx.Write(j)
+		} else {
+			ctx.WriteError(http.StatusBadRequest, "error marshaling to json", err)
+		}
+
+	})
+
+	w := httptest.NewRecorder()
+	r, _ := http.NewRequest(http.MethodGet, "/?s=test&b=", nil)
+	app.ServeHTTP(w, r)
+
+	assert.Equal(t, http.StatusOK, w.Result().StatusCode)
+	decoder := json.NewDecoder(w.Body)
+	var o QueryParamTestModel
+
+	err := decoder.Decode(&o)
+	if err != nil {
+		assert.Fail(t, "Unable to decode json response")
+	}
+
+	assert.Equal(t, o.BooleanParam, true)
+	assert.Equal(t, o.OtherParam, "test")
+}
+
+func TestBooleanQueryParamExplicitSet(t *testing.T) {
+	app := newTestRouter()
+
+	app.Resource("/").Get("test", "Test",
+		NewResponse(http.StatusOK, "desc"),
+	).Run(func(ctx Context, input QueryParamTestModel) {
+		out := &QueryParamTestModel{
+			BooleanParam: input.BooleanParam,
+			OtherParam:   input.OtherParam,
+		}
+		j, err := json.Marshal(out)
+		if err == nil {
+			ctx.Write(j)
+		} else {
+			ctx.WriteError(http.StatusBadRequest, "error marshaling to json", err)
+		}
+
+	})
+
+	w := httptest.NewRecorder()
+	r, _ := http.NewRequest(http.MethodGet, "/?s=test&b=true", nil)
+	app.ServeHTTP(w, r)
+
+	assert.Equal(t, http.StatusOK, w.Result().StatusCode)
+	decoder := json.NewDecoder(w.Body)
+	var o QueryParamTestModel
+
+	err := decoder.Decode(&o)
+	if err != nil {
+		assert.Fail(t, "Unable to decode json response")
+	}
+
+	assert.Equal(t, o.BooleanParam, true)
+	assert.Equal(t, o.OtherParam, "test")
+}
+
+func TestBooleanQueryParamNotSet(t *testing.T) {
+	app := newTestRouter()
+
+	app.Resource("/").Get("test", "Test",
+		NewResponse(http.StatusOK, "desc"),
+	).Run(func(ctx Context, input QueryParamTestModel) {
+		out := &QueryParamTestModel{
+			BooleanParam: input.BooleanParam,
+			OtherParam:   input.OtherParam,
+		}
+		j, err := json.Marshal(out)
+		if err == nil {
+			ctx.Write(j)
+		} else {
+			ctx.WriteError(http.StatusBadRequest, "error marshaling to json", err)
+		}
+
+	})
+
+	w := httptest.NewRecorder()
+	r, _ := http.NewRequest(http.MethodGet, "/?s=test", nil)
+	app.ServeHTTP(w, r)
+
+	assert.Equal(t, http.StatusOK, w.Result().StatusCode)
+	decoder := json.NewDecoder(w.Body)
+	var o QueryParamTestModel
+
+	err := decoder.Decode(&o)
+	if err != nil {
+		assert.Fail(t, "Unable to decode json response")
+	}
+
+	assert.Equal(t, o.BooleanParam, false)
+	assert.Equal(t, o.OtherParam, "test")
+}
+
+func TestStringQueryEmpty(t *testing.T) {
+	app := newTestRouter()
+
+	app.Resource("/").Get("test", "Test",
+		NewResponse(http.StatusOK, "desc"),
+	).Run(func(ctx Context, input QueryParamTestModel) {
+		out := &QueryParamTestModel{
+			BooleanParam: input.BooleanParam,
+			OtherParam:   input.OtherParam,
+		}
+		j, err := json.Marshal(out)
+		if err == nil {
+			ctx.Write(j)
+		} else {
+			ctx.WriteError(http.StatusBadRequest, "error marshaling to json", err)
+		}
+
+	})
+
+	w := httptest.NewRecorder()
+	r, _ := http.NewRequest(http.MethodGet, "/?s=&b", nil)
+	app.ServeHTTP(w, r)
+
+	assert.Equal(t, http.StatusOK, w.Result().StatusCode)
+	decoder := json.NewDecoder(w.Body)
+	var o QueryParamTestModel
+
+	err := decoder.Decode(&o)
+	if err != nil {
+		assert.Fail(t, "Unable to decode json response")
+	}
+
+	assert.Equal(t, o.BooleanParam, true)
+	assert.Equal(t, o.OtherParam, "")
+}