Repository: xanzy/go-gitlab
Pull request #1054 (2021-02-04 22:08:22)

Added support for group wiki APIs
With 13.5 a group wiki API was introduced for gitlab premium: https://docs.gitlab.com/13.8/ee/api/group_wikis.html

This PR implements `GroupWikisService` in the same way as the existing `WikisService`


Patch:
diff --git a/README.md b/README.md
index 1db5083..57459ce 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,7 @@ to add new and/or missing endpoints. Currently the following services are suppor
 - [x] Group Issue Boards
 - [x] Group Members
 - [x] Group Milestones
+- [x] Group Wikis
 - [x] Group-Level Variables
 - [x] Groups
 - [x] Instance Clusters
diff --git a/gitlab.go b/gitlab.go
index 7167d97..9d9d23c 100644
--- a/gitlab.go
+++ b/gitlab.go
@@ -127,6 +127,7 @@ type Client struct {
 	GroupMembers          *GroupMembersService
 	GroupMilestones       *GroupMilestonesService
 	GroupVariables        *GroupVariablesService
+	GroupWikis            *GroupWikisService
 	Groups                *GroupsService
 	InstanceCluster       *InstanceClustersService
 	InstanceVariables     *InstanceVariablesService
@@ -294,6 +295,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) {
 	c.GroupMembers = &GroupMembersService{client: c}
 	c.GroupMilestones = &GroupMilestonesService{client: c}
 	c.GroupVariables = &GroupVariablesService{client: c}
+	c.GroupWikis = &GroupWikisService{client: c}
 	c.Groups = &GroupsService{client: c}
 	c.InstanceCluster = &InstanceClustersService{client: c}
 	c.InstanceVariables = &InstanceVariablesService{client: c}
diff --git a/group_wikis.go b/group_wikis.go
new file mode 100644
index 0000000..74c9898
--- /dev/null
+++ b/group_wikis.go
@@ -0,0 +1,194 @@
+//
+// Copyright 2021, Markus Lackner
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gitlab
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+)
+
+// GroupWikisService handles communication with the group wikis related methods of
+// the Gitlab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/group_wikis.html
+type GroupWikisService struct {
+	client *Client
+}
+
+// GroupWiki represents a GitLab groups wiki.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/group_wikis.html
+type GroupWiki struct {
+	Content string          `json:"content"`
+	Format  WikiFormatValue `json:"format"`
+	Slug    string          `json:"slug"`
+	Title   string          `json:"title"`
+}
+
+func (w GroupWiki) String() string {
+	return Stringify(w)
+}
+
+// ListGroupWikisOptions represents the available ListGroupWikis options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_wikis.html#list-wiki-pages
+type ListGroupWikisOptions struct {
+	WithContent *bool `url:"with_content,omitempty" json:"with_content,omitempty"`
+}
+
+// ListGroupWikis lists all pages of the wiki of the given group id.
+// When with_content is set, it also returns the content of the pages.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_wikis.html#list-wiki-pages
+func (s *GroupWikisService) ListGroupWikis(gid interface{}, opt *ListGroupWikisOptions, options ...RequestOptionFunc) ([]*GroupWiki, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/wikis", pathEscape(group))
+
+	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var gws []*GroupWiki
+	resp, err := s.client.Do(req, &gws)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gws, resp, err
+}
+
+// GetGroupWikiPage gets a wiki page for a given group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_wikis.html#get-a-wiki-page
+func (s *GroupWikisService) GetGroupWikiPage(gid interface{}, slug string, options ...RequestOptionFunc) (*GroupWiki, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/wikis/%s", pathEscape(group), url.PathEscape(slug))
+
+	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gw := new(GroupWiki)
+	resp, err := s.client.Do(req, gw)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gw, resp, err
+}
+
+// CreateGroupWikiPageOptions represents options to CreateGroupWikiPage.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_wikis.html#create-a-new-wiki-page
+type CreateGroupWikiPageOptions struct {
+	Content *string          `url:"content,omitempty" json:"content,omitempty"`
+	Title   *string          `url:"title,omitempty" json:"title,omitempty"`
+	Format  *WikiFormatValue `url:"format,omitempty" json:"format,omitempty"`
+}
+
+// CreateGroupWikiPage creates a new wiki page for the given group with
+// the given title, slug, and content.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/13.8/ee/api/group_wikis.html#create-a-new-wiki-page
+func (s *GroupWikisService) CreateGroupWikiPage(gid interface{}, opt *CreateGroupWikiPageOptions, options ...RequestOptionFunc) (*GroupWiki, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/wikis", pathEscape(group))
+
+	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	w := new(GroupWiki)
+	resp, err := s.client.Do(req, w)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return w, resp, err
+}
+
+// EditGroupWikiPageOptions represents options to EditGroupWikiPage.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_wikis.html#edit-an-existing-wiki-page
+type EditGroupWikiPageOptions struct {
+	Content *string          `url:"content,omitempty" json:"content,omitempty"`
+	Title   *string          `url:"title,omitempty" json:"title,omitempty"`
+	Format  *WikiFormatValue `url:"format,omitempty" json:"format,omitempty"`
+}
+
+// EditGroupWikiPage Updates an existing wiki page. At least one parameter is
+// required to update the wiki page.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_wikis.html#edit-an-existing-wiki-page
+func (s *GroupWikisService) EditGroupWikiPage(gid interface{}, slug string, opt *EditGroupWikiPageOptions, options ...RequestOptionFunc) (*GroupWiki, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/wikis/%s", pathEscape(group), url.PathEscape(slug))
+
+	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	w := new(GroupWiki)
+	resp, err := s.client.Do(req, w)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return w, resp, err
+}
+
+// DeleteGroupWikiPage deletes a wiki page with a given slug.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_wikis.html#delete-a-wiki-page
+func (s *GroupWikisService) DeleteGroupWikiPage(gid interface{}, slug string, options ...RequestOptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/wikis/%s", pathEscape(group), url.PathEscape(slug))
+
+	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/types.go b/types.go
index ab1e2ac..2096029 100644
--- a/types.go
+++ b/types.go
@@ -338,6 +338,27 @@ func VariableType(v VariableTypeValue) *VariableTypeValue {
 	return p
 }
 
+// WikiFormat represents the available wiki formats.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html
+type WikiFormatValue string
+
+// The available wiki formats.
+const (
+	WikiFormatMarkdown WikiFormatValue = "markdown"
+	WikiFormatRDoc     WikiFormatValue = "rdoc"
+	WikiFormatASCIIDoc WikiFormatValue = "asciidoc"
+	WikiFormatOrg      WikiFormatValue = "org"
+)
+
+// WikiFormat is a helper routine that allocates a new WikiFormatValue
+// to store v and returns a pointer to it.
+func WikiFormat(v WikiFormatValue) *WikiFormatValue {
+	p := new(WikiFormatValue)
+	*p = v
+	return p
+}
+
 // MergeMethodValue represents a project merge type within GitLab.
 //
 // GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method
diff --git a/wikis.go b/wikis.go
index 9b6bd38..d693b05 100644
--- a/wikis.go
+++ b/wikis.go
@@ -28,26 +28,14 @@ type WikisService struct {
 	client *Client
 }
 
-// WikiFormat represents the available wiki formats.
-//
-// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html
-type WikiFormat string
-
-// The available wiki formats.
-const (
-	WikiFormatMarkdown WikiFormat = "markdown"
-	WikiFormatRFoc     WikiFormat = "rdoc"
-	WikiFormatASCIIDoc WikiFormat = "asciidoc"
-)
-
 // Wiki represents a GitLab wiki.
 //
 // GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html
 type Wiki struct {
-	Content string     `json:"content"`
-	Format  WikiFormat `json:"format"`
-	Slug    string     `json:"slug"`
-	Title   string     `json:"title"`
+	Content string          `json:"content"`
+	Format  WikiFormatValue `json:"format"`
+	Slug    string          `json:"slug"`
+	Title   string          `json:"title"`
 }
 
 func (w Wiki) String() string {
@@ -79,13 +67,13 @@ func (s *WikisService) ListWikis(pid interface{}, opt *ListWikisOptions, options
 		return nil, nil, err
 	}
 
-	var w []*Wiki
-	resp, err := s.client.Do(req, &w)
+	var ws []*Wiki
+	resp, err := s.client.Do(req, &ws)
 	if err != nil {
 		return nil, resp, err
 	}
 
-	return w, resp, err
+	return ws, resp, err
 }
 
 // GetWikiPage gets a wiki page for a given project.
@@ -104,8 +92,8 @@ func (s *WikisService) GetWikiPage(pid interface{}, slug string, options ...Requ
 		return nil, nil, err
 	}
 
-	var w *Wiki
-	resp, err := s.client.Do(req, &w)
+	w := new(Wiki)
+	resp, err := s.client.Do(req, w)
 	if err != nil {
 		return nil, resp, err
 	}
@@ -118,9 +106,9 @@ func (s *WikisService) GetWikiPage(pid interface{}, slug string, options ...Requ
 // GitLab API docs:
 // https://docs.gitlab.com/ce/api/wikis.html#create-a-new-wiki-page
 type CreateWikiPageOptions struct {
-	Content *string `url:"content" json:"content"`
-	Title   *string `url:"title" json:"title"`
-	Format  *string `url:"format,omitempty" json:"format,omitempty"`
+	Content *string          `url:"content,omitempty" json:"content,omitempty"`
+	Title   *string          `url:"title,omitempty" json:"title,omitempty"`
+	Format  *WikiFormatValue `url:"format,omitempty" json:"format,omitempty"`
 }
 
 // CreateWikiPage creates a new wiki page for the given repository with
@@ -154,9 +142,9 @@ func (s *WikisService) CreateWikiPage(pid interface{}, opt *CreateWikiPageOption
 // GitLab API docs:
 // https://docs.gitlab.com/ce/api/wikis.html#edit-an-existing-wiki-page
 type EditWikiPageOptions struct {
-	Content *string `url:"content" json:"content"`
-	Title   *string `url:"title" json:"title"`
-	Format  *string `url:"format,omitempty" json:"format,omitempty"`
+	Content *string          `url:"content,omitempty" json:"content,omitempty"`
+	Title   *string          `url:"title,omitempty" json:"title,omitempty"`
+	Format  *WikiFormatValue `url:"format,omitempty" json:"format,omitempty"`
 }
 
 // EditWikiPage Updates an existing wiki page. At least one parameter is


Test patch:
diff --git a/group_wikis_test.go b/group_wikis_test.go
new file mode 100644
index 0000000..4d2735e
--- /dev/null
+++ b/group_wikis_test.go
@@ -0,0 +1,119 @@
+package gitlab
+
+import (
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+func TestListGroupWikis(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/1/wikis",
+		func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, http.MethodGet)
+			fmt.Fprint(w, `[{"content":"content here","format":"markdown","slug":"deploy","title":"deploy title"}]`)
+		})
+
+	groupwikis, _, err := client.GroupWikis.ListGroupWikis(1, &ListGroupWikisOptions{})
+	if err != nil {
+		t.Errorf("GroupWikis.ListGroupWikis returned error: %v", err)
+	}
+
+	want := []*GroupWiki{{Content: "content here", Format: WikiFormatMarkdown, Slug: "deploy", Title: "deploy title"}}
+	if !reflect.DeepEqual(want, groupwikis) {
+		t.Errorf("GroupWikis.ListGroupWikis returned %+v, want %+v", groupwikis, want)
+	}
+}
+
+func TestGetGroupWikiPage(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/1/wikis/deploy",
+		func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, http.MethodGet)
+			fmt.Fprint(w, `{"content":"content here","format":"asciidoc","slug":"deploy","title":"deploy title"}`)
+		})
+
+	groupwiki, _, err := client.GroupWikis.GetGroupWikiPage(1, "deploy")
+	if err != nil {
+		t.Errorf("GroupWikis.GetGroupWikiPage returned error: %v", err)
+	}
+
+	want := &GroupWiki{Content: "content here", Format: WikiFormatASCIIDoc, Slug: "deploy", Title: "deploy title"}
+	if !reflect.DeepEqual(want, groupwiki) {
+		t.Errorf("GroupWikis.GetGroupWikiPage returned %+v, want %+v", groupwiki, want)
+	}
+}
+
+func TestCreateGroupWikiPage(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/1/wikis",
+		func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, http.MethodPost)
+			fmt.Fprint(w, `{"content":"content here","format":"rdoc","slug":"deploy","title":"deploy title"}`)
+		})
+
+	groupwiki, _, err := client.GroupWikis.CreateGroupWikiPage(1, &CreateGroupWikiPageOptions{
+		Content: String("content here"),
+		Title:   String("deploy title"),
+		Format:  WikiFormat(WikiFormatRDoc),
+	})
+	if err != nil {
+		t.Errorf("GroupWikis.CreateGroupWikiPage returned error: %v", err)
+	}
+
+	want := &GroupWiki{Content: "content here", Format: WikiFormatRDoc, Slug: "deploy", Title: "deploy title"}
+	if !reflect.DeepEqual(want, groupwiki) {
+		t.Errorf("GroupWikis.CreateGroupWikiPage returned %+v, want %+v", groupwiki, want)
+	}
+}
+
+func TestEditGroupWikiPage(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/1/wikis/deploy",
+		func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, http.MethodPut)
+			fmt.Fprint(w, `{"content":"content here","format":"asciidoc","slug":"deploy","title":"deploy title"}`)
+		})
+
+	groupwiki, _, err := client.GroupWikis.EditGroupWikiPage(1, "deploy", &EditGroupWikiPageOptions{
+		Content: String("content here"),
+		Title:   String("deploy title"),
+		Format:  WikiFormat(WikiFormatRDoc),
+	})
+	if err != nil {
+		t.Errorf("GroupWikis.EditGroupWikiPage returned error: %v", err)
+	}
+
+	want := &GroupWiki{Content: "content here", Format: WikiFormatASCIIDoc, Slug: "deploy", Title: "deploy title"}
+	if !reflect.DeepEqual(want, groupwiki) {
+		t.Errorf("GroupWikis.EditGroupWikiPage returned %+v, want %+v", groupwiki, want)
+	}
+}
+
+func TestDeleteGroupWikiPage(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/1/wikis/deploy",
+		func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, http.MethodDelete)
+			w.WriteHeader(204)
+		})
+
+	r, err := client.GroupWikis.DeleteGroupWikiPage(1, "deploy")
+	if err != nil {
+		t.Errorf("GroupWikis.DeleteGroupWikiPage returned error: %v", err)
+	}
+	if r.StatusCode != 204 {
+		t.Errorf("GroupWikis.DeleteGroupWikiPage returned wrong status code %d != 204", r.StatusCode)
+	}
+}