<|start_filename|>errors.go<|end_filename|>
package mwclient

import (
	"bytes"
	"errors"
	"fmt"

	"github.com/antonholmquist/jason"
)

// APIError represents a MediaWiki API error.
type APIError struct {
	Code, Info string
}

func (e APIError) Error() string {
	return fmt.Sprintf("%s: %s", e.Code, e.Info)
}

// APIWarnings represents a collection of MediaWiki API warnings.
type APIWarnings []struct {
	Module, Info string
}

func (w APIWarnings) Error() string {
	var buf bytes.Buffer

	amount := len(w) // amount of warnings
	if amount == 1 {
		buf.WriteString("1 warning: ")
	} else {
		buf.WriteString(fmt.Sprintf("%d warnings: ", len(w)))
	}

	for _, warn := range w {
		buf.WriteString(fmt.Sprintf("[%s: %s] ", warn.Module, warn.Info))
	}

	return buf.String()
}

// CaptchaError represents the error returned by the API when it requires the
// client to solve a CAPTCHA to perform the action requested.
type CaptchaError struct {
	Type     string `json:"type"`
	Mime     string `json:"mime"`
	ID       string `json:"id"`
	URL      string `json:"url"`
	Question string `json:"question"`
}

func (e CaptchaError) Error() string {
	if e.URL != "" {
		return fmt.Sprintf("API requires solving a CAPTCHA of type %s (%s) with ID %s at URL %s",
			e.Type, e.Mime, e.ID, e.URL)
	} else if e.Question != "" {
		return fmt.Sprintf("API requires solving a CAPTCHA of type %s (%s) with ID %s: %s",
			e.Type, e.Mime, e.ID, e.Question)
	} else {
		// Unknown CAPTCHA type
		return fmt.Sprintf("API requires solving a CAPTCHA of type %s (%s) with ID %s",
			e.Type, e.Mime, e.ID)
	}
}

// maxLagError is returned by the callf closure in the Client.call method when
// there is too much lag on the MediaWiki site. maxLagError contains a message
// from the server in the format "Waiting for $host: $lag seconds lagged\n" and
// an integer specifying how many seconds to wait before trying the request again.
type maxLagError struct {
	Message string
	Wait    int
}

func (e maxLagError) Error() string {
	return e.Message
}

// ErrAPIBusy is the error returned by an API call function when maxlag is
// enabled, and the API responds that it is busy for each of the in
// Client.Maxlag.Retries specified amount of retries.
var ErrAPIBusy = errors.New("the API is too busy. Try again later")

// ErrNoArgs is returned by API call methods that take variadic arguments when
// no arguments are passed.
var ErrNoArgs = errors.New("no arguments passed")

// extractAPIErrors extracts API errors or warnings from a given
// *jason.Object. If it finds an error, it will return an APIError.
// Otherwise it will look for warnings, and if it finds any it will return
// it/them in an APIWarning.
// extractAPIErrors is not compatible with MWAPI formatversion=1.
func extractAPIErrors(resp *jason.Object) error {
	if e, err := resp.GetObject("error"); err == nil {
		code, err1 := e.GetString("code")
		info, err2 := e.GetString("info")
		if !(err1 == nil && err2 == nil) {
			return fmt.Errorf("extractAPIErrors: 'error' object does not contain expected 'code' and 'info': %v", e)
		}
		return APIError{
			Code: code,
			Info: info,
		}
	}

	if w, err := resp.GetObject("warnings"); err == nil {
		return extractWarnings(w)
	}

	return nil
}

func extractWarnings(resp *jason.Object) error {
	var warnings APIWarnings
	for module, warningValue := range resp.Map() {
		warning, err := warningValue.Object()
		if err != nil {
			return fmt.Errorf("extractWarnings: %v: %v", err, warningValue)
		}

		info, err := warning.GetString("warnings")
		if err != nil {
			return fmt.Errorf("extractWarnings: %v: %v", err, warning)
		}
		warnings = append(warnings, APIWarnings{{module, info}}...)
	}

	return warnings
}


<|start_filename|>misc.go<|end_filename|>
package mwclient

import "net/http"

// DumpCookies exports the cookies stored in the client.
func (w *Client) DumpCookies() []*http.Cookie {
	return w.httpc.Jar.Cookies(w.apiURL)
}

// LoadCookies imports cookies into the client.
func (w *Client) LoadCookies(cookies []*http.Cookie) {
	w.httpc.Jar.SetCookies(w.apiURL, cookies)
}


<|start_filename|>query_test.go<|end_filename|>
package mwclient

import (
	"fmt"
	"net/http"
	"testing"

	"cgt.name/pkg/go-mwclient/params"
)

func TestQuery(t *testing.T) {
	reqCount := 0 // incremented on each request to queryHandler

	queryHandler := func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			panic("Bad HTTP form")
		}

		if reqCount == 0 {
			if value := r.Form.Get("continue"); value != "" {
				t.Fatalf("'continue' value not empty in first req: continue=%s",
					value)
			}
			fmt.Fprintf(w, `{"continue":{"fkcontinue":"sendthisback","continue":"-||"}}`)
		} else if reqCount == 1 {
			if value := r.Form.Get("continue"); value != "-||" {
				t.Fatalf("'continue' key has different value than '-||': continue=%s",
					value)
			}
			if r.Form.Get("fkcontinue") != "sendthisback" {
				t.Fatalf("client did not return fkcontinue parameter")
			}
			fmt.Fprintf(w, "{}") // no continue element
		} else {
			panic("reqCount somehow got a different value than 0 or 1")
		}

		reqCount++
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
	}

	server, client := setup(queryHandler)
	defer server.Close()

	q := client.NewQuery(params.Values{})
	for q.Next() {
		continue
	}
	if err := q.Err(); err != nil {
		t.Fatalf("q.Err() != nil: %v", err)
	}
}


<|start_filename|>errors_test.go<|end_filename|>
package mwclient

import (
	"testing"

	"github.com/antonholmquist/jason"
)

type ErrorType int

const (
	Eror ErrorType = iota
	Warn
	None
)

func TestExtractAPIErrors(t *testing.T) {
	var errtests = []struct {
		jsonInput  []byte
		testType   ErrorType
		warnAmount int
	}{
		{
			[]byte(`{"servedby":"mw1197","error":{"code":"nouser",
			"info":"The user parameter must be set"}}`),
			Eror,
			0,
		},
		{
			[]byte(`{"servedby":"mw1204","error":{"code":"notoken",
			"info":"The token parameter must be set"}}`),
			Eror,
			0,
		},
		{
			[]byte(`{"batchcomplete":true,"warnings":{"query":{"warnings":"Unrecognized value for parameter \"list\": invalidmodule."}}}`),
			Warn,
			1,
		},
		{
			[]byte(`{"query":{"pages":{"709377":{"pageid":709377,"ns":2,"title":
			"Bruger:Cgtdk","contentmodel":"wikitext","pagelanguage":"da",
			"touched":"2014-01-27T10:06:57Z","lastrevid":7257075,"counter":"",
			"length":695}}}}`),
			None,
			0,
		},
	}

	for i, errtest := range errtests {
		j, err := jason.NewObjectFromBytes(errtest.jsonInput)
		if err != nil {
			panic("Invalid test data: bad JSON input")
		}

		err = extractAPIErrors(j)

		switch errtest.testType {
		case Eror:
			if _, ok := err.(APIError); !ok {
				t.Errorf("(test:%d) expected APIError, got: %v", i, err)
			}
		case Warn:
			e, ok := err.(APIWarnings)
			if !ok {
				t.Errorf("(test:%d) expected APIWarnings, got: %v", i, err)
			}
			if len(e) != errtest.warnAmount {
				t.Errorf("(test:%d) expected %d warnings, got %d: %v", i,
					errtest.warnAmount, len(e), err)
			}
		case None:
			if err != nil {
				t.Errorf("(test:%d) expected nil, got !nil: %v", i, err)
			}
		}
	}
}


<|start_filename|>doc.go<|end_filename|>
/*
Package mwclient provides functionality for interacting with the MediaWiki API.

go-mwclient is intended for users who are already familiar with (or are
willing to learn) the MediaWiki API. It is intended to make dealing with
the API more convenient, but not to hide it.

go-mwclient v1 uses version 2 of the MW JSON API.

Basic usage

In the example below, basic usage of go-mwclient is shown.

	// Initialize a *Client with New(), specifying the wiki's API URL
	// and your HTTP User-Agent. Try to use a meaningful User-Agent.
	w, err := mwclient.New("https://en.wikipedia.org/w/api.php", "myWikibot")
	if err != nil {
		panic(err) // Malformed URL
	}

	parameters := params.Values{
		"action":   "query",
		"list":     "recentchanges",
	}
	response, err := wiki.Get(parameters)
	if err != nil {
		panic(err)
	}

Create a new Client object with the New() constructor, and then you are
ready to start making requests to the API. If you wish to make requests
to multiple MediaWiki sites, you must create a Client for each of them.

go-mwclient offers a few methods for making arbitrary requests to
the API: Get, GetRaw, Post, and PostRaw (see documentation for the
methods for details). They all offer the same basic interface: pass a
params.Values map (from the cgt.name/pkg/go-mwclient/params package),
receive a response and an error.

For convenience, go-mwclient offers several methods for making common
requests (login, edit, etc.), but these methods are implemented using
the same interface.

params.Values

params.Values is similar to (and a fork of) the standard library's
net/url.Values. The reason why params.Values is used instead is
that url.Values is based on a map[string][]string, rather than a
map[string]string. This is because url.Values must support multiple keys
with the same name.

The literal syntax for a map[string][]string is rather cumbersome
because the value is a slice rather than just a string, and the
MediaWiki API actually does not use multiple keys when multiple values
for the same key is required. Instead, one key is used and the values
are separated by pipes (|). It is therefore very simple to write
multi-value values in params.Values literals while

params.Values makes it simple to write multi-value values in literals
while avoiding the cumbersome []string literals for the most common case
where the is only value.

See documentation for the params package for more information.

Because of the way type identity works in Go, it is possible for callers
to pass a plain map[string]string rather than a params.Values. It is
only necessary for users to use params.Values directly if they wish to
use params.Values's methods. It makes no difference to go-mwclient.

Error handling

If an API call fails it will return an error. Many things can go wrong
during an API call: the network could be down, the API could return an
unexpected response (if the API was changed), or perhaps there's an
error in your API request.

If the error is an API error or warning (and you used the "non-Raw" Get
and Post methods), then the error/warning(s) will be parsed and returned
in either an APIError or an APIWarnings object, both of which implement
the error interface. The "Raw" request methods do not check for API
errors or warnings.

For more information about API errors and warnings, please see
https://www.mediawiki.org/wiki/API:Errors_and_warnings.

If maxlag is enabled, it may be that the API has rejected the requests
and the amount of retries (3 by default) have been tried unsuccessfully.
In that case, the error will be the variable mwclient.ErrAPIBusy.

Other methods than the core ones (i.e., other methods than Get and Post)
may return other errors.
*/
package mwclient // import "cgt.name/pkg/go-mwclient"
