Files
go-fa-api/request_options.go
2026-05-26 20:21:55 +02:00

99 lines
2.8 KiB
Go

package fa
import (
"context"
"strings"
)
// reqOverrideKey scopes the per-request override on a context. The
// transport reads it; nothing outside this package can synthesize one.
type reqOverrideKey struct{}
// requestOverride is the resolved diff between the client's default
// config and the options passed to a single call. The transport applies
// it on every outbound request whose context carries one.
//
// Only request-level fields appear here. Client-only fields (rate limiter,
// http.Client, retries, parser flags) are deliberately absent: passing the
// corresponding Option to a call is a silent no-op, which is documented on
// each Option.
type requestOverride struct {
cookies Cookies
cf CFCookies
sfw SFWMode
userAgent string
}
// applyRequestOptions resolves per-call options on top of the client's
// config and, if any request-level field actually changed, returns a
// context carrying a *requestOverride for the transport to read. When no
// options are passed or none of them touch request-level fields, the
// original context is returned unchanged so the hot path stays
// allocation-free.
func (c *Client) applyRequestOptions(ctx context.Context, opts []Option) context.Context {
if len(opts) == 0 {
return ctx
}
cfg := c.cfg
for _, o := range opts {
if o != nil {
o(&cfg)
}
}
if cfg.cookies == c.cfg.cookies &&
cfg.cf == c.cfg.cf &&
cfg.sfw == c.cfg.sfw &&
cfg.userAgent == c.cfg.userAgent {
return ctx
}
return context.WithValue(ctx, reqOverrideKey{}, &requestOverride{
cookies: cfg.cookies,
cf: cfg.cf,
sfw: cfg.sfw,
userAgent: cfg.userAgent,
})
}
// requestOverrideFrom extracts the override the transport should apply
// to this request, or nil if none was attached.
func requestOverrideFrom(ctx context.Context) *requestOverride {
if ctx == nil {
return nil
}
ov, _ := ctx.Value(reqOverrideKey{}).(*requestOverride)
return ov
}
// touchesCookies reports whether this override should replace the Cookie
// header. A UA-only override leaves the header (and thus the jar's
// cookies) alone.
func (o *requestOverride) touchesCookies() bool {
return o.cookies.A != "" ||
o.cookies.B != "" ||
o.cf.Clearance != "" ||
o.sfw != SFWAuto
}
// cookieHeader renders the override's cookies into a single Cookie
// header value. The transport writes this verbatim, replacing whatever
// the shared cookie jar produced from c.http's jar.
func (o *requestOverride) cookieHeader() string {
var parts []string
if o.cookies.A != "" {
parts = append(parts, "a="+o.cookies.A)
}
if o.cookies.B != "" {
parts = append(parts, "b="+o.cookies.B)
}
if o.cf.Clearance != "" {
parts = append(parts, "cf_clearance="+o.cf.Clearance)
}
switch o.sfw {
case SFWOn:
parts = append(parts, "sfw=1")
case SFWOff:
parts = append(parts, "sfw=0")
}
return strings.Join(parts, "; ")
}