<|start_filename|>clients/cii_blob_client.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clients

import (
	"context"
	"fmt"

	"gocloud.dev/blob"

	// Needed to link GCP drivers.
	_ "gocloud.dev/blob/gcsblob"
)

// blobClientCIIBestPractices implements the CIIBestPracticesClient interface.
// A gocloud blob client is used to communicate with the CII Best Practices data.
type blobClientCIIBestPractices struct {
	bucketURL string
}

// GetBadgeLevel implements CIIBestPracticesClient.GetBadgeLevel.
func (client *blobClientCIIBestPractices) GetBadgeLevel(ctx context.Context, uri string) (BadgeLevel, error) {
	bucket, err := blob.OpenBucket(ctx, client.bucketURL)
	if err != nil {
		return Unknown, fmt.Errorf("error during blob.OpenBucket: %w", err)
	}
	defer bucket.Close()

	objectName := fmt.Sprintf("%s/result.json", uri)

	exists, err := bucket.Exists(ctx, objectName)
	if err != nil {
		return Unknown, fmt.Errorf("error during bucket.Exists: %w", err)
	}
	if !exists {
		return NotFound, nil
	}

	jsonData, err := bucket.ReadAll(ctx, objectName)
	if err != nil {
		return Unknown, fmt.Errorf("error during bucket.ReadAll: %w", err)
	}

	parsedResponse, err := ParseBadgeResponseFromJSON(jsonData)
	if err != nil {
		return Unknown, fmt.Errorf("error parsing data: %w", err)
	}
	if len(parsedResponse) < 1 {
		return NotFound, nil
	}
	return parsedResponse[0].getBadgeLevel()
}


<|start_filename|>cron/data/writer.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package data

import (
	"encoding/csv"
	"fmt"
	"io"
	"sort"

	"github.com/jszwec/csvutil"
)

// WriteTo writes `repos` to `out`.
func WriteTo(out io.Writer, repos []RepoFormat) error {
	csvWriter := csv.NewWriter(out)
	enc := csvutil.NewEncoder(csvWriter)
	if err := enc.Encode(repos); err != nil {
		return fmt.Errorf("error during Encode: %w", err)
	}
	csvWriter.Flush()
	return nil
}

// SortAndAppendTo appends `oldRepos` and `newRepos` before sorting and writing out the result to `out`.
func SortAndAppendTo(out io.Writer, oldRepos, newRepos []RepoFormat) error {
	oldRepos = append(oldRepos, newRepos...)

	sort.SliceStable(oldRepos, func(i, j int) bool {
		return oldRepos[i].Repo < oldRepos[j].Repo
	})
	return WriteTo(out, oldRepos)
}

// SortAndAppendFrom reads from `in`, appends to newRepos and writes the sorted output to `out`.
func SortAndAppendFrom(in io.Reader, out io.Writer, newRepos []RepoFormat) error {
	iter, err := MakeIteratorFrom(in)
	if err != nil {
		return fmt.Errorf("error during MakeIterator: %w", err)
	}

	oldRepos := make([]RepoFormat, 0)
	for iter.HasNext() {
		repo, err := iter.Next()
		if err != nil {
			return fmt.Errorf("error during iter.Next: %w", err)
		}
		oldRepos = append(oldRepos, repo)
	}
	return SortAndAppendTo(out, oldRepos, newRepos)
}


<|start_filename|>clients/search.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clients

// SearchRequest queries a repo for `Query`.
// If `Filename` is provided, only matching filenames are queried.
// If `Path` is provided, only files with matching paths are queried.
type SearchRequest struct {
	Query    string
	Filename string
	Path     string
}

// SearchResponse returns the results from a search request on a repo.
type SearchResponse struct {
	Results []SearchResult
	Hits    int
}

// SearchResult represents a matching result from the search query.
type SearchResult struct {
	Path string
}


<|start_filename|>clients/githubrepo/copy.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package githubrepo

import "time"

func copyBoolPtr(src *bool, dest **bool) {
	if src != nil {
		*dest = new(bool)
		**dest = *src
	}
}

func copyStringPtr(src *string, dest **string) {
	if src != nil {
		*dest = new(string)
		**dest = *src
	}
}

func copyInt32Ptr(src *int32, dest **int32) {
	if src != nil {
		*dest = new(int32)
		**dest = *src
	}
}

func copyTimePtr(src *time.Time, dest **time.Time) {
	if src != nil {
		*dest = new(time.Time)
		**dest = *src
	}
}

func copyStringSlice(src []string, dest *[]string) {
	*dest = make([]string, len(src))
	copy(*dest, src)
}


<|start_filename|>clients/githubrepo/repo_test.go<|end_filename|>
// Copyright 2020 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package githubrepo

import (
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestRepoURL_IsValid(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name     string
		inputURL string
		expected repoURL
		wantErr  bool
	}{
		{
			name: "Valid http address",
			expected: repoURL{
				host:  "github.com",
				owner: "foo",
				repo:  "kubeflow",
			},
			inputURL: "https://github.com/foo/kubeflow",
			wantErr:  false,
		},
		{
			name: "Valid http address with trailing slash",
			expected: repoURL{
				host:  "github.com",
				owner: "foo",
				repo:  "kubeflow",
			},
			inputURL: "https://github.com/foo/kubeflow/",
			wantErr:  false,
		},
		{
			name: "Non github repository",
			expected: repoURL{
				host:  "gitlab.com",
				owner: "foo",
				repo:  "kubeflow",
			},
			inputURL: "https://gitlab.com/foo/kubeflow",
			wantErr:  true,
		},
		{
			name: "github repository",
			expected: repoURL{
				host:  "github.com",
				owner: "foo",
				repo:  "kubeflow",
			},
			inputURL: "foo/kubeflow",
			wantErr:  false,
		},
		{
			name: "github repository",
			expected: repoURL{
				host:  "github.com",
				owner: "foo",
				repo:  "kubeflow",
			},
			inputURL: "https://github.com/foo/kubeflow",
			wantErr:  false,
		},
	}
	for _, tt := range tests {
		tt := tt // Re-initializing variable so it is not changed while executing the closure below
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			r := repoURL{
				host:  tt.expected.host,
				owner: tt.expected.owner,
				repo:  tt.expected.repo,
			}
			if err := r.parse(tt.inputURL); err != nil {
				t.Errorf("repoURL.parse() error = %v", err)
			}
			if err := r.IsValid(); (err != nil) != tt.wantErr {
				t.Errorf("repoURL.IsValid() error = %v, wantErr %v", err, tt.wantErr)
			}
			if !tt.wantErr && !cmp.Equal(tt.expected, r, cmp.AllowUnexported(repoURL{})) {
				t.Errorf("Got diff: %s", cmp.Diff(tt.expected, r))
			}
		})
	}
}


<|start_filename|>clients/cii_response.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clients

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
)

const (
	inProgressResp = "in_progress"
	passingResp    = "passing"
	silverResp     = "silver"
	goldResp       = "gold"
)

var errUnsupportedBadge = errors.New("unsupported badge")

// BadgeResponse struct is used to read/write CII Best Practices badge data.
type BadgeResponse struct {
	BadgeLevel string `json:"badge_level"`
}

// getBadgeLevel parses a string badge value into BadgeLevel enum.
func (resp BadgeResponse) getBadgeLevel() (BadgeLevel, error) {
	if strings.Contains(resp.BadgeLevel, inProgressResp) {
		return InProgress, nil
	}
	if strings.Contains(resp.BadgeLevel, passingResp) {
		return Passing, nil
	}
	if strings.Contains(resp.BadgeLevel, silverResp) {
		return Silver, nil
	}
	if strings.Contains(resp.BadgeLevel, goldResp) {
		return Gold, nil
	}
	return Unknown, fmt.Errorf("%w: %s", errUnsupportedBadge, resp.BadgeLevel)
}

// AsJSON outputs BadgeResponse struct in JSON format.
func (resp BadgeResponse) AsJSON() ([]byte, error) {
	ret := []BadgeResponse{resp}
	jsonData, err := json.Marshal(ret)
	if err != nil {
		return nil, fmt.Errorf("error during json.Marshal: %w", err)
	}
	return jsonData, nil
}

// ParseBadgeResponseFromJSON parses input []byte value into []BadgeResponse.
func ParseBadgeResponseFromJSON(data []byte) ([]BadgeResponse, error) {
	parsedResponse := []BadgeResponse{}
	if err := json.Unmarshal(data, &parsedResponse); err != nil {
		return nil, fmt.Errorf("error during json.Unmarshal: %w", err)
	}
	return parsedResponse, nil
}


<|start_filename|>clients/githubrepo/roundtripper/tokens/rpc.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tokens

// Token is used for GitHub token server RPC request/response.
type Token struct {
	Value string
	ID    uint64
}

// TokenOverRPC is an RPC handler implementation for Golang RPCs.
type TokenOverRPC struct {
	client TokenAccessor
}

// Next requests for the next available GitHub token.
// Server blocks the call until a token becomes available.
func (accessor *TokenOverRPC) Next(args struct{}, token *Token) error {
	id, val := accessor.client.Next()
	*token = Token{
		ID:    id,
		Value: val,
	}
	return nil
}

// Release inserts the token at `index` back into the token pool to be used by another client.
func (accessor *TokenOverRPC) Release(id uint64, reply *struct{}) error {
	accessor.client.Release(id)
	return nil
}

// NewTokenOverRPC creates a new instance of TokenOverRPC.
func NewTokenOverRPC(client TokenAccessor) *TokenOverRPC {
	return &TokenOverRPC{
		client: client,
	}
}


<|start_filename|>clients/branch.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clients

// BranchRef represents a single branch reference and its protection rules.
type BranchRef struct {
	Name                 *string
	Protected            *bool
	BranchProtectionRule BranchProtectionRule
}

// BranchProtectionRule captures the settings enabled on a branch for security.
type BranchProtectionRule struct {
	RequiredPullRequestReviews PullRequestReviewRule
	AllowDeletions             *bool
	AllowForcePushes           *bool
	RequireLinearHistory       *bool
	EnforceAdmins              *bool
	CheckRules                 StatusChecksRule
}

// StatusChecksRule captures settings on status checks.
type StatusChecksRule struct {
	UpToDateBeforeMerge  *bool
	RequiresStatusChecks *bool
	Contexts             []string
}

// PullRequestReviewRule captures settings on a PullRequest.
type PullRequestReviewRule struct {
	RequiredApprovingReviewCount *int32
	DismissStaleReviews          *bool
	RequireCodeOwnerReviews      *bool
}


<|start_filename|>docs/checks/internal/reader.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package internal contains internal functions for reading input YAML file.
package internal

import (

	// Used to embed `checks.yaml` file.
	_ "embed"
	"fmt"

	"gopkg.in/yaml.v2"
)

//go:embed checks.yaml
var checksYAML []byte

// Check stores a check's information.
// nolint:govet
type Check struct {
	Risk        string   `yaml:"risk"`
	Short       string   `yaml:"short"`
	Description string   `yaml:"description"`
	Tags        string   `yaml:"tags"`
	Repos       string   `yaml:"repos"`
	Remediation []string `yaml:"remediation"`
	Name        string   `yaml:"-"`
	URL         string   `yaml:"-"`
}

// Doc stores the documentation for all checks.
type Doc struct {
	InternalChecks map[string]Check `yaml:"checks"`
}

// ReadDoc reads documentation from the `checks.yaml` file.
func ReadDoc() (Doc, error) {
	var m Doc
	if err := yaml.Unmarshal(checksYAML, &m); err != nil {
		return Doc{}, fmt.Errorf("yaml.Unmarshal: %w", err)
	}
	return m, nil
}


<|start_filename|>clients/githubrepo/roundtripper/tokens/round_robin.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tokens

import (
	"sync/atomic"
	"time"
)

const expiryTimeInSec = 30

// roundRobinAccessor implements TokenAccessor.
type roundRobinAccessor struct {
	accessTokens []string
	accessState  []int64
	counter      uint64
}

// Next implements TokenAccessor.Next.
func (tokens *roundRobinAccessor) Next() (uint64, string) {
	c := atomic.AddUint64(&tokens.counter, 1)
	l := len(tokens.accessTokens)
	index := c % uint64(l)

	// If selected accessToken is unavailable, wait.
	for !atomic.CompareAndSwapInt64(&tokens.accessState[index], 0, time.Now().Unix()) {
		currVal := tokens.accessState[index]
		expired := time.Now().After(time.Unix(currVal, 0).Add(expiryTimeInSec * time.Second))
		if !expired {
			continue
		}
		if atomic.CompareAndSwapInt64(&tokens.accessState[index], currVal, time.Now().Unix()) {
			break
		}
	}
	return index, tokens.accessTokens[index]
}

// Release implements TokenAccessor.Release.
func (tokens *roundRobinAccessor) Release(id uint64) {
	atomic.SwapInt64(&tokens.accessState[id], 0)
}

func makeRoundRobinAccessor(accessTokens []string) TokenAccessor {
	return &roundRobinAccessor{
		accessTokens: accessTokens,
		accessState:  make([]int64, len(accessTokens)),
	}
}


<|start_filename|>clients/githubrepo/roundtripper/tokens/accessor.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package tokens defines interface to access GitHub PATs.
package tokens

import (
	"os"
	"strings"
)

// githubAuthServer is the RPC URL for the token server.
const githubAuthServer = "GITHUB_AUTH_SERVER"

// TokenAccessor interface defines a `retrieve-once` data structure.
// Implementations of this interface must be thread-safe.
type TokenAccessor interface {
	Next() (uint64, string)
	Release(uint64)
}

func readGitHubTokens() (string, bool) {
	githubAuthTokens := []string{"GITHUB_AUTH_TOKEN", "GITHUB_TOKEN", "GH_TOKEN", "GH_AUTH_TOKEN"}
	for _, name := range githubAuthTokens {
		if token, exists := os.LookupEnv(name); exists && token != "" {
			return token, exists
		}
	}
	return "", false
}

// MakeTokenAccessor is a factory function of TokenAccessor.
func MakeTokenAccessor() TokenAccessor {
	if value, exists := readGitHubTokens(); exists {
		return makeRoundRobinAccessor(strings.Split(value, ","))
	}
	if value, exists := os.LookupEnv(githubAuthServer); exists {
		return makeRPCAccessor(value)
	}
	return nil
}


<|start_filename|>checker/detail_logger_impl.go<|end_filename|>
// Copyright 2020 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package checker

import (
	"fmt"
)

// UPGRADEv2: messages2 will ultimately
// be renamed to messages.
type logger struct {
	messages2 []CheckDetail
}

func (l *logger) Info(desc string, args ...interface{}) {
	cd := CheckDetail{Type: DetailInfo, Msg: LogMessage{Text: fmt.Sprintf(desc, args...)}}
	l.messages2 = append(l.messages2, cd)
}

func (l *logger) Warn(desc string, args ...interface{}) {
	cd := CheckDetail{Type: DetailWarn, Msg: LogMessage{Text: fmt.Sprintf(desc, args...)}}
	l.messages2 = append(l.messages2, cd)
}

func (l *logger) Debug(desc string, args ...interface{}) {
	cd := CheckDetail{Type: DetailDebug, Msg: LogMessage{Text: fmt.Sprintf(desc, args...)}}
	l.messages2 = append(l.messages2, cd)
}

// UPGRADEv3: to rename.
func (l *logger) Info3(msg *LogMessage) {
	cd := CheckDetail{
		Type: DetailInfo,
		Msg:  *msg,
	}
	cd.Msg.Version = 3
	l.messages2 = append(l.messages2, cd)
}

func (l *logger) Warn3(msg *LogMessage) {
	cd := CheckDetail{
		Type: DetailWarn,
		Msg:  *msg,
	}
	cd.Msg.Version = 3
	l.messages2 = append(l.messages2, cd)
}

func (l *logger) Debug3(msg *LogMessage) {
	cd := CheckDetail{
		Type: DetailDebug,
		Msg:  *msg,
	}
	cd.Msg.Version = 3
	l.messages2 = append(l.messages2, cd)
}


<|start_filename|>clients/cii_http_client.go<|end_filename|>
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clients

import (
	"context"
	"errors"
	"fmt"
	"io"
	"math"
	"net/http"
	"time"
)

var errTooManyRequests = errors.New("failed after exponential backoff")

// httpClientCIIBestPractices implements the CIIBestPracticesClient interface.
// A HTTP client with exponential backoff is used to communicate with the CII Best Practices servers.
type httpClientCIIBestPractices struct{}

type expBackoffTransport struct {
	numRetries uint8
}

func (transport *expBackoffTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	for i := 0; i < int(transport.numRetries); i++ {
		resp, err := http.DefaultClient.Do(req)
		if err != nil || resp.StatusCode != http.StatusTooManyRequests {
			// nolint: wrapcheck
			return resp, err
		}
		time.Sleep(time.Duration(math.Pow(2, float64(i))) * time.Second)
	}
	return nil, errTooManyRequests
}

// GetBadgeLevel implements CIIBestPracticesClient.GetBadgeLevel.
func (client *httpClientCIIBestPractices) GetBadgeLevel(ctx context.Context, uri string) (BadgeLevel, error) {
	repoURI := fmt.Sprintf("https://%s", uri)
	url := fmt.Sprintf("https://bestpractices.coreinfrastructure.org/projects.json?url=%s", repoURI)
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return Unknown, fmt.Errorf("error during http.NewRequestWithContext: %w", err)
	}

	httpClient := http.Client{
		Transport: &expBackoffTransport{
			numRetries: 3,
		},
	}
	resp, err := httpClient.Do(req)
	if err != nil {
		return Unknown, fmt.Errorf("error during http.Do: %w", err)
	}
	defer resp.Body.Close()

	jsonData, err := io.ReadAll(resp.Body)
	if err != nil {
		return Unknown, fmt.Errorf("error during io.ReadAll: %w", err)
	}

	parsedResponse, err := ParseBadgeResponseFromJSON(jsonData)
	if err != nil {
		return Unknown, fmt.Errorf("error during json parsing: %w", err)
	}
	if len(parsedResponse) < 1 {
		return NotFound, nil
	}
	return parsedResponse[0].getBadgeLevel()
}
