feat(32-02): signpost content lookups, Records and MatchPath
- ContentType, ContentTypeForController, Records and MatchPath on the registry; the pure MatchPattern for whole-segment placeholder matching - two types declaring the same Content.Model or Controller stop the boot - TestContent* cover lookups, optional Records, boot errors, MatchPattern and MatchPath precedence (first exact, else first inexact) - docs: Content descriptors in menu-item-types.md, signpost README
This commit is contained in:
104
modules/signpost/example_content_test.go
Normal file
104
modules/signpost/example_content_test.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package signpost_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"git.golem15.com/golem15/summercms/modules/signpost"
|
||||
)
|
||||
|
||||
// Article is the example's stand-in for a content plugin's record.
|
||||
type Article struct {
|
||||
ID string
|
||||
Slug string
|
||||
Title string
|
||||
Published bool
|
||||
}
|
||||
|
||||
// NewsPlugin is an example content plugin whose record type describes its
|
||||
// articles to consumers such as sitemaps.
|
||||
type NewsPlugin struct{ articles []Article }
|
||||
|
||||
func (NewsPlugin) ID() string { return "acme.news" }
|
||||
|
||||
// MenuItemTypes registers acme-article with a content descriptor: the
|
||||
// controller that edits articles, their morph type, Describe and Match.
|
||||
func (p NewsPlugin) MenuItemTypes() []pact.MenuItemType {
|
||||
return []pact.MenuItemType{{
|
||||
Key: "acme-article",
|
||||
Label: "acme.news::lang.menuitem.article",
|
||||
DefaultPattern: "/news/:slug",
|
||||
Placeholders: []string{":slug", ":id"},
|
||||
Resolve: p.resolve,
|
||||
Content: &pact.ContentDescriptor{
|
||||
Controller: "acme.news.articles",
|
||||
Model: "acme_news_articles",
|
||||
Describe: p.describe,
|
||||
Match: p.match,
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
func (p NewsPlugin) resolve(_ context.Context, in pact.MenuItemResolve) (*pact.ResolvedMenuItem, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// describe answers one article per locale; the URL is built from the
|
||||
// layered pattern it receives.
|
||||
func (p NewsPlugin) describe(_ context.Context, in pact.ContentDescribe) (*pact.ContentRecord, error) {
|
||||
for _, a := range p.articles {
|
||||
if a.ID != in.ID {
|
||||
continue
|
||||
}
|
||||
url := strings.NewReplacer(":slug", a.Slug, ":id", a.ID).Replace(in.Pattern)
|
||||
return &pact.ContentRecord{
|
||||
ID: a.ID,
|
||||
Published: a.Published,
|
||||
Locales: []pact.ContentLocale{{Locale: "en", URL: url, Title: a.Title}},
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// match reads the path's values with signpost.MatchPattern and checks every
|
||||
// one against the record: a slug finds the article, and an :id that names
|
||||
// another article makes the match inexact. Drafts never match.
|
||||
func (p NewsPlugin) match(_ context.Context, in pact.ContentMatch) (*pact.ContentMatchResult, error) {
|
||||
values, ok := signpost.MatchPattern(in.Pattern, in.Path)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
for _, a := range p.articles {
|
||||
if a.Slug != values["slug"] || !a.Published {
|
||||
continue
|
||||
}
|
||||
id, hasID := values["id"]
|
||||
return &pact.ContentMatchResult{ID: a.ID, Locale: "en", Exact: !hasID || id == a.ID}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func ExampleRegistry_MatchPath() {
|
||||
app := exampleApp() // its menu_items.yaml overrides acme-post of BlogPlugin
|
||||
news := NewsPlugin{articles: []Article{{ID: "7", Slug: "launch", Title: "Launch", Published: true}}}
|
||||
plugins := []interface{ ID() string }{BlogPlugin{}, news}
|
||||
if err := signpost.Activate(app, plugins); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
reg, _ := app.Lookup[*signpost.Registry]()
|
||||
|
||||
// A record event names the model; the registry finds its type.
|
||||
typ, _ := reg.ContentTypeForModel("acme_news_articles")
|
||||
rec, _ := reg.Describe(context.Background(), typ.Key, "7", "")
|
||||
fmt.Println(typ.Key, rec.Locales[0].URL)
|
||||
|
||||
// A visitor's path maps back to the record.
|
||||
key, res, _ := reg.MatchPath(context.Background(), "/news/launch", "")
|
||||
fmt.Println(key, res.ID, res.Exact)
|
||||
// Output:
|
||||
// acme-article /news/launch
|
||||
// acme-article 7 true
|
||||
}
|
||||
Reference in New Issue
Block a user