pave

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2025 License: Apache-2.0 Imports: 15 Imported by: 0

README

Overview

PAVE (Parse and Validate Everything) is a performant go library for parsing any data type into another.

It is designed with the following core concepts and motivations in mind:

  • Under high loads, API's often spend the majority of their time parsing input data into concrete types (i.e json, gRPC, AMQP)
  • Once you've seen how to parse something once, you should do it faster the next time.
  • Take advantage of the language features available
  • Provide a framework to easily implement efficient custom parsers with the minimal amount of code.
  • Parsing should be flexible, allow for dynamic fallbacks, and developer controlled.

As such the following design decisions are present at almost every level of code:

  • All pave operations must be concurrency safe
  • Struct tags define how to interact with a parser
  • Cache everything that will be used and has a higher computational cost than retrieval.

Usage

Features

Supported Types

As given by its name, the goal of this package is to be able to parse any input to any destination. That means, this packages end-goal is to support every base type in Go, common interfaces and composite types, and all structural combinations of the prior.

For any destination struct field, the following primitives are currently supported:

// strings
string
// ints
int, int8, int16, int32, int64
// uints
uint, uint8, uint16, uint32, uint64, uintptr
// floats
float32, float64,
// complex numbers
complex64, complex128,
// booleans
bool
// slices
[]byte
// interface
all interfaces

With many (many) more planned in the future!

Additionally, the following special struct types are already integrated

uuid.UUID{}
time.Time{}

Lastly, any type that implements one of the following interfaces:

encoding.TextUnmarshaller

Struct Tags

Struct tags are the primary way to interact with a Parser. On each field of a destination type, you will define the Binding(s) that the parser will use to populate the field

All parsers will use the tag grammar shown here. In particular, parsers will either support either

  • One binding type for a source, or
  • Multiple bindings for a source

To determine which one to use, see the documentation for the parser you want

Tag Grammar
Tag grammar:
    <field> <type> <tag>
field:
    <Go Literal>
type:
    <Go Literal>
tag:
    <parse_tag> <validate_tag>'

parse_tag:
    // Any ordering of elements is allowed.
    (binding_tag_list)? (optional_tag_list)? 

binding_tag_list:
   [<binding_tag>]^*
binding_tag:
    <binding_name>:"<binding_identifier>,<binding_modifier_list>"
binding_name, binding_identifier:
    <string>
    
binding_modifier_list:
    // Delimited with "," end-delim optional
    [<binding_modifier>]^* 
binding_modifier:
    omitempty | omiterror | omitnil | <modifier_custom>
modifier_custom:
   <parser_specific>

optional_tag_list:
   [<optional_tag>]^*
optional_tag:
    <default_tag> | <recursive_tag> | <custom_tag>
custom_tag:
    <parser_specific>
default_tag:
    default:"<string>"
recursive_tag:
    recursive:"<bool>"

validate_tag
    validate:"<...>" | nil

As an example, say you have a type corresponding to a HTTP API request that needs data from the body, query URL, and cookies such as:

type ExampleRequestWithSession struct {
	ses string `header:"Session-Token,omitempty" query:"session-token,omitempty" default:"invalid_session"`
	jwt string `cookie:"jwt"`
	
	metadata struct {
		session_active_since time.Time `json:"metadata.active_since,omitempty"`
		session_expires      time.Time `json:"metadata.expires,omitempty"`	
	}

	resourceID uuid.UUID `query:"rid"`
	origin string `header:"X-Origin"`
	page   uint8 `query:"limit,omitempty"`
	limit  int 'query:"limit,omitempty'
}

All of the configuration occurs in the struct definition. To parse an incoming request into ExampleRequestWithSession, simply provide the HTTPRequestParser with the *http.Request and struct instance.

Caching

External Library Integrations

WIP

Documentation

Overview

Package pave (Parse And Validate Everything) provides a flexible and extensible framework for parsing and validating data structures in Go.

It allows you to define custom parsers for different data sources and all you need to do to parse into a destination struct is to define the appropriate tags on the struct fields.

The package provides built-in parsers for common data sources, such as:

  • JSON (from byte slices or strings)
  • HTTP requests (from cookies, headers, query parameters, and body)
  • String maps (from map[string]string or map[string]any)
  • Map values (from map[fmt.Stringer]any)

The parsers support recursive parsing, allowing you to define nested structures and have them automatically populated from the source data.

To use the package, you may use the exported methods: - Validate(): Parse and validate with the built-in validator - WithParser(): Curry the built-in validator with a custom parser - RegisterParser(): Register a custom parser for a specific source type Or you may register your own parsers on an instance of the Validator struct.

Each struct will define its own Validate() method that will be called to validate the struct's fields after they have been populated by the parsers.

Custom parser come in two flavors:

  • OneShotSourceParser: Parses from a source type with a single binding, such as a byte slice or a string. It is used when the source is expected to be a single value.
  • MultipleSourceParser: Parses from sources with multiple interaction methods, such as a HTTP request. These parsers build a ParseExecutionChain (see [parseChainBuilder](https://pkg.go.dev/pave#ChainExecutor) and ParseChain(https://pkg.go.dev/pave#ParseExecutionChain)) that allows you to extract data from various sources like cookies, headers, query parameters, and the body of the request. Execution chains allow for flexible fallbacks and prioritization of sources when parsing, are cached, and can be reused for any Validatable type that has the correct tags.

All parsers must implement the Parser(https://pkg.go.dev/pave#SourceParser) interface, which defines the methods required for parsing data from a source into a Validatable type.

Index

Constants

View Source
const (
	ParseTagPrefix                          string = "parse"
	DefaultValueSubTagPrefix                string = "default"
	DefaultValueSubTagPrefixWithKVDelimiter string = "default:"

	DefaultKeyValueTagDelimiter string = ":"
	CommaDelimeter              string = ","
)

constants for subtag prefixes in parse subtag

View Source
const (
	JsonTagBinding     string = "json"
	CookieTagBinding   string = "cookie"
	HeaderTagBinding   string = "header"
	QueryTagBinding    string = "query"
	MapValueTagBinding string = "mapvalue"
)

constants for builtin source bindings in parse subtag

View Source
const (
	OmitEmptyBindingModifier string = "omitempty"
	OmitNilBindingModifier   string = "omitnil"
	OmitErrorBindingModifier string = "omiterror"
)

constants for builtin source binding modifiers

View Source
const (
	HTTPRequestParserName   string = "http-request-parser"
	JSONByteSliceParserName string = "json-[]byte-parser"
	JSONStringParserName    string = "json-string-parser"
	StringMapParserName     string = "stringmap-parser"
	StringAnyMapParserName  string = "map-parser"
)

Parser Name constants for built in parsers.

View Source
const (
	ContentEncodingUTF8        string = "UTF-8"
	ContentTypeApplicationJSON string = "application/json"
	ContentTypeDelimiter       string = ";"
)

Mime Type constants for content types and encodings.

Variables

View Source
var (
	ErrBindingCacheNotInitialized = errors.New("binding cache not initialized")
	ErrBindingCacheNilEntry       = errors.New("binding cache nil entry provided")
)
View Source
var (
	HTTPRequestType   reflect.Type
	JSONByteSliceType reflect.Type
	StringType        reflect.Type
	StringMapType     reflect.Type
	StringMapAnyType  reflect.Type
)

reflect.TypeOf constants for type checks

View Source
var (
	TimeType reflect.Type
	UUIDType reflect.Type
)

reflect.TypeOf constants for special struct types

View Source
var (
	// ErrNoStepBindings is returned when a parse step has no bindings
	// to execute. This can happen if the field is a struct with no bindings,
	// it will be skipped in the parse chain.
	ErrNoStepBindings             = fmt.Errorf("no bindings found for field")
	ErrFailedToParseTag           = fmt.Errorf("failed to parse tag for field")
	ErrAllBindingsFailedNoDefault = fmt.Errorf("All bindings failed with no default value for field")
	ErrFailedToBuildSubChain      = fmt.Errorf("failed to build sub-chain for field")
	ErrNilParseChain              = fmt.Errorf("parse chain is empty for type")
)
View Source
var (
	ErrNoFieldSourcesInTag            = errors.New("no fields sources defined in tag but attempted to validate field")
	ErrParserAlreadyRegistered        = errors.New("a parser with this name for this source-type is already registered")
	ErrNoParser                       = errors.New("no built-in or registered parser found for this type")
	ErrNoParserBuiltin                = errors.New("no built-in parser found for this type")
	ErrNoParserRegistered             = errors.New("no registered parser found for this type")
	ErrMultipleParsersAvailable       = errors.New("multiple parsers available for this source type, use WithParser() to specify which one")
	ErrParserNotFound                 = errors.New("specified parser not found for this source type")
	ErrNoParseExecutionChain          = errors.New("no parse execution chain found for this type")
	ErrInvalidParseExecutionChainType = errors.New("improper type passed for this parse execution chain")
)
View Source
var (
	ErrNoParseTagInField        = errors.New("no parse tag found in field")
	ErrUnallowedBindingName     = errors.New("binding name is not allowed")
	ErrEmptyBindingIdentifier   = errors.New("binding identifier cannot be empty")
	ErrInvalidBindingTagFormat  = errors.New("invalid binding tag format")
	ErrInvalidBindingInfoFormat = errors.New("invalid binding info format")
	ErrUnallowedBindingModifier = errors.New("binding modifier is not allowed")
	ErrEmptyTagValue            = errors.New("tag value cannot be empty for non-string types")
)

Base Error types for tag parsing errors

Functions

func Invalidate

func Invalidate(dest Validatable) error

func Parse

func Parse(source any, dest any, validate bool) error

func ParseTypeErasedPointer

func ParseTypeErasedPointer[S any](
	source any,
	dest any,
	parse func(source *S, dest any) error,
) error

func ParseTypeErasedSlice

func ParseTypeErasedSlice[S any](
	source any,
	dest any,
	parse func(source []S, dest any) error,
) error

func RegisterParser

func RegisterParser(parser Parser) error

Types

type BaseMBParser

type BaseMBParser[S any, C any] struct {
	PCMgr  *PCManager[S]
	BMgr   BindingManager[S, C]
	BCache *BindingCache[S, C]
	// contains filtered or unexported fields
}

BaseMBParser is a mostly implemented template for a MultiBindingParser implementation.

This struct provides common functionality for building and executing parse chains, parsing field tags into a ParseChain that can be executed, caching parse chains, caching expensive binding operations, and handling type erasure for the source type.

It is used to create a MultiBindingParser that can handle fields with multiple sources, such as HTTP requests, where fields can be sourced from cookies, headers, query parameters, and the body.

It takes two type parameters:

  • S: The type of the source that this parser works with. DO NOT use a pointer. IT WILL PANIC. All methods should take a pointer to S instead.
  • C: The type of the cached value that will be used by the parser during binding operations. This is typically a struct that contains the cached values for binding's that sweep over all possible values, allowing the parser to avoid expensive operations by reusing previously computed values.

The template optionally supports caching of expensive operations per source instance using the provided BindingCache. This is useful for parsers that rely on external binding implementation that may be expensive to call multiple times for the same source instance.

func NewBaseMBParser

func NewBaseMBParser[S any, C any](
	bMgr BindingManager[S, C],
	opts BaseMBParserOpts,
) *BaseMBParser[S, C]

s

Note: You do not have to specify type constraints. Go will infer from the BindingManager and ParseChainManager what the type constraints are for the source and cached types.

func (*BaseMBParser[S, C]) Parse

func (base *BaseMBParser[S, C]) Parse(source any, dest any) error

Parse executes the parse chain for the given source and populates the destination struct. It uses Type Erasure to allow any type of source to be passed in, as long as it matches the generic type parameter Source.

Both arguments must be pointers:

  • source: A pointer to the source type that this parser works with.
  • dest: A pointer to the destination struct that will be populated with the parsed data

func (*BaseMBParser[S, C]) SourceType

func (base *BaseMBParser[S, C]) SourceType() reflect.Type

SourceType returns the reflect.Type of the source this parser works with.

type BaseMBParserOpts

type BaseMBParserOpts struct {
	PCMOpts  PCManagerOpts
	UseCache bool
}

type Binding

type Binding struct {
	Name       string           // The name of the binding method with the source type
	Identifier string           // The identifier of this specific field on the binding method
	Modifiers  BindingModifiers // Additional modifiers for the binding.
}

Binding represents a complete view of a single possible value binding for a field. Multiple Binding's are usually defined per field.

type BindingCache

type BindingCache[S any, C any] struct {
	// contains filtered or unexported fields
}

BindingCache provides thread-safe caching of binding values per source instance. It uses the memory address of the source as the cache key, which is safe in Go since objects don't move once allocated.

func NewBindingCache

func NewBindingCache[S any, C any]() *BindingCache[S, C]

NewBindingCache creates a new thread-safe binding cache

func (*BindingCache[S, C]) Clear

func (bc *BindingCache[S, C]) Clear()

Clear removes all cache entries

func (*BindingCache[S, C]) Delete

func (bc *BindingCache[S, C]) Delete(source *S)

Delete removes the cache entry for the source

func (*BindingCache[S, C]) Get

func (bc *BindingCache[S, C]) Get(source *S) (*CacheEntry[C], bool)

Get retrieves the cache entry for the source if it exists

func (*BindingCache[S, C]) GetOrCreate

func (bc *BindingCache[S, C]) GetOrCreate(source *S, factory func() C) *CacheEntry[C]

GetOrCreate returns the cache entry for the source, creating one if it doesn't exist. The factory function is called only once per source instance, even under concurrent access.

type BindingHandlerCachedFunc

type BindingHandlerCachedFunc[S any, C any] func(
	source *S,
	entry *CacheEntry[C],
	binding Binding,
) BindingResult

BindingHandlerCachedFunc is a function type that defines how to handle a binding operation for a specific source type with caching. It takes a pointer to the source type, a CacheEntry for the cached value, and a Binding, and returns the value extracted from the source, a boolean indicating if the binding was successful, and an error if any occurred during the binding operation.

type BindingHandlerFunc

type BindingHandlerFunc[S any] func(
	source *S,
	binding Binding,
) BindingResult

BindingHandlerFunc is a function type that defines how to handle a binding operation for a specific source type. It takes a pointer to the source type and a Binding, and returns the value extracted from the source, a boolean indicating if the binding was successful, and an error if any occurred during the binding operation.

type BindingManager

type BindingManager[Source any, Cached any] interface {
	NewCached() Cached
	BindingHandler(source *Source, binding Binding) BindingResult
	BindingHandlerCached(source *Source, entry *CacheEntry[Cached], binding Binding) BindingResult
}

BindingManager is responsible for managing the binding process for a specific source type. It provides methods to create a new cache entry instance, as well as to handle binding operations for both cached and non-cached scenarios.

All implementations of this interface should be thread-safe, as well as stateless.

type BindingModifiers

type BindingModifiers struct {
	Required  bool            // If true, this is the final source to try. Error on not found.
	OmitEmpty bool            // If true, skip this source if not found
	OmitNil   bool            // If true, skip this source if the value is nil
	OmitError bool            // If true, skip this source if an error occurs
	Custom    map[string]bool // Custom modifiers for parser-specific behavior
}

BindingModifiers represents all modifiers for a binding.

The built-in modifiers control failure and fallback behavior for a single binding. Custom modifiers can be used however the BindingManager wishes to handle them.

type BindingOpts

type BindingOpts struct {
	AllowedBindingNames    []string
	CustomBindingModifiers []string
}

type BindingResult

type BindingResult struct {
	Value any
	Found bool
	Error error
}

BindingResult represents the result of a binding operation.

It contains the value extracted from the source, a boolean indicating whether the binding was successful, and an error if any occurred during the binding operation.

func BindingResultError

func BindingResultError(err error) BindingResult

BindingResultNil creates a BindingResult indicating that the binding was found but the value is nil.

func BindingResultNotFound

func BindingResultNotFound() BindingResult

BindingResultNotFound creates a BindingResult indicating that the binding was not found in the source.

func BindingResultValue

func BindingResultValue(value any) BindingResult

BindingResultValue creates a BindingResult indicating that the binding was successful and the value was found in the source.

type BindingTag

type BindingTag struct {
	Name       string
	Identifier string
	Modifiers  []string
}

Corresponds to <binding_tag> Example: form:"foo,omitnil"

type CacheEntry

type CacheEntry[C any] struct {
	// contains filtered or unexported fields
}

CacheEntry holds the cached data for a specific source instance

func (*CacheEntry[C]) GetData

func (ce *CacheEntry[C]) GetData() C

GetData returns a copy of the cached data (safe for simple types)

func (*CacheEntry[C]) ReadData

func (ce *CacheEntry[C]) ReadData(fn func(data C))

ReadData provides read access to the cached data

func (*CacheEntry[C]) WriteData

func (ce *CacheEntry[C]) WriteData(fn func(data *C))

WriteData provides write access to the cached data

type CustomTag

type CustomTag struct {
	Name  string // The name of the optional tag
	Value string // The value of the optional tag
}

type DefaultTag

type DefaultTag struct {
	Value string
}

Corresponds to <default_tag> Example: default:"5"

type HTTPBindingManager

type HTTPBindingManager struct{}

func NewHTTPBindingManager

func NewHTTPBindingManager() *HTTPBindingManager

func (*HTTPBindingManager) BindingHandler

func (mgr *HTTPBindingManager) BindingHandler(
	source *http.Request,
	binding Binding,
) BindingResult

func (*HTTPBindingManager) BindingHandlerCached

func (mgr *HTTPBindingManager) BindingHandlerCached(
	source *http.Request,
	entry *CacheEntry[HTTPRequestOnce],
	binding Binding,
) BindingResult

func (*HTTPBindingManager) CookieValue

func (mgr *HTTPBindingManager) CookieValue(
	source *http.Request, entry *CacheEntry[HTTPRequestOnce], key string,
) BindingResult

func (*HTTPBindingManager) HeaderValue

func (mgr *HTTPBindingManager) HeaderValue(
	source *http.Request, entry *CacheEntry[HTTPRequestOnce], key string,
) BindingResult

func (*HTTPBindingManager) JSONValue

func (mgr *HTTPBindingManager) JSONValue(
	source *http.Request, entry *CacheEntry[HTTPRequestOnce], key string,
) BindingResult

func (*HTTPBindingManager) NewCached

func (mgr *HTTPBindingManager) NewCached() HTTPRequestOnce

func (*HTTPBindingManager) QueryValue

func (mgr *HTTPBindingManager) QueryValue(
	source *http.Request, entry *CacheEntry[HTTPRequestOnce], key string,
) BindingResult

type HTTPRequestOnce

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

HTTPRequestOnce holds parsed HTTP request data to avoid re-parsing on subsequent accesses. It uses sync.Once to ensure that parsing is only done once per request instance. This is the `Cached` type used by the MBPTemplate for HTTPRequestParser.

func NewHTTPRequestOnce

func NewHTTPRequestOnce() HTTPRequestOnce

type HTTPRequestParser

type HTTPRequestParser struct {
	*BaseMBParser[http.Request, HTTPRequestOnce]
}

HTTPRequestParser provides a parser for HTTP requests with the the following features:

  • Parses JSON body, cookies, headers, and query parameters
  • Caches repetively parsed data by http package to avoid extra computation and allocs.
  • Supports both cached and non-cached parsing
  • Implements the MultiBindingParser interface for flexible field bindings

The following Field Bindings are supported:

  • json:'<key,[modifiers]>'`: Parses a JSON key from the request body
  • cookie:'<key,[modifiers]>'`: Parses a cookie value by key
  • header:'<key,[modifiers]>'`: Parses a header value by key
  • query:'<key,[modifiers]>'`: Parses a query parameter value by key

Like all other MultiBindingParsers, this parser caches the parsing strategy (ParseChain) for each destination type, so that only the first parse takes the time to build the chain, and subsequent parses simply execute the pre-built chain.

This parser expects the standard parse tag format. See: [tags.go](./tags.go)

This parser does not support any custom modifiers, but it does support all standard modifiers (required, omitempty, omitnil, omiterror).

func NewHTTPRequestParser

func NewHTTPRequestParser() *HTTPRequestParser

func (*HTTPRequestParser) Name

func (hp *HTTPRequestParser) Name() string

type JSONByteSliceSourceParser

type JSONByteSliceSourceParser struct {
}

func NewJsonByteSliceSourceParser

func NewJsonByteSliceSourceParser() *JSONByteSliceSourceParser

func (*JSONByteSliceSourceParser) Name

func (jbsp *JSONByteSliceSourceParser) Name() string

func (*JSONByteSliceSourceParser) Parse

func (jbsp *JSONByteSliceSourceParser) Parse(source any, dest any) error

func (*JSONByteSliceSourceParser) SourceType

func (jbsp *JSONByteSliceSourceParser) SourceType() reflect.Type

type JSONStringSourceParser

type JSONStringSourceParser struct{}

func NewJSONStringSourceParser

func NewJSONStringSourceParser() *JSONStringSourceParser

func (*JSONStringSourceParser) Name

func (jssp *JSONStringSourceParser) Name() string

func (*JSONStringSourceParser) Parse

func (jssp *JSONStringSourceParser) Parse(source any, dest any) error

func (*JSONStringSourceParser) SourceType

func (jssp *JSONStringSourceParser) SourceType() reflect.Type

type PCManager

type PCManager[S any] struct {
	Chains  map[reflect.Type]*ParseChain[S] // Cache for chains. Keyed by Destination struct type.
	CMutex  sync.RWMutex                    // Mutex for thread-safe access to chains
	Opts    PCManagerOpts                   // Options for the parse chain manager
	Handler BindingHandlerFunc[S]           // Binding Handler for this source type
}

PCManager manages parse chains for different destination struct types.

It is responsible creating, caching, retrieving, and executing parse chains for a single source type. The source type is defined by the generic type parameter Source, which is the type of data that will be parsed into destination structs.

The PCManager is thread-safe and can be used concurrently across multiple goroutines.

The BindingHandlerFunc is used to retrieve values from the source based on the bindings defined in the parse steps. This generally will be a function pointer to the BindingHandlerFunc of the BindingManager, or a closure of it that injects cached values (ex. BaseMBParser's BindingHandlerAdapter).

func NewPCManager

func NewPCManager[S any](
	handler BindingHandlerFunc[S],
	opts PCManagerOpts,
) *PCManager[S]

func (*PCManager[S]) GetParseChain

func (cman *PCManager[S]) GetParseChain(
	typ reflect.Type,
) (*ParseChain[S], error)

GetParseChain retrieves a parse chain for the given destination struct type.

If not found, it will create a new parse chain for the type and cache it.

func (*PCManager[S]) NewParseChain

func (cman *PCManager[S]) NewParseChain(
	typ reflect.Type,
) (*ParseChain[S], error)

func (*PCManager[S]) NewParseStep

func (cman *PCManager[S]) NewParseStep(
	field reflect.StructField, index int,
) (*ParseStep[S], error)

type PCManagerOpts

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

type ParseChain

type ParseChain[S any] struct {
	StructType reflect.Type          // StructType is the type of the struct being parsed
	Head       *ParseStep[S]         // Head is the first step in the chain
	Handler    BindingHandlerFunc[S] // Function to get values from sources
}

ParseChain represents a linked list of parse steps for a struct type

Uses a function-based approach for binding value retrieval, eliminating the need for each parser to reimplement the same linked list traversal logic.

The BindingHandlerFunc provides dynamic dispatch to the appropriate value retrieval method for each parser type.

It takes one generic type S

S is the Go Type that data will be sourced from (e.g http.Request)

func (*ParseChain[S]) Execute

func (chain *ParseChain[S]) Execute(
	source *S, dest any,
) error

Execute runs the entire parse chain using the provided source getter

type ParseStep

type ParseStep[S any] struct {
	Next          *ParseStep[S]  // Next is the next step in the current chain.
	SubChain      *ParseChain[S] // Sub-chain for recursive struct parsing. Nil if not a struct field.
	Bindings      []Binding      // Ordered list of bindings to try
	FieldName     string         // Name of the field for error reporting
	DefaultValue  string         // Default value for the field if bindings fail and not required to succeed
	IsStruct      bool           // if this field is a struct that needs recursive parsing
	ShouldRecurse bool           // Indicates whether the struct-type field gets 1-step populated by binding or not
	FieldIndex    int            // Index of the field in the struct
}

ParseStep represents a single step in the execution chain

type ParseTag

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

func DecodeParseTagV2

func DecodeParseTagV2(field reflect.StructField, opts ParseTagOpts) (ParseTag, error)

type ParseTagOpts

type ParseTagOpts struct {
	BindingOpts
	AllowedTagOptionals []string // List of allowed optional tags
}

type Parser

type Parser interface {
	// Parse extracts the information from the implementation and populates
	// dest's fields with the values from the source.
	//
	// Both arguments must be pointers:
	//   - source: A pointer to the source type that this parser works with.
	//   - dest: A pointer to the destination struct that will be populated with the parsed data.
	Parse(source any, dest any) error
	// SourceType returns the reflect.Type of the source this parser works with
	SourceType() reflect.Type
	// Name returns a unique identifier for this parser within its source type
	Name() string
}

Parser is the base interface for all parsers that extract information from a source type and populate a destination struct. It defines the Parse method that extracts information from the source and populates the destination struct.

Note that most parsers will use the provided Base parsers, and thus will require a small amount of boilerplate type erasure code to implement the Parse method. However, there are type erasure helpers available to simplify this process. See:

The implementations of this interface will typically come in one of two flavors:

  • A SingleBindingParser that only allows parsing a single binding per field, which is useful for simple cases where you know the source type and the bindings are straightforward.
  • A MultiBindingParser that allows for multiple bindings per field, which is useful for more complex cases where you want to try multiple sources or methods of extracting the data. This allows for more: 1. Flexibility in how fields are populated 2. Reusability of the same struct across different contexts. 3. Caching of parsed data to avoid unnecessary recomputation/gc. 4. Tolerant, configurable error handling during parsing

func GetParser

func GetParser(source any) (Parser, error)

func GetParserByName

func GetParserByName(source any, parserName string) (Parser, error)

type ParserRegistry

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

ParserRegistry is the main struct that handles validation of Validatable types using registered SourceParsers.

It provides methods to register parsers, validate data, and invalidate partially or fully validated structs.

Multiple SourceParsers can be registered for each SourceType. If only one parser is registered for a type, it will be used automatically. If multiple parsers are registered, you must use WithParser() to specify which one to use.

Each SourceParser will build and cache an execution chain for each unique Validatable type it is used with.

func NewParserRegistry

func NewParserRegistry(opts ParserRegistryOpts) (*ParserRegistry, error)

func (*ParserRegistry) Invalidate

func (reg *ParserRegistry) Invalidate(dest Validatable) error

Invalidate clears a partially or fully validated dest by setting each field to its default value.

It expects the passed v to be a pointer

An error is returned if the argument is not reflect-able

func (*ParserRegistry) Parse

func (reg *ParserRegistry) Parse(source any, dest any, validate bool) error

Parse populates dest based on the implementation of source's parsing logic.

It only succeeds if there is exactly one parser registered for data's type. To use a specific parser, you must use the WithParser() method to specify which one to use.

It expects dest to be a pointer

If validation fails, it will return the validation error and zero all of dest's fields.

func (*ParserRegistry) Register

func (reg *ParserRegistry) Register(parser Parser) error

Now your registration method can accept the non-generic interface

func (*ParserRegistry) WithParser

func (reg *ParserRegistry) WithParser(parserName string) *ParserRegistryContext

WithParser returns a ValidatorContext that will use the specified parser for validation. This is useful when multiple parsers are registered for the same source type.

type ParserRegistryContext

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

ParserRegistryContext provides a curried Registry with a specific parser selection

func WithParser

func WithParser(parserName string) *ParserRegistryContext

func (*ParserRegistryContext) Parse

func (regCtx *ParserRegistryContext) Parse(source any, dest any, validate bool) error

Parse populates dest based on the specified parser's logic. It expects the passed dest to be a pointer.

type ParserRegistryOpts

type ParserRegistryOpts struct {
	Parsers         []Parser
	ExcludeDefaults bool
}

type RecursiveTag

type RecursiveTag struct {
	Enabled bool // If true, the field should be recursively parsed
}

Corresponds to <recursive_tag>

type Validatable

type Validatable interface {
	// Validate checks the fields of the struct and returns an error
	// if any of the fields are invalid.
	Validate() error
}

Jump to

Keyboard shortcuts

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