Skip to content

Commit 2eba909

Browse files
committed
feat(nuget): support nuspec manifest download
1 parent eaab89c commit 2eba909

File tree

3 files changed

+170
-64
lines changed

3 files changed

+170
-64
lines changed

modules/packages/nuget/metadata.go

Lines changed: 114 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -71,34 +71,50 @@ type Dependency struct {
7171
Version string `json:"version"`
7272
}
7373

74+
type nuspecPackageType struct {
75+
Name string `xml:"name,attr"`
76+
}
77+
78+
type nuspecPackageTypes struct {
79+
PackageType []nuspecPackageType `xml:"packageType"`
80+
}
81+
82+
type nuspecRepository struct {
83+
URL string `xml:"url,attr,omitempty"`
84+
Type string `xml:"type,attr,omitempty"`
85+
}
86+
type nuspecDependency struct {
87+
ID string `xml:"id,attr"`
88+
Version string `xml:"version,attr"`
89+
Exclude string `xml:"exclude,attr,omitempty"`
90+
}
91+
92+
type nuspecGroup struct {
93+
TargetFramework string `xml:"targetFramework,attr"`
94+
Dependency []nuspecDependency `xml:"dependency"`
95+
}
96+
97+
type nuspecDependencies struct {
98+
Group []nuspecGroup `xml:"group"`
99+
}
100+
101+
type nuspeceMetadata struct {
102+
ID string `xml:"id"`
103+
Version string `xml:"version"`
104+
Authors string `xml:"authors"`
105+
RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance,omitempty"`
106+
ProjectURL string `xml:"projectUrl,omitempty"`
107+
Description string `xml:"description"`
108+
ReleaseNotes string `xml:"releaseNotes,omitempty"`
109+
PackageTypes *nuspecPackageTypes `xml:"packageTypes,omitempty"`
110+
Repository *nuspecRepository `xml:"repository,omitempty"`
111+
Dependencies *nuspecDependencies `xml:"dependencies,omitempty"`
112+
}
113+
74114
type nuspecPackage struct {
75-
Metadata struct {
76-
ID string `xml:"id"`
77-
Version string `xml:"version"`
78-
Authors string `xml:"authors"`
79-
RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"`
80-
ProjectURL string `xml:"projectUrl"`
81-
Description string `xml:"description"`
82-
ReleaseNotes string `xml:"releaseNotes"`
83-
PackageTypes struct {
84-
PackageType []struct {
85-
Name string `xml:"name,attr"`
86-
} `xml:"packageType"`
87-
} `xml:"packageTypes"`
88-
Repository struct {
89-
URL string `xml:"url,attr"`
90-
} `xml:"repository"`
91-
Dependencies struct {
92-
Group []struct {
93-
TargetFramework string `xml:"targetFramework,attr"`
94-
Dependency []struct {
95-
ID string `xml:"id,attr"`
96-
Version string `xml:"version,attr"`
97-
Exclude string `xml:"exclude,attr"`
98-
} `xml:"dependency"`
99-
} `xml:"group"`
100-
} `xml:"dependencies"`
101-
} `xml:"metadata"`
115+
XMLName xml.Name `xml:"package"`
116+
Xmlns string `xml:"xmlns,attr"`
117+
Metadata nuspeceMetadata `xml:"metadata"`
102118
}
103119

104120
// ParsePackageMetaData parses the metadata of a Nuget package file
@@ -149,10 +165,12 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
149165
}
150166

151167
packageType := DependencyPackage
152-
for _, pt := range p.Metadata.PackageTypes.PackageType {
153-
if pt.Name == "SymbolsPackage" {
154-
packageType = SymbolsPackage
155-
break
168+
if p.Metadata.PackageTypes != nil {
169+
for _, pt := range p.Metadata.PackageTypes.PackageType {
170+
if pt.Name == "SymbolsPackage" {
171+
packageType = SymbolsPackage
172+
break
173+
}
156174
}
157175
}
158176

@@ -161,24 +179,27 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
161179
ReleaseNotes: p.Metadata.ReleaseNotes,
162180
Authors: p.Metadata.Authors,
163181
ProjectURL: p.Metadata.ProjectURL,
164-
RepositoryURL: p.Metadata.Repository.URL,
165182
RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
166183
Dependencies: make(map[string][]Dependency),
167184
}
168-
169-
for _, group := range p.Metadata.Dependencies.Group {
170-
deps := make([]Dependency, 0, len(group.Dependency))
171-
for _, dep := range group.Dependency {
172-
if dep.ID == "" || dep.Version == "" {
173-
continue
185+
if p.Metadata.Repository != nil {
186+
m.RepositoryURL = p.Metadata.Repository.URL
187+
}
188+
if p.Metadata.Dependencies != nil {
189+
for _, group := range p.Metadata.Dependencies.Group {
190+
deps := make([]Dependency, 0, len(group.Dependency))
191+
for _, dep := range group.Dependency {
192+
if dep.ID == "" || dep.Version == "" {
193+
continue
194+
}
195+
deps = append(deps, Dependency{
196+
ID: dep.ID,
197+
Version: dep.Version,
198+
})
199+
}
200+
if len(deps) > 0 {
201+
m.Dependencies[group.TargetFramework] = deps
174202
}
175-
deps = append(deps, Dependency{
176-
ID: dep.ID,
177-
Version: dep.Version,
178-
})
179-
}
180-
if len(deps) > 0 {
181-
m.Dependencies[group.TargetFramework] = deps
182203
}
183204
}
184205
return &Package{
@@ -204,3 +225,51 @@ func toNormalizedVersion(v *version.Version) string {
204225
}
205226
return buf.String()
206227
}
228+
229+
// returning any here because we use a private type and we don't need the type for xml marshalling
230+
func GenerateNuspec(pd *Package) any {
231+
m := nuspeceMetadata{
232+
ID: pd.ID,
233+
Version: pd.Version,
234+
Authors: pd.Metadata.Authors,
235+
Description: pd.Metadata.Description,
236+
ProjectURL: pd.Metadata.ProjectURL,
237+
RequireLicenseAcceptance: pd.Metadata.RequireLicenseAcceptance,
238+
}
239+
240+
if pd.Metadata.RepositoryURL != "" {
241+
m.Repository = &nuspecRepository{
242+
URL: pd.Metadata.RepositoryURL,
243+
}
244+
}
245+
246+
groups := len(pd.Metadata.Dependencies)
247+
if groups > 0 {
248+
m.Dependencies = &nuspecDependencies{
249+
Group: make([]nuspecGroup, 0, groups),
250+
}
251+
252+
for tgf, deps := range pd.Metadata.Dependencies {
253+
if len(deps) == 0 {
254+
continue
255+
}
256+
gDeps := make([]nuspecDependency, 0, len(deps))
257+
for _, dep := range deps {
258+
gDeps = append(gDeps, nuspecDependency{
259+
ID: dep.ID,
260+
Version: dep.Version,
261+
})
262+
}
263+
264+
m.Dependencies.Group = append(m.Dependencies.Group, nuspecGroup{
265+
TargetFramework: tgf,
266+
Dependency: gDeps,
267+
})
268+
}
269+
}
270+
271+
return &nuspecPackage{
272+
Xmlns: "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd",
273+
Metadata: m,
274+
}
275+
}

routers/api/packages/nuget/nuget.go

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -387,34 +387,56 @@ func EnumeratePackageVersionsV3(ctx *context.Context) {
387387
ctx.JSON(http.StatusOK, resp)
388388
}
389389

390-
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-content-nupkg
390+
// https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-manifest-nuspec
391+
// https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-content-nupkg
391392
func DownloadPackageFile(ctx *context.Context) {
392393
packageName := ctx.Params("id")
393394
packageVersion := ctx.Params("version")
394395
filename := ctx.Params("filename")
395396

396-
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
397-
ctx,
398-
&packages_service.PackageInfo{
399-
Owner: ctx.Package.Owner,
400-
PackageType: packages_model.TypeNuGet,
401-
Name: packageName,
402-
Version: packageVersion,
403-
},
404-
&packages_service.PackageFileInfo{
405-
Filename: filename,
406-
},
407-
)
408-
if err != nil {
409-
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
397+
if filename == fmt.Sprintf("%s.nuspec", packageName) {
398+
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
399+
if err != nil {
410400
apiError(ctx, http.StatusNotFound, err)
411401
return
412402
}
413-
apiError(ctx, http.StatusInternalServerError, err)
414-
return
415-
}
416403

417-
helper.ServePackageFile(ctx, s, u, pf)
404+
pd, err := packages_model.GetPackageDescriptor(ctx, pv)
405+
if err != nil {
406+
apiError(ctx, http.StatusInternalServerError, err)
407+
return
408+
}
409+
pkg := &nuget_module.Package{
410+
ID: pd.Package.Name,
411+
Version: packageVersion,
412+
Metadata: pd.Metadata.(*nuget_module.Metadata),
413+
}
414+
415+
xmlResponse(ctx, http.StatusOK, nuget_module.GenerateNuspec(pkg))
416+
} else {
417+
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
418+
ctx,
419+
&packages_service.PackageInfo{
420+
Owner: ctx.Package.Owner,
421+
PackageType: packages_model.TypeNuGet,
422+
Name: packageName,
423+
Version: packageVersion,
424+
},
425+
&packages_service.PackageFileInfo{
426+
Filename: filename,
427+
},
428+
)
429+
if err != nil {
430+
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
431+
apiError(ctx, http.StatusNotFound, err)
432+
return
433+
}
434+
apiError(ctx, http.StatusInternalServerError, err)
435+
return
436+
}
437+
438+
helper.ServePackageFile(ctx, s, u, pf)
439+
}
418440
}
419441

420442
// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file

tests/integration/api_packages_nuget_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,21 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
353353

354354
assert.Equal(t, content, resp.Body.Bytes())
355355

356+
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.nuspec", url, packageName, packageVersion, packageName)).
357+
AddBasicAuth(user.Name)
358+
resp = MakeRequest(t, req, http.StatusOK)
359+
360+
nuspec := `<?xml version="1.0" encoding="UTF-8"?>` + "\n" +
361+
`<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"><metadata>` +
362+
`<id>` + packageName + `</id><version>` + packageVersion + `</version><authors>` + packageAuthors + `</authors><description>` + packageDescription + `</description>` +
363+
`<dependencies><group targetFramework=".NETStandard2.0">` +
364+
// https://github.com/golang/go/issues/21399 go can't generate self-closing tags
365+
`<dependency id="Microsoft.CSharp" version="4.5.0"></dependency>` +
366+
`</group></dependencies>` +
367+
`</metadata></package>`
368+
369+
assert.Equal(t, nuspec, resp.Body.String())
370+
356371
checkDownloadCount(1)
357372

358373
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion)).

0 commit comments

Comments
 (0)