package jdenticon import ( "context" "fmt" "github.com/ungluedlabs/go-jdenticon/internal/engine" "github.com/ungluedlabs/go-jdenticon/internal/util" ) // Generator provides thread-safe identicon generation with caching. // It wraps the internal engine.Generator to provide a clean public API. type Generator struct { engine *engine.Generator config Config // Store the configuration for validation during Generate calls } // NewGenerator creates a new Generator with default configuration and default caching. func NewGenerator() (*Generator, error) { engineGen, err := engine.NewDefaultGenerator() if err != nil { return nil, NewErrGenerationFailed("", 0, err) } return &Generator{ engine: engineGen, config: DefaultConfig(), }, nil } // NewGeneratorWithCacheSize creates a new Generator with the specified cache size. func NewGeneratorWithCacheSize(cacheSize int) (*Generator, error) { if cacheSize <= 0 { return nil, NewErrInvalidInput("cacheSize", fmt.Sprintf("%d", cacheSize), "must be positive") } config := engine.DefaultGeneratorConfig() config.CacheSize = cacheSize engineGen, err := engine.NewGeneratorWithConfig(config) if err != nil { return nil, NewErrCacheCreationFailed(cacheSize, err) } return &Generator{ engine: engineGen, config: DefaultConfig(), }, nil } // NewGeneratorWithConfig creates a new Generator with custom configuration and caching. func NewGeneratorWithConfig(config Config, cacheSize int) (*Generator, error) { if cacheSize <= 0 { return nil, NewErrInvalidInput("cacheSize", fmt.Sprintf("%d", cacheSize), "must be positive") } // Convert public config to internal config colorConfig, err := config.toEngineColorConfig() if err != nil { return nil, err } generatorConfig := engine.GeneratorConfig{ ColorConfig: colorConfig, CacheSize: cacheSize, MaxComplexity: config.MaxComplexity, MaxIconSize: config.MaxIconSize, } engineGen, err := engine.NewGeneratorWithConfig(generatorConfig) if err != nil { return nil, NewErrCacheCreationFailed(cacheSize, err) } return &Generator{ engine: engineGen, config: config, }, nil } // Generate creates an identicon for the given input string and size with context support. // The context can be used to set timeouts or cancel generation. // // The input string is hashed to generate a deterministic identicon. // Size must be positive and represents the width/height in pixels. // // This method applies the security limits that were configured when the Generator was created. // For different limits, create a new Generator with NewGeneratorWithConfig. // // This method is thread-safe and uses caching if configured. func (g *Generator) Generate(ctx context.Context, input string, size int) (*Icon, error) { // Apply validation using the generator's stored configuration if err := validateInputs(input, size, g.config); err != nil { return nil, err } // Check for early cancellation if err := ctx.Err(); err != nil { return nil, err } // Convert input to hash hash := util.ComputeHash(input) // Validate complexity before generation if err := validateComplexity(hash, g.config); err != nil { return nil, err } // Generate using the internal engine with context engineIcon, err := g.engine.Generate(ctx, hash, float64(size)) if err != nil { return nil, NewErrGenerationFailed(input, size, err) } // Wrap in public Icon directly return newIcon(engineIcon), nil } // GetCacheMetrics returns the cache hit and miss counts. // These metrics are thread-safe to read. func (g *Generator) GetCacheMetrics() (hits, misses int64) { return g.engine.GetCacheMetrics() }