Documentation
¶
Overview ¶
Package deeplink parses and builds the gadak:// URLs that hand a piece of gadak to someone — a link in chat or on a web page instead of a shell, a running serve, and a command with side effects.
The grammar ¶
gadak://<action>[/w/<profile>][/<subject>][?<params>] gadak://view?pj=GDK&sc=inprogress a filtered list, primary mirror gadak://view/w/oss?issue=GDK-119 the same, on the "oss" mirror
The action is the host. The optional `/w/<profile>` segment is the same one the web UI uses for a workspace mount, in the same position, so a gadak:// link and the http:// link `gadak views open` prints describe the same thing the same way. The optional subject that may follow is what the action acts on — an issue key, a page key — and the query is passed through verbatim.
Why the whole grammar exists for one action ¶
Only `view` is implemented today, and this package deliberately does not know that: it validates the *shape* and returns whichever action it read. Deciding which actions exist belongs to the app that handles them (desktop/deeplink.go), not to the parser.
The split matters because these two halves ship separately. A link lives in a chat log forever and is opened by whatever version happens to be installed, so the grammar is the part that must not change — while the set of actions grows every time a surface becomes addressable. Keeping the action list out of the parser means growing it is a table entry in the handler, and an older app meeting a newer link produces "this link needs a newer Gadak" instead of a parse error.
What `view` can say is decided on the web side: the hash carries the view params (filters, display) and the place params (which panel and screen — issue, doc, person, feed, settings tab…). The registry of place params is web/src/lib/url-state.ts; a param registered there is linkable from here the same moment, with no change in this package. A second action is for what a hash cannot express — a place with no URL, or a different kind of address entirely.
Security posture ¶
Any web page can embed a gadak:// link, so the worst one may achieve is that the user briefly looks at the wrong thing. The grammar carries no verb and no payload beyond an address: a link says *where to go*, never what to do. Handlers must keep that true — an action that writes, or that submits anything, does not belong in this scheme however convenient it would be.
Validation here is shape and size only:
- a well-formed action, an optional profile that is a safe directory name, an optional subject that cannot escape a path,
- length and arity limits that bound what a handler is asked to parse.
There is deliberately no allowlist of view parameter keys. The keys the UI understands are owned by VIEW_PARAM_KEYS in web/src/lib/view-config; a second copy here would drift the moment a new axis is added there, and the drift fails silently in the direction that matters — a link would stop working with no error anywhere. Unknown keys pass through and are ignored by the UI, which is the "wrong thing" worst case already accepted above.
Index ¶
Constants ¶
const ActionView = "view"
ActionView shows a list of issues, filtered by the link's hash. The only action implemented today; see the package comment for why the parser does not enforce that.
const Scheme = "gadak"
Scheme is the URL scheme, registered by the macOS bundle in desktop/build-app.sh. One owner, because the two are in a .go and a .sh file that nothing else connects and a disagreement is silent.
Variables ¶
var ( // ErrNotGadak reports a URL that is not a gadak:// link at all. ErrNotGadak = errors.New("deeplink: not a gadak:// URL") // ErrMalformed reports a gadak:// link this package refuses to parse. // Wrapping carries the specific rule that fired. ErrMalformed = errors.New("deeplink: malformed gadak:// URL") )
Callers must be able to tell these apart: a handler stays silent when the link simply is not ours (another app's scheme reaching a shared handler) and logs when it is ours but refused.
Functions ¶
func Compose ¶
Compose builds a gadak:// link: the inverse of Parse, in the package that owns the grammar, so the two cannot drift into disagreeing about the shape.
prefix is the workspace segment — "" for the primary mirror, "/w/<name>" otherwise — and is taken as a parameter rather than derived here on purpose. The rule mapping a profile onto that segment belongs to internal/workspace, which pulls in the server and the sync engine; a parser any process might run should not drag those along to answer a question about a URL. Callers pass workspace.Prefix(profile, "").
An empty hash yields "" for ActionView: there is no view to link to, and a link to nothing is worse than no link, because it looks like one that works.
Types ¶
type Link ¶
type Link struct {
// Action is what the link asks for, lowercased. Compare against the
// Action* constants; an unrecognised one is a link from a newer gadak,
// not an error.
Action string
// Profile is the mirror the link addresses. "" means the primary one;
// "default" is accepted and means the same, because config does.
Profile string
// Subject is what the action acts on, or "" when it needs none. No action
// uses it yet; the slot is reserved so adding one (gadak://issue/GDK-119)
// does not have to change the grammar.
Subject string
// Hash is the view hash: the raw query string exactly as the UI's hash
// router and uifocus want it, with no leading "#" or "?" and
// percent-encoding untouched. Returned verbatim — never decoded,
// re-encoded, or re-ordered — because the CLI's composeServeURL treats it
// as opaque too, and any rewrite here would make the two paths disagree.
// Empty when the link carries no query; whether that is acceptable is the
// action's rule, not the grammar's.
Hash string
}
A Link is a parsed gadak:// URL.
func Parse ¶
Parse turns a gadak:// URL into a Link.
The host is compared case-insensitively and returned lowercased; the path is not, because profile and subject are identifiers. One trailing slash before the query is tolerated, because a human retyping a link produces one.
err is ErrNotGadak when the URL is not a gadak:// link at all (the caller should stay silent) and wraps ErrMalformed when it is one this package rejects (the caller should say so).