diff --git a/bytes/bytes.go b/bytes/bytes.go index a6a6b4a..16086b0 100644 --- a/bytes/bytes.go +++ b/bytes/bytes.go @@ -15,6 +15,10 @@ package bytes +func Empty() []byte { + return Monoid.Empty() +} + func ToString(a []byte) string { return string(a) } diff --git a/context/readerioeither/http/builder/builder_test.go b/context/readerioeither/http/builder/builder_test.go index 256ff47..f0b6b00 100644 --- a/context/readerioeither/http/builder/builder_test.go +++ b/context/readerioeither/http/builder/builder_test.go @@ -32,7 +32,7 @@ import ( func TestBuilderWithQuery(t *testing.T) { // add some query withLimit := R.WithQueryArg("limit")("10") - withURL := R.WithUrl("http://www.example.org?a=b") + withURL := R.WithURL("http://www.example.org?a=b") b := F.Pipe2( R.Default, diff --git a/http/builder/builder.go b/http/builder/builder.go index 5de6cb7..9214841 100644 --- a/http/builder/builder.go +++ b/http/builder/builder.go @@ -16,9 +16,14 @@ package builder import ( + "bytes" + "crypto/sha256" + "fmt" "net/http" "net/url" + A "github.com/IBM/fp-go/array" + B "github.com/IBM/fp-go/bytes" E "github.com/IBM/fp-go/either" ENDO "github.com/IBM/fp-go/endomorphism" F "github.com/IBM/fp-go/function" @@ -29,6 +34,7 @@ import ( LZ "github.com/IBM/fp-go/lazy" L "github.com/IBM/fp-go/optics/lens" O "github.com/IBM/fp-go/option" + R "github.com/IBM/fp-go/record" S "github.com/IBM/fp-go/string" T "github.com/IBM/fp-go/tuple" ) @@ -138,6 +144,9 @@ var ( WithBytes, ENDO.Chain(WithContentType(C.FormEncoded)), ) + + // bodyAsBytes returns a []byte with a fallback to the empty array + bodyAsBytes = O.Fold(B.Empty, E.Fold(F.Ignore1of1[error](B.Empty), F.Identity[[]byte])) ) func setRawQuery(u *url.URL, raw string) *url.URL { @@ -272,6 +281,11 @@ func (builder *Builder) GetHeaderValues(name string) []string { return builder.headers.Values(name) } +// GetHash returns a hash value for the builder that can be used as a cache key +func (builder *Builder) GetHash() string { + return MakeHash(builder) +} + // Header returns a [L.Lens] for a single header func Header(name string) L.Lens[*Builder, O.Option[string]] { get := getHeader(name) @@ -342,3 +356,32 @@ func WithQueryArg(name string) func(value string) Endomorphism { func WithoutQueryArg(name string) Endomorphism { return QueryArg(name).Set(noQueryArg) } + +func hashWriteValue(buf *bytes.Buffer, value string) *bytes.Buffer { + buf.WriteString(value) + return buf +} + +func hashWriteQuery(name string, buf *bytes.Buffer, values []string) *bytes.Buffer { + buf.WriteString(name) + return A.Reduce(hashWriteValue, buf)(values) +} + +func makeBytes(b *Builder) []byte { + var buf bytes.Buffer + + buf.WriteString(b.GetMethod()) + buf.WriteString(b.GetURL()) + b.GetHeaders().Write(&buf) // #nosec: G104 + + R.ReduceOrdWithIndex[[]string, *bytes.Buffer](S.Ord)(hashWriteQuery, &buf)(b.GetQuery()) + + buf.Write(bodyAsBytes(b.GetBody())) + + return buf.Bytes() +} + +// MakeHash converts a [Builder] into a hash string, convenient to use as a cache key +func MakeHash(b *Builder) string { + return fmt.Sprintf("%x", sha256.Sum256(makeBytes(b))) +} diff --git a/http/builder/builder_test.go b/http/builder/builder_test.go index 275a851..175c04d 100644 --- a/http/builder/builder_test.go +++ b/http/builder/builder_test.go @@ -16,6 +16,7 @@ package builder import ( + "fmt" "testing" F "github.com/IBM/fp-go/function" @@ -66,3 +67,27 @@ func TestWithFormData(t *testing.T) { assert.Equal(t, C.FormEncoded, Headers.Get(res).Get(H.ContentType)) } + +func TestHash(t *testing.T) { + + b1 := F.Pipe4( + Default, + WithContentType(C.JSON), + WithHeader(H.Accept)(C.JSON), + WithURL("http://www.example.com"), + WithJSON(map[string]string{"a": "b"}), + ) + + b2 := F.Pipe4( + Default, + WithURL("http://www.example.com"), + WithHeader(H.Accept)(C.JSON), + WithContentType(C.JSON), + WithJSON(map[string]string{"a": "b"}), + ) + + assert.Equal(t, MakeHash(b1), MakeHash(b2)) + assert.NotEqual(t, MakeHash(Default), MakeHash(b2)) + + fmt.Println(MakeHash(b1)) +}