44package cache
55
66import (
7+ // Note: AX-6 — no core equivalent for SHA-256 hashing.
78 "crypto/sha256"
9+ // Note: AX-6 — no core equivalent for URL-safe base64 encoding.
810 "encoding/base64"
11+ // Note: AX-6 — no core equivalent for hex encoding.
912 "encoding/hex"
10- "encoding/json"
13+ // Note: AX-6 — no core equivalent for fs.ErrNotExist or fs interfaces returned by Medium.List.
1114 "io/fs"
15+ // Note: AX-6 — no core equivalent for URL path escaping.
1216 "net/url"
17+ // Note: AX-6 — no core equivalent for Lstat symlink checks or dynamic working directory lookup.
1318 "os"
1419 "slices"
1520 "strings"
21+ // Note: AX-6 — core.RWMutex is not available in the pinned core module.
1622 "sync"
23+ // Note: AX-6 — no core equivalent for durations or wall-clock timestamps.
1724 "time"
1825
1926 "dappco.re/go/core"
@@ -59,9 +66,125 @@ type Cache struct {
5966// ExpiresAt: time.Now().Add(time.Hour),
6067// }
6168type Entry struct {
62- Data json.RawMessage `json:"data"`
63- CachedAt time.Time `json:"cached_at"`
64- ExpiresAt time.Time `json:"expires_at"`
69+ Data rawJSON `json:"data"`
70+ CachedAt time.Time `json:"cached_at"`
71+ ExpiresAt time.Time `json:"expires_at"`
72+ }
73+
74+ type rawJSON []byte
75+
76+ func (raw rawJSON ) MarshalJSON () ([]byte , error ) {
77+ if raw == nil {
78+ return []byte ("null" ), nil
79+ }
80+ return raw , nil
81+ }
82+
83+ func (raw * rawJSON ) UnmarshalJSON (data []byte ) error {
84+ if raw == nil {
85+ return core .E ("cache.rawJSON.UnmarshalJSON" , "target is nil" , nil )
86+ }
87+ * raw = append ((* raw )[0 :0 ], data ... )
88+ return nil
89+ }
90+
91+ func marshalPrettyJSON (value any ) (string , error ) {
92+ result := core .JSONMarshal (value )
93+ if ! result .OK {
94+ return "" , result .Value .(error )
95+ }
96+ return indentJSON (result .Value .([]byte )), nil
97+ }
98+
99+ func indentJSON (data []byte ) string {
100+ var builder strings.Builder
101+ indent := 0
102+ inString := false
103+ escaped := false
104+
105+ writeIndent := func () {
106+ for i := 0 ; i < indent ; i ++ {
107+ builder .WriteString (" " )
108+ }
109+ }
110+
111+ for i , c := range data {
112+ if inString {
113+ builder .WriteByte (c )
114+ if escaped {
115+ escaped = false
116+ continue
117+ }
118+ switch c {
119+ case '\\' :
120+ escaped = true
121+ case '"' :
122+ inString = false
123+ }
124+ continue
125+ }
126+
127+ switch c {
128+ case '"' :
129+ inString = true
130+ builder .WriteByte (c )
131+ case '{' , '[' :
132+ builder .WriteByte (c )
133+ next := nextNonJSONSpace (data , i + 1 )
134+ if next >= 0 && ((c == '{' && data [next ] == '}' ) || (c == '[' && data [next ] == ']' )) {
135+ continue
136+ }
137+ indent ++
138+ builder .WriteByte ('\n' )
139+ writeIndent ()
140+ case '}' , ']' :
141+ previous := previousNonJSONSpace (data , i - 1 )
142+ if previous >= 0 && ((c == '}' && data [previous ] == '{' ) || (c == ']' && data [previous ] == '[' )) {
143+ builder .WriteByte (c )
144+ continue
145+ }
146+ if indent > 0 {
147+ indent --
148+ }
149+ builder .WriteByte ('\n' )
150+ writeIndent ()
151+ builder .WriteByte (c )
152+ case ',' :
153+ builder .WriteByte (c )
154+ builder .WriteByte ('\n' )
155+ writeIndent ()
156+ case ':' :
157+ builder .WriteString (": " )
158+ default :
159+ if ! isJSONSpace (c ) {
160+ builder .WriteByte (c )
161+ }
162+ }
163+ }
164+
165+ return builder .String ()
166+ }
167+
168+ func nextNonJSONSpace (data []byte , start int ) int {
169+ for i := start ; i < len (data ); i ++ {
170+ if ! isJSONSpace (data [i ]) {
171+ return i
172+ }
173+ }
174+ return - 1
175+ }
176+
177+ func previousNonJSONSpace (data []byte , start int ) int {
178+ for i := start ; i >= 0 ; i -- {
179+ if ! isJSONSpace (data [i ]) {
180+ return i
181+ }
182+ }
183+ return - 1
184+ }
185+
186+ func isJSONSpace (c byte ) bool {
187+ return c == ' ' || c == '\n' || c == '\r' || c == '\t'
65188}
66189
67190// BinaryMeta is the metadata for binary cache payloads.
@@ -266,17 +389,17 @@ func (cache *Cache) set(key string, data any, ttl time.Duration, useDefaultTTL b
266389
267390 now := time .Now ()
268391 entry := Entry {
269- Data : dataResult .Value .([]byte ),
392+ Data : rawJSON ( dataResult .Value .([]byte ) ),
270393 CachedAt : now ,
271394 ExpiresAt : now .Add (ttl ),
272395 }
273396
274- entryBytes , err := json . MarshalIndent (entry , "" , " " )
397+ entryJSON , err := marshalPrettyJSON (entry )
275398 if err != nil {
276399 return core .E ("cache.Set" , "failed to marshal cache entry" , err )
277400 }
278401
279- if err := cache .medium .Write (path , string ( entryBytes ) ); err != nil {
402+ if err := cache .medium .Write (path , entryJSON ); err != nil {
280403 _ = restoreFileSnapshot (cache .medium , snapshot )
281404 return core .E ("cache.set" , "failed to write cache file" , err )
282405 }
@@ -393,7 +516,7 @@ func (cache *Cache) setBinary(key string, data []byte, contentType string, ttl t
393516 ExpiresAt : now .Add (ttl ),
394517 }
395518
396- metaBytes , err := json . MarshalIndent (meta , "" , " " )
519+ metaJSON , err := marshalPrettyJSON (meta )
397520 if err != nil {
398521 return core .E ("cache.setBinary" , "failed to marshal binary metadata" , err )
399522 }
@@ -404,7 +527,7 @@ func (cache *Cache) setBinary(key string, data []byte, contentType string, ttl t
404527 return core .E ("cache.setBinary" , "failed to write binary payload" , err )
405528 }
406529
407- if err := cache .medium .Write (jsonPath , string ( metaBytes ) ); err != nil {
530+ if err := cache .medium .Write (jsonPath , metaJSON ); err != nil {
408531 _ = restoreFileSnapshot (cache .medium , binarySnapshot )
409532 _ = restoreFileSnapshot (cache .medium , jsonSnapshot )
410533 return core .E ("cache.setBinary" , "failed to write binary metadata" , err )
@@ -1337,7 +1460,7 @@ func (httpCache *HTTPCache) readResponseRecord(key string) (*cachedResponseRecor
13371460 return nil , core .E ("cache.HTTPCache.readResponseRecord" , "failed to read cached response" , err )
13381461 }
13391462
1340- var envelope map [string ]json. RawMessage
1463+ var envelope map [string ]rawJSON
13411464 envelopeResult := core .JSONUnmarshalString (raw , & envelope )
13421465 if ! envelopeResult .OK {
13431466 return nil , core .E ("cache.HTTPCache.readResponseRecord" , "failed to unmarshal cached response" , envelopeResult .Value .(error ))
@@ -1459,7 +1582,7 @@ func (httpCache *HTTPCache) Put(req CachedRequest, resp CachedResponse, body []b
14591582 Request : req ,
14601583 Response : resp ,
14611584 }
1462- meta , err := json . MarshalIndent (record , "" , " " )
1585+ meta , err := marshalPrettyJSON (record )
14631586 if err != nil {
14641587 return core .E ("cache.HTTPCache.Put" , "failed to marshal cached response" , err )
14651588 }
@@ -1469,7 +1592,7 @@ func (httpCache *HTTPCache) Put(req CachedRequest, resp CachedResponse, body []b
14691592 _ = restoreFileSnapshot (httpCache .medium , binarySnapshot )
14701593 return core .E ("cache.HTTPCache.Put" , "failed to write cached response body" , err )
14711594 }
1472- if err := httpCache .medium .Write (metaPath , string ( meta ) ); err != nil {
1595+ if err := httpCache .medium .Write (metaPath , meta ); err != nil {
14731596 _ = restoreFileSnapshot (httpCache .medium , binarySnapshot )
14741597 _ = restoreFileSnapshot (httpCache .medium , metaSnapshot )
14751598 return core .E ("cache.HTTPCache.Put" , "failed to write cached response metadata" , err )
0 commit comments