github.com/google/osv-scalibr@v0.4.1/enricher/packagedeprecation/client.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package packagedeprecation 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 22 grpcpb "deps.dev/api/v3alpha" 23 "github.com/google/osv-scalibr/log" 24 ) 25 26 const ( 27 maxBatchSize = 5000 // Per deps.dev API documentation. 28 ) 29 30 // Client is the interface for the deps.dev client. 31 type Client interface { 32 GetVersionBatch(ctx context.Context, req Request) (Response, error) 33 } 34 35 // Request is the request for the deps.dev client. 36 type Request struct { 37 VersionKeys []VersionKey 38 } 39 40 // Response is the response for the deps.dev client. 41 type Response struct { 42 Results map[VersionKey]bool 43 } 44 45 // VersionKey contains the components to query a package version on deps.dev. 46 type VersionKey struct { 47 System grpcpb.System 48 Name string 49 Version string 50 } 51 52 // GRPCClient is the GRPC client for deps.dev. 53 type GRPCClient struct { 54 client grpcpb.InsightsClient 55 } 56 57 // NewClient returns a new GRPCClient for deps.dev 58 func NewClient(c grpcpb.InsightsClient) *GRPCClient { 59 return &GRPCClient{client: c} 60 } 61 62 // GetVersionBatch queries deps.dev for deprecation status for the given versions. 63 // It handles chunking requests to deps.dev based on maxDepsdevBatchSize, and pagination of results 64 // within each chunk. 65 func (c *GRPCClient) GetVersionBatch(ctx context.Context, req Request) (Response, error) { 66 if c.client == nil { 67 return Response{}, errors.New("deps.dev gRPC client not initialized") 68 } 69 70 results := make(map[VersionKey]bool) 71 vers := req.VersionKeys 72 73 for i := 0; i < len(vers); i += maxBatchSize { 74 // Splitting list of package versions into chunks of 5000 (maxBatchSize). 75 end := min(i+maxBatchSize, len(vers)) 76 chunk := vers[i:end] 77 78 batchReq := makeBatchReq(chunk) 79 80 // Handle pagination (if any) of the batch response. 81 for { 82 batchResp, err := c.client.GetVersionBatch(ctx, batchReq) 83 if err != nil { 84 return Response{}, fmt.Errorf("depsdev.GetVersionBatch failed: %w", err) 85 } 86 87 for _, resp := range batchResp.GetResponses() { 88 // Version not found in deps.dev 89 if resp.GetVersion() == nil { 90 continue 91 } 92 93 // Using the version key from the request (instead of from responses.version) because the 94 // package and version names might be canonicalized by deps.dev. 95 reqVer := resp.GetRequest().GetVersionKey() 96 ver := VersionKey{ 97 System: reqVer.GetSystem(), 98 Name: reqVer.GetName(), 99 Version: reqVer.GetVersion(), 100 } 101 results[ver] = resp.GetVersion().GetIsDeprecated() 102 } 103 104 if batchResp.GetNextPageToken() == "" { 105 break 106 } 107 108 updateBatchReq(batchReq, batchResp.GetNextPageToken()) 109 } 110 } 111 112 log.Debugf("Package deprecation enricher: Finished querying deps.dev for deprecation status. Number of results: %d", len(results)) 113 114 return Response{Results: results}, nil 115 }