Repository: spegel-org/spegel
Pull request #897 (2025-05-23 11:58:50)

Add descriptor to header conversion
Adds helper functions to get descriptor from http headers and vice versa. Makes things easier to test.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/spegel-org/spegel/pull/897?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org) Report
All modified and coverable lines are covered by tests :white_check_mark:

| [Files with missing lines](https://app.codecov.io/gh/spegel-org/spegel/pull/897?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org) | Coverage Δ | |
|---|---|---|
| [pkg/oci/client.go](https://app.codecov.io/gh/spegel-org/spegel/pull/897?src=pr&el=tree&filepath=pkg%2Foci%2Fclient.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-cGtnL29jaS9jbGllbnQuZ28=) | `51.73% <100.00%> (+6.16%)` | :arrow_up: |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/spegel-org/spegel/pull/897/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org)

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c0e7c64..d089969 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - [#888](https://github.com/spegel-org/spegel/pull/888) Refactor OCI events to support content events.
 - [#890](https://github.com/spegel-org/spegel/pull/890) Refactor Containerd options to use config struct.
 - [#896](https://github.com/spegel-org/spegel/pull/896) Rename package mux to httpx and refactor http helpers.
+- [#897](https://github.com/spegel-org/spegel/pull/897) Add descriptor to header conversion.
 
 ### Deprecated
 
diff --git a/pkg/oci/client.go b/pkg/oci/client.go
index 40d3838..d71a321 100644
--- a/pkg/oci/client.go
+++ b/pkg/oci/client.go
@@ -205,34 +205,10 @@ func (c *Client) fetch(ctx context.Context, method string, dist DistributionPath
 			return nil, ocispec.Descriptor{}, err
 		}
 
-		dgst := dist.Digest
-		dgstStr := resp.Header.Get(HeaderDockerDigest)
-		if dgstStr != "" {
-			dgst, err = digest.Parse(dgstStr)
-			if err != nil {
-				return nil, ocispec.Descriptor{}, err
-			}
-		}
-		if dgst == "" {
-			return nil, ocispec.Descriptor{}, errors.New("digest cannot be empty")
-		}
-		mt := resp.Header.Get(httpx.HeaderContentType)
-		if mt == "" {
-			return nil, ocispec.Descriptor{}, errors.New("content type header cannot be empty")
-		}
-		cl := resp.Header.Get(httpx.HeaderContentLength)
-		if cl == "" {
-			return nil, ocispec.Descriptor{}, errors.New("content length header cannot be empty")
-		}
-		size, err := strconv.ParseInt(cl, 10, 64)
+		desc, err := DescriptorFromHeader(resp.Header)
 		if err != nil {
 			return nil, ocispec.Descriptor{}, err
 		}
-		desc := ocispec.Descriptor{
-			Digest:    dgst,
-			MediaType: mt,
-			Size:      size,
-		}
 		return resp.Body, desc, nil
 	}
 	return nil, ocispec.Descriptor{}, errors.New("could not perform request")
@@ -289,3 +265,34 @@ func getBearerToken(ctx context.Context, wwwAuth string, client *http.Client) (s
 	}
 	return tokenResp.Token, nil
 }
+
+func DescriptorFromHeader(header http.Header) (ocispec.Descriptor, error) {
+	mediaType := header.Get(httpx.HeaderContentType)
+	if mediaType == "" {
+		return ocispec.Descriptor{}, errors.New("content type cannot be empty")
+	}
+	contentLength := header.Get(httpx.HeaderContentLength)
+	if contentLength == "" {
+		return ocispec.Descriptor{}, errors.New("content length cannot be empty")
+	}
+	size, err := strconv.ParseInt(contentLength, 10, 64)
+	if err != nil {
+		return ocispec.Descriptor{}, err
+	}
+	dgst, err := digest.Parse(header.Get(HeaderDockerDigest))
+	if err != nil {
+		return ocispec.Descriptor{}, err
+	}
+	desc := ocispec.Descriptor{
+		MediaType: mediaType,
+		Size:      size,
+		Digest:    dgst,
+	}
+	return desc, nil
+}
+
+func WriteDescriptorToHeader(desc ocispec.Descriptor, header http.Header) {
+	header.Set(httpx.HeaderContentType, desc.MediaType)
+	header.Set(httpx.HeaderContentLength, strconv.FormatInt(desc.Size, 10))
+	header.Set(HeaderDockerDigest, desc.Digest.String())
+}


Test patch:
diff --git a/pkg/httpx/status_test.go b/pkg/httpx/status_test.go
index 423efa7..bf67d05 100644
--- a/pkg/httpx/status_test.go
+++ b/pkg/httpx/status_test.go
@@ -73,7 +73,7 @@ func TestStatusError(t *testing.T) {
 
 			rec := httptest.NewRecorder()
 			rec.WriteHeader(tt.statusCode)
-			rec.Header().Set("Content-Type", tt.contentType)
+			rec.Header().Set(HeaderContentType, tt.contentType)
 			rec.Body = bytes.NewBufferString(tt.body)
 
 			resp := &http.Response{
diff --git a/pkg/oci/client_test.go b/pkg/oci/client_test.go
index 8ac6439..3fcd573 100644
--- a/pkg/oci/client_test.go
+++ b/pkg/oci/client_test.go
@@ -96,3 +96,38 @@ func TestPull(t *testing.T) {
 
 	require.NotEmpty(t, pullResults)
 }
+
+func TestDescriptorHeader(t *testing.T) {
+	t.Parallel()
+
+	header := http.Header{}
+	desc := ocispec.Descriptor{
+		MediaType: "foo",
+		Size:      909,
+		Digest:    digest.Digest("sha256:b6d6089ca6c395fd563c2084f5dd7bc56a2f5e6a81413558c5be0083287a77e9"),
+	}
+
+	WriteDescriptorToHeader(desc, header)
+	require.Equal(t, "foo", header.Get(httpx.HeaderContentType))
+	require.Equal(t, "909", header.Get(httpx.HeaderContentLength))
+	require.Equal(t, "sha256:b6d6089ca6c395fd563c2084f5dd7bc56a2f5e6a81413558c5be0083287a77e9", header.Get(HeaderDockerDigest))
+	headerDesc, err := DescriptorFromHeader(header)
+	require.NoError(t, err)
+	require.Equal(t, desc, headerDesc)
+
+	header = http.Header{}
+	_, err = DescriptorFromHeader(header)
+	require.EqualError(t, err, "content type cannot be empty")
+	header.Set(httpx.HeaderContentType, "test")
+	_, err = DescriptorFromHeader(header)
+	require.EqualError(t, err, "content length cannot be empty")
+	header.Set(httpx.HeaderContentLength, "wrong")
+	_, err = DescriptorFromHeader(header)
+	require.EqualError(t, err, "strconv.ParseInt: parsing \"wrong\": invalid syntax")
+	header.Set(httpx.HeaderContentLength, "250000")
+	_, err = DescriptorFromHeader(header)
+	require.EqualError(t, err, "invalid checksum digest format")
+	header.Set(HeaderDockerDigest, "foobar")
+	_, err = DescriptorFromHeader(header)
+	require.EqualError(t, err, "invalid checksum digest format")
+}