Skip to content

Commit c8476d7

Browse files
committed
feat: support publiccode.yml 0.7
0.7 adds a generic supports key to declare the standards, regulations or platforms a software complies with. id is either an alias from the documented list or a plain URI. See publiccodeyml/publiccode.yml#327
1 parent efe4783 commit c8476d7

9 files changed

Lines changed: 217 additions & 9 deletions

File tree

parser.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func (p *Parser) parseStream(in io.Reader, fileURL *url.URL) (PublicCode, error)
249249

250250
var ve ValidationResults
251251

252-
if slices.Contains(SupportedVersions, version) && version != "0" && !strings.HasPrefix(version, "0.5") {
252+
if slices.Contains(SupportedVersions, version) && version != "0" && !strings.HasPrefix(version, "0.7") {
253253
latestVersion := SupportedVersions[len(SupportedVersions)-1]
254254
line, column := getPositionInFile("publiccodeYmlVersion", file)
255255

publiccode.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package publiccode
22

33
// SupportedVersions lists the publiccode.yml versions this parser supports.
4-
var SupportedVersions = []string{"0", "0.2", "0.2.0", "0.2.1", "0.2.2", "0.3", "0.3.0", "0.4", "0.4.0", "0.5.0", "0.5"}
4+
var SupportedVersions = []string{
5+
"0",
6+
"0.2", "0.2.0", "0.2.1", "0.2.2",
7+
"0.3", "0.3.0",
8+
"0.4", "0.4.0",
9+
"0.5.0", "0.5",
10+
"0.7.0", "0.7",
11+
}
512

613
type PublicCode interface {
714
Version() uint
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
publiccodeYmlVersion: "0"
2+
3+
name: Medusa
4+
url: "https://github.com/italia/developers.italia.it.git"
5+
6+
platforms:
7+
- web
8+
9+
categories:
10+
- cloud-management
11+
12+
developmentStatus: development
13+
14+
softwareType: "standalone/other"
15+
16+
supports:
17+
- id: alias:nonexistent
18+
19+
description:
20+
en-GB:
21+
localisedName: Medusa
22+
shortDescription: >
23+
A rather short description which
24+
is probably useless
25+
longDescription: >
26+
Very long description of this software, also split
27+
on multiple rows. You should note what the software
28+
is and why one should need it. This is 158 characters.
29+
Very long description of this software, also split
30+
on multiple rows. You should note what the software
31+
is and why one should need it. This is 316 characters.
32+
Very long description of this software, also split
33+
on multiple rows. You should note what the software
34+
is and why one should need it. This is 474 characters.
35+
Very long description of this software, also split
36+
on multiple rows. You should note what the software
37+
is and why one should need it. This is 632 characters.
38+
features:
39+
- Just one feature
40+
41+
legal:
42+
license: AGPL-3.0-or-later
43+
44+
maintenance:
45+
type: "community"
46+
47+
contacts:
48+
- name: Francesco Rossi
49+
50+
localisation:
51+
localisationReady: true
52+
availableLanguages:
53+
- en
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
publiccodeYmlVersion: "0"
2+
3+
name: Medusa
4+
url: "https://github.com/italia/developers.italia.it.git"
5+
6+
platforms:
7+
- web
8+
9+
categories:
10+
- cloud-management
11+
12+
developmentStatus: development
13+
14+
softwareType: "standalone/other"
15+
16+
supports:
17+
- id: alias:gdpr
18+
- id: alias:spid
19+
- id: "https://eur-lex.europa.eu/eli/reg/2016/679/oj"
20+
- id: "urn:iso:std:iso:27001"
21+
22+
description:
23+
en-GB:
24+
localisedName: Medusa
25+
shortDescription: >
26+
A rather short description which
27+
is probably useless
28+
longDescription: >
29+
Very long description of this software, also split
30+
on multiple rows. You should note what the software
31+
is and why one should need it. This is 158 characters.
32+
Very long description of this software, also split
33+
on multiple rows. You should note what the software
34+
is and why one should need it. This is 316 characters.
35+
Very long description of this software, also split
36+
on multiple rows. You should note what the software
37+
is and why one should need it. This is 474 characters.
38+
Very long description of this software, also split
39+
on multiple rows. You should note what the software
40+
is and why one should need it. This is 632 characters.
41+
features:
42+
- Just one feature
43+
44+
legal:
45+
license: AGPL-3.0-or-later
46+
47+
maintenance:
48+
type: "community"
49+
50+
contacts:
51+
- name: Francesco Rossi
52+
53+
localisation:
54+
localisationReady: true
55+
availableLanguages:
56+
- en

v0.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
// PublicCodeV0 defines how a publiccode.yml v0.x is structured.
1111
type PublicCodeV0 struct {
12-
PubliccodeYamlVersion string `json:"publiccodeYmlVersion" validate:"required,oneof=0 0.2 0.2.0 0.2.1 0.2.2 0.3 0.3.0 0.4 0.4.0 0.5 0.5.0" yaml:"publiccodeYmlVersion"`
12+
PubliccodeYamlVersion string `json:"publiccodeYmlVersion" validate:"required,oneof=0 0.2 0.2.0 0.2.1 0.2.2 0.3 0.3.0 0.4 0.4.0 0.5 0.5.0 0.7 0.7.0" yaml:"publiccodeYmlVersion"`
1313

1414
Name string `json:"name" validate:"required" yaml:"name"`
1515
ApplicationSuite string `json:"applicationSuite,omitempty" yaml:"applicationSuite,omitempty"`
@@ -41,6 +41,8 @@ type PublicCodeV0 struct {
4141

4242
SoftwareType string `json:"softwareType" validate:"required,oneof=standalone/mobile standalone/iot standalone/desktop standalone/web standalone/backend standalone/other addon library configurationFiles" yaml:"softwareType"`
4343

44+
Supports *[]SupportV0 `json:"supports,omitempty" validate:"omitempty,dive" yaml:"supports,omitempty"`
45+
4446
IntendedAudience *struct {
4547
Scope *[]string `json:"scope,omitempty" validate:"omitempty,dive,is_scope_v0" yaml:"scope,omitempty"`
4648
Countries *[]string `json:"countries,omitempty" validate:"omitempty,dive,iso3166_1_alpha2_lower_or_upper" yaml:"countries,omitempty"`
@@ -125,6 +127,12 @@ type OrganisationV0 struct {
125127
URI string `json:"uri" validate:"required,organisation_uri" yaml:"uri"`
126128
}
127129

130+
// SupportV0 declares a standard, regulation, framework or system the
131+
// software supports or complies with.
132+
type SupportV0 struct {
133+
ID string `json:"id" validate:"required,supports_id" yaml:"id"`
134+
}
135+
128136
// Country-specific sections
129137
//
130138
// While the standard is structured to be meaningful on an international level,

v0_test.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ func TestInvalidTestcasesV0_NoNetwork(t *testing.T) {
8181
ValidationError{"localisation.localisationReady", "localisationReady is a required field", 51, 3},
8282
ValidationError{"localisation.availableLanguages", "availableLanguages is a required field", 52, 3},
8383
},
84+
85+
// supports
86+
"supports_unknown_alias.yml": ValidationResults{
87+
ValidationError{"supports[0].id", "id contains an unknown alias (see https://github.com/publiccodeyml/publiccode.yml/blob/main/docs/standard/aliases-list.rst)", 17, 5},
88+
},
8489
}
8590

8691
dir := "testdata/v0/invalid/no-network/"
@@ -110,7 +115,7 @@ func TestInvalidTestcasesV0(t *testing.T) {
110115
"publiccodeYmlVersion_invalid.yml": ValidationResults{
111116
ValidationError{
112117
"publiccodeYmlVersion",
113-
"unsupported version: '1'. Supported versions: 0, 0.2, 0.2.0, 0.2.1, 0.2.2, 0.3, 0.3.0, 0.4, 0.4.0, 0.5.0, 0.5",
118+
"unsupported version: '1'. Supported versions: 0, 0.2, 0.2.0, 0.2.1, 0.2.2, 0.3, 0.3.0, 0.4, 0.4.0, 0.5.0, 0.5, 0.7.0, 0.7",
114119
0,
115120
0,
116121
},
@@ -604,13 +609,13 @@ func TestValidWithWarningsTestcasesV0(t *testing.T) {
604609
ValidationWarning{"description.en.genericName", "This key is DEPRECATED and will be removed in the future. It's safe to drop it", 23, 5},
605610
},
606611
"valid.minimal.v0.2.yml": ValidationResults{
607-
ValidationWarning{"publiccodeYmlVersion", "v0.2 is not the latest version, use '0'. Parsing this file as v0.5.", 1, 1},
612+
ValidationWarning{"publiccodeYmlVersion", "v0.2 is not the latest version, use '0'. Parsing this file as v0.7.", 1, 1},
608613
},
609614
"valid.minimal.v0.3.yml": ValidationResults{
610-
ValidationWarning{"publiccodeYmlVersion", "v0.3 is not the latest version, use '0'. Parsing this file as v0.5.", 1, 1},
615+
ValidationWarning{"publiccodeYmlVersion", "v0.3 is not the latest version, use '0'. Parsing this file as v0.7.", 1, 1},
611616
},
612617
"valid.minimal.v0.4.yml": ValidationResults{
613-
ValidationWarning{"publiccodeYmlVersion", "v0.4 is not the latest version, use '0'. Parsing this file as v0.5.", 1, 1},
618+
ValidationWarning{"publiccodeYmlVersion", "v0.4 is not the latest version, use '0'. Parsing this file as v0.7.", 1, 1},
614619
},
615620
"valid.mime_types.yml": ValidationResults{
616621
ValidationWarning{"inputTypes", "This key is DEPRECATED and will be removed in the future. It's safe to drop it", 48, 1},

v1_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func TestInvalidTestcasesV1(t *testing.T) {
1414
expected := ValidationResults{
1515
ValidationError{
1616
"publiccodeYmlVersion",
17-
"unsupported version: '1'. Supported versions: 0, 0.2, 0.2.0, 0.2.1, 0.2.2, 0.3, 0.3.0, 0.4, 0.4.0, 0.5.0, 0.5",
17+
"unsupported version: '1'. Supported versions: 0, 0.2, 0.2.0, 0.2.1, 0.2.2, 0.3, 0.3.0, 0.4, 0.4.0, 0.5.0, 0.5, 0.7.0, 0.7",
1818
0,
1919
0,
2020
},

validators/v0.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package validators
22

3-
import "github.com/go-playground/validator/v10"
3+
import (
4+
"net/url"
5+
"strings"
6+
7+
"github.com/go-playground/validator/v10"
8+
)
49

510
var supportedCategoriesV0 = map[string]struct{}{
611
"accounting": {},
@@ -138,6 +143,18 @@ var supportedScopesV0 = map[string]struct{}{
138143
"welfare": {},
139144
}
140145

146+
var supportsAliasesV0 = map[string]struct{}{
147+
"gdpr": {},
148+
"eidas": {},
149+
"nis2": {},
150+
"cra": {},
151+
"spid": {},
152+
"cie": {},
153+
"anpr": {},
154+
"pagopa": {},
155+
"io": {},
156+
}
157+
141158
func isCategoryV0(fl validator.FieldLevel) bool {
142159
_, ok := supportedCategoriesV0[fl.Field().String()]
143160

@@ -149,3 +166,27 @@ func isScopeV0(fl validator.FieldLevel) bool {
149166

150167
return ok
151168
}
169+
170+
// isSupportsID validates a supports[].id: either an alias in the form
171+
// alias:<name> that must exist in the alias list, or any other valid URI
172+
// (URL or URN).
173+
func isSupportsID(fl validator.FieldLevel) bool {
174+
val := fl.Field().String()
175+
176+
if alias, ok := strings.CutPrefix(val, "alias:"); ok {
177+
_, known := supportsAliasesV0[alias]
178+
179+
return known
180+
}
181+
182+
u, err := url.ParseRequestURI(val)
183+
if err != nil {
184+
return false
185+
}
186+
187+
if strings.EqualFold(u.Scheme, "urn") {
188+
return sharedValidator.Var(val, "urn_rfc2141") == nil
189+
}
190+
191+
return u.Scheme != "" && u.Host != ""
192+
}

validators/validator.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ func New() *validator.Validate {
2323
_ = validate.RegisterValidation("is_category_v0", isCategoryV0)
2424
_ = validate.RegisterValidation("is_scope_v0", isScopeV0)
2525

26+
_ = validate.RegisterValidation("supports_id", isSupportsID)
27+
2628
_ = validate.RegisterValidation("is_italian_ipa_code", isItalianIpaCode)
2729

2830
_ = validate.RegisterValidation("bcp47_keys", bcp47_keys)
@@ -198,6 +200,42 @@ func RegisterLocalErrorMessages(v *validator.Validate, trans ut.Translator) erro
198200
tag: "is_scope_v0",
199201
translation: "{0} must be a valid scope (see https://github.com/publiccodeyml/publiccode.yml/blob/main/docs/standard/scope-list.rst)", //nolint:lll // long URL in message
200202
},
203+
{
204+
tag: "supports_id",
205+
customRegisFunc: func(ut ut.Translator) error {
206+
if err := ut.Add(
207+
"supports_id",
208+
"{0} must be a known alias ('alias:<name>') or a valid URI (URL or URN)",
209+
false,
210+
); err != nil {
211+
return fmt.Errorf("registering translation: %w", err)
212+
}
213+
214+
if err := ut.Add(
215+
"supports_id_unknown_alias",
216+
"{0} contains an unknown alias (see https://github.com/publiccodeyml/publiccode.yml/blob/main/docs/standard/aliases-list.rst)", //nolint:lll // long URL in message
217+
false,
218+
); err != nil {
219+
return fmt.Errorf("registering translation: %w", err)
220+
}
221+
222+
return nil
223+
},
224+
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
225+
field := fe.Field()
226+
val, _ := fe.Value().(string)
227+
228+
if strings.HasPrefix(val, "alias:") {
229+
t, _ := ut.T("supports_id_unknown_alias", field)
230+
231+
return t
232+
}
233+
234+
t, _ := ut.T("supports_id", field)
235+
236+
return t
237+
},
238+
},
201239
{
202240
tag: "is_italian_ipa_code",
203241
translation: "{0} must be a valid Italian Public Administration Code (iPA) (see https://github.com/publiccodeyml/italian-organizations-ipa-vocabulary)", //nolint:lll // long URL in message

0 commit comments

Comments
 (0)