1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-27 22:28:29 +02:00
Files
fp-go/v2/context/readerioeither/http/builder/builder.go
Dr. Carsten Leue 92eb9715bd fix: implement some useful prisms
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-06 13:53:02 +01:00

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
}),
)
}