[v16.0/forgejo] fix: Update swift package registry to use the metadata schema from the official Swift docs (#13208)

**Backport:** #12200

(cherry picked from commit 939e5e8b0a)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12200
Reviewed-by: limiting-factor <limiting-factor@noreply.codeberg.org>
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>

Co-authored-by: Benedek Kozma <cyberbeni@gmail.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/13208
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
Mathieu Fenniak 2026-06-27 23:53:43 +02:00 committed by Gusted
commit 9d2c7d9e96
7 changed files with 87 additions and 150 deletions

View file

@ -197,7 +197,7 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
case TypeRubyGems:
metadata = &rubygems.Metadata{}
case TypeSwift:
metadata = &swift.Metadata{}
metadata = &swift.Package{}
case TypeVagrant:
metadata = &vagrant.Metadata{}
default:

View file

@ -13,7 +13,6 @@ import (
"forgejo.org/modules/json"
"forgejo.org/modules/util"
"forgejo.org/modules/validation"
"github.com/hashicorp/go-version"
)
@ -37,18 +36,8 @@ const (
// Package represents a Swift package
type Package struct {
RepositoryURLs []string
Metadata *Metadata
}
// Metadata represents the metadata of a Swift package
type Metadata struct {
Description string `json:"description,omitempty"`
Keywords []string `json:"keywords,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
License string `json:"license,omitempty"`
Author Person `json:"author"`
Manifests map[string]*Manifest `json:"manifests,omitempty"`
Manifests map[string]*Manifest
Metadata *PackageRelease
}
// Manifest represents a Package.swift file
@ -57,54 +46,31 @@ type Manifest struct {
ToolsVersion string `json:"tools_version,omitempty"`
}
// https://schema.org/SoftwareSourceCode
type SoftwareSourceCode struct {
Context []string `json:"@context"`
Type string `json:"@type"`
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description,omitempty"`
Keywords []string `json:"keywords,omitempty"`
CodeRepository string `json:"codeRepository,omitempty"`
License string `json:"license,omitempty"`
Author Person `json:"author"`
ProgrammingLanguage ProgrammingLanguage `json:"programmingLanguage"`
RepositoryURLs []string `json:"repositoryURLs,omitempty"`
// https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/registryserverspecification/#PackageRelease-type
type PackageRelease struct {
Author *Author `json:"author,omitempty"`
Description string `json:"description,omitempty"`
LicenseURL string `json:"licenseURL,omitempty"`
OriginalPublicationTime string `json:"originalPublicationTime,omitempty"`
ReadmeURL string `json:"readmeURL,omitempty"`
RepositoryURLs []string `json:"repositoryURLs,omitempty"`
}
// https://schema.org/ProgrammingLanguage
type ProgrammingLanguage struct {
Type string `json:"@type"`
Name string `json:"name"`
URL string `json:"url"`
// https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/registryserverspecification/#Author-type
type Author struct {
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Description string `json:"description,omitempty"`
Organization *Organization `json:"organization,omitempty"`
URL string `json:"url,omitempty"`
}
// https://schema.org/Person
type Person struct {
Type string `json:"@type,omitempty"`
GivenName string `json:"givenName,omitempty"`
MiddleName string `json:"middleName,omitempty"`
FamilyName string `json:"familyName,omitempty"`
}
func (p Person) String() string {
var sb strings.Builder
if p.GivenName != "" {
sb.WriteString(p.GivenName)
}
if p.MiddleName != "" {
if sb.Len() > 0 {
sb.WriteRune(' ')
}
sb.WriteString(p.MiddleName)
}
if p.FamilyName != "" {
if sb.Len() > 0 {
sb.WriteRune(' ')
}
sb.WriteString(p.FamilyName)
}
return sb.String()
// https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/registryserverspecification/#Organization-type
type Organization struct {
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Description string `json:"description,omitempty"`
URL string `json:"url,omitempty"`
}
// ParsePackage parses the Swift package upload
@ -115,9 +81,8 @@ func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) {
}
p := &Package{
Metadata: &Metadata{
Manifests: make(map[string]*Manifest),
},
Manifests: make(map[string]*Manifest),
Metadata: &PackageRelease{},
}
for _, file := range zr.File {
@ -168,34 +133,27 @@ func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) {
manifest.ToolsVersion = TrimmedVersionString(v)
}
p.Metadata.Manifests[swiftVersion] = manifest
p.Manifests[swiftVersion] = manifest
}
if _, found := p.Metadata.Manifests[""]; !found {
if _, found := p.Manifests[""]; !found {
return nil, ErrMissingManifestFile
}
if mr != nil {
var ssc *SoftwareSourceCode
if err := json.NewDecoder(mr).Decode(&ssc); err != nil {
var pr *PackageRelease
if err := json.NewDecoder(mr).Decode(&pr); err != nil {
return nil, err
}
p.Metadata.Description = ssc.Description
p.Metadata.Keywords = ssc.Keywords
p.Metadata.License = ssc.License
p.Metadata.Author = Person{
GivenName: ssc.Author.GivenName,
MiddleName: ssc.Author.MiddleName,
FamilyName: ssc.Author.FamilyName,
if pr.Author != nil {
if pr.Author.Name == "" {
return nil, util.NewInvalidArgumentErrorf("if metadata.author exists, its name can't be empty")
}
if pr.Author.Organization != nil && pr.Author.Organization.Name == "" {
return nil, util.NewInvalidArgumentErrorf("if metadata.author.organization exists, its name can't be empty")
}
}
p.Metadata.RepositoryURL = ssc.CodeRepository
if !validation.IsValidURL(p.Metadata.RepositoryURL) {
p.Metadata.RepositoryURL = ""
}
p.RepositoryURLs = ssc.RepositoryURLs
p.Metadata = pr
}
return p, nil

View file

@ -9,18 +9,19 @@ import (
"strings"
"testing"
"forgejo.org/modules/util"
"github.com/hashicorp/go-version"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
packageName = "gitea"
packageVersion = "1.0.1"
packageDescription = "Package Description"
packageRepositoryURL = "https://gitea.io/gitea/gitea"
packageAuthor = "KN4CK3R"
packageLicense = "MIT"
packageEmail = "example@example.com"
packageLicense = "https://opensource.org/license/mit"
)
func TestParsePackage(t *testing.T) {
@ -67,12 +68,12 @@ func TestParsePackage(t *testing.T) {
require.NoError(t, err)
assert.NotNil(t, p.Metadata)
assert.Empty(t, p.RepositoryURLs)
assert.Len(t, p.Metadata.Manifests, 2)
m := p.Metadata.Manifests[""]
assert.Empty(t, p.Metadata.RepositoryURLs)
assert.Len(t, p.Manifests, 2)
m := p.Manifests[""]
assert.Equal(t, "5.7", m.ToolsVersion)
assert.Equal(t, content1, m.Content)
m = p.Metadata.Manifests["5.5"]
m = p.Manifests["5.5"]
assert.Equal(t, "5.6", m.ToolsVersion)
assert.Equal(t, content2, m.Content)
})
@ -85,22 +86,34 @@ func TestParsePackage(t *testing.T) {
p, err := ParsePackage(
data,
data.Size(),
strings.NewReader(`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","keywords":["swift","package"],"license":"`+packageLicense+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`),
strings.NewReader(`{"description":"`+packageDescription+`","licenseURL":"`+packageLicense+`","author":{"name":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`),
)
assert.NotNil(t, p)
require.NoError(t, err)
assert.NotNil(t, p.Metadata)
assert.Len(t, p.Metadata.Manifests, 1)
m := p.Metadata.Manifests[""]
assert.Len(t, p.Manifests, 1)
m := p.Manifests[""]
assert.Equal(t, "5.7", m.ToolsVersion)
assert.Equal(t, packageDescription, p.Metadata.Description)
assert.ElementsMatch(t, []string{"swift", "package"}, p.Metadata.Keywords)
assert.Equal(t, packageLicense, p.Metadata.License)
assert.Equal(t, packageAuthor, p.Metadata.Author.GivenName)
assert.Equal(t, packageRepositoryURL, p.Metadata.RepositoryURL)
assert.ElementsMatch(t, []string{packageRepositoryURL}, p.RepositoryURLs)
assert.Equal(t, packageLicense, p.Metadata.LicenseURL)
assert.Equal(t, packageAuthor, p.Metadata.Author.Name)
assert.ElementsMatch(t, []string{packageRepositoryURL}, p.Metadata.RepositoryURLs)
})
t.Run("WithInvalidMetadata", func(t *testing.T) {
data := createArchive(map[string][]byte{
"Package.swift": []byte("// swift-tools-version:5.7\n//\n// Package.swift"),
})
p, err := ParsePackage(
data,
data.Size(),
strings.NewReader(`{"description":"`+packageDescription+`","licenseURL":"`+packageLicense+`","author":{"email":"`+packageEmail+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`),
)
assert.Nil(t, p)
require.ErrorIs(t, err, util.ErrInvalidArgument)
})
}

View file

@ -174,10 +174,10 @@ type Resource struct {
}
type PackageVersionMetadataResponse struct {
ID string `json:"id"`
Version string `json:"version"`
Resources []Resource `json:"resources"`
Metadata *swift_module.SoftwareSourceCode `json:"metadata"`
ID string `json:"id"`
Version string `json:"version"`
Resources []Resource `json:"resources"`
Metadata *swift_module.PackageRelease `json:"metadata"`
}
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-2
@ -200,7 +200,7 @@ func PackageVersionMetadata(ctx *context.Context) {
return
}
metadata := pd.Metadata.(*swift_module.Metadata)
metadata := pd.Metadata.(*swift_module.Package)
setResponseHeaders(ctx.Resp, &headers{})
@ -214,27 +214,7 @@ func PackageVersionMetadata(ctx *context.Context) {
Checksum: pd.Files[0].Blob.HashSHA256,
},
},
Metadata: &swift_module.SoftwareSourceCode{
Context: []string{"http://schema.org/"},
Type: "SoftwareSourceCode",
Name: pd.PackageProperties.GetByName(swift_module.PropertyName),
Version: pd.Version.Version,
Description: metadata.Description,
Keywords: metadata.Keywords,
CodeRepository: metadata.RepositoryURL,
License: metadata.License,
ProgrammingLanguage: swift_module.ProgrammingLanguage{
Type: "ComputerLanguage",
Name: "Swift",
URL: "https://swift.org",
},
Author: swift_module.Person{
Type: "Person",
GivenName: metadata.Author.GivenName,
MiddleName: metadata.Author.MiddleName,
FamilyName: metadata.Author.FamilyName,
},
},
Metadata: metadata.Metadata,
})
}
@ -267,7 +247,7 @@ func DownloadManifest(ctx *context.Context) {
swiftVersion = swift_module.TrimmedVersionString(v)
}
}
m, ok := pd.Metadata.(*swift_module.Metadata).Manifests[swiftVersion]
m, ok := pd.Metadata.(*swift_module.Package).Manifests[swiftVersion]
if !ok {
setResponseHeaders(ctx.Resp, &headers{
Status: http.StatusSeeOther,
@ -370,7 +350,7 @@ func UploadPackageFile(ctx *context.Context) {
},
SemverCompatible: true,
Creator: ctx.Doer,
Metadata: pck.Metadata,
Metadata: pck,
PackageProperties: map[string]string{
swift_module.PropertyScope: packageScope,
swift_module.PropertyName: packageName,
@ -397,7 +377,7 @@ func UploadPackageFile(ctx *context.Context) {
return
}
for _, url := range pck.RepositoryURLs {
for _, url := range pck.Metadata.RepositoryURLs {
_, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, swift_module.PropertyRepositoryURL, url)
if err != nil {
log.Error("InsertProperty failed: %v", err)

View file

@ -22,19 +22,10 @@
</div>
</div>
{{if .PackageDescriptor.Metadata.Description}}
{{if .PackageDescriptor.Metadata.Metadata.Description}}
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
<div class="ui attached segment">
{{if .PackageDescriptor.Metadata.Description}}{{.PackageDescriptor.Metadata.Description}}{{end}}
</div>
{{end}}
{{if .PackageDescriptor.Metadata.Keywords}}
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.keywords"}}</h4>
<div class="ui attached segment">
{{range .PackageDescriptor.Metadata.Keywords}}
{{.}}
{{end}}
{{.PackageDescriptor.Metadata.Metadata.Description}}
</div>
{{end}}
{{end}}

View file

@ -1,4 +1,3 @@
{{if eq .PackageDescriptor.Package.Type "swift"}}
{{if .PackageDescriptor.Metadata.Author.String}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
{{if .PackageDescriptor.Metadata.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Metadata.Author.Name}}</div>{{end}}
{{end}}

View file

@ -147,7 +147,7 @@ func TestPackageSwift(t *testing.T) {
"Package.swift": contentManifest1,
"Package@swift-5.6.swift": contentManifest2,
}),
`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
`{"description":"`+packageDescription+`","author":{"name":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
)
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeSwift)
@ -159,9 +159,9 @@ func TestPackageSwift(t *testing.T) {
assert.NotNil(t, pd.SemVer)
assert.Equal(t, packageID, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version)
assert.IsType(t, &swift_module.Metadata{}, pd.Metadata)
metadata := pd.Metadata.(*swift_module.Metadata)
assert.Equal(t, packageDescription, metadata.Description)
assert.IsType(t, &swift_module.Package{}, pd.Metadata)
metadata := pd.Metadata.(*swift_module.Package)
assert.Equal(t, packageDescription, metadata.Metadata.Description)
assert.Len(t, metadata.Manifests, 2)
assert.Equal(t, contentManifest1, metadata.Manifests[""].Content)
assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content)
@ -235,7 +235,7 @@ func TestPackageSwift(t *testing.T) {
"Package.swift": contentManifest1,
"Package@swift-5.6.swift": contentManifest2,
}),
`{"name":"`+packageName+`","version":"`+packageVersion2+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
`{"description":"`+packageDescription+`","author":{"name":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
)
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeSwift)
@ -247,9 +247,9 @@ func TestPackageSwift(t *testing.T) {
assert.NotNil(t, pd.SemVer)
assert.Equal(t, packageID, pd.Package.Name)
assert.Equal(t, packageVersion2, pd.Version.Version)
assert.IsType(t, &swift_module.Metadata{}, pd.Metadata)
metadata := pd.Metadata.(*swift_module.Metadata)
assert.Equal(t, packageDescription, metadata.Description)
assert.IsType(t, &swift_module.Package{}, pd.Metadata)
metadata := pd.Metadata.(*swift_module.Package)
assert.Equal(t, packageDescription, metadata.Metadata.Description)
assert.Len(t, metadata.Manifests, 2)
assert.Equal(t, contentManifest1, metadata.Manifests[""].Content)
assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content)
@ -350,12 +350,8 @@ func TestPackageSwift(t *testing.T) {
assert.Equal(t, "source-archive", result.Resources[0].Name)
assert.Equal(t, "application/zip", result.Resources[0].Type)
assert.Equal(t, pd.Files[0].Blob.HashSHA256, result.Resources[0].Checksum)
assert.Equal(t, "SoftwareSourceCode", result.Metadata.Type)
assert.Equal(t, packageName, result.Metadata.Name)
assert.Equal(t, packageVersion, result.Metadata.Version)
assert.Equal(t, packageDescription, result.Metadata.Description)
assert.Equal(t, "Swift", result.Metadata.ProgrammingLanguage.Name)
assert.Equal(t, packageAuthor, result.Metadata.Author.GivenName)
assert.Equal(t, packageAuthor, result.Metadata.Author.Name)
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s.json", url, packageScope, packageName, packageVersion)).
AddBasicAuth(user.Name)