github.com/google/osv-scalibr@v0.4.1/inventory/osvecosystem/parsed.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 osvecosystem provides the Parsed type which represents an OSV ecosystem string. 16 package osvecosystem 17 18 import ( 19 "encoding/json" 20 "fmt" 21 "strings" 22 23 "github.com/ossf/osv-schema/bindings/go/osvconstants" 24 ) 25 26 // Parsed represents an ecosystem-with-suffix string as defined by the [spec], parsed into 27 // a structured format. 28 // 29 // The suffix is optional and is separated from the ecosystem by a colon. 30 // 31 // For example, "Debian:7" would be parsed into Parsed{Ecosystem: constants.EcosystemDebian, Suffix: "7"} 32 // 33 // [spec]: https://ossf.github.io/osv-schema/ 34 // 35 //nolint:recvcheck 36 type Parsed struct { 37 Ecosystem osvconstants.Ecosystem 38 Suffix string 39 } 40 41 // IsEmpty returns true if the Ecosystem struct is empty. 42 func (p Parsed) IsEmpty() bool { 43 return p.Ecosystem == "" 44 } 45 46 // Equal returns true if the two Parsed structs are equal. 47 func (p Parsed) Equal(other Parsed) bool { 48 // only care about the minor version if both ecosystems have one 49 // otherwise we just assume that they're the same and move on 50 if p.Suffix != "" && other.Suffix != "" { 51 return p.Ecosystem == other.Ecosystem && p.Suffix == other.Suffix 52 } 53 54 return p.Ecosystem == other.Ecosystem 55 } 56 57 func (p Parsed) String() string { 58 str := string(p.Ecosystem) 59 60 if p.Suffix != "" { 61 str += ":" + p.Suffix 62 } 63 64 return str 65 } 66 67 // UnmarshalJSON handles unmarshalls a JSON string into a Parsed struct. 68 // 69 // This method implements the json.Unmarshaler interface. 70 func (p *Parsed) UnmarshalJSON(data []byte) error { 71 var str string 72 err := json.Unmarshal(data, &str) 73 74 if err != nil { 75 return err 76 } 77 78 *p, err = Parse(str) 79 80 return err 81 } 82 83 // MarshalJSON handles marshals a Parsed struct into a JSON string. 84 // 85 // This method implements the json.Marshaler interface. 86 func (p Parsed) MarshalJSON() ([]byte, error) { 87 return []byte(`"` + p.String() + `"`), nil 88 } 89 90 // GetValidity checks if the ecosystem is a valid OSV ecosystem value. Returns nil if valid, error otherwise. 91 func (p Parsed) GetValidity() error { 92 if p.IsEmpty() { 93 return nil 94 } 95 96 // Missing ecosystems here would be caught by the "exhaustive" linter 97 switch p.Ecosystem { 98 case osvconstants.EcosystemAlmaLinux, 99 osvconstants.EcosystemAlpaquita, 100 osvconstants.EcosystemAlpine, 101 osvconstants.EcosystemAndroid, 102 osvconstants.EcosystemBellSoftHardenedContainers, 103 osvconstants.EcosystemBioconductor, 104 osvconstants.EcosystemBitnami, 105 osvconstants.EcosystemChainguard, 106 osvconstants.EcosystemConanCenter, 107 osvconstants.EcosystemCRAN, 108 osvconstants.EcosystemCratesIO, 109 osvconstants.EcosystemDebian, 110 osvconstants.EcosystemGHC, 111 osvconstants.EcosystemGitHubActions, 112 osvconstants.EcosystemGo, 113 osvconstants.EcosystemHackage, 114 osvconstants.EcosystemHex, 115 osvconstants.EcosystemKubernetes, 116 osvconstants.EcosystemLinux, 117 osvconstants.EcosystemMageia, 118 osvconstants.EcosystemMaven, 119 osvconstants.EcosystemMinimOS, 120 osvconstants.EcosystemNPM, 121 osvconstants.EcosystemNuGet, 122 osvconstants.EcosystemOpenEuler, 123 osvconstants.EcosystemOpenSUSE, 124 osvconstants.EcosystemOSSFuzz, 125 osvconstants.EcosystemPackagist, 126 osvconstants.EcosystemPhotonOS, 127 osvconstants.EcosystemPub, 128 osvconstants.EcosystemPyPI, 129 osvconstants.EcosystemRedHat, 130 osvconstants.EcosystemRockyLinux, 131 osvconstants.EcosystemRubyGems, 132 osvconstants.EcosystemSUSE, 133 osvconstants.EcosystemSwiftURL, 134 osvconstants.EcosystemUbuntu, 135 osvconstants.EcosystemWolfi: 136 137 default: 138 return fmt.Errorf("base ecosystem does not exist in osvschema: %q", p.Ecosystem) 139 } 140 141 return nil 142 } 143 144 // MustParse parses a string into a constants.Ecosystem and an optional suffix specified with a ":" 145 // Panics if there is an invalid ecosystem 146 func MustParse(str string) Parsed { 147 parsed, err := Parse(str) 148 if err != nil { 149 panic("Failed MustParse: " + err.Error()) 150 } 151 152 return parsed 153 } 154 155 // Parse parses a string into a constants.Ecosystem and an optional suffix specified with a ":" 156 func Parse(str string) (Parsed, error) { 157 // Special case to return an empty ecosystem if str is empty 158 // This is not considered an error. 159 if str == "" { 160 return Parsed{}, nil 161 } 162 163 // We will also add a check for whether the ecosystem is valid to have a suffix here. 164 // And return an error if not. 165 ecosystem, suffix, _ := strings.Cut(str, ":") 166 167 result := Parsed{osvconstants.Ecosystem(ecosystem), suffix} 168 169 return result, result.GetValidity() 170 } 171 172 // FromEcosystem creates a Parsed struct from an osvschema.Ecosystem. 173 func FromEcosystem(ecosystem osvconstants.Ecosystem) Parsed { 174 return Parsed{Ecosystem: ecosystem} 175 }