Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

pkg.gno

5.23 Kb · 175 lines
  1package forge
  2
  3import "strings"
  4
  5// MaxPkgPathLen bounds a published package path. gno.land's own paths are far
  6// shorter; the cap exists because every stored byte is a storage deposit
  7// somebody pays.
  8const MaxPkgPathLen = 256
  9
 10// Package is the claim that a repo publishes a deployed package path.
 11//
 12// This is the join nothing else in the forge could express. A repo id is
 13// "<namespace>/<name>", exactly two parts, so `r/moul/home` can never be one:
 14// there was no way to ask "which repo publishes this package path", which is
 15// the first question a reader arriving from an explorer has.
 16//
 17// The claim is deliberately one-directional and unverified. A realm cannot ask
 18// the chain who deployed a path, so the forge cannot check that the repo really
 19// is the source of the package. What it can guarantee is that only someone with
 20// write access to the repo said so, and that the namespace of the repo is
 21// governed by r/sys/users. A reader weighs the claim the way they weigh a
 22// go.mod: it says where the author says the source lives.
 23type Package struct {
 24	Path        string // "gno.land/r/moul/home", the deployed package path
 25	RepoID      string
 26	Ref         string // the ref this was published from, "" if unstated
 27	OID         string // the object id, "" if unstated
 28	Tag         string // the release tag, "" if not published as part of one
 29	PublishedAt int64  // block height
 30	PublishedBy address
 31}
 32
 33// ValidPkgPath reports whether s is a plausible gno package path.
 34//
 35// Deliberately shallow: it checks shape, not existence, because a realm cannot
 36// ask the chain whether a path is deployed and a check that only sometimes
 37// works is worse than one that never claims to.
 38func ValidPkgPath(s string) bool {
 39	if s == "" || len(s) > MaxPkgPathLen {
 40		return false
 41	}
 42	if strings.Contains(s, "..") || strings.HasSuffix(s, "/") {
 43		return false
 44	}
 45	parts := strings.Split(s, "/")
 46	if len(parts) < 3 {
 47		return false
 48	}
 49	for _, p := range parts {
 50		if p == "" {
 51			return false
 52		}
 53		for i := 0; i < len(p); i++ {
 54			c := p[i]
 55			ok := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
 56				(c >= '0' && c <= '9') || c == '.' || c == '-' || c == '_'
 57			if !ok {
 58				return false
 59			}
 60		}
 61	}
 62	return true
 63}
 64
 65// PublishPackage records that this repo publishes path, and indexes it
 66// forge-wide so PackageRepo can answer without scanning every repo.
 67//
 68// Re-publishing the same path from the same repo overwrites the entry, which is
 69// what a redeploy is. Publishing a path another repo already claims is refused:
 70// silently moving it would let any repo owner capture another project's
 71// package path in an explorer.
 72func (f *Forge) PublishPackage(actor address, height int64, repoID, path, ref, oid, tag string) (*Package, error) {
 73	r := f.Repo(repoID)
 74	if r == nil {
 75		return nil, ErrRepoNotFound
 76	}
 77	if r.Archived {
 78		return nil, ErrRepoArchived
 79	}
 80	if !r.Can(actor, RoleWriter) {
 81		return nil, ErrUnauthorized
 82	}
 83	if !ValidPkgPath(path) {
 84		return nil, ErrInvalidPkgPath
 85	}
 86	if ref != "" && !ValidRefName(ref) {
 87		return nil, ErrInvalidRefName
 88	}
 89	if oid != "" && !ValidOID(oid) {
 90		return nil, ErrInvalidOID
 91	}
 92	if tag != "" && !ValidText(tag, MaxTagLen) {
 93		return nil, ErrInvalidText
 94	}
 95	if owner := f.packages.Get(path); owner != nil {
 96		if existing := owner.(*Package); existing.RepoID != repoID {
 97			return nil, ErrPackageClaimed
 98		}
 99	}
100
101	p := &Package{
102		Path:        path,
103		RepoID:      repoID,
104		Ref:         ref,
105		OID:         oid,
106		Tag:         tag,
107		PublishedAt: height,
108		PublishedBy: actor,
109	}
110	f.packages.Set(path, p)
111	r.packages.Set(path, p)
112	return p, nil
113}
114
115// UnpublishPackage withdraws the claim. The package stays on chain; only the
116// repo's assertion about it goes away.
117func (f *Forge) UnpublishPackage(actor address, repoID, path string) error {
118	r := f.Repo(repoID)
119	if r == nil {
120		return ErrRepoNotFound
121	}
122	if !r.Can(actor, RoleWriter) {
123		return ErrUnauthorized
124	}
125	if _, removed := r.packages.Remove(path); !removed {
126		return ErrPackageNotFound
127	}
128	f.packages.Remove(path)
129	return nil
130}
131
132// PackageRepo is the lookup an explorer makes: given a deployed path, which
133// repo claims to publish it. Returns "" when nobody does.
134func (f *Forge) PackageRepo(path string) string {
135	v := f.packages.Get(path)
136	if v == nil {
137		return ""
138	}
139	return v.(*Package).RepoID
140}
141
142// PackageAt returns the full claim for a path, or nil.
143func (f *Forge) PackageAt(path string) *Package {
144	v := f.packages.Get(path)
145	if v == nil {
146		return nil
147	}
148	return v.(*Package)
149}
150
151// PackageCount is how many paths the whole forge claims.
152func (f *Forge) PackageCount() int { return f.packages.Size() }
153
154// IteratePackages walks every claimed path in the forge, in path order.
155func (f *Forge) IteratePackages(offset, count int, cb func(*Package) bool) {
156	if count <= 0 {
157		count = f.packages.Size()
158	}
159	f.packages.IterateByOffset(offset, count, func(_ string, v any) bool {
160		return cb(v.(*Package))
161	})
162}
163
164// PackageCount is how many paths this repo claims.
165func (r *Repo) PackageCount() int { return r.packages.Size() }
166
167// IteratePackages walks the paths this repo claims, in path order.
168func (r *Repo) IteratePackages(offset, count int, cb func(*Package) bool) {
169	if count <= 0 {
170		count = r.packages.Size()
171	}
172	r.packages.IterateByOffset(offset, count, func(_ string, v any) bool {
173		return cb(v.(*Package))
174	})
175}