Code walkthrough: Gitea API

This is the first Code walkthrough I wrote while exploring the Gitea codebase.

Bonjour,

This is the first of a series of posts explaining how the Gitea code works on a given functionality. It is as if someone told the story of the processor walking a code path, hence the name: code walkthrough. It contains links to web pages explaining concepts or dependencies (such as swagger) as well as parts of the Gitea code that implements the action (such as a method, etc). A Go developer with no prior knowledge of the Gitea codebase should learn something that is not trivially deduced from reading a single Gitea method.

The Gitea API is generated using go-swagger which is an implementation of OpenAPI (version 2.0).

The /api/v1 route is mounted (this is a chi feature) on a Routes method that associates path with functions performing the expected action. For instance the /api/v1/nodeinfo path is bound to the misc.Nodeinfo function that simply returns a JSON structure with information about the Gitea application.

The chi handler for Get signature is (w http.ResponseWriter, req *http.Request) but Gitea installs the APIContexter middleware that expects a different signature from the function: (ctx *context.APIContext). The APIContext instance contains the w argument as well as a Session created with the req argument.

The comments of the misc.Nodeinfo function are interpreted by the swagger generate command line (with a few tweaks) to update the swagger json file with the behavior and data type accordingly and it will be used as a basis for the documentation.

The type returned by NodeInfo is defined in the structs module. Each member of the struct and its children is associated with a type annotation used to map the name of the field with its JSON counterpart (for instance Version is version) during serialization.

It is the responsibility of the function to behave as documented: there is no input or output validation. For instance if the function was to return additional information not mentioned in the swagger comments, it would not throw an error at runtime.

Although most path to function bindings in Routes are created using chi functions such as Get or Post, Gitea adds a few for convenience such as Combo.

The routes parameters (for instance {id} in /api/v1/repositories/{id}) can be retrieved from the context argument with helpers such as ctx.ParamsInt64(":id") that are implemented in the Gitea Context module.

chi comes with middlewares that can be added to process the incoming request or outgoing response. Gitea adds the possibilty to specify middlewares as an argument of a route function such as Get (the last argument is the function doing the work, all others before it are assumed to be middlewares). For instance the reqExploreSignIn middleware is prepended to the user.ListUserRepos function.

1 Like

When I try to access tokens at https://forge.chapril.org/api/v1/users/dachary/tokens it refuses because:

image

Which is great: I would not want my tokens to be publicly accessible. But looking at the code binding the route, I don’t see a restriction.

		// Users
		m.Group("/users", func() {
			m.Get("/search", reqExploreSignIn(), user.Search)

			m.Group("/{username}", func() {
				m.Get("", reqExploreSignIn(), user.GetInfo)

				if setting.Service.EnableUserHeatmap {
					m.Get("/heatmap", user.GetUserHeatmapData)
				}

				m.Get("/repos", reqExploreSignIn(), user.ListUserRepos)
				m.Group("/tokens", func() {
					m.Combo("").Get(user.ListAccessTokens).
						Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
					m.Combo("/{id}").Delete(user.DeleteAccessToken)
				}, reqBasicAuth())
			})
		})

:man_facepalming:

I was expecting to find it at the begining of the m.Group("/tokens"… call but the call to reqBasicAuth is at the end: }, reqBasicAuth())