// NewRouter creates a new router that matches and generates URLs that the HTTP // handler recognizes. func NewRouter(parent *muxpkg.Router) *Router { if parent == nil { parent = muxpkg.NewRouter() } parent.Path("/").Methods("GET").Name(RouteRoot) const repoURIPattern = "(?:[^./][^/]*)(?:/[^./][^/]*)*" repoPath := "/{RepoPath:" + repoURIPattern + "}" parent.Path(repoPath).Methods("GET").Name(RouteRepo) parent.Path(repoPath).Methods("POST").Name(RouteRepoCreateOrUpdate) repo := parent.PathPrefix(repoPath).Subrouter() // attach git transport endpoints repoGit := repo.PathPrefix("/.git").Subrouter() git.NewRouter(repoGit) repo.Path("/.blame/{Path:.+}").Methods("GET").Name(RouteRepoBlameFile) repo.Path("/.diff/{Base}..{Head}").Methods("GET").Name(RouteRepoDiff) repo.Path("/.cross-repo-diff/{Base}..{HeadRepoPath:" + repoURIPattern + "}:{Head}").Methods("GET").Name(RouteRepoCrossRepoDiff) repo.Path("/.branches").Methods("GET").Name(RouteRepoBranches) repo.Path("/.branches/{Branch:.+}").Methods("GET").Name(RouteRepoBranch) repo.Path("/.revs/{RevSpec:.+}").Methods("GET").Name(RouteRepoRevision) repo.Path("/.tags").Methods("GET").Name(RouteRepoTags) repo.Path("/.tags/{Tag:.+}").Methods("GET").Name(RouteRepoTag) repo.Path("/.merge-base/{CommitIDA}/{CommitIDB}").Methods("GET").Name(RouteRepoMergeBase) repo.Path("/.cross-repo-merge-base/{CommitIDA}/{BRepoPath:" + repoURIPattern + "}/{CommitIDB}").Methods("GET").Name(RouteRepoCrossRepoMergeBase) repo.Path("/.committers").Methods("GET").Name(RouteRepoCommitters) repo.Path("/.commits").Methods("GET").Name(RouteRepoCommits) commitPath := "/.commits/{CommitID}" repo.Path(commitPath).Methods("GET").Name(RouteRepoCommit) commit := repo.PathPrefix(commitPath).Subrouter() // cleanTreeVars modifies the Path route var to be a clean filepath. If it // is empty, it is changed to ".". cleanTreeVars := func(req *http.Request, match *muxpkg.RouteMatch, r *muxpkg.Route) { path := pathpkg.Clean(strings.TrimPrefix(match.Vars["Path"], "/")) if path == "" || path == "." { match.Vars["Path"] = "." } else { match.Vars["Path"] = path } } // prepareTreeVars prepares the Path route var to generate a clean URL. prepareTreeVars := func(vars map[string]string) map[string]string { if path := vars["Path"]; path == "." { vars["Path"] = "" } else { vars["Path"] = "/" + pathpkg.Clean(path) } return vars } commit.Path("/tree{Path:(?:/.*)*}").Methods("GET").PostMatchFunc(cleanTreeVars).BuildVarsFunc(prepareTreeVars).Name(RouteRepoTreeEntry) commit.Path("/search").Methods("GET").Name(RouteRepoSearch) return (*Router)(parent) }
func TestRepoRev(t *testing.T) { r := mux.NewRouter() r.Path("/" + RepoRev).PostMatchFunc(FixRepoRevVars).BuildVarsFunc(PrepareRepoRevRouteVars) tests := []struct { path string wantNoMatch bool wantVars map[string]string }{ {path: "/foo", wantVars: map[string]string{"Repo": "foo"}}, {path: "/foo@v", wantVars: map[string]string{"Repo": "foo", "Rev": "v"}}, {path: "/foo@v===" + commitID, wantVars: map[string]string{"Repo": "foo", "Rev": "v", "CommitID": commitID}}, {path: "/foo.com/bar", wantVars: map[string]string{"Repo": "foo.com/bar"}}, {path: "/foo.com/bar@v", wantVars: map[string]string{"Repo": "foo.com/bar", "Rev": "v"}}, {path: "/foo.com/bar@v===" + commitID, wantVars: map[string]string{"Repo": "foo.com/bar", "Rev": "v", "CommitID": commitID}}, {path: "/", wantNoMatch: true}, {path: "/.foo", wantNoMatch: true}, {path: "/foo/.bar", wantNoMatch: true}, } for _, test := range tests { var m mux.RouteMatch ok := r.Match(&http.Request{Method: "GET", URL: &url.URL{Path: test.path}}, &m) if ok == test.wantNoMatch { t.Errorf("%q: got match == %v, want %v", test.path, ok, !test.wantNoMatch) } if ok { if !reflect.DeepEqual(m.Vars, test.wantVars) { t.Errorf("%q: got vars == %v, want %v", test.path, m.Vars, test.wantVars) } url, err := m.Route.URLPath(pairs(m.Vars)...) if err != nil { t.Errorf("%q: URLPath: %s", test.path, err) continue } if url.Path != test.path { t.Errorf("%q: got path == %q, want %q", test.path, url.Path, test.path) } } } }
func TestTreeEntry(t *testing.T) { r := mux.NewRouter() r.Path("/x" + TreeEntryPath).PostMatchFunc(FixTreeEntryVars).BuildVarsFunc(PrepareTreeEntryRouteVars) tests := []struct { path string wantNoMatch bool wantVars map[string]string }{ {path: "/x", wantVars: map[string]string{"Path": "."}}, {path: "/x/", wantVars: map[string]string{"Path": "."}}, {path: "/x/.", wantVars: map[string]string{"Path": "."}}, {path: "/x/foo", wantVars: map[string]string{"Path": "foo"}}, {path: "/x/foo/bar", wantVars: map[string]string{"Path": "foo/bar"}}, } for _, test := range tests { var m mux.RouteMatch ok := r.Match(&http.Request{Method: "GET", URL: &url.URL{Path: test.path}}, &m) if ok == test.wantNoMatch { t.Errorf("%q: got match == %v, want %v", test.path, ok, !test.wantNoMatch) } if ok { if !reflect.DeepEqual(m.Vars, test.wantVars) { t.Errorf("%q: got vars == %v, want %v", test.path, m.Vars, test.wantVars) } url, err := m.Route.URLPath(pairs(m.Vars)...) if err != nil { t.Errorf("%q: URLPath: %s", test.path, err) continue } test.path = path.Clean(test.path) if url.Path != test.path { t.Errorf("%q: got path == %q, want %q", test.path, url.Path, test.path) } } } }
// NewAPIRouter creates a new API router with route URL pattern definitions but // no handlers attached to the routes. // // It is in a separate package from app so that other packages may use it to // generate URLs without resulting in Go import cycles (and so we can release // the router as open-source to support our client library). func NewAPIRouter(base *mux.Router) *mux.Router { if base == nil { base = mux.NewRouter() } base.StrictSlash(true) base.Path("/builds").Methods("GET").Name(Builds) builds := base.PathPrefix("/builds").Subrouter() builds.Path("/next").Methods("POST").Name(BuildDequeueNext) buildPath := "/{BID}" builds.Path(buildPath).Methods("GET").Name(Build) builds.Path(buildPath).Methods("PUT").Name(BuildUpdate) build := builds.PathPrefix(buildPath).Subrouter() build.Path("/log").Methods("GET").Name(BuildLog) build.Path("/tasks").Methods("GET").Name(BuildTasks) build.Path("/tasks").Methods("POST").Name(BuildTasksCreate) build.Path("/tasks/{TaskID}").Methods("PUT").Name(BuildTaskUpdate) build.Path("/tasks/{TaskID}/log").Methods("GET").Name(BuildTaskLog) base.Path("/repos").Methods("GET").Name(Repos) base.Path("/repos").Methods("POST").Name(ReposCreate) base.Path("/repos/github.com/{owner:[^/]+}/{repo:[^/]+}/{what:(?:badges|counters)}/{which}.{Format}").Methods("GET").Name(RedirectOldRepoBadgesAndCounters) repoRev := base.PathPrefix(`/repos/` + RepoRevSpecPattern).PostMatchFunc(FixRepoRevSpecVars).BuildVarsFunc(PrepareRepoRevSpecRouteVars).Subrouter() repoRev.Path("/.stats").Methods("PUT").Name(RepoComputeStats) repoRev.Path("/.stats").Methods("GET").Name(RepoStats) repoRev.Path("/.status").Methods("GET").Name(RepoCombinedStatus) repoRev.Path("/.status").Methods("POST").Name(RepoStatusCreate) repoRev.Path("/.authors").Methods("GET").Name(RepoAuthors) repoRev.Path("/.readme").Methods("GET").Name(RepoReadme) repoRev.Path("/.build").Methods("GET").Name(RepoBuild) repoRev.Path("/.builds").Methods("POST").Name(RepoBuildsCreate) repoRev.Path("/.dependencies").Methods("GET").Name(RepoDependencies) repoRev.PathPrefix("/.build-data"+TreeEntryPathPattern).PostMatchFunc(FixTreeEntryVars).BuildVarsFunc(PrepareTreeEntryRouteVars).Methods("GET", "HEAD", "PUT", "DELETE").Name(RepoBuildDataEntry) repoRev.Path("/.badges/{Badge}.{Format}").Methods("GET").Name(RepoBadge) // repo contains routes that are NOT specific to a revision. In these routes, the URL may not contain a revspec after the repo (that is, no "github.com/foo/bar@myrevspec"). repoPath := `/repos/` + RepoSpecPathPattern base.Path(repoPath).Methods("GET").Name(Repo) base.Path(repoPath).Methods("PUT").Name(ReposGetOrCreate) repo := base.PathPrefix(repoPath).Subrouter() repo.Path("/.clients").Methods("GET").Name(RepoClients) repo.Path("/.dependents").Methods("GET").Name(RepoDependents) repo.Path("/.external-profile").Methods("PUT").Name(RepoRefreshProfile) repo.Path("/.vcs-data").Methods("PUT").Name(RepoRefreshVCSData) repo.Path("/.settings").Methods("GET").Name(RepoSettings) repo.Path("/.settings").Methods("PUT").Name(RepoSettingsUpdate) repo.Path("/.commits").Methods("GET").Name(RepoCommits) repo.Path("/.commits/{Rev:" + PathComponentNoLeadingDot + "}/.compare").Methods("GET").Name(RepoCompareCommits) repo.Path("/.commits/{Rev:" + PathComponentNoLeadingDot + "}").Methods("GET").Name(RepoCommit) repo.Path("/.branches").Methods("GET").Name(RepoBranches) repo.Path("/.tags").Methods("GET").Name(RepoTags) repo.Path("/.badges").Methods("GET").Name(RepoBadges) repo.Path("/.counters").Methods("GET").Name(RepoCounters) repo.Path("/.counters/{Counter}.{Format}").Methods("GET").Name(RepoCounter) repo.Path("/.pulls").Methods("GET").Name(RepoPullRequests) pullPath := "/.pulls/{Pull}" repo.Path(pullPath).Methods("GET").Name(RepoPullRequest) pull := repo.PathPrefix(pullPath).Subrouter() pull.Path("/merge").Methods("PUT").Name(RepoPullRequestMerge) pull.Path("/comments").Methods("GET").Name(RepoPullRequestComments) pull.Path("/comments").Methods("POST").Name(RepoPullRequestCommentsCreate) pull.Path("/comments/{CommentID}").Methods("PATCH", "PUT").Name(RepoPullRequestCommentsEdit) pull.Path("/comments/{CommentID}").Methods("DELETE").Name(RepoPullRequestCommentsDelete) repo.Path("/.issues").Methods("GET").Name(RepoIssues) issuePath := "/.issues/{Issue}" repo.Path(issuePath).Methods("GET").Name(RepoIssue) issue := repo.PathPrefix(issuePath).Subrouter() issue.Path("/comments").Methods("GET").Name(RepoIssueComments) issue.Path("/comments").Methods("POST").Name(RepoIssueCommentsCreate) issue.Path("/comments/{CommentID}").Methods("PATCH", "PUT").Name(RepoIssueCommentsEdit) issue.Path("/comments/{CommentID}").Methods("DELETE").Name(RepoIssueCommentsDelete) deltaPath := "/.deltas/{Rev:.+}..{DeltaHeadRev:" + PathComponentNoLeadingDot + "}" repo.Path(deltaPath).Methods("GET").Name(Delta) deltas := repo.PathPrefix(deltaPath).Subrouter() deltas.Path("/.units").Methods("GET").Name(DeltaUnits) deltas.Path("/.defs").Methods("GET").Name(DeltaDefs) deltas.Path("/.dependencies").Methods("GET").Name(DeltaDependencies) deltas.Path("/.files").Methods("GET").Name(DeltaFiles) deltas.Path("/.affected-authors").Methods("GET").Name(DeltaAffectedAuthors) deltas.Path("/.affected-clients").Methods("GET").Name(DeltaAffectedClients) deltas.Path("/.affected-dependents").Methods("GET").Name(DeltaAffectedDependents) deltas.Path("/.reviewers").Methods("GET").Name(DeltaReviewers) repo.Path("/.deltas-incoming").Methods("GET").Name(DeltasIncoming) // See router_util/tree_route.go for an explanation of how we match tree // entry routes. repoRev.Path("/.tree" + TreeEntryPathPattern).PostMatchFunc(FixTreeEntryVars).BuildVarsFunc(PrepareTreeEntryRouteVars).Methods("GET").Name(RepoTreeEntry) repoRev.Path("/.tree-search").Methods("GET").Name(RepoTreeSearch) base.Path(`/people/` + PersonSpecPattern).Methods("GET").Name(Person) base.Path("/users").Methods("GET").Name(Users) userPath := `/users/` + UserSpecPattern base.Path(userPath).Methods("GET").Name(User) user := base.PathPrefix(userPath).Subrouter() user.Path("/orgs").Methods("GET").Name(UserOrgs) user.Path("/clients").Methods("GET").Name(UserClients) user.Path("/authors").Methods("GET").Name(UserAuthors) user.Path("/emails").Methods("GET").Name(UserEmails) user.Path("/repo-contributions").Methods("GET").Name(UserRepoContributions) user.Path("/repo-dependencies").Methods("GET").Name(UserRepoDependencies) user.Path("/repo-dependents").Methods("GET").Name(UserRepoDependents) user.Path("/external-profile").Methods("PUT").Name(UserRefreshProfile) user.Path("/stats").Methods("PUT").Name(UserComputeStats) user.Path("/settings").Methods("GET").Name(UserSettings) user.Path("/settings").Methods("PUT").Name(UserSettingsUpdate) base.Path("/external-users/github/{GitHubUserSpec}").Methods("GET").Name(UserFromGitHub) orgPath := "/orgs/{OrgSpec}" base.Path(orgPath).Methods("GET").Name(Org) org := base.PathPrefix(orgPath).Subrouter() org.Path("/settings").Methods("GET").Name(OrgSettings) org.Path("/settings").Methods("PUT").Name(OrgSettingsUpdate) org.Path("/members").Methods("GET").Name(OrgMembers) base.Path("/search").Methods("GET").Name(Search) base.Path("/search/complete").Methods("GET").Name(SearchComplete) base.Path("/search/suggestions").Methods("GET").Name(SearchSuggestions) base.Path("/snippet").Methods("GET", "POST", "ORIGIN").Name(Snippet) base.Path("/.defs").Methods("GET").Name(Defs) // See router_util/def_route.go for an explanation of how we match def // routes. defPath := `/.defs/` + DefPathPattern repoRev.Path(defPath).Methods("GET").PostMatchFunc(FixDefUnitVars).BuildVarsFunc(PrepareDefRouteVars).Name(Def) def := repoRev.PathPrefix(defPath).PostMatchFunc(FixDefUnitVars).BuildVarsFunc(PrepareDefRouteVars).Subrouter() def.Path("/.refs").Methods("GET").Name(DefRefs) def.Path("/.examples").Methods("GET").Name(DefExamples) def.Path("/.authors").Methods("GET").Name(DefAuthors) def.Path("/.clients").Methods("GET").Name(DefClients) def.Path("/.dependents").Methods("GET").Name(DefDependents) def.Path("/.versions").Methods("GET").Name(DefVersions) base.Path("/.units").Methods("GET").Name(Units) unitPath := `/.units/{UnitType}/{Unit:.*}` repoRev.Path(unitPath).Methods("GET").Name(Unit) base.Path("/markdown").Methods("POST").Name(Markdown) base.Path("/ext/github/webhook").Methods("POST").Name(ExtGitHubReceiveWebhook) if ExtraConfig != nil { ExtraConfig(base, user) } return base }