github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/elixir/mixlock/mixlock.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 mixlock extracts elixir mix.lock files.
    16  package mixlock
    17  
    18  import (
    19  	"context"
    20  	"path/filepath"
    21  
    22  	"github.com/google/osv-scalibr/extractor/filesystem"
    23  	"github.com/google/osv-scalibr/extractor/filesystem/internal/units"
    24  	"github.com/google/osv-scalibr/extractor/filesystem/language/erlang/mixlock/mixlockutils"
    25  	"github.com/google/osv-scalibr/inventory"
    26  	"github.com/google/osv-scalibr/plugin"
    27  	"github.com/google/osv-scalibr/stats"
    28  )
    29  
    30  const (
    31  	// Name is the unique name of this extractor.
    32  	Name = "elixir/mixlock"
    33  
    34  	// defaultMaxFileSizeBytes is the maximum file size this extractor will process.
    35  	defaultMaxFileSizeBytes = 10 * units.MiB // 10 MB
    36  )
    37  
    38  // Config is the configuration for the Elixir extractor.
    39  type Config struct {
    40  	// Stats is a stats collector for reporting metrics.
    41  	Stats stats.Collector
    42  	// MaxFileSizeBytes is the maximum file size this extractor will unmarshal. If
    43  	// `FileRequired` gets a bigger file, it will return false,
    44  	MaxFileSizeBytes int64
    45  }
    46  
    47  // DefaultConfig returns the default configuration for the Elixir extractor.
    48  func DefaultConfig() Config {
    49  	return Config{
    50  		MaxFileSizeBytes: defaultMaxFileSizeBytes,
    51  	}
    52  }
    53  
    54  // Extractor structure for mix.lock files.
    55  type Extractor struct {
    56  	stats            stats.Collector
    57  	maxFileSizeBytes int64
    58  }
    59  
    60  // New returns an Elixir extractor.
    61  //
    62  // For most use cases, initialize with:
    63  // ```
    64  // e := New(DefaultConfig())
    65  // ```
    66  func New(cfg Config) *Extractor {
    67  	return &Extractor{
    68  		stats:            cfg.Stats,
    69  		maxFileSizeBytes: cfg.MaxFileSizeBytes,
    70  	}
    71  }
    72  
    73  // NewDefault returns an extractor with the default config settings.
    74  func NewDefault() filesystem.Extractor { return New(DefaultConfig()) }
    75  
    76  // Config returns the configuration of the extractor.
    77  func (e Extractor) Config() Config {
    78  	return Config{
    79  		Stats:            e.stats,
    80  		MaxFileSizeBytes: e.maxFileSizeBytes,
    81  	}
    82  }
    83  
    84  // Name of the extractor.
    85  func (e Extractor) Name() string { return Name }
    86  
    87  // Version of the extractor.
    88  func (e Extractor) Version() int { return 0 }
    89  
    90  // Requirements of the extractor.
    91  func (e Extractor) Requirements() *plugin.Capabilities { return &plugin.Capabilities{} }
    92  
    93  // FileRequired returns true if the specified file matches the mix.lock pattern.
    94  func (e Extractor) FileRequired(api filesystem.FileAPI) bool {
    95  	path := api.Path()
    96  	if !(filepath.Base(path) == "mix.lock") {
    97  		return false
    98  	}
    99  
   100  	fileinfo, err := api.Stat()
   101  	if err != nil || (e.maxFileSizeBytes > 0 && fileinfo.Size() > e.maxFileSizeBytes) {
   102  		e.reportFileRequired(path, stats.FileRequiredResultSizeLimitExceeded)
   103  		return false
   104  	}
   105  
   106  	e.reportFileRequired(path, stats.FileRequiredResultOK)
   107  	return true
   108  }
   109  
   110  func (e Extractor) reportFileRequired(path string, result stats.FileRequiredResult) {
   111  	if e.stats == nil {
   112  		return
   113  	}
   114  	e.stats.AfterFileRequired(e.Name(), &stats.FileRequiredStats{
   115  		Path:   path,
   116  		Result: result,
   117  	})
   118  }
   119  
   120  // Extract parses the mix.lock file to extract Elixir package dependencies.
   121  func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) {
   122  	inv, err := mixlockutils.ParseMixLockFile(input)
   123  	if e.stats != nil {
   124  		var fileSizeBytes int64
   125  		if input.Info != nil {
   126  			fileSizeBytes = input.Info.Size()
   127  		}
   128  		e.stats.AfterFileExtracted(e.Name(), &stats.FileExtractedStats{
   129  			Path:          input.Path,
   130  			Result:        filesystem.ExtractorErrorToFileExtractedResult(err),
   131  			FileSizeBytes: fileSizeBytes,
   132  		})
   133  	}
   134  	return inv, err
   135  }