tkPresentation

package
v0.3.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 24, 2026 License: MIT Imports: 22 Imported by: 14

README

Presentation

Presentation layer of Infinite Toolkit (TK). It parses untrusted input, wraps responses, and provides Echo middleware. It depends on infra and domain. Part of Infinite Toolkit.

Middleware

  • PanicHandler: Handle panics in HTTP requests, log filtered stack traces (excluding domain layers), and respond with error messages; uses TRUSTED_IPS and TRUSTED_CIDRS env vars to mask sensitive information from untrusted operators. It can be used with CLI applications as well. The trust check uses the extracted requester IP. When the whole chain is trusted, that IP is the original requester as recorded by the first proxy. A client on the trusted network can claim another trusted IP by sending its own X-Forwarded-For. Overwrite the header at the proxy to prevent this.

    // ForApiInitialization
    echoInstance.Use(ApiPanicHandler)
    
    // ForCliInitialization
    defer CliPanicHandler()
    
  • LogHandler: Configure logging levels via LOG_LEVEL environment variable and initialize structured logging with slog and Zerolog.

Helpers

  • RequiredParamsInspector: Check that every required parameter is present in a request input map before further parsing.

    paramsReceived := map[string]any{"name": "John", "age": 30}
    paramsRequired := []string{"name", "email"}
    requiredParamsValidationErr := RequiredParamsInspector(paramsReceived, paramsRequired)
    
  • ApiRequestInputReader: Read and parse JSON, form data, or multipart files from Echo HTTP requests into structured data.

    inputReader := ApiRequestInputReader{}
    requestData, requestParsingErr := inputReader.Reader(echoContext)
    
  • RequesterIpExtractor: Requester IP extraction with a peer trust gate and a right-to-left trust chain walk. Reads IP_EXTRACT_HEADER env var as a comma-separated ordered chain (default: X-Forwarded-For,X-Real-IP). When RemoteAddr is not a trusted IP (local, private, link-local, or in TRUSTED_IPS/TRUSTED_CIDRS), the extractor returns it and ignores the headers, because the client connected directly. Otherwise each header is walked right-to-left; malformed entries are ignored, and the first entry that is not a trusted IP is returned. When every entry is trusted, the extractor returns the original requester — the first entry of the first header that has one. This matches Echo's RealIP semantics. Otherwise it returns RemoteAddr. The chain supports the special keywords Direct and RemoteAddr to force extraction from http.Request.RemoteAddr.

    Example: a request arrives with X-Forwarded-For: 127.0.0.1 and RemoteAddr: 172.17.0.1:54321. Both addresses are trusted, so the extractor returns 127.0.0.1, not the proxy address.

    Security: The headers are read only from trusted peers: loopback, private, link-local, or in TRUSTED_IPS/TRUSTED_CIDRS. A client on an untrusted network cannot choose the reported address. Right-to-left traversal skips values a client prepends when a trusted proxy appends the real client last. A proxy with a public address must be listed in TRUSTED_CIDRS, or the extractor reports the proxy address. Over-broad TRUSTED_CIDRS is the risk: the extractor would trust a range that includes untrusted clients.

    extractor := NewRequesterIpExtractor()
    
    ipAddress, err := extractor.Execute(httpRequest)
    
  • EnvsInspector: Inspect and validate environment variables from .env files loaded via ENV_FILE_PATH env var, supporting required and auto-fillable variables.

    optionalEnvFilePath := "/path/to/.env"
    requiredEnvVarNames := []string{"TRAIL_DATABASE_FILE_PATH", "SESSION_TOKEN_SECRET"}
    autoFillableEnvVars := []string{"SESSION_TOKEN_SECRET"}
    envsInspector := NewEnvsInspector(
      optionalEnvFilePath, requiredEnvVarNames, autoFillableEnvVars,
    )
    envsValidationErr := envsInspector.Inspect()
    
  • PaginationParser: Parse pagination parameters like pageNumber, itemsPerPage, lastSeenId, sortBy, and sortDirection from HTTP requests. A zero itemsPerPage fails with InvalidItemsPerPage; a fractional pageNumber or itemsPerPage fails with InvalidPageNumber or InvalidItemsPerPage.

    defaultPagination := tkDto.Pagination{PageNumber: 0, ItemsPerPage: 10}
    untrustedInput := map[string]any{"pageNumber": 1, "itemsPerPage": 20}
    parsedPagination, paginationParsingErr := PaginationParser(
      defaultPagination, untrustedInput,
    )
    
  • StringSliceVoParser: Convert comma-separated, semicolon-separated, or array strings into value object slices.

    rawInput := "note1,note2;note3"
    parsedNotes := StringSliceValueObjectParser(rawInput, tkValueObject.NewGenericNotes)
    
  • TimeParamsParser: Parse date ranges, timestamps, and relative times from request parameters.

    timeParamNames := []string{"createdAt", "updatedAt"}
    untrustedInput := map[string]any{"createdAt": 1609459200}
    parsedTimeParams := TimeParamsParser(timeParamNames, untrustedInput)
    fmt.Println(parsedTimeParams["createdAt"])
    
  • ResponseWrapper: A wrapper struct for liaison responses and when needed, used to emit API and CLI responses.

    apiResponse := NewApiResponseWrapper(201, accountEntity, "AccountCreatedSuccessfully")
    
    liaisonResponse := NewLiaisonResponse(
      LiaisonResponseStatusCreated, accountEntity, "AccountCreatedSuccessfully",
    )
    
    err := errors.New("AccountNotFound")
    
    liaisonResponseNoMessage := NewLiaisonResponseNoMessage(
      LiaisonResponseStatusSuccess, err.Error(),
    )
    
    liaisonApiEmissionErr := LiaisonApiResponseEmitter(echoContext, liaisonResponse)
    
    LiaisonCliResponseRenderer(liaisonResponse)
    

Documentation

Index

Constants

View Source
const (
	EnvsInspectorEnvFilePathEnvVarName string = "ENV_FILE_PATH"
)

Variables

This section is empty.

Functions

func IpExtractHeaderReader added in v0.2.7

func IpExtractHeaderReader() []tkValueObject.HttpHeader

func LiaisonApiResponseEmitter added in v0.1.1

func LiaisonApiResponseEmitter(
	echoContext echo.Context,
	liaisonResponse LiaisonResponse,
) error

func LiaisonCliResponseRenderer added in v0.1.1

func LiaisonCliResponseRenderer(liaisonResponse LiaisonResponse)

func PaginationParser added in v0.1.1

func PaginationParser(
	defaultPagination tkDto.Pagination,
	untrustedInput map[string]any,
) (parsedPagination tkDto.Pagination, err error)

func RequiredParamsInspector added in v0.0.4

func RequiredParamsInspector(paramsReceived map[string]any, paramsRequired []string) error

func SimpleCliResponseRenderer added in v0.2.3

func SimpleCliResponseRenderer(
	isSuccess bool, message string,
)

func StringSliceValueObjectParser

func StringSliceValueObjectParser[TypedObject any](
	rawInputValues any,
	valueObjectConstructor func(any) (TypedObject, error),
) []TypedObject

StringSliceValueObjectParser converts various input formats into a slice of typed objects. It accepts: - nil (returns empty slice) - string (splits by ";" or "," and parses each element) - slice (parses each element) - single value (parses as one element)

The valueObjectConstructor function is used to convert each raw value into the desired type. Invalid values are logged and skipped.

func TimeParamsParser added in v0.1.1

func TimeParamsParser(
	timeParamNames []string,
	untrustedInput map[string]any,
) map[string]*tkValueObject.UnixTime

This function parses time parameters from untrusted input. Time parameters are almost always optional, so it returns a map of pointers to tkValueObject.UnixTime. This allows the caller to easily determine if a time parameter was provided by checking if the pointer is nil. The parsed time parameters can then be directly set in a DTO.

Types

type ApiRequestInputReader added in v0.1.2

type ApiRequestInputReader struct {
}

func (ApiRequestInputReader) FormUrlEncodedDataProcessor added in v0.1.2

func (reader ApiRequestInputReader) FormUrlEncodedDataProcessor(
	requestBody map[string]any, formData map[string][]string,
) map[string]any

func (ApiRequestInputReader) MultipartFilesProcessor added in v0.1.2

func (ApiRequestInputReader) MultipartFilesProcessor(
	filesByKey map[string][]*multipart.FileHeader,
) map[string]*multipart.FileHeader

func (ApiRequestInputReader) Reader added in v0.1.2

func (reader ApiRequestInputReader) Reader(echoContext echo.Context) (map[string]any, error)

ApiRequestInputReader.Reader extracts and normalizes input data from an HTTP request into a flat map[string]any.

It processes the request body based on Content-Type, merges query parameters, path parameters, and optionally includes operator context information.

Content-Type handling:

  • application/json: Parses JSON body directly into the map. Returns "InvalidJsonBody" error if JSON is malformed.
  • application/x-www-form-urlencoded: Processes form data, preserving multiple values as string slices when a key appears more than once. Supports dot notation for nested structures (e.g., "user.name" becomes map["user"]["name"]).
  • multipart/form-data: Processes form fields similar to URL-encoded data. File uploads are normalized under the "files" key, with multiple files indexed as "key_0", "key_1", etc. Returns "InvalidMultipartFormData" error if the multipart form cannot be parsed.
  • Other/missing Content-Type: Returns "InvalidContentType" error.

Query parameters:

  • Query parameters are merged into the result map. When a query parameter has multiple values (e.g., ?tags=tag1&tags=tag2), only the FIRST value is captured. This is a design decision to optimize for the common single-value case (~99% of use cases).
  • For the rare cases where multiple values are needed, use a delimiter at the controller level. For example: ?tags=tag1;tag2 and split on ";" in your controller logic.

Route Path parameters:

  • All Echo route path parameters (e.g., :id, :name) are merged into the result map.

Operator context (if present in Echo context):

  • operatorSri: Extracted from context key and included in the result map.
  • operatorAccountId: Extracted from context key and included in the result map.
  • operatorIpAddress: If missing, populated using NewRequesterIpExtractor().Execute().

Returns:

  • A map[string]any containing all extracted request data, or
  • An echo.HTTPError with status 400 (Bad Request) and a descriptive message if parsing fails.

func (ApiRequestInputReader) StringDotNotationToHierarchicalMap added in v0.1.2

func (reader ApiRequestInputReader) StringDotNotationToHierarchicalMap(
	hierarchicalMap map[string]any, remainingKeys []string, finalValue string,
) map[string]any

type ApiResponseWrapper added in v0.1.1

type ApiResponseWrapper struct {
	Status          int    `json:"status"`
	Body            any    `json:"body"`
	ReadableMessage string `json:"readableMessage"`
}

func NewApiResponseWrapper added in v0.1.1

func NewApiResponseWrapper(
	status int,
	body any,
	readableMessage string,
) ApiResponseWrapper

type EnvsInspector added in v0.1.0

type EnvsInspector struct {
	// contains filtered or unexported fields
}

func NewEnvsInspector added in v0.1.0

func NewEnvsInspector(
	envFilePath *tkValueObject.UnixAbsoluteFilePath,
	requiredEnvVars, autoFillableEnvVars []string,
) *EnvsInspector

func (*EnvsInspector) Inspect added in v0.1.0

func (envsInspector *EnvsInspector) Inspect() (err error)

type LiaisonResponse added in v0.1.1

type LiaisonResponse struct {
	Status          LiaisonResponseStatus `json:"status"`
	Body            any                   `json:"body"`
	ReadableMessage string                `json:"readableMessage"`
}

func NewLiaisonResponse added in v0.1.1

func NewLiaisonResponse(
	status LiaisonResponseStatus,
	body any,
	readableMessage string,
) LiaisonResponse

The NewLiaisonResponse function is used when the response body contains complex data that requires a more informative message to be displayed to the user. The readable message field should describe the outcome of the operation in a clear and understandable manner, providing valuable information about the result of the request. This is particularly handy in scenarios where the body field contains details about the created resource, such as in POST requests.

func NewLiaisonResponseNoMessage added in v0.1.2

func NewLiaisonResponseNoMessage(
	status LiaisonResponseStatus,
	body any,
) LiaisonResponse

The NewLiaisonResponseNoMessage function is used when the response body is the message (string) that should be displayed to the user, so the readable message field is not needed.

type LiaisonResponseStatus added in v0.1.1

type LiaisonResponseStatus string
const (
	LiaisonResponseStatusSuccess            LiaisonResponseStatus = "success"
	LiaisonResponseStatusCreated            LiaisonResponseStatus = "created"
	LiaisonResponseStatusAccepted           LiaisonResponseStatus = "accepted"
	LiaisonResponseStatusMultiStatus        LiaisonResponseStatus = "multiStatus"
	LiaisonResponseStatusUserError          LiaisonResponseStatus = "userError"
	LiaisonResponseStatusUnauthorized       LiaisonResponseStatus = "unauthorized"
	LiaisonResponseStatusForbidden          LiaisonResponseStatus = "forbidden"
	LiaisonResponseStatusNotFound           LiaisonResponseStatus = "notFound"
	LiaisonResponseStatusTimeout            LiaisonResponseStatus = "timeout"
	LiaisonResponseStatusConflict           LiaisonResponseStatus = "conflict"
	LiaisonResponseStatusRateLimited        LiaisonResponseStatus = "rateLimited"
	LiaisonResponseStatusInfraError         LiaisonResponseStatus = "infraError"
	LiaisonResponseStatusUnknownError       LiaisonResponseStatus = "unknownError"
	LiaisonResponseStatusServiceUnavailable LiaisonResponseStatus = "serviceUnavailable"
)

type RequesterIpExtractor added in v0.2.7

type RequesterIpExtractor struct {
	// contains filtered or unexported fields
}

func NewRequesterIpExtractor added in v0.2.7

func NewRequesterIpExtractor() RequesterIpExtractor

func (RequesterIpExtractor) Execute added in v0.2.7

func (extractor RequesterIpExtractor) Execute(
	httpRequest *http.Request,
) (tkValueObject.IpAddress, error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL