Feat v0.8.5 extension composability (#72)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m44s
CI/CD / test-sqlite (push) Successful in 2m54s
CI/CD / build-and-deploy (push) Successful in 52s
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m44s
CI/CD / test-sqlite (push) Successful in 2m54s
CI/CD / build-and-deploy (push) Successful in 52s
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #72.
This commit is contained in:
@@ -37,6 +37,44 @@ var validTiers = map[string]bool{
|
||||
models.ExtTierSidecar: true,
|
||||
}
|
||||
|
||||
// ValidateComposabilityFields checks that manifest slots and contributes
|
||||
// fields follow the expected conventions. Slots values must have a
|
||||
// "description" string. Contributes keys must follow "pkg:slot" naming.
|
||||
// Returns nil if valid or missing; returns an error string otherwise.
|
||||
func ValidateComposabilityFields(manifest map[string]any) string {
|
||||
// Validate slots: map of name → {description, ...}
|
||||
if slotsRaw, ok := manifest["slots"]; ok {
|
||||
slots, ok := slotsRaw.(map[string]any)
|
||||
if !ok {
|
||||
return "slots must be an object"
|
||||
}
|
||||
for name, v := range slots {
|
||||
entry, ok := v.(map[string]any)
|
||||
if !ok {
|
||||
return "slots." + name + " must be an object"
|
||||
}
|
||||
if _, ok := entry["description"]; !ok {
|
||||
return "slots." + name + " must have a description"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate contributes: map of "pkg:slot" → {label, ...}
|
||||
if contribRaw, ok := manifest["contributes"]; ok {
|
||||
contribs, ok := contribRaw.(map[string]any)
|
||||
if !ok {
|
||||
return "contributes must be an object"
|
||||
}
|
||||
for key := range contribs {
|
||||
if !strings.Contains(key, ":") {
|
||||
return "contributes key " + key + " must follow pkg:slot convention"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── User endpoints ──────────────────────────────
|
||||
|
||||
// ListUserExtensions returns all enabled extensions for the current user,
|
||||
@@ -208,6 +246,12 @@ func (h *ExtensionHandler) AdminInstallExtension(c *gin.Context) {
|
||||
manifestMap = map[string]any{}
|
||||
}
|
||||
|
||||
// Validate composability fields (slots / contributes)
|
||||
if errMsg := ValidateComposabilityFields(manifestMap); errMsg != "" {
|
||||
c.JSON(400, gin.H{"error": "manifest: " + errMsg})
|
||||
return
|
||||
}
|
||||
|
||||
pkg := &store.PackageRegistration{
|
||||
ID: body.ExtID,
|
||||
Title: body.Name,
|
||||
|
||||
Reference in New Issue
Block a user