1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-07-15 01:24:23 +02:00

fix: refactor builder

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2024-01-11 14:12:53 +01:00
parent 38c6541254
commit 709d74b135
6 changed files with 527 additions and 338 deletions

View File

@ -0,0 +1,72 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package builder
import (
"bytes"
"context"
"net/http"
"strconv"
RIOE "github.com/IBM/fp-go/context/readerioeither"
RIOEH "github.com/IBM/fp-go/context/readerioeither/http"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
R "github.com/IBM/fp-go/http/builder"
H "github.com/IBM/fp-go/http/headers"
LZ "github.com/IBM/fp-go/lazy"
O "github.com/IBM/fp-go/option"
)
func Requester(builder *R.Builder) RIOEH.Requester {
withBody := F.Curry3(func(data []byte, url string, method string) RIOE.ReaderIOEither[*http.Request] {
return RIOE.TryCatch(func(ctx context.Context) func() (*http.Request, error) {
return func() (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(data))
if err == nil {
req.Header.Set(H.ContentLength, strconv.Itoa(len(data)))
H.Monoid.Concat(req.Header, builder.GetHeaders())
}
return req, err
}
})
})
withoutBody := F.Curry2(func(url string, method string) RIOE.ReaderIOEither[*http.Request] {
return RIOE.TryCatch(func(ctx context.Context) func() (*http.Request, error) {
return func() (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, method, url, nil)
if err == nil {
H.Monoid.Concat(req.Header, builder.GetHeaders())
}
return req, err
}
})
})
return F.Pipe5(
builder.GetBody(),
O.Fold(LZ.Of(E.Of[error](withoutBody)), E.Map[error](withBody)),
E.Ap[func(string) RIOE.ReaderIOEither[*http.Request]](builder.GetTargetUrl()),
E.Flap[error, RIOE.ReaderIOEither[*http.Request]](builder.GetMethod()),
E.GetOrElse(RIOE.Left[*http.Request]),
RIOE.Map(func(req *http.Request) *http.Request {
req.Header = H.Monoid.Concat(req.Header, builder.GetHeaders())
return req
}),
)
}

View File

@ -0,0 +1,59 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package builder
import (
"context"
"net/http"
"net/url"
"testing"
RIOE "github.com/IBM/fp-go/context/readerioeither"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
R "github.com/IBM/fp-go/http/builder"
IO "github.com/IBM/fp-go/io"
"github.com/stretchr/testify/assert"
)
func TestBuilderWithQuery(t *testing.T) {
// add some query
withLimit := R.WithQueryArg("limit")("10")
withUrl := R.WithUrl("http://www.example.org?a=b")
b := F.Pipe2(
R.Default,
withLimit,
withUrl,
)
req := F.Pipe3(
b,
Requester,
RIOE.Map(func(r *http.Request) *url.URL {
return r.URL
}),
RIOE.ChainFirstIOK(func(u *url.URL) IO.IO[any] {
return IO.FromImpure(func() {
q := u.Query()
assert.Equal(t, "10", q.Get("limit"))
assert.Equal(t, "b", q.Get("a"))
})
}),
)
assert.True(t, E.IsRight(req(context.Background())()))
}

311
http/builder/builder.go Normal file
View File

@ -0,0 +1,311 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package builder
import (
"net/http"
"net/url"
E "github.com/IBM/fp-go/either"
ENDO "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
C "github.com/IBM/fp-go/http/content"
FM "github.com/IBM/fp-go/http/form"
H "github.com/IBM/fp-go/http/headers"
J "github.com/IBM/fp-go/json"
LZ "github.com/IBM/fp-go/lazy"
L "github.com/IBM/fp-go/optics/lens"
O "github.com/IBM/fp-go/option"
S "github.com/IBM/fp-go/string"
T "github.com/IBM/fp-go/tuple"
)
type (
Builder struct {
method O.Option[string]
url string
headers http.Header
body O.Option[E.Either[error, []byte]]
query url.Values
}
// Endomorphism returns an [ENDO.Endomorphism] that transforms a builder
Endomorphism = ENDO.Endomorphism[*Builder]
)
var (
// Default is the default builder
Default = &Builder{method: O.Some(defaultMethod()), headers: make(http.Header), body: noBody}
defaultMethod = F.Constant(http.MethodGet)
// Monoid is the [M.Monoid] for the [Endomorphism]
Monoid = ENDO.Monoid[*Builder]()
// Url is a [L.Lens] for the URL
Url = L.MakeLensRef((*Builder).GetUrl, (*Builder).SetUrl)
// Method is a [L.Lens] for the HTTP method
Method = L.MakeLensRef((*Builder).GetMethod, (*Builder).SetMethod)
// Body is a [L.Lens] for the request body
Body = L.MakeLensRef((*Builder).GetBody, (*Builder).SetBody)
// Headers is a [L.Lens] for the complete set of request headers
Headers = L.MakeLensRef((*Builder).GetHeaders, (*Builder).SetHeaders)
// Query is a [L.Lens] for the set of query parameters
Query = L.MakeLensRef((*Builder).GetQuery, (*Builder).SetQuery)
rawQuery = L.MakeLensRef(getRawQuery, setRawQuery)
getHeader = F.Bind2of2((*Builder).GetHeader)
delHeader = F.Bind2of2((*Builder).DelHeader)
setHeader = F.Bind2of3((*Builder).SetHeader)
noHeader = O.None[string]()
noBody = O.None[E.Either[error, []byte]]()
noQueryArg = O.None[string]()
parseUrl = E.Eitherize1(url.Parse)
parseQuery = E.Eitherize1(url.ParseQuery)
// WithQuery creates a [Endomorphism] for a complete set of query parameters
WithQuery = Query.Set
// WithMethod creates a [Endomorphism] for a certain method
WithMethod = Method.Set
// WithUrl creates a [Endomorphism] for a certain method
WithUrl = Url.Set
// WithHeaders creates a [Endomorphism] for a set of headers
WithHeaders = Headers.Set
// WithBody creates a [Endomorphism] for a request body
WithBody = F.Flow2(
O.Of[E.Either[error, []byte]],
Body.Set,
)
// WithBytes creates a [Endomorphism] for a request body using bytes
WithBytes = F.Flow2(
E.Of[error, []byte],
WithBody,
)
// WithContentType adds the [H.ContentType] header
WithContentType = WithHeader(H.ContentType)
// WithAuthorization adds the [H.Authorization] header
WithAuthorization = WithHeader(H.Authorization)
// WithGet adds the [http.MethodGet] method
WithGet = WithMethod(http.MethodGet)
// WithPost adds the [http.MethodPost] method
WithPost = WithMethod(http.MethodPost)
// WithPut adds the [http.MethodPut] method
WithPut = WithMethod(http.MethodPut)
// WithDelete adds the [http.MethodDelete] method
WithDelete = WithMethod(http.MethodDelete)
// WithBearer creates a [Endomorphism] to add a Bearer [H.Authorization] header
WithBearer = F.Flow2(
S.Format[string]("Bearer %s"),
WithAuthorization,
)
// WithoutBody creates a [Endomorphism] to remove the body
WithoutBody = F.Pipe1(
noBody,
Body.Set,
)
// WithFormData creates a [Endomorphism] to send form data payload
WithFormData = F.Flow4(
url.Values.Encode,
S.ToBytes,
WithBytes,
ENDO.Chain(WithContentType(C.FormEncoded)),
)
)
func setRawQuery(u *url.URL, raw string) *url.URL {
u.RawQuery = raw
return u
}
func getRawQuery(u *url.URL) string {
return u.RawQuery
}
func (builder *Builder) clone() *Builder {
cpy := *builder
cpy.headers = cpy.headers.Clone()
return &cpy
}
// GetTargetUrl constructs a full URL with query parameters on top of the provided URL string
func (builder *Builder) GetTargetUrl() E.Either[error, string] {
// construct the final URL
return F.Pipe3(
builder,
Url.Get,
parseUrl,
E.Chain(F.Flow4(
T.Replicate2[*url.URL],
T.Map2(
F.Flow2(
F.Curry2(setRawQuery),
E.Of[error, func(string) *url.URL],
),
F.Flow3(
rawQuery.Get,
parseQuery,
E.Map[error](F.Flow2(
F.Curry2(FM.ValuesMonoid.Concat)(builder.GetQuery()),
(url.Values).Encode,
)),
),
),
T.Tupled2(E.MonadAp[*url.URL, error, string]),
E.Map[error]((*url.URL).String),
)),
)
}
func (builder *Builder) GetUrl() string {
return builder.url
}
func (builder *Builder) GetMethod() string {
return F.Pipe1(
builder.method,
O.GetOrElse(defaultMethod),
)
}
func (builder *Builder) GetHeaders() http.Header {
return builder.headers
}
func (builder *Builder) GetQuery() url.Values {
return builder.query
}
func (builder *Builder) SetQuery(query url.Values) *Builder {
builder.query = query
return builder
}
func (builder *Builder) GetBody() O.Option[E.Either[error, []byte]] {
return builder.body
}
func (builder *Builder) SetMethod(method string) *Builder {
builder.method = O.Some(method)
return builder
}
func (builder *Builder) SetUrl(url string) *Builder {
builder.url = url
return builder
}
func (builder *Builder) SetHeaders(headers http.Header) *Builder {
builder.headers = headers
return builder
}
func (builder *Builder) SetBody(body O.Option[E.Either[error, []byte]]) *Builder {
builder.body = body
return builder
}
func (builder *Builder) SetHeader(name, value string) *Builder {
builder.headers.Set(name, value)
return builder
}
func (builder *Builder) DelHeader(name string) *Builder {
builder.headers.Del(name)
return builder
}
func (builder *Builder) GetHeader(name string) O.Option[string] {
return F.Pipe2(
name,
builder.headers.Get,
O.FromPredicate(S.IsNonEmpty),
)
}
func (builder *Builder) GetHeaderValues(name string) []string {
return builder.headers.Values(name)
}
// Header returns a [L.Lens] for a single header
func Header(name string) L.Lens[*Builder, O.Option[string]] {
get := getHeader(name)
set := F.Bind1of2(setHeader(name))
del := F.Flow2(
LZ.Of[*Builder],
LZ.Map(delHeader(name)),
)
return L.MakeLens(get, func(b *Builder, value O.Option[string]) *Builder {
cpy := b.clone()
return F.Pipe1(
value,
O.Fold(del(cpy), set(cpy)),
)
})
}
// WithHeader creates a [Endomorphism] for a certain header
func WithHeader(name string) func(value string) Endomorphism {
return F.Flow2(
O.Of[string],
Header(name).Set,
)
}
// WithoutHeader creates a [Endomorphism] to remove a certain header
func WithoutHeader(name string) Endomorphism {
return Header(name).Set(noHeader)
}
// WithJson creates a [Endomorphism] to send JSON payload
func WithJson[T any](data T) Endomorphism {
return Monoid.Concat(
F.Pipe2(
data,
J.Marshal[T],
WithBody,
),
WithContentType(C.Json),
)
}
// QueryArg is a [L.Lens] for the first value of a query argument
func QueryArg(name string) L.Lens[*Builder, O.Option[string]] {
return F.Pipe1(
Query,
L.Compose[*Builder](FM.AtValue(name)),
)
}
// WithQueryArg creates a [Endomorphism] for a certain query argument
func WithQueryArg(name string) func(value string) Endomorphism {
return F.Flow2(
O.Of[string],
QueryArg(name).Set,
)
}
// WithoutQueryArg creates a [Endomorphism] that removes a query argument
func WithoutQueryArg(name string) Endomorphism {
return QueryArg(name).Set(noQueryArg)
}

View File

@ -0,0 +1,68 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package builder
import (
"testing"
F "github.com/IBM/fp-go/function"
C "github.com/IBM/fp-go/http/content"
FD "github.com/IBM/fp-go/http/form"
H "github.com/IBM/fp-go/http/headers"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
func TestBuilder(t *testing.T) {
name := H.ContentType
withContentType := WithHeader(name)
withoutContentType := WithoutHeader(name)
b1 := F.Pipe1(
Default,
withContentType(C.Json),
)
b2 := F.Pipe1(
b1,
withContentType(C.TextPlain),
)
b3 := F.Pipe1(
b2,
withoutContentType,
)
assert.Equal(t, O.None[string](), Default.GetHeader(name))
assert.Equal(t, O.Of(C.Json), b1.GetHeader(name))
assert.Equal(t, O.Of(C.TextPlain), b2.GetHeader(name))
assert.Equal(t, O.None[string](), b3.GetHeader(name))
}
func TestWithFormData(t *testing.T) {
data := F.Pipe1(
FD.Default,
FD.WithValue("a")("b"),
)
res := F.Pipe1(
Default,
WithFormData(data),
)
assert.Equal(t, C.FormEncoded, Headers.Get(res).Get(H.ContentType))
}

View File

@ -18,213 +18,26 @@ package builder
import (
"bytes"
"net/http"
"net/url"
"strconv"
E "github.com/IBM/fp-go/either"
ENDO "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
C "github.com/IBM/fp-go/http/content"
FM "github.com/IBM/fp-go/http/form"
R "github.com/IBM/fp-go/http/builder"
H "github.com/IBM/fp-go/http/headers"
IOG "github.com/IBM/fp-go/io/generic"
IOE "github.com/IBM/fp-go/ioeither"
IOEH "github.com/IBM/fp-go/ioeither/http"
J "github.com/IBM/fp-go/json"
LZ "github.com/IBM/fp-go/lazy"
L "github.com/IBM/fp-go/optics/lens"
O "github.com/IBM/fp-go/option"
S "github.com/IBM/fp-go/string"
T "github.com/IBM/fp-go/tuple"
)
type (
Builder struct {
method O.Option[string]
url string
headers http.Header
body O.Option[IOE.IOEither[error, []byte]]
query url.Values
}
// Endomorphism returns an [ENDO.Endomorphism] that transforms a builder
Endomorphism = ENDO.Endomorphism[*Builder]
)
var (
// Default is the default builder
Default = &Builder{method: O.Some(defaultMethod()), headers: make(http.Header), body: noBody}
defaultMethod = F.Constant(http.MethodGet)
// Monoid is the [M.Monoid] for the [Endomorphism]
Monoid = ENDO.Monoid[*Builder]()
// Url is a [L.Lens] for the URL
Url = L.MakeLensRef((*Builder).GetUrl, (*Builder).SetUrl)
// Method is a [L.Lens] for the HTTP method
Method = L.MakeLensRef((*Builder).GetMethod, (*Builder).SetMethod)
// Body is a [L.Lens] for the request body
Body = L.MakeLensRef((*Builder).GetBody, (*Builder).SetBody)
// Headers is a [L.Lens] for the complete set of request headers
Headers = L.MakeLensRef((*Builder).GetHeaders, (*Builder).SetHeaders)
// Query is a [L.Lens] for the set of query parameters
Query = L.MakeLensRef((*Builder).GetQuery, (*Builder).SetQuery)
rawQuery = L.MakeLensRef(getRawQuery, setRawQuery)
getHeader = F.Bind2of2((*Builder).GetHeader)
delHeader = F.Bind2of2((*Builder).DelHeader)
setHeader = F.Bind2of3((*Builder).SetHeader)
noHeader = O.None[string]()
noBody = O.None[IOE.IOEither[error, []byte]]()
noQueryArg = O.None[string]()
parseUrl = E.Eitherize1(url.Parse)
parseQuery = E.Eitherize1(url.ParseQuery)
// WithQuery creates a [Endomorphism] for a complete set of query parameters
WithQuery = Query.Set
// WithMethod creates a [Endomorphism] for a certain method
WithMethod = Method.Set
// WithUrl creates a [Endomorphism] for a certain method
WithUrl = Url.Set
// WithHeaders creates a [Endomorphism] for a set of headers
WithHeaders = Headers.Set
// WithBody creates a [Endomorphism] for a request body
WithBody = F.Flow2(
O.Of[IOE.IOEither[error, []byte]],
Body.Set,
)
// WithBytes creates a [Endomorphism] for a request body using bytes
WithBytes = F.Flow2(
IOE.Of[error, []byte],
WithBody,
)
// WithContentType adds the [H.ContentType] header
WithContentType = WithHeader(H.ContentType)
// WithAuthorization adds the [H.Authorization] header
WithAuthorization = WithHeader(H.Authorization)
// WithGet adds the [http.MethodGet] method
WithGet = WithMethod(http.MethodGet)
// WithPost adds the [http.MethodPost] method
WithPost = WithMethod(http.MethodPost)
// WithPut adds the [http.MethodPut] method
WithPut = WithMethod(http.MethodPut)
// WithDelete adds the [http.MethodDelete] method
WithDelete = WithMethod(http.MethodDelete)
// WithBearer creates a [Endomorphism] to add a Bearer [H.Authorization] header
WithBearer = F.Flow2(
S.Format[string]("Bearer %s"),
WithAuthorization,
)
// Requester creates a requester from a builder
Requester = (*Builder).Requester
// WithoutBody creates a [Endomorphism] to remove the body
WithoutBody = F.Pipe1(
noBody,
Body.Set,
)
)
func setRawQuery(u *url.URL, raw string) *url.URL {
u.RawQuery = raw
return u
}
func getRawQuery(u *url.URL) string {
return u.RawQuery
}
func (builder *Builder) clone() *Builder {
cpy := *builder
cpy.headers = cpy.headers.Clone()
return &cpy
}
func (builder *Builder) GetUrl() string {
return builder.url
}
func (builder *Builder) GetMethod() string {
return F.Pipe1(
builder.method,
O.GetOrElse(defaultMethod),
)
}
func (builder *Builder) GetHeaders() http.Header {
return builder.headers
}
func (builder *Builder) GetQuery() url.Values {
return builder.query
}
func (builder *Builder) SetQuery(query url.Values) *Builder {
builder.query = query
return builder
}
func (builder *Builder) GetBody() O.Option[IOE.IOEither[error, []byte]] {
return builder.body
}
func (builder *Builder) SetMethod(method string) *Builder {
builder.method = O.Some(method)
return builder
}
func (builder *Builder) SetUrl(url string) *Builder {
builder.url = url
return builder
}
func (builder *Builder) SetHeaders(headers http.Header) *Builder {
builder.headers = headers
return builder
}
func (builder *Builder) SetBody(body O.Option[IOE.IOEither[error, []byte]]) *Builder {
builder.body = body
return builder
}
func (builder *Builder) SetHeader(name, value string) *Builder {
builder.headers.Set(name, value)
return builder
}
func (builder *Builder) DelHeader(name string) *Builder {
builder.headers.Del(name)
return builder
}
func (builder *Builder) GetHeader(name string) O.Option[string] {
return F.Pipe2(
name,
builder.headers.Get,
O.FromPredicate(S.IsNonEmpty),
)
}
func (builder *Builder) GetHeaderValues(name string) []string {
return builder.headers.Values(name)
}
func (builder *Builder) Requester() IOEH.Requester {
func Requester(builder *R.Builder) IOEH.Requester {
withBody := F.Curry3(func(data []byte, url string, method string) IOE.IOEither[error, *http.Request] {
return IOE.TryCatchError(func() (*http.Request, error) {
req, err := http.NewRequest(method, url, bytes.NewReader(data))
if err == nil {
req.Header.Set(H.ContentLength, strconv.Itoa(len(data)))
H.Monoid.Concat(req.Header, builder.headers)
H.Monoid.Concat(req.Header, builder.GetHeaders())
}
return req, err
})
@ -234,127 +47,21 @@ func (builder *Builder) Requester() IOEH.Requester {
return IOE.TryCatchError(func() (*http.Request, error) {
req, err := http.NewRequest(method, url, nil)
if err == nil {
H.Monoid.Concat(req.Header, builder.headers)
H.Monoid.Concat(req.Header, builder.GetHeaders())
}
return req, err
})
})
// construct the final URL
targetUrl := F.Pipe3(
builder,
Url.Get,
parseUrl,
E.Chain(F.Flow4(
T.Replicate2[*url.URL],
T.Map2(
F.Flow2(
F.Curry2(setRawQuery),
E.Of[error, func(string) *url.URL],
),
F.Flow3(
rawQuery.Get,
parseQuery,
E.Map[error](F.Flow2(
F.Curry2(FM.ValuesMonoid.Concat)(builder.GetQuery()),
(url.Values).Encode,
)),
),
),
T.Tupled2(E.MonadAp[*url.URL, error, string]),
E.Map[error]((*url.URL).String),
)),
)
return F.Pipe6(
builder,
Body.Get,
O.Fold(LZ.Of(IOE.Of[error](withoutBody)), IOE.Map[error](withBody)),
IOG.Map[IOE.IOEither[error, func(string) func(string) IOE.IOEither[error, *http.Request]], IOE.IOEither[error, func(string) IOE.IOEither[error, *http.Request]]](E.Ap[func(string) IOE.IOEither[error, *http.Request]](targetUrl)),
IOE.Flap[error, IOE.IOEither[error, *http.Request]](builder.GetMethod()),
IOE.Flatten[error, *http.Request],
return F.Pipe5(
builder.GetBody(),
O.Fold(LZ.Of(E.Of[error](withoutBody)), E.Map[error](withBody)),
E.Ap[func(string) IOE.IOEither[error, *http.Request]](builder.GetTargetUrl()),
E.Flap[error, IOE.IOEither[error, *http.Request]](builder.GetMethod()),
E.GetOrElse(IOE.Left[*http.Request, error]),
IOE.Map[error](func(req *http.Request) *http.Request {
req.Header = H.Monoid.Concat(req.Header, builder.GetHeaders())
return req
}),
)
}
// Header returns a [L.Lens] for a single header
func Header(name string) L.Lens[*Builder, O.Option[string]] {
get := getHeader(name)
set := F.Bind1of2(setHeader(name))
del := F.Flow2(
LZ.Of[*Builder],
LZ.Map(delHeader(name)),
)
return L.MakeLens(get, func(b *Builder, value O.Option[string]) *Builder {
cpy := b.clone()
return F.Pipe1(
value,
O.Fold(del(cpy), set(cpy)),
)
})
}
// WithHeader creates a [Endomorphism] for a certain header
func WithHeader(name string) func(value string) Endomorphism {
return F.Flow2(
O.Of[string],
Header(name).Set,
)
}
// WithoutHeader creates a [Endomorphism] to remove a certain header
func WithoutHeader(name string) Endomorphism {
return Header(name).Set(noHeader)
}
// WithFormData creates a [Endomorphism] to send form data payload
func WithFormData(value url.Values) Endomorphism {
return F.Flow2(
F.Pipe4(
value,
url.Values.Encode,
S.ToBytes,
IOE.Of[error, []byte],
WithBody,
),
WithContentType(C.FormEncoded),
)
}
// WithJson creates a [Endomorphism] to send JSON payload
func WithJson[T any](data T) Endomorphism {
return F.Flow2(
F.Pipe3(
data,
J.Marshal[T],
IOE.FromEither[error, []byte],
WithBody,
),
WithContentType(C.Json),
)
}
// QueryArg is a [L.Lens] for the first value of a query argument
func QueryArg(name string) L.Lens[*Builder, O.Option[string]] {
return F.Pipe1(
Query,
L.Compose[*Builder](FM.AtValue(name)),
)
}
// WithQueryArg creates a [Endomorphism] for a certain query argument
func WithQueryArg(name string) func(value string) Endomorphism {
return F.Flow2(
O.Of[string],
QueryArg(name).Set,
)
}
// WithoutQueryArg creates a [Endomorphism] that removes a query argument
func WithoutQueryArg(name string) Endomorphism {
return QueryArg(name).Set(noQueryArg)
}

View File

@ -22,54 +22,26 @@ import (
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
C "github.com/IBM/fp-go/http/content"
H "github.com/IBM/fp-go/http/headers"
R "github.com/IBM/fp-go/http/builder"
IO "github.com/IBM/fp-go/io"
IOE "github.com/IBM/fp-go/ioeither"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
func TestBuilder(t *testing.T) {
name := H.ContentType
withContentType := WithHeader(name)
withoutContentType := WithoutHeader(name)
b1 := F.Pipe1(
Default,
withContentType(C.Json),
)
b2 := F.Pipe1(
b1,
withContentType(C.TextPlain),
)
b3 := F.Pipe1(
b2,
withoutContentType,
)
assert.Equal(t, O.None[string](), Default.GetHeader(name))
assert.Equal(t, O.Of(C.Json), b1.GetHeader(name))
assert.Equal(t, O.Of(C.TextPlain), b2.GetHeader(name))
assert.Equal(t, O.None[string](), b3.GetHeader(name))
}
func TestBuilderWithQuery(t *testing.T) {
// add some query
withLimit := WithQueryArg("limit")("10")
withUrl := WithUrl("http://www.example.org?a=b")
withLimit := R.WithQueryArg("limit")("10")
withUrl := R.WithUrl("http://www.example.org?a=b")
b := F.Pipe2(
Default,
R.Default,
withLimit,
withUrl,
)
req := F.Pipe2(
b.Requester(),
req := F.Pipe3(
b,
Requester,
IOE.Map[error](func(r *http.Request) *url.URL {
return r.URL
}),