Files
sm-seo-plugin/classes/strict.go
Jakub Zych 4cb0fbfebb feat: URL normalisation, strict URLs and the one-hop redirect decision
- Normalize: https, www or bare host, double slashes and index files,
  trailing slash add/remove, optional ASCII lowercasing (defaults change
  nothing but scheme, slashes and index files)
- strict URLs through signpost MatchPath: a path that names a record
  without being its address 301s to the record's address
- resolve runs normalisation, redirects, strict URLs, then SEO; the
  location is the final address, so a visitor makes one hop
- P1 warns when a record's canonical URL redirects
2026-10-27 11:41:45 +01:00

69 lines
2.2 KiB
Go

package classes
import (
"context"
"net/url"
"strings"
"git.golem15.com/golem15/summercms/modules/pact"
)
// StrictMatch is the record behind a path and whether the path is its
// address (D-20c). Key is "" when no published record answers the path.
type StrictMatch struct {
Key string
Match *pact.ContentMatchResult
Record *pact.ContentRecord
// Canonical is the record's own path in the matched locale, in the
// site's address form; "" when the descriptor answers none.
Canonical string
// Mismatch is true when the path names the record but is not its
// address: a wrong category or parent, a locale prefix with another
// locale's slug, a moved record. It then 301s to Canonical.
Mismatch bool
}
// Strict finds the published record behind path through the signpost
// patterns (MatchPath) and checks every segment against the record's own
// address (D-20c): a path that names a record without being its address
// is a mismatch, so a wrong URL never answers ok. The comparison uses the
// site's address form (opts), so a strict 301 and normalisation never
// send a visitor back and forth.
func (r *Resolver) Strict(ctx context.Context, path string, opts NormalizeOptions) (StrictMatch, error) {
var out StrictMatch
reg := r.Service.Registry()
if reg == nil {
return out, nil
}
unprefixed := r.Service.UnprefixedLocale()
// Content patterns carry no trailing slash; the site's trailing-slash
// rule is applied to the record's address below.
key, match, err := reg.MatchPath(ctx, trimSlash(path), unprefixed)
if err != nil || match == nil {
return out, err
}
rec, err := reg.Describe(ctx, key, match.ID, unprefixed)
if err != nil || rec == nil || !rec.Published {
return out, err
}
out.Key, out.Match, out.Record = key, match, rec
described := ""
for _, l := range rec.Locales {
if l.Locale == match.Locale {
described = strings.TrimSpace(l.URL)
}
}
if described == "" {
out.Mismatch = !match.Exact
return out, nil
}
du, err := url.Parse(described)
if err != nil {
return out, nil
}
canonical, _ := Normalize(&url.URL{Path: du.Path}, opts)
out.Canonical = canonical.Path
out.Mismatch = !match.Exact || canonical.Path != path
return out, nil
}