@@ -18,8 +18,10 @@ package schemavalidators
1818import (
1919 _ "embed"
2020 "errors"
21+ "fmt"
2122 "slices"
2223 "strings"
24+ "sync"
2325
2426 "github.com/hashicorp/go-multierror"
2527 "github.com/santhosh-tekuri/jsonschema/v5"
@@ -119,69 +121,131 @@ var (
119121 aiCodingSessionSpecVersion0_1 string
120122)
121123
122- // schemaURLMapping maps the schema URL to the schema content. This is used to compile the schema validators
123- // against the schemas on external_schemas/*. This is done in the init function.
124- // The keys are the URLs of the schemas and the values are the schema content that can be found in the embedded
125- // files.
126- var schemaURLMapping = map [string ]string {
127- "http://cyclonedx.org/schema/jsf-0.82.schema.json" : jsfSpecVersion0_82 ,
128- "http://cyclonedx.org/schema/spdx.schema.json" : spdxSpec ,
129- "http://cyclonedx.org/schema/bom-1.5.schema.json" : bomSpecVersion1_5 ,
130- "http://cyclonedx.org/schema/bom-1.6.schema.json" : bomSpecVersion1_6 ,
131- "https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json" : casfSpecVersion2_0 ,
132- "https://docs.oasis-open.org/csaf/csaf/v2.1/csaf_json_schema.json" : casfSpecVersion2_1 ,
133- "https://www.first.org/cvss/cvss-v2.0.json" : cvssSpecVersion2_0 ,
134- "https://www.first.org/cvss/cvss-v3.0.json" : cvssSpecVersion3_0 ,
135- "https://www.first.org/cvss/cvss-v3.1.json" : cvssSpecVersion3_1 ,
136- "https://www.first.org/cvss/cvss-v4.0.json" : cvssSpecVersion4_0 ,
137- "https://chainloop.dev/schemas/runner-context-response-0.1.schema.json" : runnerContextSpecVersion0_1 ,
138- "https://schemas.chainloop.dev/prinfo/1.0/pr-info.schema.json" : prInfoSpecVersion1_0 ,
139- "https://schemas.chainloop.dev/prinfo/1.1/pr-info.schema.json" : prInfoSpecVersion1_1 ,
140- "https://schemas.chainloop.dev/prinfo/1.2/pr-info.schema.json" : prInfoSpecVersion1_2 ,
141- "https://schemas.chainloop.dev/prinfo/1.3/pr-info.schema.json" : prInfoSpecVersion1_3 ,
142- "https://schemas.chainloop.dev/aiagentconfig/0.1/ai-agent-config.schema.json" : aiAgentConfigSpecVersion0_1 ,
143- "https://schemas.chainloop.dev/aicodingsession/0.1/ai-coding-session.schema.json" : aiCodingSessionSpecVersion0_1 ,
124+ var (
125+ compiledCycloneDxSchemas map [CycloneDXVersion ]* jsonschema.Schema
126+ cycloneDxOnce sync.Once
127+ compiledCSAFSchemas map [CSAFVersion ]* jsonschema.Schema
128+ csafOnce sync.Once
129+ compiledRunnerContextSchemas map [RunnerContextVersion ]* jsonschema.Schema
130+ runnerContextOnce sync.Once
131+ compiledPRInfoSchemas map [PRInfoVersion ]* jsonschema.Schema
132+ prInfoOnce sync.Once
133+ compiledAIAgentConfigSchemas map [AIAgentConfigVersion ]* jsonschema.Schema
134+ aiAgentConfigOnce sync.Once
135+ compiledAICodingSessionSchemas map [AICodingSessionVersion ]* jsonschema.Schema
136+ aiCodingSessionOnce sync.Once
137+ )
138+
139+ func initCycloneDxSchemas () {
140+ compiler := jsonschema .NewCompiler ()
141+ if err := compiler .AddResource ("http://cyclonedx.org/schema/jsf-0.82.schema.json" , strings .NewReader (jsfSpecVersion0_82 )); err != nil {
142+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "http://cyclonedx.org/schema/jsf-0.82.schema.json" , err ))
143+ }
144+ if err := compiler .AddResource ("http://cyclonedx.org/schema/spdx.schema.json" , strings .NewReader (spdxSpec )); err != nil {
145+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "http://cyclonedx.org/schema/spdx.schema.json" , err ))
146+ }
147+ if err := compiler .AddResource ("http://cyclonedx.org/schema/bom-1.5.schema.json" , strings .NewReader (bomSpecVersion1_5 )); err != nil {
148+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "http://cyclonedx.org/schema/bom-1.5.schema.json" , err ))
149+ }
150+ if err := compiler .AddResource ("http://cyclonedx.org/schema/bom-1.6.schema.json" , strings .NewReader (bomSpecVersion1_6 )); err != nil {
151+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "http://cyclonedx.org/schema/bom-1.6.schema.json" , err ))
152+ }
153+
154+ // MustCompile panics if the embedded schema is malformed. This is a build-time
155+ // invariant: the schemas are embedded at compile time and must always be valid.
156+ compiledCycloneDxSchemas = map [CycloneDXVersion ]* jsonschema.Schema {
157+ CycloneDXVersion1_5 : compiler .MustCompile ("http://cyclonedx.org/schema/bom-1.5.schema.json" ),
158+ CycloneDXVersion1_6 : compiler .MustCompile ("http://cyclonedx.org/schema/bom-1.6.schema.json" ),
159+ }
144160}
145161
146- var compiledCycloneDxSchemas map [CycloneDXVersion ]* jsonschema.Schema
147- var compiledCSAFSchemas map [CSAFVersion ]* jsonschema.Schema
148- var compiledRunnerContextSchemas map [RunnerContextVersion ]* jsonschema.Schema
149- var compiledPRInfoSchemas map [PRInfoVersion ]* jsonschema.Schema
150- var compiledAIAgentConfigSchemas map [AIAgentConfigVersion ]* jsonschema.Schema
151- var compiledAICodingSessionSchemas map [AICodingSessionVersion ]* jsonschema.Schema
162+ func initCSAFSchemas () {
163+ compiler := jsonschema .NewCompiler ()
164+ if err := compiler .AddResource ("https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json" , strings .NewReader (casfSpecVersion2_0 )); err != nil {
165+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json" , err ))
166+ }
167+ if err := compiler .AddResource ("https://docs.oasis-open.org/csaf/csaf/v2.1/csaf_json_schema.json" , strings .NewReader (casfSpecVersion2_1 )); err != nil {
168+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://docs.oasis-open.org/csaf/csaf/v2.1/csaf_json_schema.json" , err ))
169+ }
170+ if err := compiler .AddResource ("https://www.first.org/cvss/cvss-v2.0.json" , strings .NewReader (cvssSpecVersion2_0 )); err != nil {
171+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://www.first.org/cvss/cvss-v2.0.json" , err ))
172+ }
173+ if err := compiler .AddResource ("https://www.first.org/cvss/cvss-v3.0.json" , strings .NewReader (cvssSpecVersion3_0 )); err != nil {
174+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://www.first.org/cvss/cvss-v3.0.json" , err ))
175+ }
176+ if err := compiler .AddResource ("https://www.first.org/cvss/cvss-v3.1.json" , strings .NewReader (cvssSpecVersion3_1 )); err != nil {
177+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://www.first.org/cvss/cvss-v3.1.json" , err ))
178+ }
179+ if err := compiler .AddResource ("https://www.first.org/cvss/cvss-v4.0.json" , strings .NewReader (cvssSpecVersion4_0 )); err != nil {
180+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://www.first.org/cvss/cvss-v4.0.json" , err ))
181+ }
182+
183+ compiledCSAFSchemas = map [CSAFVersion ]* jsonschema.Schema {
184+ CSAFVersion2_0 : compiler .MustCompile ("https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json" ),
185+ CSAFVersion2_1 : compiler .MustCompile ("https://docs.oasis-open.org/csaf/csaf/v2.1/csaf_json_schema.json" ),
186+ }
187+ }
152188
153- func init () {
189+ func initRunnerContextSchemas () {
154190 compiler := jsonschema .NewCompiler ()
155- for url , schema := range schemaURLMapping {
156- _ = compiler . AddResource ( url , strings . NewReader ( schema ))
191+ if err := compiler . AddResource ( "https://chainloop.dev/schemas/runner-context-response-0.1.schema.json" , strings . NewReader ( runnerContextSpecVersion0_1 )); err != nil {
192+ panic ( fmt . Sprintf ( "schemavalidators: failed to add resource %s: %v" , "https://chainloop.dev/schemas/runner-context-response-0.1. schema.json" , err ))
157193 }
158194
159- compiledCycloneDxSchemas = make (map [CycloneDXVersion ]* jsonschema.Schema )
160- compiledCycloneDxSchemas [CycloneDXVersion1_5 ] = compiler .MustCompile ("http://cyclonedx.org/schema/bom-1.5.schema.json" )
161- compiledCycloneDxSchemas [CycloneDXVersion1_6 ] = compiler .MustCompile ("http://cyclonedx.org/schema/bom-1.6.schema.json" )
195+ compiledRunnerContextSchemas = map [RunnerContextVersion ]* jsonschema.Schema {
196+ RunnerContextVersion0_1 : compiler .MustCompile ("https://chainloop.dev/schemas/runner-context-response-0.1.schema.json" ),
197+ }
198+ }
162199
163- compiledCSAFSchemas = make (map [CSAFVersion ]* jsonschema.Schema )
164- compiledCSAFSchemas [CSAFVersion2_0 ] = compiler .MustCompile ("https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json" )
165- compiledCSAFSchemas [CSAFVersion2_1 ] = compiler .MustCompile ("https://docs.oasis-open.org/csaf/csaf/v2.1/csaf_json_schema.json" )
200+ func initPRInfoSchemas () {
201+ compiler := jsonschema .NewCompiler ()
202+ if err := compiler .AddResource ("https://schemas.chainloop.dev/prinfo/1.0/pr-info.schema.json" , strings .NewReader (prInfoSpecVersion1_0 )); err != nil {
203+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://schemas.chainloop.dev/prinfo/1.0/pr-info.schema.json" , err ))
204+ }
205+ if err := compiler .AddResource ("https://schemas.chainloop.dev/prinfo/1.1/pr-info.schema.json" , strings .NewReader (prInfoSpecVersion1_1 )); err != nil {
206+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://schemas.chainloop.dev/prinfo/1.1/pr-info.schema.json" , err ))
207+ }
208+ if err := compiler .AddResource ("https://schemas.chainloop.dev/prinfo/1.2/pr-info.schema.json" , strings .NewReader (prInfoSpecVersion1_2 )); err != nil {
209+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://schemas.chainloop.dev/prinfo/1.2/pr-info.schema.json" , err ))
210+ }
211+ if err := compiler .AddResource ("https://schemas.chainloop.dev/prinfo/1.3/pr-info.schema.json" , strings .NewReader (prInfoSpecVersion1_3 )); err != nil {
212+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://schemas.chainloop.dev/prinfo/1.3/pr-info.schema.json" , err ))
213+ }
166214
167- compiledRunnerContextSchemas = make (map [RunnerContextVersion ]* jsonschema.Schema )
168- compiledRunnerContextSchemas [RunnerContextVersion0_1 ] = compiler .MustCompile ("https://chainloop.dev/schemas/runner-context-response-0.1.schema.json" )
215+ compiledPRInfoSchemas = map [PRInfoVersion ]* jsonschema.Schema {
216+ PRInfoVersion1_0 : compiler .MustCompile ("https://schemas.chainloop.dev/prinfo/1.0/pr-info.schema.json" ),
217+ PRInfoVersion1_1 : compiler .MustCompile ("https://schemas.chainloop.dev/prinfo/1.1/pr-info.schema.json" ),
218+ PRInfoVersion1_2 : compiler .MustCompile ("https://schemas.chainloop.dev/prinfo/1.2/pr-info.schema.json" ),
219+ PRInfoVersion1_3 : compiler .MustCompile ("https://schemas.chainloop.dev/prinfo/1.3/pr-info.schema.json" ),
220+ }
221+ }
169222
170- compiledPRInfoSchemas = make ( map [ PRInfoVersion ] * jsonschema. Schema )
171- compiledPRInfoSchemas [ PRInfoVersion1_0 ] = compiler . MustCompile ( "https://schemas.chainloop.dev/prinfo/1.0/pr-info.schema.json" )
172- compiledPRInfoSchemas [ PRInfoVersion1_1 ] = compiler .MustCompile ("https://schemas.chainloop.dev/prinfo/1 .1/pr-info .schema.json" )
173- compiledPRInfoSchemas [ PRInfoVersion1_2 ] = compiler . MustCompile ( " https://schemas.chainloop.dev/prinfo/1.2/pr-info .schema.json" )
174- compiledPRInfoSchemas [ PRInfoVersion1_3 ] = compiler . MustCompile ( "https://schemas.chainloop.dev/prinfo/1.3/pr-info.schema.json" )
223+ func initAIAgentConfigSchemas () {
224+ compiler := jsonschema . NewCompiler ( )
225+ if err : = compiler .AddResource ("https://schemas.chainloop.dev/aiagentconfig/0 .1/ai-agent-config .schema.json" , strings . NewReader ( aiAgentConfigSpecVersion0_1 )); err != nil {
226+ panic ( fmt . Sprintf ( "schemavalidators: failed to add resource %s: %v" , " https://schemas.chainloop.dev/aiagentconfig/0.1/ai-agent-config .schema.json", err ) )
227+ }
175228
176- compiledAIAgentConfigSchemas = make (map [AIAgentConfigVersion ]* jsonschema.Schema )
177- compiledAIAgentConfigSchemas [AIAgentConfigVersion0_1 ] = compiler .MustCompile ("https://schemas.chainloop.dev/aiagentconfig/0.1/ai-agent-config.schema.json" )
229+ compiledAIAgentConfigSchemas = map [AIAgentConfigVersion ]* jsonschema.Schema {
230+ AIAgentConfigVersion0_1 : compiler .MustCompile ("https://schemas.chainloop.dev/aiagentconfig/0.1/ai-agent-config.schema.json" ),
231+ }
232+ }
178233
179- compiledAICodingSessionSchemas = make (map [AICodingSessionVersion ]* jsonschema.Schema )
180- compiledAICodingSessionSchemas [AICodingSessionVersion0_1 ] = compiler .MustCompile ("https://schemas.chainloop.dev/aicodingsession/0.1/ai-coding-session.schema.json" )
234+ func initAICodingSessionSchemas () {
235+ compiler := jsonschema .NewCompiler ()
236+ if err := compiler .AddResource ("https://schemas.chainloop.dev/aicodingsession/0.1/ai-coding-session.schema.json" , strings .NewReader (aiCodingSessionSpecVersion0_1 )); err != nil {
237+ panic (fmt .Sprintf ("schemavalidators: failed to add resource %s: %v" , "https://schemas.chainloop.dev/aicodingsession/0.1/ai-coding-session.schema.json" , err ))
238+ }
239+
240+ compiledAICodingSessionSchemas = map [AICodingSessionVersion ]* jsonschema.Schema {
241+ AICodingSessionVersion0_1 : compiler .MustCompile ("https://schemas.chainloop.dev/aicodingsession/0.1/ai-coding-session.schema.json" ),
242+ }
181243}
182244
183245// ValidateCycloneDX validates the given object against the specified CycloneDX schema version.
184246func ValidateCycloneDX (data interface {}, version CycloneDXVersion ) error {
247+ cycloneDxOnce .Do (initCycloneDxSchemas )
248+
185249 if version == "" {
186250 version = CycloneDXVersion1_6
187251 }
@@ -222,6 +286,8 @@ func ValidateCycloneDX(data interface{}, version CycloneDXVersion) error {
222286// ValidateCSAF validates the given object against a CSAF schema version.
223287// The schema version is determined by the "csaf_version" field in the object.
224288func ValidateCSAF (data interface {}) error {
289+ csafOnce .Do (initCSAFSchemas )
290+
225291 var errs error
226292 err := compiledCSAFSchemas [CSAFVersion2_1 ].Validate (data )
227293 if err != nil {
@@ -249,6 +315,8 @@ func ValidateCSAF(data interface{}) error {
249315// ValidateChainloopRunnerContext validates the runner context schema.
250316// The schema version is determined by the "id" field in the object.
251317func ValidateChainloopRunnerContext (data interface {}, version RunnerContextVersion ) error {
318+ runnerContextOnce .Do (initRunnerContextSchemas )
319+
252320 if version == "" {
253321 version = RunnerContextVersion0_1
254322 }
@@ -271,6 +339,8 @@ func ValidateChainloopRunnerContext(data interface{}, version RunnerContextVersi
271339
272340// ValidatePRInfo validates the PR/MR info schema.
273341func ValidatePRInfo (data interface {}, version PRInfoVersion ) error {
342+ prInfoOnce .Do (initPRInfoSchemas )
343+
274344 if version == "" {
275345 version = PRInfoVersion1_3
276346 }
@@ -293,6 +363,8 @@ func ValidatePRInfo(data interface{}, version PRInfoVersion) error {
293363
294364// ValidateAIAgentConfig validates the AI agent config schema.
295365func ValidateAIAgentConfig (data any , version AIAgentConfigVersion ) error {
366+ aiAgentConfigOnce .Do (initAIAgentConfigSchemas )
367+
296368 if version == "" {
297369 version = AIAgentConfigVersion0_1
298370 }
@@ -315,6 +387,8 @@ func ValidateAIAgentConfig(data any, version AIAgentConfigVersion) error {
315387
316388// ValidateAICodingSession validates the AI coding session schema.
317389func ValidateAICodingSession (data any , version AICodingSessionVersion ) error {
390+ aiCodingSessionOnce .Do (initAICodingSessionSchemas )
391+
318392 if version == "" {
319393 version = AICodingSessionVersion0_1
320394 }
0 commit comments