mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-27 22:28:29 +02:00
156 lines
5.3 KiB
Go
156 lines
5.3 KiB
Go
// Copyright (c) 2023 - 2025 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 provides utilities for building HTTP requests in a functional way
|
|
// using the ReaderIOEither monad. It integrates with the http/builder package to
|
|
// create composable, type-safe HTTP request builders with proper error handling
|
|
// and context support.
|
|
//
|
|
// The main function, Requester, converts a Builder from the http/builder package
|
|
// into a ReaderIOEither that produces HTTP requests. This allows for:
|
|
// - Immutable request building with method chaining
|
|
// - Automatic header management including Content-Length
|
|
// - Support for requests with and without bodies
|
|
// - Proper error handling wrapped in Either
|
|
// - Context propagation for cancellation and timeouts
|
|
//
|
|
// Example usage:
|
|
//
|
|
// import (
|
|
// "context"
|
|
// B "github.com/IBM/fp-go/v2/http/builder"
|
|
// RB "github.com/IBM/fp-go/v2/context/readerioeither/http/builder"
|
|
// )
|
|
//
|
|
// builder := F.Pipe3(
|
|
// B.Default,
|
|
// B.WithURL("https://api.example.com/users"),
|
|
// B.WithMethod("POST"),
|
|
// B.WithJSONBody(userData),
|
|
// )
|
|
//
|
|
// requester := RB.Requester(builder)
|
|
// result := requester(context.Background())()
|
|
package builder
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
RIOE "github.com/IBM/fp-go/v2/context/readerioeither"
|
|
RIOEH "github.com/IBM/fp-go/v2/context/readerioeither/http"
|
|
E "github.com/IBM/fp-go/v2/either"
|
|
F "github.com/IBM/fp-go/v2/function"
|
|
R "github.com/IBM/fp-go/v2/http/builder"
|
|
H "github.com/IBM/fp-go/v2/http/headers"
|
|
LZ "github.com/IBM/fp-go/v2/lazy"
|
|
O "github.com/IBM/fp-go/v2/option"
|
|
)
|
|
|
|
// Requester converts an http/builder.Builder into a ReaderIOEither that produces HTTP requests.
|
|
// It handles both requests with and without bodies, automatically managing headers including
|
|
// Content-Length for requests with bodies.
|
|
//
|
|
// The function performs the following operations:
|
|
// 1. Extracts the request body (if present) from the builder
|
|
// 2. Creates appropriate request constructor (with or without body)
|
|
// 3. Applies the target URL from the builder
|
|
// 4. Applies the HTTP method from the builder
|
|
// 5. Merges headers from the builder into the request
|
|
// 6. Handles any errors that occur during request construction
|
|
//
|
|
// For requests with a body:
|
|
// - Sets the Content-Length header automatically
|
|
// - Uses bytes.NewReader to create the request body
|
|
// - Merges builder headers into the request
|
|
//
|
|
// For requests without a body:
|
|
// - Creates a request with nil body
|
|
// - Merges builder headers into the request
|
|
//
|
|
// Parameters:
|
|
// - builder: A pointer to an http/builder.Builder containing request configuration
|
|
//
|
|
// Returns:
|
|
// - A Requester (ReaderIOEither[*http.Request]) that, when executed with a context,
|
|
// produces either an error or a configured *http.Request
|
|
//
|
|
// Example with body:
|
|
//
|
|
// import (
|
|
// B "github.com/IBM/fp-go/v2/http/builder"
|
|
// RB "github.com/IBM/fp-go/v2/context/readerioeither/http/builder"
|
|
// )
|
|
//
|
|
// builder := F.Pipe3(
|
|
// B.Default,
|
|
// B.WithURL("https://api.example.com/users"),
|
|
// B.WithMethod("POST"),
|
|
// B.WithJSONBody(map[string]string{"name": "John"}),
|
|
// )
|
|
// requester := RB.Requester(builder)
|
|
// result := requester(context.Background())()
|
|
//
|
|
// Example without body:
|
|
//
|
|
// builder := F.Pipe2(
|
|
// B.Default,
|
|
// B.WithURL("https://api.example.com/users"),
|
|
// B.WithMethod("GET"),
|
|
// )
|
|
// requester := RB.Requester(builder)
|
|
// result := requester(context.Background())()
|
|
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
|
|
}),
|
|
)
|
|
}
|