Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion cache/cache.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2025-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT

package cache
Expand All @@ -21,10 +21,30 @@ type SchemaCacheEntry struct {
ResourceNodes map[string]*yaml.Node
}

// SchemaResourceCacheEntry holds one rendered document-level JSON Schema resource.
// Resource entries are shared by many compiled schema entry points from the same parsed document.
// RenderedNode may point at source YAML for generic validation; consumers must treat it as read-only.
type SchemaResourceCacheEntry struct {
RenderedInline []byte
ReferenceSchema string
RenderedJSON []byte
RenderedNode *yaml.Node
SourceRootNode *yaml.Node // Keeps pointer-identity cache keys live for the cache entry lifetime.
}

// SchemaCache defines the interface for schema caching implementations.
// The key is a uint64 hash of the schema (from schema.GoLow().Hash()).
type SchemaCache interface {
Load(key uint64) (*SchemaCacheEntry, bool)
Store(key uint64, value *SchemaCacheEntry)
Range(f func(key uint64, value *SchemaCacheEntry) bool)
}

// SchemaResourceCache caches rendered document resources by parsed document identity.
// Entries are immutable once stored; implementations must be safe for concurrent use.
type SchemaResourceCache interface {
Load(key string) (*SchemaResourceCacheEntry, bool)
Store(key string, value *SchemaResourceCacheEntry)
Range(f func(key string, value *SchemaResourceCacheEntry) bool)
Release()
}
163 changes: 162 additions & 1 deletion cache/cache_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2025 Princess B33f Heavy Industries / Dave Shanley
// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT

package cache

import (
"sync"
"testing"

"github.com/pb33f/libopenapi/datamodel/high/base"
Expand Down Expand Up @@ -76,6 +77,30 @@ func TestDefaultCache_StoreNilCache(t *testing.T) {
assert.Nil(t, cache)
}

func TestDefaultCache_Release(t *testing.T) {
cache := NewDefaultCache()
cache.Store(1, &SchemaCacheEntry{RenderedInline: []byte("one")})
cache.Store(2, &SchemaCacheEntry{RenderedInline: []byte("two")})

cache.Release()

loaded, ok := cache.Load(1)
assert.False(t, ok)
assert.Nil(t, loaded)

count := 0
cache.Range(func(key uint64, value *SchemaCacheEntry) bool {
count++
return true
})
assert.Equal(t, 0, count)

cache.Release()

var nilCache *DefaultCache
nilCache.Release()
}

func TestDefaultCache_Range(t *testing.T) {
cache := NewDefaultCache()

Expand Down Expand Up @@ -205,6 +230,142 @@ func TestDefaultCache_MultipleKeys(t *testing.T) {
assert.Equal(t, []byte("value3"), val3.RenderedInline)
}

func TestNewDefaultSchemaResourceCache(t *testing.T) {
cache := NewDefaultSchemaResourceCache()

assert.NotNil(t, cache)
assert.NotNil(t, cache.m)
}

func TestDefaultSchemaResourceCache_StoreLoadRangeAndOverwrite(t *testing.T) {
cache := NewDefaultSchemaResourceCache()
first := &SchemaResourceCacheEntry{
RenderedInline: []byte("first"),
ReferenceSchema: "first",
RenderedJSON: []byte(`{"type":"object"}`),
}
second := &SchemaResourceCacheEntry{
RenderedInline: []byte("second"),
RenderedJSON: []byte(`{"type":"string"}`),
}

cache.Store("resource", first)
loaded, ok := cache.Load("resource")
require.True(t, ok)
assert.Equal(t, first.RenderedInline, loaded.RenderedInline)
assert.Equal(t, first.ReferenceSchema, loaded.ReferenceSchema)

cache.Store("resource", second)
loaded, ok = cache.Load("resource")
require.True(t, ok)
assert.Equal(t, second.RenderedInline, loaded.RenderedInline)

seen := 0
cache.Range(func(key string, value *SchemaResourceCacheEntry) bool {
seen++
assert.Equal(t, "resource", key)
assert.Equal(t, second.RenderedJSON, value.RenderedJSON)
return false
})
assert.Equal(t, 1, seen)
}

func TestDefaultSchemaResourceCache_Release(t *testing.T) {
cache := NewDefaultSchemaResourceCache()
cache.Store("one", &SchemaResourceCacheEntry{RenderedInline: []byte("one")})
cache.Store("two", &SchemaResourceCacheEntry{RenderedInline: []byte("two")})

cache.Release()

loaded, ok := cache.Load("one")
assert.False(t, ok)
assert.Nil(t, loaded)

count := 0
cache.Range(func(key string, value *SchemaResourceCacheEntry) bool {
count++
return true
})
assert.Equal(t, 0, count)

cache.Release()

var nilCache *DefaultSchemaResourceCache
nilCache.Release()
}

func TestDefaultSchemaResourceCache_EdgeCases(t *testing.T) {
cache := NewDefaultSchemaResourceCache()
loaded, ok := cache.Load("missing")
assert.False(t, ok)
assert.Nil(t, loaded)

var nilCache *DefaultSchemaResourceCache
loaded, ok = nilCache.Load("missing")
assert.False(t, ok)
assert.Nil(t, loaded)
nilCache.Store("resource", &SchemaResourceCacheEntry{})

called := false
nilCache.Range(func(key string, value *SchemaResourceCacheEntry) bool {
called = true
return true
})
assert.False(t, called)

count := 0
cache.Range(func(key string, value *SchemaResourceCacheEntry) bool {
count++
return true
})
assert.Equal(t, 0, count)

cache.m.Store(42, &SchemaResourceCacheEntry{})
cache.m.Store("invalid", "not-a-resource-entry")
cache.Store("valid", &SchemaResourceCacheEntry{RenderedInline: []byte("ok")})
var keys []string
cache.Range(func(key string, value *SchemaResourceCacheEntry) bool {
keys = append(keys, key)
return true
})
assert.Equal(t, []string{"valid"}, keys)
}

func TestDefaultSchemaResourceCache_ThreadSafety(t *testing.T) {
cache := NewDefaultSchemaResourceCache()
var wg sync.WaitGroup

for i := 0; i < 20; i++ {
wg.Add(1)
go func(val int) {
defer wg.Done()
cache.Store(string(rune('a'+val)), &SchemaResourceCacheEntry{
RenderedInline: []byte{byte(val)},
RenderedJSON: []byte{byte(val)},
})
}(i)
}
wg.Wait()

for i := 0; i < 20; i++ {
wg.Add(1)
go func(val int) {
defer wg.Done()
loaded, ok := cache.Load(string(rune('a' + val)))
assert.True(t, ok)
assert.NotNil(t, loaded)
}(i)
}
wg.Wait()

count := 0
cache.Range(func(key string, value *SchemaResourceCacheEntry) bool {
count++
return true
})
assert.Equal(t, 20, count)
}

func TestDefaultCache_ThreadSafety(t *testing.T) {
cache := NewDefaultCache()

Expand Down
73 changes: 72 additions & 1 deletion cache/default_cache.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT

package cache

import "sync"
Expand All @@ -7,13 +10,42 @@ type DefaultCache struct {
m *sync.Map
}

var _ SchemaCache = &DefaultCache{}
// DefaultSchemaResourceCache is the default thread-safe cache for rendered document resources.
type DefaultSchemaResourceCache struct {
m *sync.Map
}

var (
_ SchemaCache = &DefaultCache{}
_ SchemaResourceCache = &DefaultSchemaResourceCache{}
)

// NewDefaultCache creates a new DefaultCache with an initialized sync.Map.
func NewDefaultCache() *DefaultCache {
return &DefaultCache{m: &sync.Map{}}
}

// NewDefaultSchemaResourceCache creates a default cache for rendered document resources.
func NewDefaultSchemaResourceCache() *DefaultSchemaResourceCache {
return &DefaultSchemaResourceCache{m: &sync.Map{}}
}

// Release clears all cached schema entries.
func (c *DefaultCache) Release() {
if c == nil || c.m == nil {
return
}
c.m.Clear()
}

// Release clears all cached rendered document resources.
func (c *DefaultSchemaResourceCache) Release() {
if c == nil || c.m == nil {
return
}
c.m.Clear()
}

// Load retrieves a schema from the cache.
func (c *DefaultCache) Load(key uint64) (*SchemaCacheEntry, bool) {
if c == nil || c.m == nil {
Expand Down Expand Up @@ -52,3 +84,42 @@ func (c *DefaultCache) Range(f func(key uint64, value *SchemaCacheEntry) bool) {
return f(key, val)
})
}

// Load retrieves a rendered document resource from the cache.
func (c *DefaultSchemaResourceCache) Load(key string) (*SchemaResourceCacheEntry, bool) {
if c == nil || c.m == nil {
return nil, false
}
val, ok := c.m.Load(key)
if !ok {
return nil, false
}
resourceCache, ok := val.(*SchemaResourceCacheEntry)
return resourceCache, ok
}

// Store saves a rendered document resource to the cache.
func (c *DefaultSchemaResourceCache) Store(key string, value *SchemaResourceCacheEntry) {
if c == nil || c.m == nil {
return
}
c.m.Store(key, value)
}

// Range calls f for each rendered document resource cache entry.
func (c *DefaultSchemaResourceCache) Range(f func(key string, value *SchemaResourceCacheEntry) bool) {
if c == nil || c.m == nil {
return
}
c.m.Range(func(k, v interface{}) bool {
key, ok := k.(string)
if !ok {
return true
}
val, ok := v.(*SchemaResourceCacheEntry)
if !ok {
return true
}
return f(key, val)
})
}
Loading
Loading