From 8f62eb02905530691704bb1a2d6bb9b5bb6bef5a Mon Sep 17 00:00:00 2001 From: Mickael Remond Date: Sun, 31 Jul 2016 18:13:11 +0200 Subject: [PATCH] Refactor error management + user_resources call --- api.go | 97 +++++++++++++++++++++++++++++++++++++++----- client.go | 29 ++++++++++++- client_test.go | 9 ++++ cmd/ejabberd/main.go | 36 ++++++++++++---- 4 files changed, 150 insertions(+), 21 deletions(-) diff --git a/api.go b/api.go index fb9d73a..b207306 100644 --- a/api.go +++ b/api.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/url" + "strings" ) // Response is the common interface for all ejabberd API call results. @@ -95,7 +96,7 @@ func (s statsRequest) parseResponse(body []byte) (Response, error) { var resp Stats err := json.Unmarshal(body, &resp) if err != nil { - return Error{Code: 99, Message: "Cannot parse JSON response"}, err + return resp, APIError{Code: 99, Message: err.Error()} } resp.Name = s.Name return resp, err @@ -163,7 +164,7 @@ func (r registerRequest) parseResponse(body []byte) (Response, error) { var resp Register err := json.Unmarshal(body, &resp) if err != nil { - return Error{Code: 99, Message: "Cannot parse JSON response"}, err + return resp, APIError{Code: 99, Message: err.Error()} } return resp, nil } @@ -229,7 +230,7 @@ func (o offlineCountRequest) parseResponse(body []byte) (Response, error) { var resp OfflineCount err := json.Unmarshal(body, &resp) if err != nil { - return Error{Code: 99, Message: "Cannot parse JSON response"}, err + return resp, APIError{Code: 99, Message: err.Error()} } resp.Name = "offline_count" resp.JID = o.JID @@ -242,30 +243,104 @@ func (o OfflineCount) String() string { //============================================================================== -// Error represents ejabberd error returned by the server as result of -// ejabberd API calls. -type Error struct { +// UserResources contains the result of the call to ejabberd +// user_resources API. +type UserResources struct { + JID string `json:"jid"` + Resources []string `json:"resources"` +} + +// JSON represents UserResources as a JSON string, for further +// processing with other tools. +func (u UserResources) JSON() string { + body, _ := json.Marshal(u) + return string(body) +} + +type userResourcesRequest struct { + JID string `json:"jid"` +} + +func (u userResourcesRequest) params() (apiParams, error) { + var query url.Values + jid, err := parseJID(u.JID) + if err != nil { + return apiParams{}, err + } + + type userResources struct { + User string `json:"user"` + Server string `json:"server"` + } + + data := userResources{ + User: jid.username, + Server: jid.domain, + } + + body, err := json.Marshal(data) + if err != nil { + return apiParams{}, err + } + + if err != nil { + return apiParams{}, err + } + + return apiParams{ + name: "user_resources", + version: 1, + + method: "POST", + query: query, + body: body, + }, nil +} + +func (u userResourcesRequest) parseResponse(body []byte) (Response, error) { + var resp UserResources + + var data []string + err := json.Unmarshal(body, &data) + if err != nil { + return resp, APIError{Code: 99, Message: err.Error()} + } + resp.JID = u.JID + resp.Resources = data + return resp, nil +} + +func (u UserResources) String() string { + resources := strings.Join(u.Resources, ",") + return fmt.Sprintf("%s", resources) +} + +//============================================================================== + +// APIError represents ejabberd error returned by the server as result +// of ejabberd API calls. +type APIError struct { Status string `json:"status"` Code int `json:"code"` Message string `json:"message"` } -func parseError(body []byte) (Error, error) { - var resp Error +func parseError(body []byte) (APIError, error) { + var resp APIError err := json.Unmarshal(body, &resp) if err != nil { - return Error{Code: 99, Message: "Cannot parse JSON response"}, err + return resp, APIError{Code: 99, Message: err.Error()} } return resp, nil } // JSON represents ejabberd error response as a JSON string, for further // processing with other tools. -func (e Error) JSON() string { +func (e APIError) JSON() string { body, _ := json.Marshal(e) return string(body) } -func (e Error) String() string { +func (e APIError) Error() string { return fmt.Sprintf("Error %d: %s", e.Code, e.Message) } diff --git a/client.go b/client.go index 2dcf582..e248198 100644 --- a/client.go +++ b/client.go @@ -32,11 +32,15 @@ type Client struct { func (c Client) call(req request) (Response, error) { code, result, err := c.callRaw(req) if err != nil { - return Error{Code: 99}, err + return APIError{Code: 99}, err } if code != 200 { - return parseError(result) + apiError, err := parseError(result) + if err != nil { + return nil, err + } + return nil, apiError } return req.parseResponse(result) @@ -63,6 +67,7 @@ func (c Client) callRaw(req request) (int, []byte, error) { if p.admin { r.Header.Set("X-Admin", "true") } else if needAdminForUser(req, c.Token.JID) { + fmt.Println("MREMOND Set admin") r.Header.Set("X-Admin", "true") } r.Header.Set("Content-Type", "application/json") @@ -218,6 +223,26 @@ func (c Client) GetOfflineCount(bareJID string) (OfflineCount, error) { //============================================================================== +// UserResources returns the list of resources connected for a given +// user. It can be called as a user, if you try to read your own +// connected resources. It can also be called as an admin and in that +// case, you can read the connected resources for any any user on the +// server. +func (c Client) UserResources(bareJID string) (UserResources, error) { + command := userResourcesRequest{ + JID: bareJID, + } + + result, err := c.call(command) + if err != nil { + return UserResources{}, err + } + resp := result.(UserResources) + return resp, nil +} + +//============================================================================== + // Prepare HTTP client settings with proper values, like default // timeout. func defaultHTTPClient(timeout time.Duration) *http.Client { diff --git a/client_test.go b/client_test.go index ee00138..cff0734 100644 --- a/client_test.go +++ b/client_test.go @@ -1,6 +1,7 @@ package ejabberd_test import ( + "flag" "fmt" "net/http" "net/http/httptest" @@ -50,6 +51,12 @@ func ExampleClient_GetToken() { } func ExampleClient_Stats() { + if flag.Lookup("test.v") == nil { + fmt.Println("normal run") + } else { + fmt.Println("run under go test") + } + t := ejabberd.OAuthToken{AccessToken: "XjlJg0KF2wagT0A5dcYghePl8npsiEic"} client := ejabberd.Client{BaseURL: "http://localhost:5281", Token: t} @@ -58,4 +65,6 @@ func ExampleClient_Stats() { } else { fmt.Println(stats.Name, stats.Value) } + // Output: + // 1 } diff --git a/cmd/ejabberd/main.go b/cmd/ejabberd/main.go index 9d12fdd..b6fcaf5 100644 --- a/cmd/ejabberd/main.go +++ b/cmd/ejabberd/main.go @@ -27,11 +27,15 @@ var ( stats = app.Command("stats", "Get ejabberd statistics.") statsName = stats.Arg("name", "Name of stats to query.").Required().String() + // ========= admin ========= + register = app.Command("register", "Create a new user.") + registerJID = register.Flag("jid", "JID of the user to create.").Short('j').Required().String() + registerPassword = register.Flag("password", "Password to set for created user.").Short('p').Required().String() + // ========= user ========= user = app.Command("user", "Operations to perform on users.") - userOperation = user.Arg("operation", "Operation").Required().Enum("register") - userJID = user.Flag("jid", "JID of the user to perform operation on.").Short('j').Required().String() - userPassword = user.Flag("password", "User password").Short('p').Required().String() + userOperation = user.Arg("operation", "Operation").Required().Enum("resources") + userJID = user.Flag("jid", "JID of the user to perform operation on.").Short('j').String() // ========= offline ========= offline = app.Command("offline", "Operations to perform on offline store.") @@ -68,6 +72,8 @@ func execute(command string) { var resp ejabberd.Response switch command { + case register.FullCommand(): + resp = registerCommand(c, *registerJID, *registerPassword) case stats.FullCommand(): resp = statsCommand(c) case user.FullCommand(): @@ -106,6 +112,16 @@ func getToken() { //============================================================================== +func registerCommand(c ejabberd.Client, j, p string) ejabberd.Response { + resp, err := c.RegisterUser(j, p) + if err != nil { + kingpin.Fatalf("user registration error for %s: %s", j, err) + } + return resp +} + +//============================================================================== + func statsCommand(c ejabberd.Client) ejabberd.Response { resp, err := c.Stats(*statsName) if err != nil { @@ -119,16 +135,20 @@ func statsCommand(c ejabberd.Client) ejabberd.Response { func userCommand(c ejabberd.Client, op string) ejabberd.Response { var resp ejabberd.Response switch op { - case "register": - resp = registerCommand(c, *userJID, *userPassword) + case "resources": + resp = resourcesCommand(c, *userJID) } return resp } -func registerCommand(c ejabberd.Client, j, p string) ejabberd.Response { - resp, err := c.RegisterUser(j, p) +func resourcesCommand(c ejabberd.Client, jid string) ejabberd.Response { + if jid == "" { + jid = c.Token.JID + } + + resp, err := c.UserResources(jid) if err != nil { - kingpin.Fatalf("user register error for %s: %s", j, err) + kingpin.Fatalf("%s: %s", jid, err) } return resp }