Repository: line/line-bot-sdk-go (go)

Issue:
Validate rich menu object
NEWS:
https://developers.line.biz/en/news/2022/04/05/release-validate-rich-menu-object/

- [Validate rich menu object](https://developers.line.biz/en/reference/messaging-api/#validate-rich-menu-object)

Pull request description:
add ValidateRichMenuObjectMethod
Resolves #317 

Patch:
diff --git a/linebot/client.go b/linebot/client.go
index 9248830..6702263 100644
--- a/linebot/client.go
+++ b/linebot/client.go
@@ -67,6 +67,7 @@ const (
 	APIEndpointUploadRichMenuImage        = "/v2/bot/richmenu/%s/content" // Download: GET / Upload: POST
 	APIEndpointBulkLinkRichMenu           = "/v2/bot/richmenu/bulk/link"
 	APIEndpointBulkUnlinkRichMenu         = "/v2/bot/richmenu/bulk/unlink"
+	APIEndpointValidateRichMenuObject     = "/v2/bot/richmenu/validate"
 
 	APIEndpointCreateRichMenuAlias = "/v2/bot/richmenu/alias"
 	APIEndpointGetRichMenuAlias    = "/v2/bot/richmenu/alias/%s"
diff --git a/linebot/richmenu.go b/linebot/richmenu.go
index a67af27..1c4c406 100644
--- a/linebot/richmenu.go
+++ b/linebot/richmenu.go
@@ -825,3 +825,56 @@ func (call *GetRichMenuAliasListCall) Do() ([]*RichMenuAliasResponse, error) {
 	defer closeResponse(res)
 	return decodeToRichMenuAliasListResponse(res)
 }
+
+// ValidateRichMenuObject method
+func (client *Client) ValidateRichMenuObject(richMenu RichMenu) *ValidateRichMenuObjectCall {
+	return &ValidateRichMenuObjectCall{
+		c:        client,
+		richMenu: richMenu,
+	}
+}
+
+// ValidateRichMenuObjectCall type
+type ValidateRichMenuObjectCall struct {
+	c   *Client
+	ctx context.Context
+
+	richMenu RichMenu
+}
+
+// WithContext method
+func (call *ValidateRichMenuObjectCall) WithContext(ctx context.Context) *ValidateRichMenuObjectCall {
+	call.ctx = ctx
+	return call
+}
+
+func (call *ValidateRichMenuObjectCall) encodeJSON(w io.Writer) error {
+	enc := json.NewEncoder(w)
+	return enc.Encode(&struct {
+		Size        RichMenuSize `json:"size"`
+		Selected    bool         `json:"selected"`
+		Name        string       `json:"name"`
+		ChatBarText string       `json:"chatBarText"`
+		Areas       []AreaDetail `json:"areas"`
+	}{
+		Size:        call.richMenu.Size,
+		Selected:    call.richMenu.Selected,
+		Name:        call.richMenu.Name,
+		ChatBarText: call.richMenu.ChatBarText,
+		Areas:       call.richMenu.Areas,
+	})
+}
+
+// Do method
+func (call *ValidateRichMenuObjectCall) Do() (*BasicResponse, error) {
+	var buf bytes.Buffer
+	if err := call.encodeJSON(&buf); err != nil {
+		return nil, err
+	}
+	res, err := call.c.post(call.ctx, APIEndpointValidateRichMenuObject, &buf)
+	if err != nil {
+		return nil, err
+	}
+	defer closeResponse(res)
+	return decodeToBasicResponse(res)
+}


Test patch:
diff --git a/linebot/richmenu_test.go b/linebot/richmenu_test.go
index d6aa510..c1a184d 100644
--- a/linebot/richmenu_test.go
+++ b/linebot/richmenu_test.go
@@ -1101,3 +1101,93 @@ func TestListRichMenuAlias(t *testing.T) {
 		})
 	}
 }
+
+func TestValidateRichMenuObject(t *testing.T) {
+	type want struct {
+		RequestBody []byte
+		Response    *BasicResponse
+		Error       error
+	}
+	testCases := []struct {
+		Request      RichMenu
+		Response     []byte
+		ResponseCode int
+		Want         want
+	}{
+		{
+			Request: RichMenu{
+				Size:        RichMenuSize{Width: 2500, Height: 1686},
+				Selected:    false,
+				Name:        "Menu1",
+				ChatBarText: "ChatText",
+				Areas: []AreaDetail{
+					{
+						Bounds: RichMenuBounds{X: 0, Y: 0, Width: 2500, Height: 1686},
+						Action: RichMenuAction{Type: RichMenuActionTypePostback, Data: "action=buy&itemid=123", DisplayText: "Buy", Label: "Buy"},
+					},
+				},
+			},
+			ResponseCode: 200,
+			Response:     []byte(`{}`),
+			Want: want{
+				RequestBody: []byte(`{"size":{"width":2500,"height":1686},"selected":false,"name":"Menu1","chatBarText":"ChatText","areas":[{"bounds":{"x":0,"y":0,"width":2500,"height":1686},"action":{"type":"postback","displayText":"Buy","label":"Buy","data":"action=buy\u0026itemid=123"}}]}` + "\n"),
+				Response:    &BasicResponse{},
+			},
+		},
+	}
+
+	var currentTestIdx int
+	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		tc := testCases[currentTestIdx]
+		if r.Method != http.MethodPost {
+			t.Errorf("Method %s; want %s", r.Method, http.MethodPost)
+		}
+		if r.URL.Path != APIEndpointValidateRichMenuObject {
+			t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointValidateRichMenuObject)
+		}
+		body, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(body, tc.Want.RequestBody) {
+			t.Errorf("RequestBody\n %s; want\n %s", body, tc.Want.RequestBody)
+		}
+		w.WriteHeader(tc.ResponseCode)
+		w.Write(tc.Response)
+	}))
+	defer server.Close()
+
+	dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		t.Error("Unexpected data API call")
+		w.WriteHeader(404)
+		w.Write([]byte(`{"message":"Not found"}`))
+	}))
+	defer dataServer.Close()
+
+	client, err := mockClient(server, dataServer)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for i, tc := range testCases {
+		currentTestIdx = i
+		t.Run(strconv.Itoa(i), func(t *testing.T) {
+			res, err := client.ValidateRichMenuObject(tc.Request).Do()
+			if tc.Want.Error != nil {
+				if !reflect.DeepEqual(err, tc.Want.Error) {
+					t.Errorf("Error %v; want %v", err, tc.Want.Error)
+				}
+			} else {
+				if err != nil {
+					t.Error(err)
+				}
+			}
+			if tc.Want.Response != nil {
+				if !reflect.DeepEqual(res, tc.Want.Response) {
+					t.Errorf("Response %v; want %v", res, tc.Want.Response)
+				}
+			}
+		})
+	}
+}