go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/web/config.go (about)

     1  /*
     2  
     3  Copyright (c) 2023 - Present. Will Charczuk. All rights reserved.
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository.
     5  
     6  */
     7  
     8  package web
     9  
    10  import (
    11  	"context"
    12  	"fmt"
    13  	"net/http"
    14  	"time"
    15  
    16  	"go.charczuk.com/sdk/configutil"
    17  )
    18  
    19  // Config is an object used to set up a web app.
    20  type Config struct {
    21  	Addr    string            `json:"addr,omitempty" yaml:"addr,omitempty"`
    22  	BaseURL string            `json:"baseURL,omitempty" yaml:"baseURL,omitempty"`
    23  	Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"`
    24  
    25  	SkipTrailingSlashRedirects bool `json:"skipTrailingSlashRedirects,omitempty" yaml:"skipTrailingSlashRedirects,omitempty"`
    26  	SkipHandlingMethodOptions  bool `json:"skipHandlingMethodOptions,omitempty" yaml:"skipHandlingMethodOptions,omitempty"`
    27  	SkipMethodNotAllowed       bool `json:"skipMethodNotAllowed,omitempty" yaml:"skipMethodNotAllowed,omitempty"`
    28  
    29  	MaxHeaderBytes      int           `json:"maxHeaderBytes,omitempty" yaml:"maxHeaderBytes,omitempty"`
    30  	ReadTimeout         time.Duration `json:"readTimeout,omitempty" yaml:"readTimeout,omitempty"`
    31  	ReadHeaderTimeout   time.Duration `json:"readHeaderTimeout,omitempty" yaml:"readHeaderTimeout,omitempty"`
    32  	WriteTimeout        time.Duration `json:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty"`
    33  	IdleTimeout         time.Duration `json:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty"`
    34  	ShutdownGracePeriod time.Duration `json:"shutdownGracePeriod,omitempty" yaml:"shutdownGracePeriod,omitempty"`
    35  }
    36  
    37  // IsZero returns if the config is unset or not.
    38  func (c Config) IsZero() bool {
    39  	return c.Addr == ""
    40  }
    41  
    42  // Resolve resolves the config from other sources.
    43  func (c *Config) Resolve(ctx context.Context) error {
    44  	return configutil.Resolve(ctx,
    45  		configutil.Set(&c.Addr, configutil.Env[string]("BIND_ADDR"), configutil.Lazy(&c.Addr), c.ResolveAddrFromPort),
    46  		configutil.Set(&c.BaseURL, configutil.Env[string]("BASE_URL"), configutil.Lazy(&c.BaseURL)),
    47  		configutil.Set(&c.MaxHeaderBytes, configutil.Env[int]("MAX_HEADER_BYTES"), configutil.Lazy(&c.MaxHeaderBytes)),
    48  		configutil.Set(&c.ReadTimeout, configutil.Env[time.Duration]("READ_TIMEOUT"), configutil.Lazy(&c.ReadTimeout)),
    49  		configutil.Set(&c.ReadHeaderTimeout, configutil.Env[time.Duration]("READ_HEADER_TIMEOUT"), configutil.Lazy(&c.ReadHeaderTimeout)),
    50  		configutil.Set(&c.WriteTimeout, configutil.Env[time.Duration]("WRITE_TIMEOUT"), configutil.Lazy(&c.WriteTimeout)),
    51  		configutil.Set(&c.IdleTimeout, configutil.Env[time.Duration]("IDLE_TIMEOUT"), configutil.Lazy(&c.IdleTimeout)),
    52  		configutil.Set(&c.ShutdownGracePeriod, configutil.Env[time.Duration]("SHUTDOWN_GRACE_PERIOD"), configutil.Lazy(&c.ShutdownGracePeriod)),
    53  	)
    54  }
    55  
    56  // ResolveAddrFromPort resolves the `Addr` field from a `$PORT` environment variable.
    57  func (c *Config) ResolveAddrFromPort(ctx context.Context) (*string, error) {
    58  	if port, _ := configutil.Env[string]("PORT")(ctx); port != nil && *port != "" {
    59  		addr := fmt.Sprintf(":%s", *port)
    60  		return &addr, nil
    61  	}
    62  	return nil, nil
    63  }
    64  
    65  // ApplyTo applies a given config to an app.
    66  func (c *Config) ApplyTo(app *App) {
    67  	app.BaseURL = c.BaseURL
    68  	app.ShutdownGracePeriod = c.ShutdownGracePeriod
    69  	app.Addr = c.Addr
    70  	app.MaxHeaderBytes = c.MaxHeaderBytes
    71  	app.ReadTimeout = c.ReadTimeout
    72  	app.ReadHeaderTimeout = c.ReadHeaderTimeout
    73  	app.WriteTimeout = c.WriteTimeout
    74  	app.IdleTimeout = c.IdleTimeout
    75  	app.SkipHandlingMethodOptions = c.SkipHandlingMethodOptions
    76  	app.SkipMethodNotAllowed = c.SkipMethodNotAllowed
    77  	app.SkipTrailingSlashRedirects = c.SkipTrailingSlashRedirects
    78  	app.Headers = MergeHeaders(app.Headers, CopySingleHeaders(c.Headers))
    79  }
    80  
    81  // MergeHeaders merges headers.
    82  func MergeHeaders(headers ...http.Header) http.Header {
    83  	output := make(http.Header)
    84  	for _, header := range headers {
    85  		for key, values := range header {
    86  			for _, value := range values {
    87  				output.Add(key, value)
    88  			}
    89  		}
    90  	}
    91  	return output
    92  }
    93  
    94  // CopySingleHeaders copies headers in single value format.
    95  func CopySingleHeaders(headers map[string]string) http.Header {
    96  	output := make(http.Header)
    97  	for key, value := range headers {
    98  		output[key] = []string{value}
    99  	}
   100  	return output
   101  }