@@ -28,16 +28,36 @@ var (
2828 ErrCacheEntryNotFound = errors .New ("cache entry not found" )
2929)
3030
31- // isJSONContentType checks if the content type indicates JSON response
32- // Handles cases like "application/json" and "application/json; charset=utf-8"
33- func isJSONContentType (contentType string ) bool {
34- // Remove any leading/trailing whitespace and convert to lowercase
35- contentType = strings .TrimSpace (strings .ToLower (contentType ))
36-
37- // Check if it starts with "application/json"
38- return strings .HasPrefix (contentType , "application/json" )
31+ // CacheClient defines the interface for cache API operations.
32+ // This interface is implemented by Client and can be mocked for testing.
33+ type CacheClient interface {
34+ // CacheRegistry retrieves information about a cache registry.
35+ // Returns the registry configuration or an error if not found.
36+ CacheRegistry (ctx context.Context , registry string ) (CacheRegistryResp , error )
37+
38+ // CachePeekExists checks if a cache entry exists for the given key.
39+ // Returns the cache metadata, a boolean indicating existence (true if found), and any error.
40+ // When the cache entry is not found, returns (resp, false, nil) with resp.Message set.
41+ CachePeekExists (ctx context.Context , registry string , req CachePeekReq ) (CachePeekResp , bool , error )
42+
43+ // CacheCreate creates a new cache entry and returns upload information.
44+ // Returns upload instructions including the upload ID and storage location.
45+ CacheCreate (ctx context.Context , registry string , req CacheCreateReq ) (CacheCreateResp , error )
46+
47+ // CacheCommit marks a cache entry as committed after successful upload.
48+ // The upload ID from CacheCreate must be provided.
49+ CacheCommit (ctx context.Context , registry string , req CacheCommitReq ) (CacheCommitResp , error )
50+
51+ // CacheRetrieve retrieves download information for a cache entry.
52+ // Returns the cache metadata, a boolean indicating if found (true if exists), and any error.
53+ // When the cache entry is not found, returns (resp, false, nil) with resp.Message set.
54+ // The response may indicate a fallback key was used via resp.Fallback.
55+ CacheRetrieve (ctx context.Context , registry string , req CacheRetrieveReq ) (CacheRetrieveResp , bool , error )
3956}
4057
58+ // Verify that Client implements CacheClient
59+ var _ CacheClient = (* Client )(nil )
60+
4161type Client struct {
4262 client * http.Client
4363 endpoint string
@@ -89,11 +109,23 @@ type CachePeekReq struct {
89109}
90110
91111type CachePeekResp struct {
92- Store string `json:"store"` // The store used for the cache entry
93- Digest string `json:"digest"`
94- ExpiresAt time.Time `json:"expires_at"`
95- Compression string `json:"compression"`
96- Message string `json:"message"`
112+ Store string `json:"store"` // The store used for the cache entry
113+ Digest string `json:"digest"`
114+ ExpiresAt time.Time `json:"expires_at"`
115+ Compression string `json:"compression"`
116+ Message string `json:"message"`
117+ FileSize int `json:"file_size"`
118+ Paths []string `json:"paths"`
119+ Pipeline string `json:"pipeline"`
120+ Branch string `json:"branch"`
121+ Owner string `json:"owner"`
122+ Platform string `json:"platform"`
123+ Key string `json:"key"`
124+ FallbackKeys []string `json:"fallback_keys"`
125+ CreatedAt time.Time `json:"created_at"`
126+ AgentID string `json:"agent_id"`
127+ JobID string `json:"job_id"`
128+ BuildID string `json:"build_id"`
97129}
98130
99131type CacheRegistryResp struct {
@@ -362,9 +394,32 @@ func doRequest[T any, V any](ctx context.Context, client *http.Client, method st
362394 }
363395
364396 // read the response body
365- if err = json .NewDecoder (res .Body ).Decode (& resp ); err != nil {
397+ respBody , err := io .ReadAll (res .Body )
398+ if err != nil {
399+ return nil , resp , trace .NewError (span , "failed to read response body: %w" , err )
400+ }
401+
402+ slog .Debug ("API call" , "method" , method , "url" , url , "status" , res .StatusCode , "body" , string (respBody ))
403+
404+ if err = json .Unmarshal (respBody , & resp ); err != nil {
366405 return nil , resp , trace .NewError (span , "failed to decode response body: %w" , err )
367406 }
368407
369408 return res , resp , nil
370409}
410+
411+ // isJSONContentType checks if the content type indicates JSON response
412+ // Handles cases like "application/json", "application/json; charset=utf-8",
413+ // and structured syntax suffixes like "application/problem+json"
414+ func isJSONContentType (contentType string ) bool {
415+ // Remove any leading/trailing whitespace and convert to lowercase
416+ contentType = strings .TrimSpace (strings .ToLower (contentType ))
417+
418+ // Remove any parameters (e.g., "; charset=utf-8")
419+ if idx := strings .Index (contentType , ";" ); idx != - 1 {
420+ contentType = strings .TrimSpace (contentType [:idx ])
421+ }
422+
423+ // Check if it's application/json or application/*+json (e.g., application/problem+json)
424+ return contentType == "application/json" || strings .HasPrefix (contentType , "application/" ) && strings .HasSuffix (contentType , "+json" )
425+ }
0 commit comments