Skip to content

Commit cc5bf3f

Browse files
authored
Merge pull request #667 from package-url/improve-requirement-schema
Improve requirement schema
2 parents 2fb05da + de0438a commit cc5bf3f

37 files changed

+237
-73
lines changed

etc/scripts/purl_test.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@
2626
from pydantic import BaseModel, ConfigDict, Field
2727

2828

29+
class PackageUrlTestDefinition(BaseModel):
30+
model_config = ConfigDict(
31+
extra="forbid",
32+
)
33+
field_schema: Optional[Any] = Field(
34+
None,
35+
alias="$schema",
36+
description="Contains the URL of the JSON schema for Package-URL tests.",
37+
title="JSON schema",
38+
)
39+
tests: Optional[list[PurlTest]] = Field(
40+
None,
41+
description="A list of Package-URL build and parse tests.",
42+
min_length=1,
43+
title="Test suite",
44+
)
45+
46+
2947
class PurlComponents(BaseModel):
3048
model_config = ConfigDict(
3149
extra="forbid",
@@ -70,21 +88,3 @@ class PurlTest(BaseModel):
7088
description="The reason why this test is is expected to fail if expected_failure is true.",
7189
title="Expected failure reason",
7290
)
73-
74-
75-
class PurlTestDefinition(BaseModel):
76-
model_config = ConfigDict(
77-
extra="forbid",
78-
)
79-
field_schema: Optional[Any] = Field(
80-
None,
81-
alias="$schema",
82-
description="Contains the URL of the JSON schema for Package-URL tests.",
83-
title="JSON schema",
84-
)
85-
tests: Optional[list[PurlTest]] = Field(
86-
None,
87-
description="A list of Package-URL build and parse tests.",
88-
min_length=1,
89-
title="Test suite",
90-
)

etc/scripts/purl_type_definition.py

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,32 @@
2323

2424
from __future__ import annotations
2525
from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel
26-
from typing import Any, Literal, Optional
26+
from typing import Literal, Optional, Union
2727

2828

2929
class Example(RootModel[str]):
3030
root: str = Field(..., pattern="^pkg:[a-z][a-z0-9-\\.]+/.*$")
3131

3232

33+
class OptionalRequirement(RootModel[Literal["optional"]]):
34+
root: Literal["optional"] = Field(
35+
...,
36+
description="States that this PURL component is optional for a PURL type.",
37+
title="Component optional requirement",
38+
)
39+
40+
3341
class PackageUrlTypeDefinition(BaseModel):
3442
model_config = ConfigDict(
3543
extra="forbid",
3644
)
37-
field_schema: Optional[Any] = Field(
38-
None,
39-
alias="$schema",
40-
description="Contains the URL of the JSON schema for Package-URL type definition.",
41-
title="JSON schema",
45+
field_schema: Literal["https://packageurl.org/schemas/purl-type-definition.schema-1.0.json"] = (
46+
Field(
47+
"https://packageurl.org/schemas/purl-type-definition.schema-1.0.json",
48+
alias="$schema",
49+
description="Contains the URL of the JSON schema for Package-URL type definition.",
50+
title="JSON schema",
51+
)
4252
)
4353
field_id: str = Field(
4454
...,
@@ -64,31 +74,47 @@ class PackageUrlTypeDefinition(BaseModel):
6474
..., description="The description of this PURL type.", title="Description"
6575
)
6676
repository: Repository = Field(
67-
..., description="Package repository usage for this PURL type.", title="Repository"
77+
..., description="The package repository usage for this PURL type.", title="Repository"
6878
)
6979
namespace_definition: NamespaceDefinition = Field(
7080
...,
71-
description="Definition of the namespace component for this PURL type.",
81+
description=(
82+
"Definition of the namespace component for this PURL type. The PURL namespace component"
83+
" must be required, optional or prohibited for a specific PURL type definition."
84+
),
7285
title="Namespace definition",
7386
)
74-
name_definition: PurlComponentDefinition = Field(
87+
name_definition: NameDefinition = Field(
7588
...,
76-
description="Definition of the name component for this PURL type.",
89+
description=(
90+
"Definition of the name component for this PURL type. The PURL name component is"
91+
" required for all PURL type definitions."
92+
),
7793
title="Name definition",
7894
)
79-
version_definition: Optional[PurlComponentDefinition] = Field(
95+
version_definition: Optional[VersionDefinition] = Field(
8096
None,
81-
description="Definition of the version component for this PURL type.",
97+
description=(
98+
"Definition of the version component for this PURL type. The PURL version component is"
99+
" optional for a specific PURL type definition."
100+
),
82101
title="Version definition",
83102
)
84103
qualifiers_definition: Optional[list[QualifiersDefinitionItem]] = Field(
85104
None,
86-
description="Definition for the qualifiers specific to this PURL type.",
105+
description=(
106+
"Definition of the qualifiers specific to this PURL type. The PURL qualifiers component"
107+
" is optional for a specific PURL type, but a qualifiers key or keys may be required"
108+
" for a specific PURL type."
109+
),
87110
title="Qualifiers definition",
88111
)
89-
subpath_definition: Optional[PurlComponentDefinition] = Field(
112+
subpath_definition: Optional[SubpathDefinition] = Field(
90113
None,
91-
description="Definition for the subpath for this PURL type.",
114+
description=(
115+
"The definition for the subpath for this PURL type. The PURL subpath component is"
116+
" optional for a specific PURL type definition."
117+
),
92118
title="Subpath definition",
93119
)
94120
examples: list[Example] = Field(
@@ -105,11 +131,19 @@ class PackageUrlTypeDefinition(BaseModel):
105131
)
106132

107133

134+
class ProhibitedRequirement(RootModel[Literal["prohibited"]]):
135+
root: Literal["prohibited"] = Field(
136+
...,
137+
description="States that this PURL component is prohibited for a PURL type.",
138+
title="Component prohibited requirement",
139+
)
140+
141+
108142
class PurlComponentDefinition(BaseModel):
109143
permitted_characters: Optional[str] = Field(
110144
None,
111145
description=(
112-
"Regular expression (ECMA-262 dialect) defining the 'Permitted characters' for this"
146+
"A regular expression (ECMA-262 dialect) defining the 'Permitted characters' for this"
113147
" component of this Package-URL type. If provided, this must be a subset of the"
114148
" 'Permitted characters' defined in the PURL specification."
115149
),
@@ -148,7 +182,11 @@ class QualifiersDefinitionItem(BaseModel):
148182
extra="forbid",
149183
)
150184
key: str = Field(..., description="The key for the qualifier.", title="Qualifier key")
151-
requirement: Optional[Requirement] = None
185+
requirement: Optional[Union[OptionalRequirement, RequiredRequirement]] = Field(
186+
None,
187+
description="States that a PURL qualifier key is optional or required for a PURL type.",
188+
title="Qualifier key requirement",
189+
)
152190
description: str = Field(
153191
..., description="The description of this qualifier.", title="Description"
154192
)
@@ -168,7 +206,7 @@ class Repository(BaseModel):
168206
)
169207
use_repository: bool = Field(
170208
...,
171-
description="true if this PURL type use a public package repository.",
209+
description="true if this PURL type uses a public package repository.",
172210
title="Use repository",
173211
)
174212
default_repository_url: Optional[AnyUrl] = Field(
@@ -179,13 +217,40 @@ class Repository(BaseModel):
179217
note: Optional[str] = Field(None, description="Extra note text.", title="Note")
180218

181219

182-
class Requirement(RootModel[Literal["required", "optional", "prohibited"]]):
183-
root: Literal["required", "optional", "prohibited"] = Field(
220+
class RequiredRequirement(RootModel[Literal["required"]]):
221+
root: Literal["required"] = Field(
184222
...,
185-
description="States if this PURL component is required, optional, or prohibited.",
186-
title="Component requirement",
223+
description="States that this PURL component is required for a PURL type.",
224+
title="Component required requirement",
225+
)
226+
227+
228+
class SubpathDefinition(PurlComponentDefinition):
229+
requirement: OptionalRequirement = Field(
230+
..., description="States that the PURL subpath is optional.", title="Subpath requirement"
231+
)
232+
233+
234+
class VersionDefinition(PurlComponentDefinition):
235+
requirement: OptionalRequirement = Field(
236+
..., description="States that the PURL version is optional.", title="Version requirement"
237+
)
238+
239+
240+
class NameDefinition(PurlComponentDefinition):
241+
requirement: RequiredRequirement = Field(
242+
...,
243+
description="States that the PURL name component is always required.",
244+
title="Name component requirement",
187245
)
188246

189247

190248
class NamespaceDefinition(PurlComponentDefinition):
191-
requirement: Requirement
249+
requirement: Union[OptionalRequirement, RequiredRequirement, ProhibitedRequirement] = Field(
250+
...,
251+
description=(
252+
"States that the PURL namespace component is optional, required or prohibited for a"
253+
" PURL type."
254+
),
255+
title="Namespace requirement",
256+
)

etc/scripts/purl_types_index.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
from pydantic import Field, RootModel
2626

2727

28-
class PackageUrlTypesList(RootModel[list[str]]):
28+
class PackageUrlTypesIndex(RootModel[list[str]]):
2929
root: list[str] = Field(
3030
...,
31-
description="A list of the registered Package-URL types.",
32-
title="Package-URL types list.",
31+
description="An index of registered Package-URL types.",
32+
title="Package-URL types index",
3333
)

schemas/purl-type-definition.schema.json

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,23 @@
66
"type": "object",
77
"additionalProperties": false,
88
"definitions": {
9-
"requirement": {
10-
"title": "Component requirement",
11-
"description": "States whether this PURL component is required, optional, or prohibited.",
12-
"type": "string",
13-
"enum": [
14-
"required",
15-
"optional",
16-
"prohibited"
17-
],
18-
"meta:enum": {
19-
"required": "This PURL component is required for this PURL type.",
20-
"optional": "This PURL component is optional for this PURL type.",
21-
"prohibited": "This PURL component is prohibited: it must not be present for this PURL type."
22-
}
23-
},
249
"optional_requirement": {
2510
"title": "Component optional requirement",
2611
"description": "States that this PURL component is optional for a PURL type.",
2712
"type": "string",
28-
"constant": "optional"
13+
"const": "optional"
2914
},
3015
"required_requirement": {
3116
"title": "Component required requirement",
3217
"description": "States that this PURL component is required for a PURL type.",
3318
"type": "string",
34-
"constant": "required"
19+
"const": "required"
20+
},
21+
"prohibited_requirement": {
22+
"title": "Component prohibited requirement",
23+
"description": "States that this PURL component is prohibited for a PURL type.",
24+
"type": "string",
25+
"const": "prohibited"
3526
},
3627
"purl_component_definition": {
3728
"title": "PURL component definition",
@@ -86,7 +77,7 @@
8677
"$schema": {
8778
"title": "JSON schema",
8879
"description": "Contains the URL of the JSON schema for Package-URL type definition.",
89-
"constant": "https://packageurl.org/schemas/purl-type.schema-1.0.json",
80+
"const": "https://packageurl.org/schemas/purl-type-definition.schema-1.0.json",
9081
"format": "uri"
9182
},
9283
"$id": {
@@ -157,7 +148,20 @@
157148
],
158149
"properties": {
159150
"requirement": {
160-
"$ref": "#/definitions/requirement"
151+
"title": "Namespace requirement",
152+
"description": "States that the PURL namespace component is optional, required or prohibited for a PURL type.",
153+
"type": "string",
154+
"oneOf": [
155+
{
156+
"$ref": "#/definitions/optional_requirement"
157+
},
158+
{
159+
"$ref": "#/definitions/required_requirement"
160+
},
161+
{
162+
"$ref": "#/definitions/prohibited_requirement"
163+
}
164+
]
161165
}
162166
},
163167
"allOf": [
@@ -175,7 +179,14 @@
175179
],
176180
"properties": {
177181
"requirement": {
178-
"$ref": "#/definitions/required_requirement"
182+
"title": "Name component requirement",
183+
"description": "States that the PURL name component is always required.",
184+
"type": "string",
185+
"oneOf": [
186+
{
187+
"$ref": "#/definitions/required_requirement"
188+
}
189+
]
179190
}
180191
},
181192
"allOf": [
@@ -193,7 +204,14 @@
193204
],
194205
"properties": {
195206
"requirement": {
196-
"$ref": "#/definitions/optional_requirement"
207+
"title": "Version requirement",
208+
"description": "States that the PURL version is optional.",
209+
"type": "string",
210+
"oneOf": [
211+
{
212+
"$ref": "#/definitions/optional_requirement"
213+
}
214+
]
197215
}
198216
},
199217
"allOf": [
@@ -224,7 +242,17 @@
224242
"type": "string"
225243
},
226244
"requirement": {
227-
"$ref": "#/definitions/requirement"
245+
"title": "Qualifier key requirement",
246+
"description": "States that a PURL qualifier key is optional or required for a PURL type.",
247+
"type": "string",
248+
"oneOf": [
249+
{
250+
"$ref": "#/definitions/optional_requirement"
251+
},
252+
{
253+
"$ref": "#/definitions/required_requirement"
254+
}
255+
]
228256
},
229257
"description": {
230258
"title": "Description",
@@ -253,7 +281,14 @@
253281
],
254282
"properties": {
255283
"requirement": {
256-
"$ref": "#/definitions/optional_requirement"
284+
"title": "Subpath requirement",
285+
"description": "States that the PURL subpath is optional.",
286+
"type": "string",
287+
"oneOf": [
288+
{
289+
"$ref": "#/definitions/optional_requirement"
290+
}
291+
]
257292
}
258293
},
259294
"allOf": [

0 commit comments

Comments
 (0)