github.com/hamba/avro/v2@v2.22.1-0.20240518180522-aff3955acf7d/config.go (about)

     1  package avro
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"sync"
     7  
     8  	"github.com/modern-go/reflect2"
     9  )
    10  
    11  const (
    12  	defaultMaxByteSliceSize = 1_048_576 // 1 MiB
    13  )
    14  
    15  // DefaultConfig is the default API.
    16  var DefaultConfig = Config{}.Freeze()
    17  
    18  // Config customises how the codec should behave.
    19  type Config struct {
    20  	// TagKey is the struct tag key used when en/decoding structs.
    21  	// This defaults to "avro".
    22  	TagKey string
    23  
    24  	// BlockLength is the length of blocks for maps and arrays.
    25  	// This defaults to 100.
    26  	BlockLength int
    27  
    28  	// DisableBlockSizeHeader disables encoding of an array/map size in bytes.
    29  	// Encoded array/map will be prefixed with only the number of elements in
    30  	// contrast with default behavior which prefixes them with the number of elements
    31  	// and the total number of bytes in the array/map. Both approaches are valid according to the
    32  	// Avro specification, however not all decoders support the latter.
    33  	DisableBlockSizeHeader bool
    34  
    35  	// UnionResolutionError determines if an error will be returned
    36  	// when a type cannot be resolved while decoding a union.
    37  	UnionResolutionError bool
    38  
    39  	// PartialUnionTypeResolution dictates if the union type resolution
    40  	// should be attempted even when not all union types are registered.
    41  	// When enabled, the underlying type will get resolved if it is registered
    42  	// even if other types of the union are not. If resolution fails, logic
    43  	// falls back to default union resolution behavior based on the value of
    44  	// UnionResolutionError.
    45  	PartialUnionTypeResolution bool
    46  
    47  	// Disable caching layer for encoders and decoders, forcing them to get rebuilt on every
    48  	// call to Marshal() and Unmarshal()
    49  	DisableCaching bool
    50  
    51  	// MaxByteSliceSize is the maximum size of `bytes` or `string` types the Reader will create, defaulting to 1MiB.
    52  	// If this size is exceeded, the Reader returns an error. This can be disabled by setting a negative number.
    53  	MaxByteSliceSize int
    54  
    55  	// MaxSliceAllocSize is the maximum size that the decoder will allocate, set to the max heap
    56  	// allocation size by default.
    57  	// If this size is exceeded, the decoder returns an error.
    58  	MaxSliceAllocSize int
    59  }
    60  
    61  // Freeze makes the configuration immutable.
    62  func (c Config) Freeze() API {
    63  	api := &frozenConfig{
    64  		config:   c,
    65  		resolver: NewTypeResolver(),
    66  	}
    67  
    68  	api.readerPool = &sync.Pool{
    69  		New: func() any {
    70  			return &Reader{
    71  				cfg:    api,
    72  				reader: nil,
    73  				buf:    nil,
    74  				head:   0,
    75  				tail:   0,
    76  			}
    77  		},
    78  	}
    79  	api.writerPool = &sync.Pool{
    80  		New: func() any {
    81  			return &Writer{
    82  				cfg:   api,
    83  				out:   nil,
    84  				buf:   make([]byte, 0, 512),
    85  				Error: nil,
    86  			}
    87  		},
    88  	}
    89  
    90  	return api
    91  }
    92  
    93  // API represents a frozen Config.
    94  type API interface {
    95  	// Marshal returns the Avro encoding of v.
    96  	Marshal(schema Schema, v any) ([]byte, error)
    97  
    98  	// Unmarshal parses the Avro encoded data and stores the result in the value pointed to by v.
    99  	// If v is nil or not a pointer, Unmarshal returns an error.
   100  	Unmarshal(schema Schema, data []byte, v any) error
   101  
   102  	// NewEncoder returns a new encoder that writes to w using schema.
   103  	NewEncoder(schema Schema, w io.Writer) *Encoder
   104  
   105  	// NewDecoder returns a new decoder that reads from reader r using schema.
   106  	NewDecoder(schema Schema, r io.Reader) *Decoder
   107  
   108  	// DecoderOf returns the value decoder for a given schema and type.
   109  	DecoderOf(schema Schema, typ reflect2.Type) ValDecoder
   110  
   111  	// EncoderOf returns the value encoder for a given schema and type.
   112  	EncoderOf(schema Schema, tpy reflect2.Type) ValEncoder
   113  
   114  	// Register registers names to their types for resolution. All primitive types are pre-registered.
   115  	Register(name string, obj any)
   116  }
   117  
   118  type frozenConfig struct {
   119  	config Config
   120  
   121  	decoderCache sync.Map // map[cacheKey]ValDecoder
   122  	encoderCache sync.Map // map[cacheKey]ValEncoder
   123  
   124  	readerPool *sync.Pool
   125  	writerPool *sync.Pool
   126  
   127  	resolver *TypeResolver
   128  }
   129  
   130  func (c *frozenConfig) Marshal(schema Schema, v any) ([]byte, error) {
   131  	writer := c.borrowWriter()
   132  
   133  	writer.WriteVal(schema, v)
   134  	if err := writer.Error; err != nil {
   135  		c.returnWriter(writer)
   136  		return nil, err
   137  	}
   138  
   139  	result := writer.Buffer()
   140  	copied := make([]byte, len(result))
   141  	copy(copied, result)
   142  
   143  	c.returnWriter(writer)
   144  	return copied, nil
   145  }
   146  
   147  func (c *frozenConfig) borrowWriter() *Writer {
   148  	writer := c.writerPool.Get().(*Writer)
   149  	writer.Reset(nil)
   150  	return writer
   151  }
   152  
   153  func (c *frozenConfig) returnWriter(writer *Writer) {
   154  	writer.out = nil
   155  	writer.Error = nil
   156  
   157  	c.writerPool.Put(writer)
   158  }
   159  
   160  func (c *frozenConfig) Unmarshal(schema Schema, data []byte, v any) error {
   161  	reader := c.borrowReader(data)
   162  
   163  	reader.ReadVal(schema, v)
   164  	err := reader.Error
   165  	c.returnReader(reader)
   166  
   167  	if errors.Is(err, io.EOF) {
   168  		return nil
   169  	}
   170  
   171  	return err
   172  }
   173  
   174  func (c *frozenConfig) borrowReader(data []byte) *Reader {
   175  	reader := c.readerPool.Get().(*Reader)
   176  	reader.Reset(data)
   177  	return reader
   178  }
   179  
   180  func (c *frozenConfig) returnReader(reader *Reader) {
   181  	reader.Error = nil
   182  	c.readerPool.Put(reader)
   183  }
   184  
   185  func (c *frozenConfig) NewEncoder(schema Schema, w io.Writer) *Encoder {
   186  	writer, ok := w.(*Writer)
   187  	if !ok {
   188  		writer = NewWriter(w, 512, WithWriterConfig(c))
   189  	}
   190  	return &Encoder{
   191  		s: schema,
   192  		w: writer,
   193  	}
   194  }
   195  
   196  func (c *frozenConfig) NewDecoder(schema Schema, r io.Reader) *Decoder {
   197  	reader := NewReader(r, 512, WithReaderConfig(c))
   198  	return &Decoder{
   199  		s: schema,
   200  		r: reader,
   201  	}
   202  }
   203  
   204  func (c *frozenConfig) Register(name string, obj any) {
   205  	c.resolver.Register(name, obj)
   206  }
   207  
   208  type cacheKey struct {
   209  	fingerprint [32]byte
   210  	rtype       uintptr
   211  }
   212  
   213  func (c *frozenConfig) addDecoderToCache(fingerprint [32]byte, rtype uintptr, dec ValDecoder) {
   214  	if c.config.DisableCaching {
   215  		return
   216  	}
   217  	key := cacheKey{fingerprint: fingerprint, rtype: rtype}
   218  	c.decoderCache.Store(key, dec)
   219  }
   220  
   221  func (c *frozenConfig) getDecoderFromCache(fingerprint [32]byte, rtype uintptr) ValDecoder {
   222  	if c.config.DisableCaching {
   223  		return nil
   224  	}
   225  	key := cacheKey{fingerprint: fingerprint, rtype: rtype}
   226  	if dec, ok := c.decoderCache.Load(key); ok {
   227  		return dec.(ValDecoder)
   228  	}
   229  
   230  	return nil
   231  }
   232  
   233  func (c *frozenConfig) addEncoderToCache(fingerprint [32]byte, rtype uintptr, enc ValEncoder) {
   234  	if c.config.DisableCaching {
   235  		return
   236  	}
   237  	key := cacheKey{fingerprint: fingerprint, rtype: rtype}
   238  	c.encoderCache.Store(key, enc)
   239  }
   240  
   241  func (c *frozenConfig) getEncoderFromCache(fingerprint [32]byte, rtype uintptr) ValEncoder {
   242  	if c.config.DisableCaching {
   243  		return nil
   244  	}
   245  	key := cacheKey{fingerprint: fingerprint, rtype: rtype}
   246  	if enc, ok := c.encoderCache.Load(key); ok {
   247  		return enc.(ValEncoder)
   248  	}
   249  
   250  	return nil
   251  }
   252  
   253  func (c *frozenConfig) getTagKey() string {
   254  	tagKey := c.config.TagKey
   255  	if tagKey == "" {
   256  		return "avro"
   257  	}
   258  	return tagKey
   259  }
   260  
   261  func (c *frozenConfig) getBlockLength() int {
   262  	blockSize := c.config.BlockLength
   263  	if blockSize <= 0 {
   264  		return 100
   265  	}
   266  	return blockSize
   267  }
   268  
   269  func (c *frozenConfig) getMaxByteSliceSize() int {
   270  	size := c.config.MaxByteSliceSize
   271  	if size == 0 {
   272  		return defaultMaxByteSliceSize
   273  	}
   274  	return size
   275  }
   276  
   277  func (c *frozenConfig) getMaxSliceAllocSize() int {
   278  	size := c.config.MaxSliceAllocSize
   279  	if size > maxAllocSize || size <= 0 {
   280  		return maxAllocSize
   281  	}
   282  	return size
   283  }