mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 01:32:33 +00:00
Godeps to lock in dependencies
This commit is contained in:
117
Godeps/Godeps.json
generated
Normal file
117
Godeps/Godeps.json
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/vault",
|
||||
"GoVersion": "go1.4.2",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/armon/go-metrics",
|
||||
"Rev": "a54701ebec11868993bc198c3f315353e9de2ed6"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/armon/go-radix",
|
||||
"Rev": "0bab926c3433cfd6490c6d3c504a7b471362390c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||
"Comment": "v1.2-88-ga197e5d",
|
||||
"Rev": "a197e5d40516f2e9f74dcee085a5f2d4604e94df"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/go-github/github",
|
||||
"Rev": "0aaa85be4f3087c6dd815a69e291775d4e83f9ea"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/go-querystring/query",
|
||||
"Rev": "547ef5ac979778feb2f760cdb5f4eae1a2207b86"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/aws-sdk-go/aws",
|
||||
"Comment": "tf0.4.0-3-ge6ea019",
|
||||
"Rev": "e6ea0192eee4640f32ec73c0cbb71f63e4f2b65a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/aws-sdk-go/gen/endpoints",
|
||||
"Comment": "tf0.4.0-3-ge6ea019",
|
||||
"Rev": "e6ea0192eee4640f32ec73c0cbb71f63e4f2b65a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/aws-sdk-go/gen/iam",
|
||||
"Comment": "tf0.4.0-3-ge6ea019",
|
||||
"Rev": "e6ea0192eee4640f32ec73c0cbb71f63e4f2b65a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/consul/api",
|
||||
"Comment": "v0.5.0-199-g205af6b",
|
||||
"Rev": "205af6ba750b88863e6ee50c7c3d19edc180a6f6"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/errwrap",
|
||||
"Rev": "7554cd9344cec97297fa6649b055a8c98c2a1e55"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/go-multierror",
|
||||
"Rev": "fcdddc395df1ddf4247c69bd436e84cfa0733f7e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/go-syslog",
|
||||
"Rev": "42a2b573b664dbf281bd48c3cc12c086b17a39ba"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/golang-lru",
|
||||
"Rev": "d85392d6bc30546d352f52f2632814cde4201d44"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/hcl",
|
||||
"Rev": "513e04c400ee2e81e97f5e011c08fb42c6f69b84"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/logutils",
|
||||
"Rev": "367a65d59043b4f846d179341d138f01f988c186"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/lib/pq",
|
||||
"Comment": "go1.0-cutoff-40-g8910d1c",
|
||||
"Rev": "8910d1c3a4bda5c97c50bc38543953f1f1e1f8bb"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/cli",
|
||||
"Rev": "afc399c273e70173826fb6f518a48edff23fe897"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/copystructure",
|
||||
"Rev": "6fc66267e9da7d155a9d3bd489e00dad02666dc6"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/go-homedir",
|
||||
"Rev": "1f6da4a72e57d4e7edd4a7295a585e0a3999a2d4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/mapstructure",
|
||||
"Rev": "442e588f213303bec7936deba67901f8fc8f18b1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/reflectwalk",
|
||||
"Rev": "242be0c275dedfba00a616563e6db75ab8f279ec"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ryanuber/columnize",
|
||||
"Comment": "v2.0.1-6-g44cb478",
|
||||
"Rev": "44cb4788b2ec3c3d158dd3d1b50aba7d66f4b59a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vaughan0/go-ini",
|
||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ssh/terminal",
|
||||
"Rev": "c84e1f8e3a7e322d497cd16c0e8a13c7e127baf3"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "ff8eb9a34a5cbb9941ffc6f84a19a8014c2646ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2",
|
||||
"Rev": "ec6d5d770f531108a6464462b2201b74fcd09314"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
||||
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/pkg
|
||||
/bin
|
||||
22
Godeps/_workspace/src/github.com/armon/go-metrics/.gitignore
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/armon/go-metrics/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
20
Godeps/_workspace/src/github.com/armon/go-metrics/LICENSE
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/armon/go-metrics/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Armon Dadgar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
71
Godeps/_workspace/src/github.com/armon/go-metrics/README.md
generated
vendored
Normal file
71
Godeps/_workspace/src/github.com/armon/go-metrics/README.md
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
go-metrics
|
||||
==========
|
||||
|
||||
This library provides a `metrics` package which can be used to instrument code,
|
||||
expose application metrics, and profile runtime performance in a flexible manner.
|
||||
|
||||
Current API: [](https://godoc.org/github.com/armon/go-metrics)
|
||||
|
||||
Sinks
|
||||
=====
|
||||
|
||||
The `metrics` package makes use of a `MetricSink` interface to support delivery
|
||||
to any type of backend. Currently the following sinks are provided:
|
||||
|
||||
* StatsiteSink : Sinks to a [statsite](https://github.com/armon/statsite/) instance (TCP)
|
||||
* StatsdSink: Sinks to a [StatsD](https://github.com/etsy/statsd/) / statsite instance (UDP)
|
||||
* PrometheusSink: Sinks to a [Prometheus](http://prometheus.io/) metrics endpoint (exposed via HTTP for scrapes)
|
||||
* InmemSink : Provides in-memory aggregation, can be used to export stats
|
||||
* FanoutSink : Sinks to multiple sinks. Enables writing to multiple statsite instances for example.
|
||||
* BlackholeSink : Sinks to nowhere
|
||||
|
||||
In addition to the sinks, the `InmemSignal` can be used to catch a signal,
|
||||
and dump a formatted output of recent metrics. For example, when a process gets
|
||||
a SIGUSR1, it can dump to stderr recent performance metrics for debugging.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Here is an example of using the package:
|
||||
|
||||
func SlowMethod() {
|
||||
// Profiling the runtime of a method
|
||||
defer metrics.MeasureSince([]string{"SlowMethod"}, time.Now())
|
||||
}
|
||||
|
||||
// Configure a statsite sink as the global metrics sink
|
||||
sink, _ := metrics.NewStatsiteSink("statsite:8125")
|
||||
metrics.NewGlobal(metrics.DefaultConfig("service-name"), sink)
|
||||
|
||||
// Emit a Key/Value pair
|
||||
metrics.EmitKey([]string{"questions", "meaning of life"}, 42)
|
||||
|
||||
|
||||
Here is an example of setting up an signal handler:
|
||||
|
||||
// Setup the inmem sink and signal handler
|
||||
inm := metrics.NewInmemSink(10*time.Second, time.Minute)
|
||||
sig := metrics.DefaultInmemSignal(inm)
|
||||
metrics.NewGlobal(metrics.DefaultConfig("service-name"), inm)
|
||||
|
||||
// Run some code
|
||||
inm.SetGauge([]string{"foo"}, 42)
|
||||
inm.EmitKey([]string{"bar"}, 30)
|
||||
|
||||
inm.IncrCounter([]string{"baz"}, 42)
|
||||
inm.IncrCounter([]string{"baz"}, 1)
|
||||
inm.IncrCounter([]string{"baz"}, 80)
|
||||
|
||||
inm.AddSample([]string{"method", "wow"}, 42)
|
||||
inm.AddSample([]string{"method", "wow"}, 100)
|
||||
inm.AddSample([]string{"method", "wow"}, 22)
|
||||
|
||||
....
|
||||
|
||||
When a signal comes in, output like the following will be dumped to stderr:
|
||||
|
||||
[2014-01-28 14:57:33.04 -0800 PST][G] 'foo': 42.000
|
||||
[2014-01-28 14:57:33.04 -0800 PST][P] 'bar': 30.000
|
||||
[2014-01-28 14:57:33.04 -0800 PST][C] 'baz': Count: 3 Min: 1.000 Mean: 41.000 Max: 80.000 Stddev: 39.509
|
||||
[2014-01-28 14:57:33.04 -0800 PST][S] 'method.wow': Count: 3 Min: 22.000 Mean: 54.667 Max: 100.000 Stddev: 40.513
|
||||
|
||||
12
Godeps/_workspace/src/github.com/armon/go-metrics/const_unix.go
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/armon/go-metrics/const_unix.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build !windows
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultSignal is used with DefaultInmemSignal
|
||||
DefaultSignal = syscall.SIGUSR1
|
||||
)
|
||||
13
Godeps/_workspace/src/github.com/armon/go-metrics/const_windows.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/armon/go-metrics/const_windows.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build windows
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultSignal is used with DefaultInmemSignal
|
||||
// Windows has no SIGUSR1, use SIGBREAK
|
||||
DefaultSignal = syscall.Signal(21)
|
||||
)
|
||||
239
Godeps/_workspace/src/github.com/armon/go-metrics/inmem.go
generated
vendored
Normal file
239
Godeps/_workspace/src/github.com/armon/go-metrics/inmem.go
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InmemSink provides a MetricSink that does in-memory aggregation
|
||||
// without sending metrics over a network. It can be embedded within
|
||||
// an application to provide profiling information.
|
||||
type InmemSink struct {
|
||||
// How long is each aggregation interval
|
||||
interval time.Duration
|
||||
|
||||
// Retain controls how many metrics interval we keep
|
||||
retain time.Duration
|
||||
|
||||
// maxIntervals is the maximum length of intervals.
|
||||
// It is retain / interval.
|
||||
maxIntervals int
|
||||
|
||||
// intervals is a slice of the retained intervals
|
||||
intervals []*IntervalMetrics
|
||||
intervalLock sync.RWMutex
|
||||
}
|
||||
|
||||
// IntervalMetrics stores the aggregated metrics
|
||||
// for a specific interval
|
||||
type IntervalMetrics struct {
|
||||
sync.RWMutex
|
||||
|
||||
// The start time of the interval
|
||||
Interval time.Time
|
||||
|
||||
// Gauges maps the key to the last set value
|
||||
Gauges map[string]float32
|
||||
|
||||
// Points maps the string to the list of emitted values
|
||||
// from EmitKey
|
||||
Points map[string][]float32
|
||||
|
||||
// Counters maps the string key to a sum of the counter
|
||||
// values
|
||||
Counters map[string]*AggregateSample
|
||||
|
||||
// Samples maps the key to an AggregateSample,
|
||||
// which has the rolled up view of a sample
|
||||
Samples map[string]*AggregateSample
|
||||
}
|
||||
|
||||
// NewIntervalMetrics creates a new IntervalMetrics for a given interval
|
||||
func NewIntervalMetrics(intv time.Time) *IntervalMetrics {
|
||||
return &IntervalMetrics{
|
||||
Interval: intv,
|
||||
Gauges: make(map[string]float32),
|
||||
Points: make(map[string][]float32),
|
||||
Counters: make(map[string]*AggregateSample),
|
||||
Samples: make(map[string]*AggregateSample),
|
||||
}
|
||||
}
|
||||
|
||||
// AggregateSample is used to hold aggregate metrics
|
||||
// about a sample
|
||||
type AggregateSample struct {
|
||||
Count int // The count of emitted pairs
|
||||
Sum float64 // The sum of values
|
||||
SumSq float64 // The sum of squared values
|
||||
Min float64 // Minimum value
|
||||
Max float64 // Maximum value
|
||||
}
|
||||
|
||||
// Computes a Stddev of the values
|
||||
func (a *AggregateSample) Stddev() float64 {
|
||||
num := (float64(a.Count) * a.SumSq) - math.Pow(a.Sum, 2)
|
||||
div := float64(a.Count * (a.Count - 1))
|
||||
if div == 0 {
|
||||
return 0
|
||||
}
|
||||
return math.Sqrt(num / div)
|
||||
}
|
||||
|
||||
// Computes a mean of the values
|
||||
func (a *AggregateSample) Mean() float64 {
|
||||
if a.Count == 0 {
|
||||
return 0
|
||||
}
|
||||
return a.Sum / float64(a.Count)
|
||||
}
|
||||
|
||||
// Ingest is used to update a sample
|
||||
func (a *AggregateSample) Ingest(v float64) {
|
||||
a.Count++
|
||||
a.Sum += v
|
||||
a.SumSq += (v * v)
|
||||
if v < a.Min || a.Count == 1 {
|
||||
a.Min = v
|
||||
}
|
||||
if v > a.Max || a.Count == 1 {
|
||||
a.Max = v
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AggregateSample) String() string {
|
||||
if a.Count == 0 {
|
||||
return "Count: 0"
|
||||
} else if a.Stddev() == 0 {
|
||||
return fmt.Sprintf("Count: %d Sum: %0.3f", a.Count, a.Sum)
|
||||
} else {
|
||||
return fmt.Sprintf("Count: %d Min: %0.3f Mean: %0.3f Max: %0.3f Stddev: %0.3f Sum: %0.3f",
|
||||
a.Count, a.Min, a.Mean(), a.Max, a.Stddev(), a.Sum)
|
||||
}
|
||||
}
|
||||
|
||||
// NewInmemSink is used to construct a new in-memory sink.
|
||||
// Uses an aggregation interval and maximum retention period.
|
||||
func NewInmemSink(interval, retain time.Duration) *InmemSink {
|
||||
i := &InmemSink{
|
||||
interval: interval,
|
||||
retain: retain,
|
||||
maxIntervals: int(retain / interval),
|
||||
}
|
||||
i.intervals = make([]*IntervalMetrics, 0, i.maxIntervals)
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *InmemSink) SetGauge(key []string, val float32) {
|
||||
k := i.flattenKey(key)
|
||||
intv := i.getInterval()
|
||||
|
||||
intv.Lock()
|
||||
defer intv.Unlock()
|
||||
intv.Gauges[k] = val
|
||||
}
|
||||
|
||||
func (i *InmemSink) EmitKey(key []string, val float32) {
|
||||
k := i.flattenKey(key)
|
||||
intv := i.getInterval()
|
||||
|
||||
intv.Lock()
|
||||
defer intv.Unlock()
|
||||
vals := intv.Points[k]
|
||||
intv.Points[k] = append(vals, val)
|
||||
}
|
||||
|
||||
func (i *InmemSink) IncrCounter(key []string, val float32) {
|
||||
k := i.flattenKey(key)
|
||||
intv := i.getInterval()
|
||||
|
||||
intv.Lock()
|
||||
defer intv.Unlock()
|
||||
|
||||
agg := intv.Counters[k]
|
||||
if agg == nil {
|
||||
agg = &AggregateSample{}
|
||||
intv.Counters[k] = agg
|
||||
}
|
||||
agg.Ingest(float64(val))
|
||||
}
|
||||
|
||||
func (i *InmemSink) AddSample(key []string, val float32) {
|
||||
k := i.flattenKey(key)
|
||||
intv := i.getInterval()
|
||||
|
||||
intv.Lock()
|
||||
defer intv.Unlock()
|
||||
|
||||
agg := intv.Samples[k]
|
||||
if agg == nil {
|
||||
agg = &AggregateSample{}
|
||||
intv.Samples[k] = agg
|
||||
}
|
||||
agg.Ingest(float64(val))
|
||||
}
|
||||
|
||||
// Data is used to retrieve all the aggregated metrics
|
||||
// Intervals may be in use, and a read lock should be acquired
|
||||
func (i *InmemSink) Data() []*IntervalMetrics {
|
||||
// Get the current interval, forces creation
|
||||
i.getInterval()
|
||||
|
||||
i.intervalLock.RLock()
|
||||
defer i.intervalLock.RUnlock()
|
||||
|
||||
intervals := make([]*IntervalMetrics, len(i.intervals))
|
||||
copy(intervals, i.intervals)
|
||||
return intervals
|
||||
}
|
||||
|
||||
func (i *InmemSink) getExistingInterval(intv time.Time) *IntervalMetrics {
|
||||
i.intervalLock.RLock()
|
||||
defer i.intervalLock.RUnlock()
|
||||
|
||||
n := len(i.intervals)
|
||||
if n > 0 && i.intervals[n-1].Interval == intv {
|
||||
return i.intervals[n-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *InmemSink) createInterval(intv time.Time) *IntervalMetrics {
|
||||
i.intervalLock.Lock()
|
||||
defer i.intervalLock.Unlock()
|
||||
|
||||
// Check for an existing interval
|
||||
n := len(i.intervals)
|
||||
if n > 0 && i.intervals[n-1].Interval == intv {
|
||||
return i.intervals[n-1]
|
||||
}
|
||||
|
||||
// Add the current interval
|
||||
current := NewIntervalMetrics(intv)
|
||||
i.intervals = append(i.intervals, current)
|
||||
n++
|
||||
|
||||
// Truncate the intervals if they are too long
|
||||
if n >= i.maxIntervals {
|
||||
copy(i.intervals[0:], i.intervals[n-i.maxIntervals:])
|
||||
i.intervals = i.intervals[:i.maxIntervals]
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
// getInterval returns the current interval to write to
|
||||
func (i *InmemSink) getInterval() *IntervalMetrics {
|
||||
intv := time.Now().Truncate(i.interval)
|
||||
if m := i.getExistingInterval(intv); m != nil {
|
||||
return m
|
||||
}
|
||||
return i.createInterval(intv)
|
||||
}
|
||||
|
||||
// Flattens the key for formatting, removes spaces
|
||||
func (i *InmemSink) flattenKey(parts []string) string {
|
||||
joined := strings.Join(parts, ".")
|
||||
return strings.Replace(joined, " ", "_", -1)
|
||||
}
|
||||
100
Godeps/_workspace/src/github.com/armon/go-metrics/inmem_signal.go
generated
vendored
Normal file
100
Godeps/_workspace/src/github.com/armon/go-metrics/inmem_signal.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// InmemSignal is used to listen for a given signal, and when received,
|
||||
// to dump the current metrics from the InmemSink to an io.Writer
|
||||
type InmemSignal struct {
|
||||
signal syscall.Signal
|
||||
inm *InmemSink
|
||||
w io.Writer
|
||||
sigCh chan os.Signal
|
||||
|
||||
stop bool
|
||||
stopCh chan struct{}
|
||||
stopLock sync.Mutex
|
||||
}
|
||||
|
||||
// NewInmemSignal creates a new InmemSignal which listens for a given signal,
|
||||
// and dumps the current metrics out to a writer
|
||||
func NewInmemSignal(inmem *InmemSink, sig syscall.Signal, w io.Writer) *InmemSignal {
|
||||
i := &InmemSignal{
|
||||
signal: sig,
|
||||
inm: inmem,
|
||||
w: w,
|
||||
sigCh: make(chan os.Signal, 1),
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
signal.Notify(i.sigCh, sig)
|
||||
go i.run()
|
||||
return i
|
||||
}
|
||||
|
||||
// DefaultInmemSignal returns a new InmemSignal that responds to SIGUSR1
|
||||
// and writes output to stderr. Windows uses SIGBREAK
|
||||
func DefaultInmemSignal(inmem *InmemSink) *InmemSignal {
|
||||
return NewInmemSignal(inmem, DefaultSignal, os.Stderr)
|
||||
}
|
||||
|
||||
// Stop is used to stop the InmemSignal from listening
|
||||
func (i *InmemSignal) Stop() {
|
||||
i.stopLock.Lock()
|
||||
defer i.stopLock.Unlock()
|
||||
|
||||
if i.stop {
|
||||
return
|
||||
}
|
||||
i.stop = true
|
||||
close(i.stopCh)
|
||||
signal.Stop(i.sigCh)
|
||||
}
|
||||
|
||||
// run is a long running routine that handles signals
|
||||
func (i *InmemSignal) run() {
|
||||
for {
|
||||
select {
|
||||
case <-i.sigCh:
|
||||
i.dumpStats()
|
||||
case <-i.stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dumpStats is used to dump the data to output writer
|
||||
func (i *InmemSignal) dumpStats() {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
data := i.inm.Data()
|
||||
// Skip the last period which is still being aggregated
|
||||
for i := 0; i < len(data)-1; i++ {
|
||||
intv := data[i]
|
||||
intv.RLock()
|
||||
for name, val := range intv.Gauges {
|
||||
fmt.Fprintf(buf, "[%v][G] '%s': %0.3f\n", intv.Interval, name, val)
|
||||
}
|
||||
for name, vals := range intv.Points {
|
||||
for _, val := range vals {
|
||||
fmt.Fprintf(buf, "[%v][P] '%s': %0.3f\n", intv.Interval, name, val)
|
||||
}
|
||||
}
|
||||
for name, agg := range intv.Counters {
|
||||
fmt.Fprintf(buf, "[%v][C] '%s': %s\n", intv.Interval, name, agg)
|
||||
}
|
||||
for name, agg := range intv.Samples {
|
||||
fmt.Fprintf(buf, "[%v][S] '%s': %s\n", intv.Interval, name, agg)
|
||||
}
|
||||
intv.RUnlock()
|
||||
}
|
||||
|
||||
// Write out the bytes
|
||||
i.w.Write(buf.Bytes())
|
||||
}
|
||||
46
Godeps/_workspace/src/github.com/armon/go-metrics/inmem_signal_test.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/armon/go-metrics/inmem_signal_test.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInmemSignal(t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
inm := NewInmemSink(10*time.Millisecond, 50*time.Millisecond)
|
||||
sig := NewInmemSignal(inm, syscall.SIGUSR1, buf)
|
||||
defer sig.Stop()
|
||||
|
||||
inm.SetGauge([]string{"foo"}, 42)
|
||||
inm.EmitKey([]string{"bar"}, 42)
|
||||
inm.IncrCounter([]string{"baz"}, 42)
|
||||
inm.AddSample([]string{"wow"}, 42)
|
||||
|
||||
// Wait for period to end
|
||||
time.Sleep(15 * time.Millisecond)
|
||||
|
||||
// Send signal!
|
||||
syscall.Kill(os.Getpid(), syscall.SIGUSR1)
|
||||
|
||||
// Wait for flush
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Check the output
|
||||
out := string(buf.Bytes())
|
||||
if !strings.Contains(out, "[G] 'foo': 42") {
|
||||
t.Fatalf("bad: %v", out)
|
||||
}
|
||||
if !strings.Contains(out, "[P] 'bar': 42") {
|
||||
t.Fatalf("bad: %v", out)
|
||||
}
|
||||
if !strings.Contains(out, "[C] 'baz': Count: 1 Sum: 42") {
|
||||
t.Fatalf("bad: %v", out)
|
||||
}
|
||||
if !strings.Contains(out, "[S] 'wow': Count: 1 Sum: 42") {
|
||||
t.Fatalf("bad: %v", out)
|
||||
}
|
||||
}
|
||||
95
Godeps/_workspace/src/github.com/armon/go-metrics/inmem_test.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/armon/go-metrics/inmem_test.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInmemSink(t *testing.T) {
|
||||
inm := NewInmemSink(10*time.Millisecond, 50*time.Millisecond)
|
||||
|
||||
data := inm.Data()
|
||||
if len(data) != 1 {
|
||||
t.Fatalf("bad: %v", data)
|
||||
}
|
||||
|
||||
// Add data points
|
||||
inm.SetGauge([]string{"foo", "bar"}, 42)
|
||||
inm.EmitKey([]string{"foo", "bar"}, 42)
|
||||
inm.IncrCounter([]string{"foo", "bar"}, 20)
|
||||
inm.IncrCounter([]string{"foo", "bar"}, 22)
|
||||
inm.AddSample([]string{"foo", "bar"}, 20)
|
||||
inm.AddSample([]string{"foo", "bar"}, 22)
|
||||
|
||||
data = inm.Data()
|
||||
if len(data) != 1 {
|
||||
t.Fatalf("bad: %v", data)
|
||||
}
|
||||
|
||||
intvM := data[0]
|
||||
intvM.RLock()
|
||||
|
||||
if time.Now().Sub(intvM.Interval) > 10*time.Millisecond {
|
||||
t.Fatalf("interval too old")
|
||||
}
|
||||
if intvM.Gauges["foo.bar"] != 42 {
|
||||
t.Fatalf("bad val: %v", intvM.Gauges)
|
||||
}
|
||||
if intvM.Points["foo.bar"][0] != 42 {
|
||||
t.Fatalf("bad val: %v", intvM.Points)
|
||||
}
|
||||
|
||||
agg := intvM.Counters["foo.bar"]
|
||||
if agg.Count != 2 {
|
||||
t.Fatalf("bad val: %v", agg)
|
||||
}
|
||||
if agg.Sum != 42 {
|
||||
t.Fatalf("bad val: %v", agg)
|
||||
}
|
||||
if agg.SumSq != 884 {
|
||||
t.Fatalf("bad val: %v", agg)
|
||||
}
|
||||
if agg.Min != 20 {
|
||||
t.Fatalf("bad val: %v", agg)
|
||||
}
|
||||
if agg.Max != 22 {
|
||||
t.Fatalf("bad val: %v", agg)
|
||||
}
|
||||
if agg.Mean() != 21 {
|
||||
t.Fatalf("bad val: %v", agg)
|
||||
}
|
||||
if agg.Stddev() != math.Sqrt(2) {
|
||||
t.Fatalf("bad val: %v", agg)
|
||||
}
|
||||
|
||||
if agg = intvM.Samples["foo.bar"]; agg == nil {
|
||||
t.Fatalf("missing sample")
|
||||
}
|
||||
|
||||
intvM.RUnlock()
|
||||
|
||||
for i := 1; i < 10; i++ {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
inm.SetGauge([]string{"foo", "bar"}, 42)
|
||||
data = inm.Data()
|
||||
if len(data) != min(i+1, 5) {
|
||||
t.Fatalf("bad: %v", data)
|
||||
}
|
||||
}
|
||||
|
||||
// Should not exceed 5 intervals!
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
inm.SetGauge([]string{"foo", "bar"}, 42)
|
||||
data = inm.Data()
|
||||
if len(data) != 5 {
|
||||
t.Fatalf("bad: %v", data)
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
115
Godeps/_workspace/src/github.com/armon/go-metrics/metrics.go
generated
vendored
Normal file
115
Godeps/_workspace/src/github.com/armon/go-metrics/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (m *Metrics) SetGauge(key []string, val float32) {
|
||||
if m.HostName != "" && m.EnableHostname {
|
||||
key = insert(0, m.HostName, key)
|
||||
}
|
||||
if m.EnableTypePrefix {
|
||||
key = insert(0, "gauge", key)
|
||||
}
|
||||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
m.sink.SetGauge(key, val)
|
||||
}
|
||||
|
||||
func (m *Metrics) EmitKey(key []string, val float32) {
|
||||
if m.EnableTypePrefix {
|
||||
key = insert(0, "kv", key)
|
||||
}
|
||||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
m.sink.EmitKey(key, val)
|
||||
}
|
||||
|
||||
func (m *Metrics) IncrCounter(key []string, val float32) {
|
||||
if m.EnableTypePrefix {
|
||||
key = insert(0, "counter", key)
|
||||
}
|
||||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
m.sink.IncrCounter(key, val)
|
||||
}
|
||||
|
||||
func (m *Metrics) AddSample(key []string, val float32) {
|
||||
if m.EnableTypePrefix {
|
||||
key = insert(0, "sample", key)
|
||||
}
|
||||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
m.sink.AddSample(key, val)
|
||||
}
|
||||
|
||||
func (m *Metrics) MeasureSince(key []string, start time.Time) {
|
||||
if m.EnableTypePrefix {
|
||||
key = insert(0, "timer", key)
|
||||
}
|
||||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
now := time.Now()
|
||||
elapsed := now.Sub(start)
|
||||
msec := float32(elapsed.Nanoseconds()) / float32(m.TimerGranularity)
|
||||
m.sink.AddSample(key, msec)
|
||||
}
|
||||
|
||||
// Periodically collects runtime stats to publish
|
||||
func (m *Metrics) collectStats() {
|
||||
for {
|
||||
time.Sleep(m.ProfileInterval)
|
||||
m.emitRuntimeStats()
|
||||
}
|
||||
}
|
||||
|
||||
// Emits various runtime statsitics
|
||||
func (m *Metrics) emitRuntimeStats() {
|
||||
// Export number of Goroutines
|
||||
numRoutines := runtime.NumGoroutine()
|
||||
m.SetGauge([]string{"runtime", "num_goroutines"}, float32(numRoutines))
|
||||
|
||||
// Export memory stats
|
||||
var stats runtime.MemStats
|
||||
runtime.ReadMemStats(&stats)
|
||||
m.SetGauge([]string{"runtime", "alloc_bytes"}, float32(stats.Alloc))
|
||||
m.SetGauge([]string{"runtime", "sys_bytes"}, float32(stats.Sys))
|
||||
m.SetGauge([]string{"runtime", "malloc_count"}, float32(stats.Mallocs))
|
||||
m.SetGauge([]string{"runtime", "free_count"}, float32(stats.Frees))
|
||||
m.SetGauge([]string{"runtime", "heap_objects"}, float32(stats.HeapObjects))
|
||||
m.SetGauge([]string{"runtime", "total_gc_pause_ns"}, float32(stats.PauseTotalNs))
|
||||
m.SetGauge([]string{"runtime", "total_gc_runs"}, float32(stats.NumGC))
|
||||
|
||||
// Export info about the last few GC runs
|
||||
num := stats.NumGC
|
||||
|
||||
// Handle wrap around
|
||||
if num < m.lastNumGC {
|
||||
m.lastNumGC = 0
|
||||
}
|
||||
|
||||
// Ensure we don't scan more than 256
|
||||
if num-m.lastNumGC >= 256 {
|
||||
m.lastNumGC = num - 255
|
||||
}
|
||||
|
||||
for i := m.lastNumGC; i < num; i++ {
|
||||
pause := stats.PauseNs[i%256]
|
||||
m.AddSample([]string{"runtime", "gc_pause_ns"}, float32(pause))
|
||||
}
|
||||
m.lastNumGC = num
|
||||
}
|
||||
|
||||
// Inserts a string value at an index into the slice
|
||||
func insert(i int, v string, s []string) []string {
|
||||
s = append(s, "")
|
||||
copy(s[i+1:], s[i:])
|
||||
s[i] = v
|
||||
return s
|
||||
}
|
||||
262
Godeps/_workspace/src/github.com/armon/go-metrics/metrics_test.go
generated
vendored
Normal file
262
Godeps/_workspace/src/github.com/armon/go-metrics/metrics_test.go
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func mockMetric() (*MockSink, *Metrics) {
|
||||
m := &MockSink{}
|
||||
met := &Metrics{sink: m}
|
||||
return m, met
|
||||
}
|
||||
|
||||
func TestMetrics_SetGauge(t *testing.T) {
|
||||
m, met := mockMetric()
|
||||
met.SetGauge([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.HostName = "test"
|
||||
met.EnableHostname = true
|
||||
met.SetGauge([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "test" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.EnableTypePrefix = true
|
||||
met.SetGauge([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "gauge" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.ServiceName = "service"
|
||||
met.SetGauge([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "service" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetrics_EmitKey(t *testing.T) {
|
||||
m, met := mockMetric()
|
||||
met.EmitKey([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.EnableTypePrefix = true
|
||||
met.EmitKey([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "kv" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.ServiceName = "service"
|
||||
met.EmitKey([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "service" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetrics_IncrCounter(t *testing.T) {
|
||||
m, met := mockMetric()
|
||||
met.IncrCounter([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.EnableTypePrefix = true
|
||||
met.IncrCounter([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "counter" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.ServiceName = "service"
|
||||
met.IncrCounter([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "service" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetrics_AddSample(t *testing.T) {
|
||||
m, met := mockMetric()
|
||||
met.AddSample([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.EnableTypePrefix = true
|
||||
met.AddSample([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "sample" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.ServiceName = "service"
|
||||
met.AddSample([]string{"key"}, float32(1))
|
||||
if m.keys[0][0] != "service" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] != 1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetrics_MeasureSince(t *testing.T) {
|
||||
m, met := mockMetric()
|
||||
met.TimerGranularity = time.Millisecond
|
||||
n := time.Now()
|
||||
met.MeasureSince([]string{"key"}, n)
|
||||
if m.keys[0][0] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] > 0.1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.TimerGranularity = time.Millisecond
|
||||
met.EnableTypePrefix = true
|
||||
met.MeasureSince([]string{"key"}, n)
|
||||
if m.keys[0][0] != "timer" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] > 0.1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
|
||||
m, met = mockMetric()
|
||||
met.TimerGranularity = time.Millisecond
|
||||
met.ServiceName = "service"
|
||||
met.MeasureSince([]string{"key"}, n)
|
||||
if m.keys[0][0] != "service" || m.keys[0][1] != "key" {
|
||||
t.Fatalf("")
|
||||
}
|
||||
if m.vals[0] > 0.1 {
|
||||
t.Fatalf("")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetrics_EmitRuntimeStats(t *testing.T) {
|
||||
runtime.GC()
|
||||
m, met := mockMetric()
|
||||
met.emitRuntimeStats()
|
||||
|
||||
if m.keys[0][0] != "runtime" || m.keys[0][1] != "num_goroutines" {
|
||||
t.Fatalf("bad key %v", m.keys)
|
||||
}
|
||||
if m.vals[0] <= 1 {
|
||||
t.Fatalf("bad val: %v", m.vals)
|
||||
}
|
||||
|
||||
if m.keys[1][0] != "runtime" || m.keys[1][1] != "alloc_bytes" {
|
||||
t.Fatalf("bad key %v", m.keys)
|
||||
}
|
||||
if m.vals[1] <= 40000 {
|
||||
t.Fatalf("bad val: %v", m.vals)
|
||||
}
|
||||
|
||||
if m.keys[2][0] != "runtime" || m.keys[2][1] != "sys_bytes" {
|
||||
t.Fatalf("bad key %v", m.keys)
|
||||
}
|
||||
if m.vals[2] <= 100000 {
|
||||
t.Fatalf("bad val: %v", m.vals)
|
||||
}
|
||||
|
||||
if m.keys[3][0] != "runtime" || m.keys[3][1] != "malloc_count" {
|
||||
t.Fatalf("bad key %v", m.keys)
|
||||
}
|
||||
if m.vals[3] <= 100 {
|
||||
t.Fatalf("bad val: %v", m.vals)
|
||||
}
|
||||
|
||||
if m.keys[4][0] != "runtime" || m.keys[4][1] != "free_count" {
|
||||
t.Fatalf("bad key %v", m.keys)
|
||||
}
|
||||
if m.vals[4] <= 100 {
|
||||
t.Fatalf("bad val: %v", m.vals)
|
||||
}
|
||||
|
||||
if m.keys[5][0] != "runtime" || m.keys[5][1] != "heap_objects" {
|
||||
t.Fatalf("bad key %v", m.keys)
|
||||
}
|
||||
if m.vals[5] <= 100 {
|
||||
t.Fatalf("bad val: %v", m.vals)
|
||||
}
|
||||
|
||||
if m.keys[6][0] != "runtime" || m.keys[6][1] != "total_gc_pause_ns" {
|
||||
t.Fatalf("bad key %v", m.keys)
|
||||
}
|
||||
if m.vals[6] <= 100000 {
|
||||
t.Fatalf("bad val: %v", m.vals)
|
||||
}
|
||||
|
||||
if m.keys[7][0] != "runtime" || m.keys[7][1] != "total_gc_runs" {
|
||||
t.Fatalf("bad key %v", m.keys)
|
||||
}
|
||||
if m.vals[7] <= 1 {
|
||||
t.Fatalf("bad val: %v", m.vals)
|
||||
}
|
||||
|
||||
if m.keys[8][0] != "runtime" || m.keys[8][1] != "gc_pause_ns" {
|
||||
t.Fatalf("bad key %v", m.keys)
|
||||
}
|
||||
if m.vals[8] <= 1000 {
|
||||
t.Fatalf("bad val: %v", m.vals)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsert(t *testing.T) {
|
||||
k := []string{"hi", "bob"}
|
||||
exp := []string{"hi", "there", "bob"}
|
||||
out := insert(1, "there", k)
|
||||
if !reflect.DeepEqual(exp, out) {
|
||||
t.Fatalf("bad insert %v %v", exp, out)
|
||||
}
|
||||
}
|
||||
88
Godeps/_workspace/src/github.com/armon/go-metrics/prometheus/prometheus.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/armon/go-metrics/prometheus/prometheus.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// +build go1.3
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type PrometheusSink struct {
|
||||
mu sync.Mutex
|
||||
gauges map[string]prometheus.Gauge
|
||||
summaries map[string]prometheus.Summary
|
||||
counters map[string]prometheus.Counter
|
||||
}
|
||||
|
||||
func NewPrometheusSink() (*PrometheusSink, error) {
|
||||
return &PrometheusSink{
|
||||
gauges: make(map[string]prometheus.Gauge),
|
||||
summaries: make(map[string]prometheus.Summary),
|
||||
counters: make(map[string]prometheus.Counter),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PrometheusSink) flattenKey(parts []string) string {
|
||||
joined := strings.Join(parts, "_")
|
||||
joined = strings.Replace(joined, " ", "_", -1)
|
||||
joined = strings.Replace(joined, ".", "_", -1)
|
||||
joined = strings.Replace(joined, "-", "_", -1)
|
||||
return joined
|
||||
}
|
||||
|
||||
func (p *PrometheusSink) SetGauge(parts []string, val float32) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
key := p.flattenKey(parts)
|
||||
g, ok := p.gauges[key]
|
||||
if !ok {
|
||||
g = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: key,
|
||||
Help: key,
|
||||
})
|
||||
prometheus.MustRegister(g)
|
||||
p.gauges[key] = g
|
||||
}
|
||||
g.Set(float64(val))
|
||||
}
|
||||
|
||||
func (p *PrometheusSink) AddSample(parts []string, val float32) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
key := p.flattenKey(parts)
|
||||
g, ok := p.summaries[key]
|
||||
if !ok {
|
||||
g = prometheus.NewSummary(prometheus.SummaryOpts{
|
||||
Name: key,
|
||||
Help: key,
|
||||
MaxAge: 10 * time.Second,
|
||||
})
|
||||
prometheus.MustRegister(g)
|
||||
p.summaries[key] = g
|
||||
}
|
||||
g.Observe(float64(val))
|
||||
}
|
||||
|
||||
// EmitKey is not implemented. Prometheus doesn’t offer a type for which an
|
||||
// arbitrary number of values is retained, as Prometheus works with a pull
|
||||
// model, rather than a push model.
|
||||
func (p *PrometheusSink) EmitKey(key []string, val float32) {
|
||||
}
|
||||
|
||||
func (p *PrometheusSink) IncrCounter(parts []string, val float32) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
key := p.flattenKey(parts)
|
||||
g, ok := p.counters[key]
|
||||
if !ok {
|
||||
g = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: key,
|
||||
Help: key,
|
||||
})
|
||||
prometheus.MustRegister(g)
|
||||
p.counters[key] = g
|
||||
}
|
||||
g.Add(float64(val))
|
||||
}
|
||||
52
Godeps/_workspace/src/github.com/armon/go-metrics/sink.go
generated
vendored
Normal file
52
Godeps/_workspace/src/github.com/armon/go-metrics/sink.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package metrics
|
||||
|
||||
// The MetricSink interface is used to transmit metrics information
|
||||
// to an external system
|
||||
type MetricSink interface {
|
||||
// A Gauge should retain the last value it is set to
|
||||
SetGauge(key []string, val float32)
|
||||
|
||||
// Should emit a Key/Value pair for each call
|
||||
EmitKey(key []string, val float32)
|
||||
|
||||
// Counters should accumulate values
|
||||
IncrCounter(key []string, val float32)
|
||||
|
||||
// Samples are for timing information, where quantiles are used
|
||||
AddSample(key []string, val float32)
|
||||
}
|
||||
|
||||
// BlackholeSink is used to just blackhole messages
|
||||
type BlackholeSink struct{}
|
||||
|
||||
func (*BlackholeSink) SetGauge(key []string, val float32) {}
|
||||
func (*BlackholeSink) EmitKey(key []string, val float32) {}
|
||||
func (*BlackholeSink) IncrCounter(key []string, val float32) {}
|
||||
func (*BlackholeSink) AddSample(key []string, val float32) {}
|
||||
|
||||
// FanoutSink is used to sink to fanout values to multiple sinks
|
||||
type FanoutSink []MetricSink
|
||||
|
||||
func (fh FanoutSink) SetGauge(key []string, val float32) {
|
||||
for _, s := range fh {
|
||||
s.SetGauge(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (fh FanoutSink) EmitKey(key []string, val float32) {
|
||||
for _, s := range fh {
|
||||
s.EmitKey(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (fh FanoutSink) IncrCounter(key []string, val float32) {
|
||||
for _, s := range fh {
|
||||
s.IncrCounter(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (fh FanoutSink) AddSample(key []string, val float32) {
|
||||
for _, s := range fh {
|
||||
s.AddSample(key, val)
|
||||
}
|
||||
}
|
||||
120
Godeps/_workspace/src/github.com/armon/go-metrics/sink_test.go
generated
vendored
Normal file
120
Godeps/_workspace/src/github.com/armon/go-metrics/sink_test.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type MockSink struct {
|
||||
keys [][]string
|
||||
vals []float32
|
||||
}
|
||||
|
||||
func (m *MockSink) SetGauge(key []string, val float32) {
|
||||
m.keys = append(m.keys, key)
|
||||
m.vals = append(m.vals, val)
|
||||
}
|
||||
func (m *MockSink) EmitKey(key []string, val float32) {
|
||||
m.keys = append(m.keys, key)
|
||||
m.vals = append(m.vals, val)
|
||||
}
|
||||
func (m *MockSink) IncrCounter(key []string, val float32) {
|
||||
m.keys = append(m.keys, key)
|
||||
m.vals = append(m.vals, val)
|
||||
}
|
||||
func (m *MockSink) AddSample(key []string, val float32) {
|
||||
m.keys = append(m.keys, key)
|
||||
m.vals = append(m.vals, val)
|
||||
}
|
||||
|
||||
func TestFanoutSink_Gauge(t *testing.T) {
|
||||
m1 := &MockSink{}
|
||||
m2 := &MockSink{}
|
||||
fh := &FanoutSink{m1, m2}
|
||||
|
||||
k := []string{"test"}
|
||||
v := float32(42.0)
|
||||
fh.SetGauge(k, v)
|
||||
|
||||
if !reflect.DeepEqual(m1.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m2.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m1.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m2.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFanoutSink_Key(t *testing.T) {
|
||||
m1 := &MockSink{}
|
||||
m2 := &MockSink{}
|
||||
fh := &FanoutSink{m1, m2}
|
||||
|
||||
k := []string{"test"}
|
||||
v := float32(42.0)
|
||||
fh.EmitKey(k, v)
|
||||
|
||||
if !reflect.DeepEqual(m1.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m2.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m1.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m2.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFanoutSink_Counter(t *testing.T) {
|
||||
m1 := &MockSink{}
|
||||
m2 := &MockSink{}
|
||||
fh := &FanoutSink{m1, m2}
|
||||
|
||||
k := []string{"test"}
|
||||
v := float32(42.0)
|
||||
fh.IncrCounter(k, v)
|
||||
|
||||
if !reflect.DeepEqual(m1.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m2.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m1.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m2.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFanoutSink_Sample(t *testing.T) {
|
||||
m1 := &MockSink{}
|
||||
m2 := &MockSink{}
|
||||
fh := &FanoutSink{m1, m2}
|
||||
|
||||
k := []string{"test"}
|
||||
v := float32(42.0)
|
||||
fh.AddSample(k, v)
|
||||
|
||||
if !reflect.DeepEqual(m1.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m2.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m1.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m2.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
}
|
||||
95
Godeps/_workspace/src/github.com/armon/go-metrics/start.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/armon/go-metrics/start.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config is used to configure metrics settings
|
||||
type Config struct {
|
||||
ServiceName string // Prefixed with keys to seperate services
|
||||
HostName string // Hostname to use. If not provided and EnableHostname, it will be os.Hostname
|
||||
EnableHostname bool // Enable prefixing gauge values with hostname
|
||||
EnableRuntimeMetrics bool // Enables profiling of runtime metrics (GC, Goroutines, Memory)
|
||||
EnableTypePrefix bool // Prefixes key with a type ("counter", "gauge", "timer")
|
||||
TimerGranularity time.Duration // Granularity of timers.
|
||||
ProfileInterval time.Duration // Interval to profile runtime metrics
|
||||
}
|
||||
|
||||
// Metrics represents an instance of a metrics sink that can
|
||||
// be used to emit
|
||||
type Metrics struct {
|
||||
Config
|
||||
lastNumGC uint32
|
||||
sink MetricSink
|
||||
}
|
||||
|
||||
// Shared global metrics instance
|
||||
var globalMetrics *Metrics
|
||||
|
||||
func init() {
|
||||
// Initialize to a blackhole sink to avoid errors
|
||||
globalMetrics = &Metrics{sink: &BlackholeSink{}}
|
||||
}
|
||||
|
||||
// DefaultConfig provides a sane default configuration
|
||||
func DefaultConfig(serviceName string) *Config {
|
||||
c := &Config{
|
||||
ServiceName: serviceName, // Use client provided service
|
||||
HostName: "",
|
||||
EnableHostname: true, // Enable hostname prefix
|
||||
EnableRuntimeMetrics: true, // Enable runtime profiling
|
||||
EnableTypePrefix: false, // Disable type prefix
|
||||
TimerGranularity: time.Millisecond, // Timers are in milliseconds
|
||||
ProfileInterval: time.Second, // Poll runtime every second
|
||||
}
|
||||
|
||||
// Try to get the hostname
|
||||
name, _ := os.Hostname()
|
||||
c.HostName = name
|
||||
return c
|
||||
}
|
||||
|
||||
// New is used to create a new instance of Metrics
|
||||
func New(conf *Config, sink MetricSink) (*Metrics, error) {
|
||||
met := &Metrics{}
|
||||
met.Config = *conf
|
||||
met.sink = sink
|
||||
|
||||
// Start the runtime collector
|
||||
if conf.EnableRuntimeMetrics {
|
||||
go met.collectStats()
|
||||
}
|
||||
return met, nil
|
||||
}
|
||||
|
||||
// NewGlobal is the same as New, but it assigns the metrics object to be
|
||||
// used globally as well as returning it.
|
||||
func NewGlobal(conf *Config, sink MetricSink) (*Metrics, error) {
|
||||
metrics, err := New(conf, sink)
|
||||
if err == nil {
|
||||
globalMetrics = metrics
|
||||
}
|
||||
return metrics, err
|
||||
}
|
||||
|
||||
// Proxy all the methods to the globalMetrics instance
|
||||
func SetGauge(key []string, val float32) {
|
||||
globalMetrics.SetGauge(key, val)
|
||||
}
|
||||
|
||||
func EmitKey(key []string, val float32) {
|
||||
globalMetrics.EmitKey(key, val)
|
||||
}
|
||||
|
||||
func IncrCounter(key []string, val float32) {
|
||||
globalMetrics.IncrCounter(key, val)
|
||||
}
|
||||
|
||||
func AddSample(key []string, val float32) {
|
||||
globalMetrics.AddSample(key, val)
|
||||
}
|
||||
|
||||
func MeasureSince(key []string, start time.Time) {
|
||||
globalMetrics.MeasureSince(key, start)
|
||||
}
|
||||
110
Godeps/_workspace/src/github.com/armon/go-metrics/start_test.go
generated
vendored
Normal file
110
Godeps/_workspace/src/github.com/armon/go-metrics/start_test.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
conf := DefaultConfig("service")
|
||||
if conf.ServiceName != "service" {
|
||||
t.Fatalf("Bad name")
|
||||
}
|
||||
if conf.HostName == "" {
|
||||
t.Fatalf("missing hostname")
|
||||
}
|
||||
if !conf.EnableHostname || !conf.EnableRuntimeMetrics {
|
||||
t.Fatalf("expect true")
|
||||
}
|
||||
if conf.EnableTypePrefix {
|
||||
t.Fatalf("expect false")
|
||||
}
|
||||
if conf.TimerGranularity != time.Millisecond {
|
||||
t.Fatalf("bad granularity")
|
||||
}
|
||||
if conf.ProfileInterval != time.Second {
|
||||
t.Fatalf("bad interval")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GlobalMetrics_SetGauge(t *testing.T) {
|
||||
m := &MockSink{}
|
||||
globalMetrics = &Metrics{sink: m}
|
||||
|
||||
k := []string{"test"}
|
||||
v := float32(42.0)
|
||||
SetGauge(k, v)
|
||||
|
||||
if !reflect.DeepEqual(m.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GlobalMetrics_EmitKey(t *testing.T) {
|
||||
m := &MockSink{}
|
||||
globalMetrics = &Metrics{sink: m}
|
||||
|
||||
k := []string{"test"}
|
||||
v := float32(42.0)
|
||||
EmitKey(k, v)
|
||||
|
||||
if !reflect.DeepEqual(m.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GlobalMetrics_IncrCounter(t *testing.T) {
|
||||
m := &MockSink{}
|
||||
globalMetrics = &Metrics{sink: m}
|
||||
|
||||
k := []string{"test"}
|
||||
v := float32(42.0)
|
||||
IncrCounter(k, v)
|
||||
|
||||
if !reflect.DeepEqual(m.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GlobalMetrics_AddSample(t *testing.T) {
|
||||
m := &MockSink{}
|
||||
globalMetrics = &Metrics{sink: m}
|
||||
|
||||
k := []string{"test"}
|
||||
v := float32(42.0)
|
||||
AddSample(k, v)
|
||||
|
||||
if !reflect.DeepEqual(m.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m.vals[0], v) {
|
||||
t.Fatalf("val not equal")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GlobalMetrics_MeasureSince(t *testing.T) {
|
||||
m := &MockSink{}
|
||||
globalMetrics = &Metrics{sink: m}
|
||||
globalMetrics.TimerGranularity = time.Millisecond
|
||||
|
||||
k := []string{"test"}
|
||||
now := time.Now()
|
||||
MeasureSince(k, now)
|
||||
|
||||
if !reflect.DeepEqual(m.keys[0], k) {
|
||||
t.Fatalf("key not equal")
|
||||
}
|
||||
if m.vals[0] > 0.1 {
|
||||
t.Fatalf("val too large %v", m.vals[0])
|
||||
}
|
||||
}
|
||||
154
Godeps/_workspace/src/github.com/armon/go-metrics/statsd.go
generated
vendored
Normal file
154
Godeps/_workspace/src/github.com/armon/go-metrics/statsd.go
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// statsdMaxLen is the maximum size of a packet
|
||||
// to send to statsd
|
||||
statsdMaxLen = 1400
|
||||
)
|
||||
|
||||
// StatsdSink provides a MetricSink that can be used
|
||||
// with a statsite or statsd metrics server. It uses
|
||||
// only UDP packets, while StatsiteSink uses TCP.
|
||||
type StatsdSink struct {
|
||||
addr string
|
||||
metricQueue chan string
|
||||
}
|
||||
|
||||
// NewStatsdSink is used to create a new StatsdSink
|
||||
func NewStatsdSink(addr string) (*StatsdSink, error) {
|
||||
s := &StatsdSink{
|
||||
addr: addr,
|
||||
metricQueue: make(chan string, 4096),
|
||||
}
|
||||
go s.flushMetrics()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Close is used to stop flushing to statsd
|
||||
func (s *StatsdSink) Shutdown() {
|
||||
close(s.metricQueue)
|
||||
}
|
||||
|
||||
func (s *StatsdSink) SetGauge(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsdSink) EmitKey(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|kv\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsdSink) IncrCounter(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsdSink) AddSample(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val))
|
||||
}
|
||||
|
||||
// Flattens the key for formatting, removes spaces
|
||||
func (s *StatsdSink) flattenKey(parts []string) string {
|
||||
joined := strings.Join(parts, ".")
|
||||
return strings.Map(func(r rune) rune {
|
||||
switch r {
|
||||
case ':':
|
||||
fallthrough
|
||||
case ' ':
|
||||
return '_'
|
||||
default:
|
||||
return r
|
||||
}
|
||||
}, joined)
|
||||
}
|
||||
|
||||
// Does a non-blocking push to the metrics queue
|
||||
func (s *StatsdSink) pushMetric(m string) {
|
||||
select {
|
||||
case s.metricQueue <- m:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Flushes metrics
|
||||
func (s *StatsdSink) flushMetrics() {
|
||||
var sock net.Conn
|
||||
var err error
|
||||
var wait <-chan time.Time
|
||||
ticker := time.NewTicker(flushInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
CONNECT:
|
||||
// Create a buffer
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
// Attempt to connect
|
||||
sock, err = net.Dial("udp", s.addr)
|
||||
if err != nil {
|
||||
log.Printf("[ERR] Error connecting to statsd! Err: %s", err)
|
||||
goto WAIT
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case metric, ok := <-s.metricQueue:
|
||||
// Get a metric from the queue
|
||||
if !ok {
|
||||
goto QUIT
|
||||
}
|
||||
|
||||
// Check if this would overflow the packet size
|
||||
if len(metric)+buf.Len() > statsdMaxLen {
|
||||
_, err := sock.Write(buf.Bytes())
|
||||
buf.Reset()
|
||||
if err != nil {
|
||||
log.Printf("[ERR] Error writing to statsd! Err: %s", err)
|
||||
goto WAIT
|
||||
}
|
||||
}
|
||||
|
||||
// Append to the buffer
|
||||
buf.WriteString(metric)
|
||||
|
||||
case <-ticker.C:
|
||||
if buf.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := sock.Write(buf.Bytes())
|
||||
buf.Reset()
|
||||
if err != nil {
|
||||
log.Printf("[ERR] Error flushing to statsd! Err: %s", err)
|
||||
goto WAIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WAIT:
|
||||
// Wait for a while
|
||||
wait = time.After(time.Duration(5) * time.Second)
|
||||
for {
|
||||
select {
|
||||
// Dequeue the messages to avoid backlog
|
||||
case _, ok := <-s.metricQueue:
|
||||
if !ok {
|
||||
goto QUIT
|
||||
}
|
||||
case <-wait:
|
||||
goto CONNECT
|
||||
}
|
||||
}
|
||||
QUIT:
|
||||
s.metricQueue = nil
|
||||
}
|
||||
105
Godeps/_workspace/src/github.com/armon/go-metrics/statsd_test.go
generated
vendored
Normal file
105
Godeps/_workspace/src/github.com/armon/go-metrics/statsd_test.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStatsd_Flatten(t *testing.T) {
|
||||
s := &StatsdSink{}
|
||||
flat := s.flattenKey([]string{"a", "b", "c", "d"})
|
||||
if flat != "a.b.c.d" {
|
||||
t.Fatalf("Bad flat")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsd_PushFullQueue(t *testing.T) {
|
||||
q := make(chan string, 1)
|
||||
q <- "full"
|
||||
|
||||
s := &StatsdSink{metricQueue: q}
|
||||
s.pushMetric("omit")
|
||||
|
||||
out := <-q
|
||||
if out != "full" {
|
||||
t.Fatalf("bad val %v", out)
|
||||
}
|
||||
|
||||
select {
|
||||
case v := <-q:
|
||||
t.Fatalf("bad val %v", v)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsd_Conn(t *testing.T) {
|
||||
addr := "127.0.0.1:7524"
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
list, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 7524})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer list.Close()
|
||||
buf := make([]byte, 1500)
|
||||
n, err := list.Read(buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
buf = buf[:n]
|
||||
reader := bufio.NewReader(bytes.NewReader(buf))
|
||||
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err %s", err)
|
||||
}
|
||||
if line != "gauge.val:1.000000|g\n" {
|
||||
t.Fatalf("bad line %s", line)
|
||||
}
|
||||
|
||||
line, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err %s", err)
|
||||
}
|
||||
if line != "key.other:2.000000|kv\n" {
|
||||
t.Fatalf("bad line %s", line)
|
||||
}
|
||||
|
||||
line, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err %s", err)
|
||||
}
|
||||
if line != "counter.me:3.000000|c\n" {
|
||||
t.Fatalf("bad line %s", line)
|
||||
}
|
||||
|
||||
line, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err %s", err)
|
||||
}
|
||||
if line != "sample.slow_thingy:4.000000|ms\n" {
|
||||
t.Fatalf("bad line %s", line)
|
||||
}
|
||||
|
||||
done <- true
|
||||
}()
|
||||
s, err := NewStatsdSink(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("bad error")
|
||||
}
|
||||
|
||||
s.SetGauge([]string{"gauge", "val"}, float32(1))
|
||||
s.EmitKey([]string{"key", "other"}, float32(2))
|
||||
s.IncrCounter([]string{"counter", "me"}, float32(3))
|
||||
s.AddSample([]string{"sample", "slow thingy"}, float32(4))
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
s.Shutdown()
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("timeout")
|
||||
}
|
||||
}
|
||||
142
Godeps/_workspace/src/github.com/armon/go-metrics/statsite.go
generated
vendored
Normal file
142
Godeps/_workspace/src/github.com/armon/go-metrics/statsite.go
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// We force flush the statsite metrics after this period of
|
||||
// inactivity. Prevents stats from getting stuck in a buffer
|
||||
// forever.
|
||||
flushInterval = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
// StatsiteSink provides a MetricSink that can be used with a
|
||||
// statsite metrics server
|
||||
type StatsiteSink struct {
|
||||
addr string
|
||||
metricQueue chan string
|
||||
}
|
||||
|
||||
// NewStatsiteSink is used to create a new StatsiteSink
|
||||
func NewStatsiteSink(addr string) (*StatsiteSink, error) {
|
||||
s := &StatsiteSink{
|
||||
addr: addr,
|
||||
metricQueue: make(chan string, 4096),
|
||||
}
|
||||
go s.flushMetrics()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Close is used to stop flushing to statsite
|
||||
func (s *StatsiteSink) Shutdown() {
|
||||
close(s.metricQueue)
|
||||
}
|
||||
|
||||
func (s *StatsiteSink) SetGauge(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsiteSink) EmitKey(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|kv\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsiteSink) IncrCounter(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsiteSink) AddSample(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val))
|
||||
}
|
||||
|
||||
// Flattens the key for formatting, removes spaces
|
||||
func (s *StatsiteSink) flattenKey(parts []string) string {
|
||||
joined := strings.Join(parts, ".")
|
||||
return strings.Map(func(r rune) rune {
|
||||
switch r {
|
||||
case ':':
|
||||
fallthrough
|
||||
case ' ':
|
||||
return '_'
|
||||
default:
|
||||
return r
|
||||
}
|
||||
}, joined)
|
||||
}
|
||||
|
||||
// Does a non-blocking push to the metrics queue
|
||||
func (s *StatsiteSink) pushMetric(m string) {
|
||||
select {
|
||||
case s.metricQueue <- m:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Flushes metrics
|
||||
func (s *StatsiteSink) flushMetrics() {
|
||||
var sock net.Conn
|
||||
var err error
|
||||
var wait <-chan time.Time
|
||||
var buffered *bufio.Writer
|
||||
ticker := time.NewTicker(flushInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
CONNECT:
|
||||
// Attempt to connect
|
||||
sock, err = net.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.Printf("[ERR] Error connecting to statsite! Err: %s", err)
|
||||
goto WAIT
|
||||
}
|
||||
|
||||
// Create a buffered writer
|
||||
buffered = bufio.NewWriter(sock)
|
||||
|
||||
for {
|
||||
select {
|
||||
case metric, ok := <-s.metricQueue:
|
||||
// Get a metric from the queue
|
||||
if !ok {
|
||||
goto QUIT
|
||||
}
|
||||
|
||||
// Try to send to statsite
|
||||
_, err := buffered.Write([]byte(metric))
|
||||
if err != nil {
|
||||
log.Printf("[ERR] Error writing to statsite! Err: %s", err)
|
||||
goto WAIT
|
||||
}
|
||||
case <-ticker.C:
|
||||
if err := buffered.Flush(); err != nil {
|
||||
log.Printf("[ERR] Error flushing to statsite! Err: %s", err)
|
||||
goto WAIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WAIT:
|
||||
// Wait for a while
|
||||
wait = time.After(time.Duration(5) * time.Second)
|
||||
for {
|
||||
select {
|
||||
// Dequeue the messages to avoid backlog
|
||||
case _, ok := <-s.metricQueue:
|
||||
if !ok {
|
||||
goto QUIT
|
||||
}
|
||||
case <-wait:
|
||||
goto CONNECT
|
||||
}
|
||||
}
|
||||
QUIT:
|
||||
s.metricQueue = nil
|
||||
}
|
||||
101
Godeps/_workspace/src/github.com/armon/go-metrics/statsite_test.go
generated
vendored
Normal file
101
Godeps/_workspace/src/github.com/armon/go-metrics/statsite_test.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func acceptConn(addr string) net.Conn {
|
||||
ln, _ := net.Listen("tcp", addr)
|
||||
conn, _ := ln.Accept()
|
||||
return conn
|
||||
}
|
||||
|
||||
func TestStatsite_Flatten(t *testing.T) {
|
||||
s := &StatsiteSink{}
|
||||
flat := s.flattenKey([]string{"a", "b", "c", "d"})
|
||||
if flat != "a.b.c.d" {
|
||||
t.Fatalf("Bad flat")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsite_PushFullQueue(t *testing.T) {
|
||||
q := make(chan string, 1)
|
||||
q <- "full"
|
||||
|
||||
s := &StatsiteSink{metricQueue: q}
|
||||
s.pushMetric("omit")
|
||||
|
||||
out := <-q
|
||||
if out != "full" {
|
||||
t.Fatalf("bad val %v", out)
|
||||
}
|
||||
|
||||
select {
|
||||
case v := <-q:
|
||||
t.Fatalf("bad val %v", v)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsite_Conn(t *testing.T) {
|
||||
addr := "localhost:7523"
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
conn := acceptConn(addr)
|
||||
reader := bufio.NewReader(conn)
|
||||
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err %s", err)
|
||||
}
|
||||
if line != "gauge.val:1.000000|g\n" {
|
||||
t.Fatalf("bad line %s", line)
|
||||
}
|
||||
|
||||
line, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err %s", err)
|
||||
}
|
||||
if line != "key.other:2.000000|kv\n" {
|
||||
t.Fatalf("bad line %s", line)
|
||||
}
|
||||
|
||||
line, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err %s", err)
|
||||
}
|
||||
if line != "counter.me:3.000000|c\n" {
|
||||
t.Fatalf("bad line %s", line)
|
||||
}
|
||||
|
||||
line, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err %s", err)
|
||||
}
|
||||
if line != "sample.slow_thingy:4.000000|ms\n" {
|
||||
t.Fatalf("bad line %s", line)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
done <- true
|
||||
}()
|
||||
s, err := NewStatsiteSink(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("bad error")
|
||||
}
|
||||
|
||||
s.SetGauge([]string{"gauge", "val"}, float32(1))
|
||||
s.EmitKey([]string{"key", "other"}, float32(2))
|
||||
s.IncrCounter([]string{"counter", "me"}, float32(3))
|
||||
s.AddSample([]string{"sample", "slow thingy"}, float32(4))
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
s.Shutdown()
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("timeout")
|
||||
}
|
||||
}
|
||||
22
Godeps/_workspace/src/github.com/armon/go-radix/.gitignore
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/armon/go-radix/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
3
Godeps/_workspace/src/github.com/armon/go-radix/.travis.yml
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/armon/go-radix/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
20
Godeps/_workspace/src/github.com/armon/go-radix/LICENSE
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/armon/go-radix/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Armon Dadgar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
36
Godeps/_workspace/src/github.com/armon/go-radix/README.md
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/armon/go-radix/README.md
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
go-radix [](https://travis-ci.org/armon/go-radix)
|
||||
=========
|
||||
|
||||
Provides the `radix` package that implements a [radix tree](http://en.wikipedia.org/wiki/Radix_tree).
|
||||
The package only provides a single `Tree` implementation, optimized for sparse nodes.
|
||||
|
||||
As a radix tree, it provides the following:
|
||||
* O(k) operations. In many cases, this can be faster than a hash table since
|
||||
the hash function is an O(k) operation, and hash tables have very poor cache locality.
|
||||
* Minimum / Maximum value lookups
|
||||
* Ordered iteration
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
The full documentation is available on [Godoc](http://godoc.org/github.com/armon/go-radix).
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
Below is a simple example of usage
|
||||
|
||||
```go
|
||||
// Create a tree
|
||||
r := radix.New()
|
||||
r.Insert("foo", 1)
|
||||
r.Insert("bar", 2)
|
||||
r.Insert("foobar", 2)
|
||||
|
||||
// Find the longest prefix match
|
||||
m, _, _ := r.LongestPrefix("foozip")
|
||||
if m != "foo" {
|
||||
panic("should be foo")
|
||||
}
|
||||
```
|
||||
|
||||
498
Godeps/_workspace/src/github.com/armon/go-radix/radix.go
generated
vendored
Normal file
498
Godeps/_workspace/src/github.com/armon/go-radix/radix.go
generated
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
package radix
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WalkFn is used when walking the tree. Takes a
|
||||
// key and value, returning if iteration should
|
||||
// be terminated.
|
||||
type WalkFn func(s string, v interface{}) bool
|
||||
|
||||
// leafNode is used to represent a value
|
||||
type leafNode struct {
|
||||
key string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
// edge is used to represent an edge node
|
||||
type edge struct {
|
||||
label byte
|
||||
node *node
|
||||
}
|
||||
|
||||
type node struct {
|
||||
// leaf is used to store possible leaf
|
||||
leaf *leafNode
|
||||
|
||||
// prefix is the common prefix we ignore
|
||||
prefix string
|
||||
|
||||
// Edges should be stored in-order for iteration.
|
||||
// We avoid a fully materialized slice to save memory,
|
||||
// since in most cases we expect to be sparse
|
||||
edges edges
|
||||
}
|
||||
|
||||
func (n *node) isLeaf() bool {
|
||||
return n.leaf != nil
|
||||
}
|
||||
|
||||
func (n *node) addEdge(e edge) {
|
||||
n.edges = append(n.edges, e)
|
||||
n.edges.Sort()
|
||||
}
|
||||
|
||||
func (n *node) replaceEdge(e edge) {
|
||||
num := len(n.edges)
|
||||
idx := sort.Search(num, func(i int) bool {
|
||||
return n.edges[i].label >= e.label
|
||||
})
|
||||
if idx < num && n.edges[idx].label == e.label {
|
||||
n.edges[idx].node = e.node
|
||||
return
|
||||
}
|
||||
panic("replacing missing edge")
|
||||
}
|
||||
|
||||
func (n *node) getEdge(label byte) *node {
|
||||
num := len(n.edges)
|
||||
idx := sort.Search(num, func(i int) bool {
|
||||
return n.edges[i].label >= label
|
||||
})
|
||||
if idx < num && n.edges[idx].label == label {
|
||||
return n.edges[idx].node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) delEdge(label byte) {
|
||||
num := len(n.edges)
|
||||
idx := sort.Search(num, func(i int) bool {
|
||||
return n.edges[i].label >= label
|
||||
})
|
||||
if idx < num && n.edges[idx].label == label {
|
||||
copy(n.edges[idx:], n.edges[idx+1:])
|
||||
n.edges[len(n.edges)-1] = edge{}
|
||||
n.edges = n.edges[:len(n.edges)-1]
|
||||
}
|
||||
}
|
||||
|
||||
type edges []edge
|
||||
|
||||
func (e edges) Len() int {
|
||||
return len(e)
|
||||
}
|
||||
|
||||
func (e edges) Less(i, j int) bool {
|
||||
return e[i].label < e[j].label
|
||||
}
|
||||
|
||||
func (e edges) Swap(i, j int) {
|
||||
e[i], e[j] = e[j], e[i]
|
||||
}
|
||||
|
||||
func (e edges) Sort() {
|
||||
sort.Sort(e)
|
||||
}
|
||||
|
||||
// Tree implements a radix tree. This can be treated as a
|
||||
// Dictionary abstract data type. The main advantage over
|
||||
// a standard hash map is prefix-based lookups and
|
||||
// ordered iteration,
|
||||
type Tree struct {
|
||||
root *node
|
||||
size int
|
||||
}
|
||||
|
||||
// New returns an empty Tree
|
||||
func New() *Tree {
|
||||
return NewFromMap(nil)
|
||||
}
|
||||
|
||||
// NewFromMap returns a new tree containing the keys
|
||||
// from an existing map
|
||||
func NewFromMap(m map[string]interface{}) *Tree {
|
||||
t := &Tree{root: &node{}}
|
||||
for k, v := range m {
|
||||
t.Insert(k, v)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Len is used to return the number of elements in the tree
|
||||
func (t *Tree) Len() int {
|
||||
return t.size
|
||||
}
|
||||
|
||||
// longestPrefix finds the length of the shared prefix
|
||||
// of two strings
|
||||
func longestPrefix(k1, k2 string) int {
|
||||
max := len(k1)
|
||||
if l := len(k2); l < max {
|
||||
max = l
|
||||
}
|
||||
var i int
|
||||
for i = 0; i < max; i++ {
|
||||
if k1[i] != k2[i] {
|
||||
break
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// Insert is used to add a newentry or update
|
||||
// an existing entry. Returns if updated.
|
||||
func (t *Tree) Insert(s string, v interface{}) (interface{}, bool) {
|
||||
var parent *node
|
||||
n := t.root
|
||||
search := s
|
||||
for {
|
||||
// Handle key exhaution
|
||||
if len(search) == 0 {
|
||||
if n.isLeaf() {
|
||||
old := n.leaf.val
|
||||
n.leaf.val = v
|
||||
return old, true
|
||||
} else {
|
||||
n.leaf = &leafNode{
|
||||
key: s,
|
||||
val: v,
|
||||
}
|
||||
t.size++
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the edge
|
||||
parent = n
|
||||
n = n.getEdge(search[0])
|
||||
|
||||
// No edge, create one
|
||||
if n == nil {
|
||||
e := edge{
|
||||
label: search[0],
|
||||
node: &node{
|
||||
leaf: &leafNode{
|
||||
key: s,
|
||||
val: v,
|
||||
},
|
||||
prefix: search,
|
||||
},
|
||||
}
|
||||
parent.addEdge(e)
|
||||
t.size++
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Determine longest prefix of the search key on match
|
||||
commonPrefix := longestPrefix(search, n.prefix)
|
||||
if commonPrefix == len(n.prefix) {
|
||||
search = search[commonPrefix:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Split the node
|
||||
t.size++
|
||||
child := &node{
|
||||
prefix: search[:commonPrefix],
|
||||
}
|
||||
parent.replaceEdge(edge{
|
||||
label: search[0],
|
||||
node: child,
|
||||
})
|
||||
|
||||
// Restore the existing node
|
||||
child.addEdge(edge{
|
||||
label: n.prefix[commonPrefix],
|
||||
node: n,
|
||||
})
|
||||
n.prefix = n.prefix[commonPrefix:]
|
||||
|
||||
// Create a new leaf node
|
||||
leaf := &leafNode{
|
||||
key: s,
|
||||
val: v,
|
||||
}
|
||||
|
||||
// If the new key is a subset, add to to this node
|
||||
search = search[commonPrefix:]
|
||||
if len(search) == 0 {
|
||||
child.leaf = leaf
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Create a new edge for the node
|
||||
child.addEdge(edge{
|
||||
label: search[0],
|
||||
node: &node{
|
||||
leaf: leaf,
|
||||
prefix: search,
|
||||
},
|
||||
})
|
||||
return nil, false
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Delete is used to delete a key, returning the previous
|
||||
// value and if it was deleted
|
||||
func (t *Tree) Delete(s string) (interface{}, bool) {
|
||||
var parent *node
|
||||
var label byte
|
||||
n := t.root
|
||||
search := s
|
||||
for {
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
if !n.isLeaf() {
|
||||
break
|
||||
}
|
||||
goto DELETE
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
parent = n
|
||||
label = search[0]
|
||||
n = n.getEdge(label)
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
|
||||
DELETE:
|
||||
// Delete the leaf
|
||||
leaf := n.leaf
|
||||
n.leaf = nil
|
||||
t.size--
|
||||
|
||||
// Check if we should delete this node from the parent
|
||||
if parent != nil && len(n.edges) == 0 {
|
||||
parent.delEdge(label)
|
||||
}
|
||||
|
||||
// Check if we should merge this node
|
||||
if n != t.root && len(n.edges) == 1 {
|
||||
n.mergeChild()
|
||||
}
|
||||
|
||||
// Check if we should merge the parent's other child
|
||||
if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() {
|
||||
parent.mergeChild()
|
||||
}
|
||||
|
||||
return leaf.val, true
|
||||
}
|
||||
|
||||
func (n *node) mergeChild() {
|
||||
e := n.edges[0]
|
||||
child := e.node
|
||||
n.prefix = n.prefix + child.prefix
|
||||
n.leaf = child.leaf
|
||||
n.edges = child.edges
|
||||
}
|
||||
|
||||
// Get is used to lookup a specific key, returning
|
||||
// the value and if it was found
|
||||
func (t *Tree) Get(s string) (interface{}, bool) {
|
||||
n := t.root
|
||||
search := s
|
||||
for {
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
if n.isLeaf() {
|
||||
return n.leaf.val, true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
n = n.getEdge(search[0])
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// LongestPrefix is like Get, but instead of an
|
||||
// exact match, it will return the longest prefix match.
|
||||
func (t *Tree) LongestPrefix(s string) (string, interface{}, bool) {
|
||||
var last *leafNode
|
||||
n := t.root
|
||||
search := s
|
||||
for {
|
||||
// Look for a leaf node
|
||||
if n.isLeaf() {
|
||||
last = n.leaf
|
||||
}
|
||||
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
n = n.getEdge(search[0])
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if last != nil {
|
||||
return last.key, last.val, true
|
||||
}
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
// Minimum is used to return the minimum value in the tree
|
||||
func (t *Tree) Minimum() (string, interface{}, bool) {
|
||||
n := t.root
|
||||
for {
|
||||
if n.isLeaf() {
|
||||
return n.leaf.key, n.leaf.val, true
|
||||
}
|
||||
if len(n.edges) > 0 {
|
||||
n = n.edges[0].node
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
// Maximum is used to return the maximum value in the tree
|
||||
func (t *Tree) Maximum() (string, interface{}, bool) {
|
||||
n := t.root
|
||||
for {
|
||||
if num := len(n.edges); num > 0 {
|
||||
n = n.edges[num-1].node
|
||||
continue
|
||||
}
|
||||
if n.isLeaf() {
|
||||
return n.leaf.key, n.leaf.val, true
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
// Walk is used to walk the tree
|
||||
func (t *Tree) Walk(fn WalkFn) {
|
||||
recursiveWalk(t.root, fn)
|
||||
}
|
||||
|
||||
// WalkPrefix is used to walk the tree under a prefix
|
||||
func (t *Tree) WalkPrefix(prefix string, fn WalkFn) {
|
||||
n := t.root
|
||||
search := prefix
|
||||
for {
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
recursiveWalk(n, fn)
|
||||
return
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
n = n.getEdge(search[0])
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
|
||||
} else if strings.HasPrefix(n.prefix, search) {
|
||||
// Child may be under our search prefix
|
||||
recursiveWalk(n, fn)
|
||||
return
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// WalkPath is used to walk the tree, but only visiting nodes
|
||||
// from the root down to a given leaf. Where WalkPrefix walks
|
||||
// all the entries *under* the given prefix, this walks the
|
||||
// entries *above* the given prefix.
|
||||
func (t *Tree) WalkPath(path string, fn WalkFn) {
|
||||
n := t.root
|
||||
search := path
|
||||
for {
|
||||
// Visit the leaf values if any
|
||||
if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
n = n.getEdge(search[0])
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recursiveWalk is used to do a pre-order walk of a node
|
||||
// recursively. Returns true if the walk should be aborted
|
||||
func recursiveWalk(n *node, fn WalkFn) bool {
|
||||
// Visit the leaf values if any
|
||||
if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Recurse on the children
|
||||
for _, e := range n.edges {
|
||||
if recursiveWalk(e.node, fn) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ToMap is used to walk the tree and convert it into a map
|
||||
func (t *Tree) ToMap() map[string]interface{} {
|
||||
out := make(map[string]interface{}, t.size)
|
||||
t.Walk(func(k string, v interface{}) bool {
|
||||
out[k] = v
|
||||
return false
|
||||
})
|
||||
return out
|
||||
}
|
||||
319
Godeps/_workspace/src/github.com/armon/go-radix/radix_test.go
generated
vendored
Normal file
319
Godeps/_workspace/src/github.com/armon/go-radix/radix_test.go
generated
vendored
Normal file
@@ -0,0 +1,319 @@
|
||||
package radix
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRadix(t *testing.T) {
|
||||
var min, max string
|
||||
inp := make(map[string]interface{})
|
||||
for i := 0; i < 1000; i++ {
|
||||
gen := generateUUID()
|
||||
inp[gen] = i
|
||||
if gen < min || i == 0 {
|
||||
min = gen
|
||||
}
|
||||
if gen > max || i == 0 {
|
||||
max = gen
|
||||
}
|
||||
}
|
||||
|
||||
r := NewFromMap(inp)
|
||||
if r.Len() != len(inp) {
|
||||
t.Fatalf("bad length: %v %v", r.Len(), len(inp))
|
||||
}
|
||||
|
||||
r.Walk(func(k string, v interface{}) bool {
|
||||
println(k)
|
||||
return false
|
||||
})
|
||||
|
||||
for k, v := range inp {
|
||||
out, ok := r.Get(k)
|
||||
if !ok {
|
||||
t.Fatalf("missing key: %v", k)
|
||||
}
|
||||
if out != v {
|
||||
t.Fatalf("value mis-match: %v %v", out, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Check min and max
|
||||
outMin, _, _ := r.Minimum()
|
||||
if outMin != min {
|
||||
t.Fatalf("bad minimum: %v %v", outMin, min)
|
||||
}
|
||||
outMax, _, _ := r.Maximum()
|
||||
if outMax != max {
|
||||
t.Fatalf("bad maximum: %v %v", outMax, max)
|
||||
}
|
||||
|
||||
for k, v := range inp {
|
||||
out, ok := r.Delete(k)
|
||||
if !ok {
|
||||
t.Fatalf("missing key: %v", k)
|
||||
}
|
||||
if out != v {
|
||||
t.Fatalf("value mis-match: %v %v", out, v)
|
||||
}
|
||||
}
|
||||
if r.Len() != 0 {
|
||||
t.Fatalf("bad length: %v", r.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoot(t *testing.T) {
|
||||
r := New()
|
||||
_, ok := r.Delete("")
|
||||
if ok {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
_, ok = r.Insert("", true)
|
||||
if ok {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
val, ok := r.Get("")
|
||||
if !ok || val != true {
|
||||
t.Fatalf("bad: %v", val)
|
||||
}
|
||||
val, ok = r.Delete("")
|
||||
if !ok || val != true {
|
||||
t.Fatalf("bad: %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
|
||||
r := New()
|
||||
|
||||
s := []string{"", "A", "AB"}
|
||||
|
||||
for _, ss := range s {
|
||||
r.Insert(ss, true)
|
||||
}
|
||||
|
||||
for _, ss := range s {
|
||||
_, ok := r.Delete(ss)
|
||||
if !ok {
|
||||
t.Fatalf("bad %q", ss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLongestPrefix(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
keys := []string{
|
||||
"",
|
||||
"foo",
|
||||
"foobar",
|
||||
"foobarbaz",
|
||||
"foobarbazzip",
|
||||
"foozip",
|
||||
}
|
||||
for _, k := range keys {
|
||||
r.Insert(k, nil)
|
||||
}
|
||||
if r.Len() != len(keys) {
|
||||
t.Fatalf("bad len: %v %v", r.Len(), len(keys))
|
||||
}
|
||||
|
||||
type exp struct {
|
||||
inp string
|
||||
out string
|
||||
}
|
||||
cases := []exp{
|
||||
{"a", ""},
|
||||
{"abc", ""},
|
||||
{"fo", ""},
|
||||
{"foo", "foo"},
|
||||
{"foob", "foo"},
|
||||
{"foobar", "foobar"},
|
||||
{"foobarba", "foobar"},
|
||||
{"foobarbaz", "foobarbaz"},
|
||||
{"foobarbazzi", "foobarbaz"},
|
||||
{"foobarbazzip", "foobarbazzip"},
|
||||
{"foozi", "foo"},
|
||||
{"foozip", "foozip"},
|
||||
{"foozipzap", "foozip"},
|
||||
}
|
||||
for _, test := range cases {
|
||||
m, _, ok := r.LongestPrefix(test.inp)
|
||||
if !ok {
|
||||
t.Fatalf("no match: %v", test)
|
||||
}
|
||||
if m != test.out {
|
||||
t.Fatalf("mis-match: %v %v", m, test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkPrefix(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
keys := []string{
|
||||
"foobar",
|
||||
"foo/bar/baz",
|
||||
"foo/baz/bar",
|
||||
"foo/zip/zap",
|
||||
"zipzap",
|
||||
}
|
||||
for _, k := range keys {
|
||||
r.Insert(k, nil)
|
||||
}
|
||||
if r.Len() != len(keys) {
|
||||
t.Fatalf("bad len: %v %v", r.Len(), len(keys))
|
||||
}
|
||||
|
||||
type exp struct {
|
||||
inp string
|
||||
out []string
|
||||
}
|
||||
cases := []exp{
|
||||
exp{
|
||||
"f",
|
||||
[]string{"foobar", "foo/bar/baz", "foo/baz/bar", "foo/zip/zap"},
|
||||
},
|
||||
exp{
|
||||
"foo",
|
||||
[]string{"foobar", "foo/bar/baz", "foo/baz/bar", "foo/zip/zap"},
|
||||
},
|
||||
exp{
|
||||
"foob",
|
||||
[]string{"foobar"},
|
||||
},
|
||||
exp{
|
||||
"foo/",
|
||||
[]string{"foo/bar/baz", "foo/baz/bar", "foo/zip/zap"},
|
||||
},
|
||||
exp{
|
||||
"foo/b",
|
||||
[]string{"foo/bar/baz", "foo/baz/bar"},
|
||||
},
|
||||
exp{
|
||||
"foo/ba",
|
||||
[]string{"foo/bar/baz", "foo/baz/bar"},
|
||||
},
|
||||
exp{
|
||||
"foo/bar",
|
||||
[]string{"foo/bar/baz"},
|
||||
},
|
||||
exp{
|
||||
"foo/bar/baz",
|
||||
[]string{"foo/bar/baz"},
|
||||
},
|
||||
exp{
|
||||
"foo/bar/bazoo",
|
||||
[]string{},
|
||||
},
|
||||
exp{
|
||||
"z",
|
||||
[]string{"zipzap"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
out := []string{}
|
||||
fn := func(s string, v interface{}) bool {
|
||||
out = append(out, s)
|
||||
return false
|
||||
}
|
||||
r.WalkPrefix(test.inp, fn)
|
||||
sort.Strings(out)
|
||||
sort.Strings(test.out)
|
||||
if !reflect.DeepEqual(out, test.out) {
|
||||
t.Fatalf("mis-match: %v %v", out, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkPath(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
keys := []string{
|
||||
"foo",
|
||||
"foo/bar",
|
||||
"foo/bar/baz",
|
||||
"foo/baz/bar",
|
||||
"foo/zip/zap",
|
||||
"zipzap",
|
||||
}
|
||||
for _, k := range keys {
|
||||
r.Insert(k, nil)
|
||||
}
|
||||
if r.Len() != len(keys) {
|
||||
t.Fatalf("bad len: %v %v", r.Len(), len(keys))
|
||||
}
|
||||
|
||||
type exp struct {
|
||||
inp string
|
||||
out []string
|
||||
}
|
||||
cases := []exp{
|
||||
exp{
|
||||
"f",
|
||||
[]string{},
|
||||
},
|
||||
exp{
|
||||
"foo",
|
||||
[]string{"foo"},
|
||||
},
|
||||
exp{
|
||||
"foo/",
|
||||
[]string{"foo"},
|
||||
},
|
||||
exp{
|
||||
"foo/ba",
|
||||
[]string{"foo"},
|
||||
},
|
||||
exp{
|
||||
"foo/bar",
|
||||
[]string{"foo", "foo/bar"},
|
||||
},
|
||||
exp{
|
||||
"foo/bar/baz",
|
||||
[]string{"foo", "foo/bar", "foo/bar/baz"},
|
||||
},
|
||||
exp{
|
||||
"foo/bar/bazoo",
|
||||
[]string{"foo", "foo/bar", "foo/bar/baz"},
|
||||
},
|
||||
exp{
|
||||
"z",
|
||||
[]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
out := []string{}
|
||||
fn := func(s string, v interface{}) bool {
|
||||
out = append(out, s)
|
||||
return false
|
||||
}
|
||||
r.WalkPath(test.inp, fn)
|
||||
sort.Strings(out)
|
||||
sort.Strings(test.out)
|
||||
if !reflect.DeepEqual(out, test.out) {
|
||||
t.Fatalf("mis-match: %v %v", out, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generateUUID is used to generate a random UUID
|
||||
func generateUUID() string {
|
||||
buf := make([]byte, 16)
|
||||
if _, err := crand.Read(buf); err != nil {
|
||||
panic(fmt.Errorf("failed to read random bytes: %v", err))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
|
||||
buf[0:4],
|
||||
buf[4:6],
|
||||
buf[6:8],
|
||||
buf[8:10],
|
||||
buf[10:16])
|
||||
}
|
||||
8
Godeps/_workspace/src/github.com/go-sql-driver/mysql/.gitignore
generated
vendored
Normal file
8
Godeps/_workspace/src/github.com/go-sql-driver/mysql/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Icon?
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
10
Godeps/_workspace/src/github.com/go-sql-driver/mysql/.travis.yml
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/go-sql-driver/mysql/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- tip
|
||||
|
||||
before_script:
|
||||
- mysql -e 'create database gotest;'
|
||||
41
Godeps/_workspace/src/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
Normal file
41
Godeps/_workspace/src/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# This is the official list of Go-MySQL-Driver authors for copyright purposes.
|
||||
|
||||
# If you are submitting a patch, please add your name or the name of the
|
||||
# organization which holds the copyright to this list in alphabetical order.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name <email address>
|
||||
# The email address is not required for organizations.
|
||||
# Please keep the list sorted.
|
||||
|
||||
|
||||
# Individual Persons
|
||||
|
||||
Aaron Hopkins <go-sql-driver at die.net>
|
||||
Arne Hormann <arnehormann at gmail.com>
|
||||
Carlos Nieto <jose.carlos at menteslibres.net>
|
||||
Chris Moos <chris at tech9computers.com>
|
||||
DisposaBoy <disposaboy at dby.me>
|
||||
Frederick Mayle <frederickmayle at gmail.com>
|
||||
Gustavo Kristic <gkristic at gmail.com>
|
||||
Hanno Braun <mail at hannobraun.com>
|
||||
Henri Yandell <flamefew at gmail.com>
|
||||
INADA Naoki <songofacandy at gmail.com>
|
||||
James Harr <james.harr at gmail.com>
|
||||
Jian Zhen <zhenjl at gmail.com>
|
||||
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
||||
Kamil Dziedzic <kamil at klecza.pl>
|
||||
Leonardo YongUk Kim <dalinaum at gmail.com>
|
||||
Lucas Liu <extrafliu at gmail.com>
|
||||
Luke Scott <luke at webconnex.com>
|
||||
Michael Woolnough <michael.woolnough at gmail.com>
|
||||
Nicola Peduzzi <thenikso at gmail.com>
|
||||
Runrioter Wung <runrioter at gmail.com>
|
||||
Xiaobing Jiang <s7v7nislands at gmail.com>
|
||||
Xiuming Chen <cc at cxm.cc>
|
||||
|
||||
# Organizations
|
||||
|
||||
Barracuda Networks, Inc.
|
||||
Google Inc.
|
||||
Stripe Inc.
|
||||
92
Godeps/_workspace/src/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
Normal file
92
Godeps/_workspace/src/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
## HEAD
|
||||
|
||||
Changes:
|
||||
|
||||
- Go 1.1 is no longer supported
|
||||
- Use decimals field from MySQL to format time types (#249)
|
||||
- Buffer optimizations (#269)
|
||||
- TLS ServerName defaults to the host (#283)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
|
||||
- Fixed handling of queries without columns and rows (#255)
|
||||
- Fixed a panic when SetKeepAlive() failed (#298)
|
||||
|
||||
New Features:
|
||||
- Support for returning table alias on Columns() (#289)
|
||||
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318)
|
||||
|
||||
|
||||
## Version 1.2 (2014-06-03)
|
||||
|
||||
Changes:
|
||||
|
||||
- We switched back to a "rolling release". `go get` installs the current master branch again
|
||||
- Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver
|
||||
- Exported errors to allow easy checking from application code
|
||||
- Enabled TCP Keepalives on TCP connections
|
||||
- Optimized INFILE handling (better buffer size calculation, lazy init, ...)
|
||||
- The DSN parser also checks for a missing separating slash
|
||||
- Faster binary date / datetime to string formatting
|
||||
- Also exported the MySQLWarning type
|
||||
- mysqlConn.Close returns the first error encountered instead of ignoring all errors
|
||||
- writePacket() automatically writes the packet size to the header
|
||||
- readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
|
||||
|
||||
New Features:
|
||||
|
||||
- `RegisterDial` allows the usage of a custom dial function to establish the network connection
|
||||
- Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter
|
||||
- Logging of critical errors is configurable with `SetLogger`
|
||||
- Google CloudSQL support
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Allow more than 32 parameters in prepared statements
|
||||
- Various old_password fixes
|
||||
- Fixed TestConcurrent test to pass Go's race detection
|
||||
- Fixed appendLengthEncodedInteger for large numbers
|
||||
- Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo)
|
||||
|
||||
|
||||
## Version 1.1 (2013-11-02)
|
||||
|
||||
Changes:
|
||||
|
||||
- Go-MySQL-Driver now requires Go 1.1
|
||||
- Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore
|
||||
- Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors
|
||||
- `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")`
|
||||
- DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'.
|
||||
- Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
|
||||
- Optimized the buffer for reading
|
||||
- stmt.Query now caches column metadata
|
||||
- New Logo
|
||||
- Changed the copyright header to include all contributors
|
||||
- Improved the LOAD INFILE documentation
|
||||
- The driver struct is now exported to make the driver directly accessible
|
||||
- Refactored the driver tests
|
||||
- Added more benchmarks and moved all to a separate file
|
||||
- Other small refactoring
|
||||
|
||||
New Features:
|
||||
|
||||
- Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure
|
||||
- Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs
|
||||
- Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
|
||||
- Convert to DB timezone when inserting `time.Time`
|
||||
- Splitted packets (more than 16MB) are now merged correctly
|
||||
- Fixed false positive `io.EOF` errors when the data was fully read
|
||||
- Avoid panics on reuse of closed connections
|
||||
- Fixed empty string producing false nil values
|
||||
- Fixed sign byte for positive TIME fields
|
||||
|
||||
|
||||
## Version 1.0 (2013-05-14)
|
||||
|
||||
Initial Release
|
||||
40
Godeps/_workspace/src/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
Normal file
40
Godeps/_workspace/src/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
|
||||
|
||||
Please provide the following minimum information:
|
||||
* Your Go-MySQL-Driver version (or git SHA)
|
||||
* Your Go version (run `go version` in your console)
|
||||
* A detailed issue description
|
||||
* Error Log if present
|
||||
* If possible, a short example
|
||||
|
||||
|
||||
## Contributing Code
|
||||
|
||||
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
|
||||
Don't forget to add yourself to the AUTHORS file.
|
||||
|
||||
### Pull Requests Checklist
|
||||
|
||||
Please check the following points before submitting your pull request:
|
||||
- [x] Code compiles correctly
|
||||
- [x] Created tests, if possible
|
||||
- [x] All tests pass
|
||||
- [x] Extended the README / documentation, if necessary
|
||||
- [x] Added yourself to the AUTHORS file
|
||||
|
||||
### Code Review
|
||||
|
||||
Everyone is invited to review and comment on pull requests.
|
||||
If it looks fine to you, comment with "LGTM" (Looks good to me).
|
||||
|
||||
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
|
||||
|
||||
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
|
||||
|
||||
## Development Ideas
|
||||
|
||||
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
|
||||
373
Godeps/_workspace/src/github.com/go-sql-driver/mysql/LICENSE
generated
vendored
Normal file
373
Godeps/_workspace/src/github.com/go-sql-driver/mysql/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
374
Godeps/_workspace/src/github.com/go-sql-driver/mysql/README.md
generated
vendored
Normal file
374
Godeps/_workspace/src/github.com/go-sql-driver/mysql/README.md
generated
vendored
Normal file
@@ -0,0 +1,374 @@
|
||||
# Go-MySQL-Driver
|
||||
|
||||
A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package
|
||||
|
||||

|
||||
|
||||
**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases)
|
||||
|
||||
[](https://travis-ci.org/go-sql-driver/mysql)
|
||||
|
||||
---------------------------------------
|
||||
* [Features](#features)
|
||||
* [Requirements](#requirements)
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [DSN (Data Source Name)](#dsn-data-source-name)
|
||||
* [Password](#password)
|
||||
* [Protocol](#protocol)
|
||||
* [Address](#address)
|
||||
* [Parameters](#parameters)
|
||||
* [Examples](#examples)
|
||||
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
|
||||
* [time.Time support](#timetime-support)
|
||||
* [Unicode support](#unicode-support)
|
||||
* [Testing / Development](#testing--development)
|
||||
* [License](#license)
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## Features
|
||||
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
|
||||
* Native Go implementation. No C-bindings, just pure Go
|
||||
* Connections over TCP/IPv4, TCP/IPv6 or Unix domain sockets
|
||||
* Automatic handling of broken connections
|
||||
* Automatic Connection Pooling *(by database/sql package)*
|
||||
* Supports queries larger than 16MB
|
||||
* Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support.
|
||||
* Intelligent `LONG DATA` handling in prepared statements
|
||||
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
|
||||
* Optional `time.Time` parsing
|
||||
* Optional placeholder interpolation
|
||||
|
||||
## Requirements
|
||||
* Go 1.2 or higher
|
||||
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## Installation
|
||||
Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell:
|
||||
```bash
|
||||
$ go get github.com/go-sql-driver/mysql
|
||||
```
|
||||
Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
||||
|
||||
## Usage
|
||||
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then.
|
||||
|
||||
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
|
||||
```go
|
||||
import "database/sql"
|
||||
import _ "github.com/go-sql-driver/mysql"
|
||||
|
||||
db, err := sql.Open("mysql", "user:password@/dbname")
|
||||
```
|
||||
|
||||
[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
|
||||
|
||||
|
||||
### DSN (Data Source Name)
|
||||
|
||||
The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
|
||||
```
|
||||
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||
```
|
||||
|
||||
A DSN in its fullest form:
|
||||
```
|
||||
username:password@protocol(address)/dbname?param=value
|
||||
```
|
||||
|
||||
Except for the databasename, all values are optional. So the minimal DSN is:
|
||||
```
|
||||
/dbname
|
||||
```
|
||||
|
||||
If you do not want to preselect a database, leave `dbname` empty:
|
||||
```
|
||||
/
|
||||
```
|
||||
This has the same effect as an empty DSN string:
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
#### Password
|
||||
Passwords can consist of any character. Escaping is **not** necessary.
|
||||
|
||||
#### Protocol
|
||||
See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available.
|
||||
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
|
||||
|
||||
#### Address
|
||||
For TCP and UDP networks, addresses have the form `host:port`.
|
||||
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
|
||||
The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
|
||||
|
||||
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
|
||||
|
||||
#### Parameters
|
||||
*Parameters are case-sensitive!*
|
||||
|
||||
Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`.
|
||||
|
||||
##### `allowAllFiles`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
|
||||
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
|
||||
|
||||
##### `allowOldPasswords`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
|
||||
|
||||
##### `charset`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <name>
|
||||
Default: none
|
||||
```
|
||||
|
||||
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
|
||||
|
||||
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
|
||||
Unless you need the fallback behavior, please use `collation` instead.
|
||||
|
||||
##### `collation`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <name>
|
||||
Default: utf8_general_ci
|
||||
```
|
||||
|
||||
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
|
||||
|
||||
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
|
||||
|
||||
##### `clientFoundRows`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
|
||||
|
||||
##### `columnsWithAlias`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
|
||||
|
||||
```
|
||||
SELECT u.id FROM users as u
|
||||
```
|
||||
|
||||
will return `u.id` instead of just `id` if `columnsWithAlias=true`.
|
||||
|
||||
##### `interpolateParams`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
|
||||
|
||||
*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
|
||||
|
||||
##### `loc`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <escaped name>
|
||||
Default: UTC
|
||||
```
|
||||
|
||||
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
|
||||
|
||||
Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
|
||||
|
||||
|
||||
##### `parseTime`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
||||
|
||||
|
||||
##### `strict`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`strict=true` enables the strict mode in which MySQL warnings are treated as errors.
|
||||
|
||||
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes. See the [examples](#examples) for an DSN example.
|
||||
|
||||
|
||||
##### `timeout`
|
||||
|
||||
```
|
||||
Type: decimal number
|
||||
Default: OS default
|
||||
```
|
||||
|
||||
*Driver* side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
|
||||
|
||||
|
||||
##### `tls`
|
||||
|
||||
```
|
||||
Type: bool / string
|
||||
Valid Values: true, false, skip-verify, <name>
|
||||
Default: false
|
||||
```
|
||||
|
||||
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
|
||||
|
||||
|
||||
##### System Variables
|
||||
|
||||
All other parameters are interpreted as system variables:
|
||||
* `autocommit`: `"SET autocommit=<value>"`
|
||||
* `time_zone`: `"SET time_zone=<value>"`
|
||||
* [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"`
|
||||
* `param`: `"SET <param>=<value>"`
|
||||
|
||||
*The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!*
|
||||
|
||||
#### Examples
|
||||
```
|
||||
user@unix(/path/to/socket)/dbname
|
||||
```
|
||||
|
||||
```
|
||||
root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
|
||||
```
|
||||
|
||||
```
|
||||
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
|
||||
```
|
||||
|
||||
Use the [strict mode](#strict) but ignore notes:
|
||||
```
|
||||
user:password@/dbname?strict=true&sql_notes=false
|
||||
```
|
||||
|
||||
TCP via IPv6:
|
||||
```
|
||||
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
|
||||
```
|
||||
|
||||
TCP on a remote host, e.g. Amazon RDS:
|
||||
```
|
||||
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
|
||||
```
|
||||
|
||||
Google Cloud SQL on App Engine:
|
||||
```
|
||||
user@cloudsql(project-id:instance-name)/dbname
|
||||
```
|
||||
|
||||
TCP using default port (3306) on localhost:
|
||||
```
|
||||
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
|
||||
```
|
||||
|
||||
Use the default protocol (tcp) and host (localhost:3306):
|
||||
```
|
||||
user:password@/dbname
|
||||
```
|
||||
|
||||
No Database preselected:
|
||||
```
|
||||
user:password@/
|
||||
```
|
||||
|
||||
### `LOAD DATA LOCAL INFILE` support
|
||||
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
|
||||
```go
|
||||
import "github.com/go-sql-driver/mysql"
|
||||
```
|
||||
|
||||
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
|
||||
|
||||
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then.
|
||||
|
||||
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
|
||||
|
||||
|
||||
### `time.Time` support
|
||||
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
|
||||
|
||||
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
||||
|
||||
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
||||
|
||||
Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
|
||||
|
||||
|
||||
### Unicode support
|
||||
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
|
||||
|
||||
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
|
||||
|
||||
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
|
||||
|
||||
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
|
||||
|
||||
|
||||
## Testing / Development
|
||||
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
||||
|
||||
Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
|
||||
If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
|
||||
|
||||
See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## License
|
||||
Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
||||
|
||||
Mozilla summarizes the license scope as follows:
|
||||
> MPL: The copyleft applies to any files containing MPLed code.
|
||||
|
||||
|
||||
That means:
|
||||
* You can **use** the **unchanged** source code both in private and commercially
|
||||
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
|
||||
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
|
||||
|
||||
Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
|
||||
|
||||
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
||||
|
||||

|
||||
|
||||
19
Godeps/_workspace/src/github.com/go-sql-driver/mysql/appengine.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/go-sql-driver/mysql/appengine.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"appengine/cloudsql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDial("cloudsql", cloudsql.Dial)
|
||||
}
|
||||
246
Godeps/_workspace/src/github.com/go-sql-driver/mysql/benchmark_test.go
generated
vendored
Normal file
246
Godeps/_workspace/src/github.com/go-sql-driver/mysql/benchmark_test.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TB testing.B
|
||||
|
||||
func (tb *TB) check(err error) {
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tb *TB) checkDB(db *sql.DB, err error) *sql.DB {
|
||||
tb.check(err)
|
||||
return db
|
||||
}
|
||||
|
||||
func (tb *TB) checkRows(rows *sql.Rows, err error) *sql.Rows {
|
||||
tb.check(err)
|
||||
return rows
|
||||
}
|
||||
|
||||
func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {
|
||||
tb.check(err)
|
||||
return stmt
|
||||
}
|
||||
|
||||
func initDB(b *testing.B, queries ...string) *sql.DB {
|
||||
tb := (*TB)(b)
|
||||
db := tb.checkDB(sql.Open("mysql", dsn))
|
||||
for _, query := range queries {
|
||||
if _, err := db.Exec(query); err != nil {
|
||||
if w, ok := err.(MySQLWarnings); ok {
|
||||
b.Logf("Warning on %q: %v", query, w)
|
||||
} else {
|
||||
b.Fatalf("Error on %q: %v", query, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
const concurrencyLevel = 10
|
||||
|
||||
func BenchmarkQuery(b *testing.B) {
|
||||
tb := (*TB)(b)
|
||||
b.StopTimer()
|
||||
b.ReportAllocs()
|
||||
db := initDB(b,
|
||||
"DROP TABLE IF EXISTS foo",
|
||||
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
|
||||
`INSERT INTO foo VALUES (1, "one")`,
|
||||
`INSERT INTO foo VALUES (2, "two")`,
|
||||
)
|
||||
db.SetMaxIdleConns(concurrencyLevel)
|
||||
defer db.Close()
|
||||
|
||||
stmt := tb.checkStmt(db.Prepare("SELECT val FROM foo WHERE id=?"))
|
||||
defer stmt.Close()
|
||||
|
||||
remain := int64(b.N)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrencyLevel)
|
||||
defer wg.Wait()
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < concurrencyLevel; i++ {
|
||||
go func() {
|
||||
for {
|
||||
if atomic.AddInt64(&remain, -1) < 0 {
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
var got string
|
||||
tb.check(stmt.QueryRow(1).Scan(&got))
|
||||
if got != "one" {
|
||||
b.Errorf("query = %q; want one", got)
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExec(b *testing.B) {
|
||||
tb := (*TB)(b)
|
||||
b.StopTimer()
|
||||
b.ReportAllocs()
|
||||
db := tb.checkDB(sql.Open("mysql", dsn))
|
||||
db.SetMaxIdleConns(concurrencyLevel)
|
||||
defer db.Close()
|
||||
|
||||
stmt := tb.checkStmt(db.Prepare("DO 1"))
|
||||
defer stmt.Close()
|
||||
|
||||
remain := int64(b.N)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrencyLevel)
|
||||
defer wg.Wait()
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < concurrencyLevel; i++ {
|
||||
go func() {
|
||||
for {
|
||||
if atomic.AddInt64(&remain, -1) < 0 {
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := stmt.Exec(); err != nil {
|
||||
b.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// data, but no db writes
|
||||
var roundtripSample []byte
|
||||
|
||||
func initRoundtripBenchmarks() ([]byte, int, int) {
|
||||
if roundtripSample == nil {
|
||||
roundtripSample = []byte(strings.Repeat("0123456789abcdef", 1024*1024))
|
||||
}
|
||||
return roundtripSample, 16, len(roundtripSample)
|
||||
}
|
||||
|
||||
func BenchmarkRoundtripTxt(b *testing.B) {
|
||||
b.StopTimer()
|
||||
sample, min, max := initRoundtripBenchmarks()
|
||||
sampleString := string(sample)
|
||||
b.ReportAllocs()
|
||||
tb := (*TB)(b)
|
||||
db := tb.checkDB(sql.Open("mysql", dsn))
|
||||
defer db.Close()
|
||||
b.StartTimer()
|
||||
var result string
|
||||
for i := 0; i < b.N; i++ {
|
||||
length := min + i
|
||||
if length > max {
|
||||
length = max
|
||||
}
|
||||
test := sampleString[0:length]
|
||||
rows := tb.checkRows(db.Query(`SELECT "` + test + `"`))
|
||||
if !rows.Next() {
|
||||
rows.Close()
|
||||
b.Fatalf("crashed")
|
||||
}
|
||||
err := rows.Scan(&result)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
b.Fatalf("crashed")
|
||||
}
|
||||
if result != test {
|
||||
rows.Close()
|
||||
b.Errorf("mismatch")
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRoundtripBin(b *testing.B) {
|
||||
b.StopTimer()
|
||||
sample, min, max := initRoundtripBenchmarks()
|
||||
b.ReportAllocs()
|
||||
tb := (*TB)(b)
|
||||
db := tb.checkDB(sql.Open("mysql", dsn))
|
||||
defer db.Close()
|
||||
stmt := tb.checkStmt(db.Prepare("SELECT ?"))
|
||||
defer stmt.Close()
|
||||
b.StartTimer()
|
||||
var result sql.RawBytes
|
||||
for i := 0; i < b.N; i++ {
|
||||
length := min + i
|
||||
if length > max {
|
||||
length = max
|
||||
}
|
||||
test := sample[0:length]
|
||||
rows := tb.checkRows(stmt.Query(test))
|
||||
if !rows.Next() {
|
||||
rows.Close()
|
||||
b.Fatalf("crashed")
|
||||
}
|
||||
err := rows.Scan(&result)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
b.Fatalf("crashed")
|
||||
}
|
||||
if !bytes.Equal(result, test) {
|
||||
rows.Close()
|
||||
b.Errorf("mismatch")
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInterpolation(b *testing.B) {
|
||||
mc := &mysqlConn{
|
||||
cfg: &config{
|
||||
interpolateParams: true,
|
||||
loc: time.UTC,
|
||||
},
|
||||
maxPacketAllowed: maxPacketSize,
|
||||
maxWriteSize: maxPacketSize - 1,
|
||||
buf: newBuffer(nil),
|
||||
}
|
||||
|
||||
args := []driver.Value{
|
||||
int64(42424242),
|
||||
float64(math.Pi),
|
||||
false,
|
||||
time.Unix(1423411542, 807015000),
|
||||
[]byte("bytes containing special chars ' \" \a \x00"),
|
||||
"string containing special chars ' \" \a \x00",
|
||||
}
|
||||
q := "SELECT ?, ?, ?, ?, ?, ?"
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := mc.interpolateParams(q, args)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Godeps/_workspace/src/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
Normal file
136
Godeps/_workspace/src/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import "io"
|
||||
|
||||
const defaultBufSize = 4096
|
||||
|
||||
// A buffer which is used for both reading and writing.
|
||||
// This is possible since communication on each connection is synchronous.
|
||||
// In other words, we can't write and read simultaneously on the same connection.
|
||||
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
||||
// Also highly optimized for this particular use case.
|
||||
type buffer struct {
|
||||
buf []byte
|
||||
rd io.Reader
|
||||
idx int
|
||||
length int
|
||||
}
|
||||
|
||||
func newBuffer(rd io.Reader) buffer {
|
||||
var b [defaultBufSize]byte
|
||||
return buffer{
|
||||
buf: b[:],
|
||||
rd: rd,
|
||||
}
|
||||
}
|
||||
|
||||
// fill reads into the buffer until at least _need_ bytes are in it
|
||||
func (b *buffer) fill(need int) error {
|
||||
n := b.length
|
||||
|
||||
// move existing data to the beginning
|
||||
if n > 0 && b.idx > 0 {
|
||||
copy(b.buf[0:n], b.buf[b.idx:])
|
||||
}
|
||||
|
||||
// grow buffer if necessary
|
||||
// TODO: let the buffer shrink again at some point
|
||||
// Maybe keep the org buf slice and swap back?
|
||||
if need > len(b.buf) {
|
||||
// Round up to the next multiple of the default size
|
||||
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
|
||||
copy(newBuf, b.buf)
|
||||
b.buf = newBuf
|
||||
}
|
||||
|
||||
b.idx = 0
|
||||
|
||||
for {
|
||||
nn, err := b.rd.Read(b.buf[n:])
|
||||
n += nn
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
if n < need {
|
||||
continue
|
||||
}
|
||||
b.length = n
|
||||
return nil
|
||||
|
||||
case io.EOF:
|
||||
if n >= need {
|
||||
b.length = n
|
||||
return nil
|
||||
}
|
||||
return io.ErrUnexpectedEOF
|
||||
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns next N bytes from buffer.
|
||||
// The returned slice is only guaranteed to be valid until the next read
|
||||
func (b *buffer) readNext(need int) ([]byte, error) {
|
||||
if b.length < need {
|
||||
// refill
|
||||
if err := b.fill(need); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
offset := b.idx
|
||||
b.idx += need
|
||||
b.length -= need
|
||||
return b.buf[offset:b.idx], nil
|
||||
}
|
||||
|
||||
// returns a buffer with the requested size.
|
||||
// If possible, a slice from the existing buffer is returned.
|
||||
// Otherwise a bigger buffer is made.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeBuffer(length int) []byte {
|
||||
if b.length > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// test (cheap) general case first
|
||||
if length <= defaultBufSize || length <= cap(b.buf) {
|
||||
return b.buf[:length]
|
||||
}
|
||||
|
||||
if length < maxPacketSize {
|
||||
b.buf = make([]byte, length)
|
||||
return b.buf
|
||||
}
|
||||
return make([]byte, length)
|
||||
}
|
||||
|
||||
// shortcut which can be used if the requested buffer is guaranteed to be
|
||||
// smaller than defaultBufSize
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeSmallBuffer(length int) []byte {
|
||||
if b.length == 0 {
|
||||
return b.buf[:length]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// takeCompleteBuffer returns the complete existing buffer.
|
||||
// This can be used if the necessary buffer size is unknown.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeCompleteBuffer() []byte {
|
||||
if b.length == 0 {
|
||||
return b.buf
|
||||
}
|
||||
return nil
|
||||
}
|
||||
250
Godeps/_workspace/src/github.com/go-sql-driver/mysql/collations.go
generated
vendored
Normal file
250
Godeps/_workspace/src/github.com/go-sql-driver/mysql/collations.go
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
const defaultCollation byte = 33 // utf8_general_ci
|
||||
|
||||
// A list of available collations mapped to the internal ID.
|
||||
// To update this map use the following MySQL query:
|
||||
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
|
||||
var collations = map[string]byte{
|
||||
"big5_chinese_ci": 1,
|
||||
"latin2_czech_cs": 2,
|
||||
"dec8_swedish_ci": 3,
|
||||
"cp850_general_ci": 4,
|
||||
"latin1_german1_ci": 5,
|
||||
"hp8_english_ci": 6,
|
||||
"koi8r_general_ci": 7,
|
||||
"latin1_swedish_ci": 8,
|
||||
"latin2_general_ci": 9,
|
||||
"swe7_swedish_ci": 10,
|
||||
"ascii_general_ci": 11,
|
||||
"ujis_japanese_ci": 12,
|
||||
"sjis_japanese_ci": 13,
|
||||
"cp1251_bulgarian_ci": 14,
|
||||
"latin1_danish_ci": 15,
|
||||
"hebrew_general_ci": 16,
|
||||
"tis620_thai_ci": 18,
|
||||
"euckr_korean_ci": 19,
|
||||
"latin7_estonian_cs": 20,
|
||||
"latin2_hungarian_ci": 21,
|
||||
"koi8u_general_ci": 22,
|
||||
"cp1251_ukrainian_ci": 23,
|
||||
"gb2312_chinese_ci": 24,
|
||||
"greek_general_ci": 25,
|
||||
"cp1250_general_ci": 26,
|
||||
"latin2_croatian_ci": 27,
|
||||
"gbk_chinese_ci": 28,
|
||||
"cp1257_lithuanian_ci": 29,
|
||||
"latin5_turkish_ci": 30,
|
||||
"latin1_german2_ci": 31,
|
||||
"armscii8_general_ci": 32,
|
||||
"utf8_general_ci": 33,
|
||||
"cp1250_czech_cs": 34,
|
||||
"ucs2_general_ci": 35,
|
||||
"cp866_general_ci": 36,
|
||||
"keybcs2_general_ci": 37,
|
||||
"macce_general_ci": 38,
|
||||
"macroman_general_ci": 39,
|
||||
"cp852_general_ci": 40,
|
||||
"latin7_general_ci": 41,
|
||||
"latin7_general_cs": 42,
|
||||
"macce_bin": 43,
|
||||
"cp1250_croatian_ci": 44,
|
||||
"utf8mb4_general_ci": 45,
|
||||
"utf8mb4_bin": 46,
|
||||
"latin1_bin": 47,
|
||||
"latin1_general_ci": 48,
|
||||
"latin1_general_cs": 49,
|
||||
"cp1251_bin": 50,
|
||||
"cp1251_general_ci": 51,
|
||||
"cp1251_general_cs": 52,
|
||||
"macroman_bin": 53,
|
||||
"utf16_general_ci": 54,
|
||||
"utf16_bin": 55,
|
||||
"utf16le_general_ci": 56,
|
||||
"cp1256_general_ci": 57,
|
||||
"cp1257_bin": 58,
|
||||
"cp1257_general_ci": 59,
|
||||
"utf32_general_ci": 60,
|
||||
"utf32_bin": 61,
|
||||
"utf16le_bin": 62,
|
||||
"binary": 63,
|
||||
"armscii8_bin": 64,
|
||||
"ascii_bin": 65,
|
||||
"cp1250_bin": 66,
|
||||
"cp1256_bin": 67,
|
||||
"cp866_bin": 68,
|
||||
"dec8_bin": 69,
|
||||
"greek_bin": 70,
|
||||
"hebrew_bin": 71,
|
||||
"hp8_bin": 72,
|
||||
"keybcs2_bin": 73,
|
||||
"koi8r_bin": 74,
|
||||
"koi8u_bin": 75,
|
||||
"latin2_bin": 77,
|
||||
"latin5_bin": 78,
|
||||
"latin7_bin": 79,
|
||||
"cp850_bin": 80,
|
||||
"cp852_bin": 81,
|
||||
"swe7_bin": 82,
|
||||
"utf8_bin": 83,
|
||||
"big5_bin": 84,
|
||||
"euckr_bin": 85,
|
||||
"gb2312_bin": 86,
|
||||
"gbk_bin": 87,
|
||||
"sjis_bin": 88,
|
||||
"tis620_bin": 89,
|
||||
"ucs2_bin": 90,
|
||||
"ujis_bin": 91,
|
||||
"geostd8_general_ci": 92,
|
||||
"geostd8_bin": 93,
|
||||
"latin1_spanish_ci": 94,
|
||||
"cp932_japanese_ci": 95,
|
||||
"cp932_bin": 96,
|
||||
"eucjpms_japanese_ci": 97,
|
||||
"eucjpms_bin": 98,
|
||||
"cp1250_polish_ci": 99,
|
||||
"utf16_unicode_ci": 101,
|
||||
"utf16_icelandic_ci": 102,
|
||||
"utf16_latvian_ci": 103,
|
||||
"utf16_romanian_ci": 104,
|
||||
"utf16_slovenian_ci": 105,
|
||||
"utf16_polish_ci": 106,
|
||||
"utf16_estonian_ci": 107,
|
||||
"utf16_spanish_ci": 108,
|
||||
"utf16_swedish_ci": 109,
|
||||
"utf16_turkish_ci": 110,
|
||||
"utf16_czech_ci": 111,
|
||||
"utf16_danish_ci": 112,
|
||||
"utf16_lithuanian_ci": 113,
|
||||
"utf16_slovak_ci": 114,
|
||||
"utf16_spanish2_ci": 115,
|
||||
"utf16_roman_ci": 116,
|
||||
"utf16_persian_ci": 117,
|
||||
"utf16_esperanto_ci": 118,
|
||||
"utf16_hungarian_ci": 119,
|
||||
"utf16_sinhala_ci": 120,
|
||||
"utf16_german2_ci": 121,
|
||||
"utf16_croatian_ci": 122,
|
||||
"utf16_unicode_520_ci": 123,
|
||||
"utf16_vietnamese_ci": 124,
|
||||
"ucs2_unicode_ci": 128,
|
||||
"ucs2_icelandic_ci": 129,
|
||||
"ucs2_latvian_ci": 130,
|
||||
"ucs2_romanian_ci": 131,
|
||||
"ucs2_slovenian_ci": 132,
|
||||
"ucs2_polish_ci": 133,
|
||||
"ucs2_estonian_ci": 134,
|
||||
"ucs2_spanish_ci": 135,
|
||||
"ucs2_swedish_ci": 136,
|
||||
"ucs2_turkish_ci": 137,
|
||||
"ucs2_czech_ci": 138,
|
||||
"ucs2_danish_ci": 139,
|
||||
"ucs2_lithuanian_ci": 140,
|
||||
"ucs2_slovak_ci": 141,
|
||||
"ucs2_spanish2_ci": 142,
|
||||
"ucs2_roman_ci": 143,
|
||||
"ucs2_persian_ci": 144,
|
||||
"ucs2_esperanto_ci": 145,
|
||||
"ucs2_hungarian_ci": 146,
|
||||
"ucs2_sinhala_ci": 147,
|
||||
"ucs2_german2_ci": 148,
|
||||
"ucs2_croatian_ci": 149,
|
||||
"ucs2_unicode_520_ci": 150,
|
||||
"ucs2_vietnamese_ci": 151,
|
||||
"ucs2_general_mysql500_ci": 159,
|
||||
"utf32_unicode_ci": 160,
|
||||
"utf32_icelandic_ci": 161,
|
||||
"utf32_latvian_ci": 162,
|
||||
"utf32_romanian_ci": 163,
|
||||
"utf32_slovenian_ci": 164,
|
||||
"utf32_polish_ci": 165,
|
||||
"utf32_estonian_ci": 166,
|
||||
"utf32_spanish_ci": 167,
|
||||
"utf32_swedish_ci": 168,
|
||||
"utf32_turkish_ci": 169,
|
||||
"utf32_czech_ci": 170,
|
||||
"utf32_danish_ci": 171,
|
||||
"utf32_lithuanian_ci": 172,
|
||||
"utf32_slovak_ci": 173,
|
||||
"utf32_spanish2_ci": 174,
|
||||
"utf32_roman_ci": 175,
|
||||
"utf32_persian_ci": 176,
|
||||
"utf32_esperanto_ci": 177,
|
||||
"utf32_hungarian_ci": 178,
|
||||
"utf32_sinhala_ci": 179,
|
||||
"utf32_german2_ci": 180,
|
||||
"utf32_croatian_ci": 181,
|
||||
"utf32_unicode_520_ci": 182,
|
||||
"utf32_vietnamese_ci": 183,
|
||||
"utf8_unicode_ci": 192,
|
||||
"utf8_icelandic_ci": 193,
|
||||
"utf8_latvian_ci": 194,
|
||||
"utf8_romanian_ci": 195,
|
||||
"utf8_slovenian_ci": 196,
|
||||
"utf8_polish_ci": 197,
|
||||
"utf8_estonian_ci": 198,
|
||||
"utf8_spanish_ci": 199,
|
||||
"utf8_swedish_ci": 200,
|
||||
"utf8_turkish_ci": 201,
|
||||
"utf8_czech_ci": 202,
|
||||
"utf8_danish_ci": 203,
|
||||
"utf8_lithuanian_ci": 204,
|
||||
"utf8_slovak_ci": 205,
|
||||
"utf8_spanish2_ci": 206,
|
||||
"utf8_roman_ci": 207,
|
||||
"utf8_persian_ci": 208,
|
||||
"utf8_esperanto_ci": 209,
|
||||
"utf8_hungarian_ci": 210,
|
||||
"utf8_sinhala_ci": 211,
|
||||
"utf8_german2_ci": 212,
|
||||
"utf8_croatian_ci": 213,
|
||||
"utf8_unicode_520_ci": 214,
|
||||
"utf8_vietnamese_ci": 215,
|
||||
"utf8_general_mysql500_ci": 223,
|
||||
"utf8mb4_unicode_ci": 224,
|
||||
"utf8mb4_icelandic_ci": 225,
|
||||
"utf8mb4_latvian_ci": 226,
|
||||
"utf8mb4_romanian_ci": 227,
|
||||
"utf8mb4_slovenian_ci": 228,
|
||||
"utf8mb4_polish_ci": 229,
|
||||
"utf8mb4_estonian_ci": 230,
|
||||
"utf8mb4_spanish_ci": 231,
|
||||
"utf8mb4_swedish_ci": 232,
|
||||
"utf8mb4_turkish_ci": 233,
|
||||
"utf8mb4_czech_ci": 234,
|
||||
"utf8mb4_danish_ci": 235,
|
||||
"utf8mb4_lithuanian_ci": 236,
|
||||
"utf8mb4_slovak_ci": 237,
|
||||
"utf8mb4_spanish2_ci": 238,
|
||||
"utf8mb4_roman_ci": 239,
|
||||
"utf8mb4_persian_ci": 240,
|
||||
"utf8mb4_esperanto_ci": 241,
|
||||
"utf8mb4_hungarian_ci": 242,
|
||||
"utf8mb4_sinhala_ci": 243,
|
||||
"utf8mb4_german2_ci": 244,
|
||||
"utf8mb4_croatian_ci": 245,
|
||||
"utf8mb4_unicode_520_ci": 246,
|
||||
"utf8mb4_vietnamese_ci": 247,
|
||||
}
|
||||
|
||||
// A blacklist of collations which is unsafe to interpolate parameters.
|
||||
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
|
||||
var unsafeCollations = map[byte]bool{
|
||||
1: true, // big5_chinese_ci
|
||||
13: true, // sjis_japanese_ci
|
||||
28: true, // gbk_chinese_ci
|
||||
84: true, // big5_bin
|
||||
86: true, // gb2312_bin
|
||||
87: true, // gbk_bin
|
||||
88: true, // sjis_bin
|
||||
95: true, // cp932_japanese_ci
|
||||
96: true, // cp932_bin
|
||||
}
|
||||
402
Godeps/_workspace/src/github.com/go-sql-driver/mysql/connection.go
generated
vendored
Normal file
402
Godeps/_workspace/src/github.com/go-sql-driver/mysql/connection.go
generated
vendored
Normal file
@@ -0,0 +1,402 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mysqlConn struct {
|
||||
buf buffer
|
||||
netConn net.Conn
|
||||
affectedRows uint64
|
||||
insertId uint64
|
||||
cfg *config
|
||||
maxPacketAllowed int
|
||||
maxWriteSize int
|
||||
flags clientFlag
|
||||
status statusFlag
|
||||
sequence uint8
|
||||
parseTime bool
|
||||
strict bool
|
||||
}
|
||||
|
||||
type config struct {
|
||||
user string
|
||||
passwd string
|
||||
net string
|
||||
addr string
|
||||
dbname string
|
||||
params map[string]string
|
||||
loc *time.Location
|
||||
tls *tls.Config
|
||||
timeout time.Duration
|
||||
collation uint8
|
||||
allowAllFiles bool
|
||||
allowOldPasswords bool
|
||||
clientFoundRows bool
|
||||
columnsWithAlias bool
|
||||
interpolateParams bool
|
||||
}
|
||||
|
||||
// Handles parameters set in DSN after the connection is established
|
||||
func (mc *mysqlConn) handleParams() (err error) {
|
||||
for param, val := range mc.cfg.params {
|
||||
switch param {
|
||||
// Charset
|
||||
case "charset":
|
||||
charsets := strings.Split(val, ",")
|
||||
for i := range charsets {
|
||||
// ignore errors here - a charset may not exist
|
||||
err = mc.exec("SET NAMES " + charsets[i])
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// time.Time parsing
|
||||
case "parseTime":
|
||||
var isBool bool
|
||||
mc.parseTime, isBool = readBool(val)
|
||||
if !isBool {
|
||||
return errors.New("Invalid Bool value: " + val)
|
||||
}
|
||||
|
||||
// Strict mode
|
||||
case "strict":
|
||||
var isBool bool
|
||||
mc.strict, isBool = readBool(val)
|
||||
if !isBool {
|
||||
return errors.New("Invalid Bool value: " + val)
|
||||
}
|
||||
|
||||
// Compression
|
||||
case "compress":
|
||||
err = errors.New("Compression not implemented yet")
|
||||
return
|
||||
|
||||
// System Vars
|
||||
default:
|
||||
err = mc.exec("SET " + param + "=" + val + "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Begin() (driver.Tx, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
err := mc.exec("START TRANSACTION")
|
||||
if err == nil {
|
||||
return &mysqlTx{mc}, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Close() (err error) {
|
||||
// Makes Close idempotent
|
||||
if mc.netConn != nil {
|
||||
err = mc.writeCommandPacket(comQuit)
|
||||
if err == nil {
|
||||
err = mc.netConn.Close()
|
||||
} else {
|
||||
mc.netConn.Close()
|
||||
}
|
||||
mc.netConn = nil
|
||||
}
|
||||
|
||||
mc.cfg = nil
|
||||
mc.buf.rd = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stmt := &mysqlStmt{
|
||||
mc: mc,
|
||||
}
|
||||
|
||||
// Read Result
|
||||
columnCount, err := stmt.readPrepareResultPacket()
|
||||
if err == nil {
|
||||
if stmt.paramCount > 0 {
|
||||
if err = mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if columnCount > 0 {
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
|
||||
buf := mc.buf.takeCompleteBuffer()
|
||||
if buf == nil {
|
||||
// can not take the buffer. Something must be wrong with the connection
|
||||
errLog.Print(ErrBusyBuffer)
|
||||
return "", driver.ErrBadConn
|
||||
}
|
||||
buf = buf[:0]
|
||||
argPos := 0
|
||||
|
||||
for i := 0; i < len(query); i++ {
|
||||
q := strings.IndexByte(query[i:], '?')
|
||||
if q == -1 {
|
||||
buf = append(buf, query[i:]...)
|
||||
break
|
||||
}
|
||||
buf = append(buf, query[i:i+q]...)
|
||||
i += q
|
||||
|
||||
arg := args[argPos]
|
||||
argPos++
|
||||
|
||||
if arg == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := arg.(type) {
|
||||
case int64:
|
||||
buf = strconv.AppendInt(buf, v, 10)
|
||||
case float64:
|
||||
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
|
||||
case bool:
|
||||
if v {
|
||||
buf = append(buf, '1')
|
||||
} else {
|
||||
buf = append(buf, '0')
|
||||
}
|
||||
case time.Time:
|
||||
if v.IsZero() {
|
||||
buf = append(buf, "'0000-00-00'"...)
|
||||
} else {
|
||||
v := v.In(mc.cfg.loc)
|
||||
v = v.Add(time.Nanosecond * 500) // To round under microsecond
|
||||
year := v.Year()
|
||||
year100 := year / 100
|
||||
year1 := year % 100
|
||||
month := v.Month()
|
||||
day := v.Day()
|
||||
hour := v.Hour()
|
||||
minute := v.Minute()
|
||||
second := v.Second()
|
||||
micro := v.Nanosecond() / 1000
|
||||
|
||||
buf = append(buf, []byte{
|
||||
'\'',
|
||||
digits10[year100], digits01[year100],
|
||||
digits10[year1], digits01[year1],
|
||||
'-',
|
||||
digits10[month], digits01[month],
|
||||
'-',
|
||||
digits10[day], digits01[day],
|
||||
' ',
|
||||
digits10[hour], digits01[hour],
|
||||
':',
|
||||
digits10[minute], digits01[minute],
|
||||
':',
|
||||
digits10[second], digits01[second],
|
||||
}...)
|
||||
|
||||
if micro != 0 {
|
||||
micro10000 := micro / 10000
|
||||
micro100 := micro / 100 % 100
|
||||
micro1 := micro % 100
|
||||
buf = append(buf, []byte{
|
||||
'.',
|
||||
digits10[micro10000], digits01[micro10000],
|
||||
digits10[micro100], digits01[micro100],
|
||||
digits10[micro1], digits01[micro1],
|
||||
}...)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
}
|
||||
case []byte:
|
||||
if v == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
} else {
|
||||
buf = append(buf, '\'')
|
||||
if mc.status&statusNoBackslashEscapes == 0 {
|
||||
buf = escapeBytesBackslash(buf, v)
|
||||
} else {
|
||||
buf = escapeBytesQuotes(buf, v)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
}
|
||||
case string:
|
||||
buf = append(buf, '\'')
|
||||
if mc.status&statusNoBackslashEscapes == 0 {
|
||||
buf = escapeStringBackslash(buf, v)
|
||||
} else {
|
||||
buf = escapeStringQuotes(buf, v)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
default:
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
|
||||
if len(buf)+4 > mc.maxPacketAllowed {
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
}
|
||||
if argPos != len(args) {
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if len(args) != 0 {
|
||||
if !mc.cfg.interpolateParams {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
|
||||
prepared, err := mc.interpolateParams(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = prepared
|
||||
args = nil
|
||||
}
|
||||
mc.affectedRows = 0
|
||||
mc.insertId = 0
|
||||
|
||||
err := mc.exec(query)
|
||||
if err == nil {
|
||||
return &mysqlResult{
|
||||
affectedRows: int64(mc.affectedRows),
|
||||
insertId: int64(mc.insertId),
|
||||
}, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Internal function to execute commands
|
||||
func (mc *mysqlConn) exec(query string) error {
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comQuery, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil && resLen > 0 {
|
||||
if err = mc.readUntilEOF(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if len(args) != 0 {
|
||||
if !mc.cfg.interpolateParams {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
// try client-side prepare to reduce roundtrip
|
||||
prepared, err := mc.interpolateParams(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = prepared
|
||||
args = nil
|
||||
}
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comQuery, query)
|
||||
if err == nil {
|
||||
// Read Result
|
||||
var resLen int
|
||||
resLen, err = mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
rows := new(textRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen == 0 {
|
||||
// no columns, no more data
|
||||
return emptyRows{}, nil
|
||||
}
|
||||
// Columns
|
||||
rows.columns, err = mc.readColumns(resLen)
|
||||
return rows, err
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Gets the value of the given MySQL System Variable
|
||||
// The returned byte slice is only valid until the next read
|
||||
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
|
||||
// Send command
|
||||
if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
rows := new(textRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
if err := mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dest := make([]driver.Value, resLen)
|
||||
if err = rows.readRow(dest); err == nil {
|
||||
return dest[0].([]byte), mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
154
Godeps/_workspace/src/github.com/go-sql-driver/mysql/const.go
generated
vendored
Normal file
154
Godeps/_workspace/src/github.com/go-sql-driver/mysql/const.go
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
const (
|
||||
minProtocolVersion byte = 10
|
||||
maxPacketSize = 1<<24 - 1
|
||||
timeFormat = "2006-01-02 15:04:05.999999"
|
||||
)
|
||||
|
||||
// MySQL constants documentation:
|
||||
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
||||
|
||||
const (
|
||||
iOK byte = 0x00
|
||||
iLocalInFile byte = 0xfb
|
||||
iEOF byte = 0xfe
|
||||
iERR byte = 0xff
|
||||
)
|
||||
|
||||
type clientFlag uint32
|
||||
|
||||
const (
|
||||
clientLongPassword clientFlag = 1 << iota
|
||||
clientFoundRows
|
||||
clientLongFlag
|
||||
clientConnectWithDB
|
||||
clientNoSchema
|
||||
clientCompress
|
||||
clientODBC
|
||||
clientLocalFiles
|
||||
clientIgnoreSpace
|
||||
clientProtocol41
|
||||
clientInteractive
|
||||
clientSSL
|
||||
clientIgnoreSIGPIPE
|
||||
clientTransactions
|
||||
clientReserved
|
||||
clientSecureConn
|
||||
clientMultiStatements
|
||||
clientMultiResults
|
||||
)
|
||||
|
||||
const (
|
||||
comQuit byte = iota + 1
|
||||
comInitDB
|
||||
comQuery
|
||||
comFieldList
|
||||
comCreateDB
|
||||
comDropDB
|
||||
comRefresh
|
||||
comShutdown
|
||||
comStatistics
|
||||
comProcessInfo
|
||||
comConnect
|
||||
comProcessKill
|
||||
comDebug
|
||||
comPing
|
||||
comTime
|
||||
comDelayedInsert
|
||||
comChangeUser
|
||||
comBinlogDump
|
||||
comTableDump
|
||||
comConnectOut
|
||||
comRegisterSlave
|
||||
comStmtPrepare
|
||||
comStmtExecute
|
||||
comStmtSendLongData
|
||||
comStmtClose
|
||||
comStmtReset
|
||||
comSetOption
|
||||
comStmtFetch
|
||||
)
|
||||
|
||||
const (
|
||||
fieldTypeDecimal byte = iota
|
||||
fieldTypeTiny
|
||||
fieldTypeShort
|
||||
fieldTypeLong
|
||||
fieldTypeFloat
|
||||
fieldTypeDouble
|
||||
fieldTypeNULL
|
||||
fieldTypeTimestamp
|
||||
fieldTypeLongLong
|
||||
fieldTypeInt24
|
||||
fieldTypeDate
|
||||
fieldTypeTime
|
||||
fieldTypeDateTime
|
||||
fieldTypeYear
|
||||
fieldTypeNewDate
|
||||
fieldTypeVarChar
|
||||
fieldTypeBit
|
||||
)
|
||||
const (
|
||||
fieldTypeNewDecimal byte = iota + 0xf6
|
||||
fieldTypeEnum
|
||||
fieldTypeSet
|
||||
fieldTypeTinyBLOB
|
||||
fieldTypeMediumBLOB
|
||||
fieldTypeLongBLOB
|
||||
fieldTypeBLOB
|
||||
fieldTypeVarString
|
||||
fieldTypeString
|
||||
fieldTypeGeometry
|
||||
)
|
||||
|
||||
type fieldFlag uint16
|
||||
|
||||
const (
|
||||
flagNotNULL fieldFlag = 1 << iota
|
||||
flagPriKey
|
||||
flagUniqueKey
|
||||
flagMultipleKey
|
||||
flagBLOB
|
||||
flagUnsigned
|
||||
flagZeroFill
|
||||
flagBinary
|
||||
flagEnum
|
||||
flagAutoIncrement
|
||||
flagTimestamp
|
||||
flagSet
|
||||
flagUnknown1
|
||||
flagUnknown2
|
||||
flagUnknown3
|
||||
flagUnknown4
|
||||
)
|
||||
|
||||
// http://dev.mysql.com/doc/internals/en/status-flags.html
|
||||
|
||||
type statusFlag uint16
|
||||
|
||||
const (
|
||||
statusInTrans statusFlag = 1 << iota
|
||||
statusInAutocommit
|
||||
statusReserved // Not in documentation
|
||||
statusMoreResultsExists
|
||||
statusNoGoodIndexUsed
|
||||
statusNoIndexUsed
|
||||
statusCursorExists
|
||||
statusLastRowSent
|
||||
statusDbDropped
|
||||
statusNoBackslashEscapes
|
||||
statusMetadataChanged
|
||||
statusQueryWasSlow
|
||||
statusPsOutParams
|
||||
statusInTransReadonly
|
||||
statusSessionStateChanged
|
||||
)
|
||||
140
Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver.go
generated
vendored
Normal file
140
Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// The driver should be used via the database/sql package:
|
||||
//
|
||||
// import "database/sql"
|
||||
// import _ "github.com/go-sql-driver/mysql"
|
||||
//
|
||||
// db, err := sql.Open("mysql", "user:password@/dbname")
|
||||
//
|
||||
// See https://github.com/go-sql-driver/mysql#usage for details
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"net"
|
||||
)
|
||||
|
||||
// This struct is exported to make the driver directly accessible.
|
||||
// In general the driver is used via the database/sql package.
|
||||
type MySQLDriver struct{}
|
||||
|
||||
// DialFunc is a function which can be used to establish the network connection.
|
||||
// Custom dial functions must be registered with RegisterDial
|
||||
type DialFunc func(addr string) (net.Conn, error)
|
||||
|
||||
var dials map[string]DialFunc
|
||||
|
||||
// RegisterDial registers a custom dial function. It can then be used by the
|
||||
// network address mynet(addr), where mynet is the registered new network.
|
||||
// addr is passed as a parameter to the dial function.
|
||||
func RegisterDial(net string, dial DialFunc) {
|
||||
if dials == nil {
|
||||
dials = make(map[string]DialFunc)
|
||||
}
|
||||
dials[net] = dial
|
||||
}
|
||||
|
||||
// Open new Connection.
|
||||
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
||||
// the DSN string is formated
|
||||
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||
var err error
|
||||
|
||||
// New mysqlConn
|
||||
mc := &mysqlConn{
|
||||
maxPacketAllowed: maxPacketSize,
|
||||
maxWriteSize: maxPacketSize - 1,
|
||||
}
|
||||
mc.cfg, err = parseDSN(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Connect to Server
|
||||
if dial, ok := dials[mc.cfg.net]; ok {
|
||||
mc.netConn, err = dial(mc.cfg.addr)
|
||||
} else {
|
||||
nd := net.Dialer{Timeout: mc.cfg.timeout}
|
||||
mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Enable TCP Keepalives on TCP connections
|
||||
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
||||
if err := tc.SetKeepAlive(true); err != nil {
|
||||
// Don't send COM_QUIT before handshake.
|
||||
mc.netConn.Close()
|
||||
mc.netConn = nil
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mc.buf = newBuffer(mc.netConn)
|
||||
|
||||
// Reading Handshake Initialization Packet
|
||||
cipher, err := mc.readInitPacket()
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send Client Authentication Packet
|
||||
if err = mc.writeAuthPacket(cipher); err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read Result Packet
|
||||
err = mc.readResultOK()
|
||||
if err != nil {
|
||||
// Retry with old authentication method, if allowed
|
||||
if mc.cfg != nil && mc.cfg.allowOldPasswords && err == ErrOldPassword {
|
||||
if err = mc.writeOldAuthPacket(cipher); err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err = mc.readResultOK(); err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Get max allowed packet size
|
||||
maxap, err := mc.getSystemVar("max_allowed_packet")
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
mc.maxPacketAllowed = stringToInt(maxap) - 1
|
||||
if mc.maxPacketAllowed < maxPacketSize {
|
||||
mc.maxWriteSize = mc.maxPacketAllowed
|
||||
}
|
||||
|
||||
// Handle DSN Params
|
||||
err = mc.handleParams()
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mc, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
sql.Register("mysql", &MySQLDriver{})
|
||||
}
|
||||
1614
Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver_test.go
generated
vendored
Normal file
1614
Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
129
Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors.go
generated
vendored
Normal file
129
Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Various errors the driver might return. Can change between driver versions.
|
||||
var (
|
||||
ErrInvalidConn = errors.New("Invalid Connection")
|
||||
ErrMalformPkt = errors.New("Malformed Packet")
|
||||
ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS")
|
||||
ErrOldPassword = errors.New("This server only supports the insecure old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
|
||||
ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+")
|
||||
ErrPktSync = errors.New("Commands out of sync. You can't run this command now")
|
||||
ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")
|
||||
ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
|
||||
ErrBusyBuffer = errors.New("Busy buffer")
|
||||
)
|
||||
|
||||
var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
// Logger is used to log critical error messages.
|
||||
type Logger interface {
|
||||
Print(v ...interface{})
|
||||
}
|
||||
|
||||
// SetLogger is used to set the logger for critical errors.
|
||||
// The initial logger is os.Stderr.
|
||||
func SetLogger(logger Logger) error {
|
||||
if logger == nil {
|
||||
return errors.New("logger is nil")
|
||||
}
|
||||
errLog = logger
|
||||
return nil
|
||||
}
|
||||
|
||||
// MySQLError is an error type which represents a single MySQL error
|
||||
type MySQLError struct {
|
||||
Number uint16
|
||||
Message string
|
||||
}
|
||||
|
||||
func (me *MySQLError) Error() string {
|
||||
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
|
||||
}
|
||||
|
||||
// MySQLWarnings is an error type which represents a group of one or more MySQL
|
||||
// warnings
|
||||
type MySQLWarnings []MySQLWarning
|
||||
|
||||
func (mws MySQLWarnings) Error() string {
|
||||
var msg string
|
||||
for i, warning := range mws {
|
||||
if i > 0 {
|
||||
msg += "\r\n"
|
||||
}
|
||||
msg += fmt.Sprintf(
|
||||
"%s %s: %s",
|
||||
warning.Level,
|
||||
warning.Code,
|
||||
warning.Message,
|
||||
)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// MySQLWarning is an error type which represents a single MySQL warning.
|
||||
// Warnings are returned in groups only. See MySQLWarnings
|
||||
type MySQLWarning struct {
|
||||
Level string
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) getWarnings() (err error) {
|
||||
rows, err := mc.Query("SHOW WARNINGS", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var warnings = MySQLWarnings{}
|
||||
var values = make([]driver.Value, 3)
|
||||
|
||||
for {
|
||||
err = rows.Next(values)
|
||||
switch err {
|
||||
case nil:
|
||||
warning := MySQLWarning{}
|
||||
|
||||
if raw, ok := values[0].([]byte); ok {
|
||||
warning.Level = string(raw)
|
||||
} else {
|
||||
warning.Level = fmt.Sprintf("%s", values[0])
|
||||
}
|
||||
if raw, ok := values[1].([]byte); ok {
|
||||
warning.Code = string(raw)
|
||||
} else {
|
||||
warning.Code = fmt.Sprintf("%s", values[1])
|
||||
}
|
||||
if raw, ok := values[2].([]byte); ok {
|
||||
warning.Message = string(raw)
|
||||
} else {
|
||||
warning.Message = fmt.Sprintf("%s", values[0])
|
||||
}
|
||||
|
||||
warnings = append(warnings, warning)
|
||||
|
||||
case io.EOF:
|
||||
return warnings
|
||||
|
||||
default:
|
||||
rows.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors_test.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors_test.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestErrorsSetLogger(t *testing.T) {
|
||||
previous := errLog
|
||||
defer func() {
|
||||
errLog = previous
|
||||
}()
|
||||
|
||||
// set up logger
|
||||
const expected = "prefix: test\n"
|
||||
buffer := bytes.NewBuffer(make([]byte, 0, 64))
|
||||
logger := log.New(buffer, "prefix: ", 0)
|
||||
|
||||
// print
|
||||
SetLogger(logger)
|
||||
errLog.Print("test")
|
||||
|
||||
// check result
|
||||
if actual := buffer.String(); actual != expected {
|
||||
t.Errorf("expected %q, got %q", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorsStrictIgnoreNotes(t *testing.T) {
|
||||
runTests(t, dsn+"&sql_notes=false", func(dbt *DBTest) {
|
||||
dbt.mustExec("DROP TABLE IF EXISTS does_not_exist")
|
||||
})
|
||||
}
|
||||
162
Godeps/_workspace/src/github.com/go-sql-driver/mysql/infile.go
generated
vendored
Normal file
162
Godeps/_workspace/src/github.com/go-sql-driver/mysql/infile.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
fileRegister map[string]bool
|
||||
readerRegister map[string]func() io.Reader
|
||||
)
|
||||
|
||||
// RegisterLocalFile adds the given file to the file whitelist,
|
||||
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
|
||||
// Alternatively you can allow the use of all local files with
|
||||
// the DSN parameter 'allowAllFiles=true'
|
||||
//
|
||||
// filePath := "/home/gopher/data.csv"
|
||||
// mysql.RegisterLocalFile(filePath)
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterLocalFile(filePath string) {
|
||||
// lazy map init
|
||||
if fileRegister == nil {
|
||||
fileRegister = make(map[string]bool)
|
||||
}
|
||||
|
||||
fileRegister[strings.Trim(filePath, `"`)] = true
|
||||
}
|
||||
|
||||
// DeregisterLocalFile removes the given filepath from the whitelist.
|
||||
func DeregisterLocalFile(filePath string) {
|
||||
delete(fileRegister, strings.Trim(filePath, `"`))
|
||||
}
|
||||
|
||||
// RegisterReaderHandler registers a handler function which is used
|
||||
// to receive a io.Reader.
|
||||
// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
|
||||
// If the handler returns a io.ReadCloser Close() is called when the
|
||||
// request is finished.
|
||||
//
|
||||
// mysql.RegisterReaderHandler("data", func() io.Reader {
|
||||
// var csvReader io.Reader // Some Reader that returns CSV data
|
||||
// ... // Open Reader here
|
||||
// return csvReader
|
||||
// })
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterReaderHandler(name string, handler func() io.Reader) {
|
||||
// lazy map init
|
||||
if readerRegister == nil {
|
||||
readerRegister = make(map[string]func() io.Reader)
|
||||
}
|
||||
|
||||
readerRegister[name] = handler
|
||||
}
|
||||
|
||||
// DeregisterReaderHandler removes the ReaderHandler function with
|
||||
// the given name from the registry.
|
||||
func DeregisterReaderHandler(name string) {
|
||||
delete(readerRegister, name)
|
||||
}
|
||||
|
||||
func deferredClose(err *error, closer io.Closer) {
|
||||
closeErr := closer.Close()
|
||||
if *err == nil {
|
||||
*err = closeErr
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
|
||||
var rdr io.Reader
|
||||
var data []byte
|
||||
|
||||
if strings.HasPrefix(name, "Reader::") { // io.Reader
|
||||
name = name[8:]
|
||||
if handler, inMap := readerRegister[name]; inMap {
|
||||
rdr = handler()
|
||||
if rdr != nil {
|
||||
data = make([]byte, 4+mc.maxWriteSize)
|
||||
|
||||
if cl, ok := rdr.(io.Closer); ok {
|
||||
defer deferredClose(&err, cl)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Reader '%s' is <nil>", name)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Reader '%s' is not registered", name)
|
||||
}
|
||||
} else { // File
|
||||
name = strings.Trim(name, `"`)
|
||||
if mc.cfg.allowAllFiles || fileRegister[name] {
|
||||
var file *os.File
|
||||
var fi os.FileInfo
|
||||
|
||||
if file, err = os.Open(name); err == nil {
|
||||
defer deferredClose(&err, file)
|
||||
|
||||
// get file size
|
||||
if fi, err = file.Stat(); err == nil {
|
||||
rdr = file
|
||||
if fileSize := int(fi.Size()); fileSize <= mc.maxWriteSize {
|
||||
data = make([]byte, 4+fileSize)
|
||||
} else if fileSize <= mc.maxPacketAllowed {
|
||||
data = make([]byte, 4+mc.maxWriteSize)
|
||||
} else {
|
||||
err = fmt.Errorf("Local File '%s' too large: Size: %d, Max: %d", name, fileSize, mc.maxPacketAllowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name)
|
||||
}
|
||||
}
|
||||
|
||||
// send content packets
|
||||
if err == nil {
|
||||
var n int
|
||||
for err == nil {
|
||||
n, err = rdr.Read(data[4:])
|
||||
if n > 0 {
|
||||
if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
// send empty packet (termination)
|
||||
if data == nil {
|
||||
data = make([]byte, 4)
|
||||
}
|
||||
if ioErr := mc.writePacket(data[:4]); ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
|
||||
// read OK packet
|
||||
if err == nil {
|
||||
return mc.readResultOK()
|
||||
} else {
|
||||
mc.readPacket()
|
||||
}
|
||||
return err
|
||||
}
|
||||
1138
Godeps/_workspace/src/github.com/go-sql-driver/mysql/packets.go
generated
vendored
Normal file
1138
Godeps/_workspace/src/github.com/go-sql-driver/mysql/packets.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
Godeps/_workspace/src/github.com/go-sql-driver/mysql/result.go
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/go-sql-driver/mysql/result.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
type mysqlResult struct {
|
||||
affectedRows int64
|
||||
insertId int64
|
||||
}
|
||||
|
||||
func (res *mysqlResult) LastInsertId() (int64, error) {
|
||||
return res.insertId, nil
|
||||
}
|
||||
|
||||
func (res *mysqlResult) RowsAffected() (int64, error) {
|
||||
return res.affectedRows, nil
|
||||
}
|
||||
102
Godeps/_workspace/src/github.com/go-sql-driver/mysql/rows.go
generated
vendored
Normal file
102
Godeps/_workspace/src/github.com/go-sql-driver/mysql/rows.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"io"
|
||||
)
|
||||
|
||||
type mysqlField struct {
|
||||
tableName string
|
||||
name string
|
||||
flags fieldFlag
|
||||
fieldType byte
|
||||
decimals byte
|
||||
}
|
||||
|
||||
type mysqlRows struct {
|
||||
mc *mysqlConn
|
||||
columns []mysqlField
|
||||
}
|
||||
|
||||
type binaryRows struct {
|
||||
mysqlRows
|
||||
}
|
||||
|
||||
type textRows struct {
|
||||
mysqlRows
|
||||
}
|
||||
|
||||
type emptyRows struct{}
|
||||
|
||||
func (rows *mysqlRows) Columns() []string {
|
||||
columns := make([]string, len(rows.columns))
|
||||
if rows.mc.cfg.columnsWithAlias {
|
||||
for i := range columns {
|
||||
columns[i] = rows.columns[i].tableName + "." + rows.columns[i].name
|
||||
}
|
||||
} else {
|
||||
for i := range columns {
|
||||
columns[i] = rows.columns[i].name
|
||||
}
|
||||
}
|
||||
return columns
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) Close() error {
|
||||
mc := rows.mc
|
||||
if mc == nil {
|
||||
return nil
|
||||
}
|
||||
if mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
// Remove unread packets from stream
|
||||
err := mc.readUntilEOF()
|
||||
rows.mc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (rows *binaryRows) Next(dest []driver.Value) error {
|
||||
if mc := rows.mc; mc != nil {
|
||||
if mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest)
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (rows *textRows) Next(dest []driver.Value) error {
|
||||
if mc := rows.mc; mc != nil {
|
||||
if mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest)
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (rows emptyRows) Columns() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rows emptyRows) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rows emptyRows) Next(dest []driver.Value) error {
|
||||
return io.EOF
|
||||
}
|
||||
112
Godeps/_workspace/src/github.com/go-sql-driver/mysql/statement.go
generated
vendored
Normal file
112
Godeps/_workspace/src/github.com/go-sql-driver/mysql/statement.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
)
|
||||
|
||||
type mysqlStmt struct {
|
||||
mc *mysqlConn
|
||||
id uint32
|
||||
paramCount int
|
||||
columns []mysqlField // cached from the first query
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Close() error {
|
||||
if stmt.mc == nil || stmt.mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
|
||||
err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
|
||||
stmt.mc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) NumInput() int {
|
||||
return stmt.paramCount
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
if stmt.mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mc := stmt.mc
|
||||
|
||||
mc.affectedRows = 0
|
||||
mc.insertId = 0
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
err = mc.readUntilEOF()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rows
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
if err == nil {
|
||||
return &mysqlResult{
|
||||
affectedRows: int64(mc.affectedRows),
|
||||
insertId: int64(mc.insertId),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
if stmt.mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mc := stmt.mc
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows := new(binaryRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
// If not cached, read them and cache them
|
||||
if stmt.columns == nil {
|
||||
rows.columns, err = mc.readColumns(resLen)
|
||||
stmt.columns = rows.columns
|
||||
} else {
|
||||
rows.columns = stmt.columns
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
31
Godeps/_workspace/src/github.com/go-sql-driver/mysql/transaction.go
generated
vendored
Normal file
31
Godeps/_workspace/src/github.com/go-sql-driver/mysql/transaction.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
type mysqlTx struct {
|
||||
mc *mysqlConn
|
||||
}
|
||||
|
||||
func (tx *mysqlTx) Commit() (err error) {
|
||||
if tx.mc == nil || tx.mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
err = tx.mc.exec("COMMIT")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *mysqlTx) Rollback() (err error) {
|
||||
if tx.mc == nil || tx.mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
err = tx.mc.exec("ROLLBACK")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
||||
963
Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils.go
generated
vendored
Normal file
963
Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils.go
generated
vendored
Normal file
@@ -0,0 +1,963 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
|
||||
|
||||
errInvalidDSNUnescaped = errors.New("Invalid DSN: Did you forget to escape a param value?")
|
||||
errInvalidDSNAddr = errors.New("Invalid DSN: Network Address not terminated (missing closing brace)")
|
||||
errInvalidDSNNoSlash = errors.New("Invalid DSN: Missing the slash separating the database name")
|
||||
errInvalidDSNUnsafeCollation = errors.New("Invalid DSN: interpolateParams can be used with ascii, latin1, utf8 and utf8mb4 charset")
|
||||
)
|
||||
|
||||
func init() {
|
||||
tlsConfigRegister = make(map[string]*tls.Config)
|
||||
}
|
||||
|
||||
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
||||
// Use the key as a value in the DSN where tls=value.
|
||||
//
|
||||
// rootCertPool := x509.NewCertPool()
|
||||
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
|
||||
// log.Fatal("Failed to append PEM.")
|
||||
// }
|
||||
// clientCert := make([]tls.Certificate, 0, 1)
|
||||
// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// clientCert = append(clientCert, certs)
|
||||
// mysql.RegisterTLSConfig("custom", &tls.Config{
|
||||
// RootCAs: rootCertPool,
|
||||
// Certificates: clientCert,
|
||||
// })
|
||||
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
||||
//
|
||||
func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
|
||||
return fmt.Errorf("Key '%s' is reserved", key)
|
||||
}
|
||||
|
||||
tlsConfigRegister[key] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeregisterTLSConfig removes the tls.Config associated with key.
|
||||
func DeregisterTLSConfig(key string) {
|
||||
delete(tlsConfigRegister, key)
|
||||
}
|
||||
|
||||
// parseDSN parses the DSN string to a config
|
||||
func parseDSN(dsn string) (cfg *config, err error) {
|
||||
// New config with some default values
|
||||
cfg = &config{
|
||||
loc: time.UTC,
|
||||
collation: defaultCollation,
|
||||
}
|
||||
|
||||
// TODO: use strings.IndexByte when we can depend on Go 1.2
|
||||
|
||||
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||
foundSlash := false
|
||||
for i := len(dsn) - 1; i >= 0; i-- {
|
||||
if dsn[i] == '/' {
|
||||
foundSlash = true
|
||||
var j, k int
|
||||
|
||||
// left part is empty if i <= 0
|
||||
if i > 0 {
|
||||
// [username[:password]@][protocol[(address)]]
|
||||
// Find the last '@' in dsn[:i]
|
||||
for j = i; j >= 0; j-- {
|
||||
if dsn[j] == '@' {
|
||||
// username[:password]
|
||||
// Find the first ':' in dsn[:j]
|
||||
for k = 0; k < j; k++ {
|
||||
if dsn[k] == ':' {
|
||||
cfg.passwd = dsn[k+1 : j]
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.user = dsn[:k]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// [protocol[(address)]]
|
||||
// Find the first '(' in dsn[j+1:i]
|
||||
for k = j + 1; k < i; k++ {
|
||||
if dsn[k] == '(' {
|
||||
// dsn[i-1] must be == ')' if an address is specified
|
||||
if dsn[i-1] != ')' {
|
||||
if strings.ContainsRune(dsn[k+1:i], ')') {
|
||||
return nil, errInvalidDSNUnescaped
|
||||
}
|
||||
return nil, errInvalidDSNAddr
|
||||
}
|
||||
cfg.addr = dsn[k+1 : i-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.net = dsn[j+1 : k]
|
||||
}
|
||||
|
||||
// dbname[?param1=value1&...¶mN=valueN]
|
||||
// Find the first '?' in dsn[i+1:]
|
||||
for j = i + 1; j < len(dsn); j++ {
|
||||
if dsn[j] == '?' {
|
||||
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.dbname = dsn[i+1 : j]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundSlash && len(dsn) > 0 {
|
||||
return nil, errInvalidDSNNoSlash
|
||||
}
|
||||
|
||||
if cfg.interpolateParams && unsafeCollations[cfg.collation] {
|
||||
return nil, errInvalidDSNUnsafeCollation
|
||||
}
|
||||
|
||||
// Set default network if empty
|
||||
if cfg.net == "" {
|
||||
cfg.net = "tcp"
|
||||
}
|
||||
|
||||
// Set default address if empty
|
||||
if cfg.addr == "" {
|
||||
switch cfg.net {
|
||||
case "tcp":
|
||||
cfg.addr = "127.0.0.1:3306"
|
||||
case "unix":
|
||||
cfg.addr = "/tmp/mysql.sock"
|
||||
default:
|
||||
return nil, errors.New("Default addr for network '" + cfg.net + "' unknown")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseDSNParams parses the DSN "query string"
|
||||
// Values must be url.QueryEscape'ed
|
||||
func parseDSNParams(cfg *config, params string) (err error) {
|
||||
for _, v := range strings.Split(params, "&") {
|
||||
param := strings.SplitN(v, "=", 2)
|
||||
if len(param) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// cfg params
|
||||
switch value := param[1]; param[0] {
|
||||
|
||||
// Enable client side placeholder substitution
|
||||
case "interpolateParams":
|
||||
var isBool bool
|
||||
cfg.interpolateParams, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Disable INFILE whitelist / enable all files
|
||||
case "allowAllFiles":
|
||||
var isBool bool
|
||||
cfg.allowAllFiles, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Use old authentication mode (pre MySQL 4.1)
|
||||
case "allowOldPasswords":
|
||||
var isBool bool
|
||||
cfg.allowOldPasswords, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Switch "rowsAffected" mode
|
||||
case "clientFoundRows":
|
||||
var isBool bool
|
||||
cfg.clientFoundRows, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Collation
|
||||
case "collation":
|
||||
collation, ok := collations[value]
|
||||
if !ok {
|
||||
// Note possibility for false negatives:
|
||||
// could be triggered although the collation is valid if the
|
||||
// collations map does not contain entries the server supports.
|
||||
err = errors.New("unknown collation")
|
||||
return
|
||||
}
|
||||
cfg.collation = collation
|
||||
break
|
||||
|
||||
case "columnsWithAlias":
|
||||
var isBool bool
|
||||
cfg.columnsWithAlias, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Time Location
|
||||
case "loc":
|
||||
if value, err = url.QueryUnescape(value); err != nil {
|
||||
return
|
||||
}
|
||||
cfg.loc, err = time.LoadLocation(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Dial Timeout
|
||||
case "timeout":
|
||||
cfg.timeout, err = time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TLS-Encryption
|
||||
case "tls":
|
||||
boolValue, isBool := readBool(value)
|
||||
if isBool {
|
||||
if boolValue {
|
||||
cfg.tls = &tls.Config{}
|
||||
}
|
||||
} else {
|
||||
if strings.ToLower(value) == "skip-verify" {
|
||||
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
||||
} else if tlsConfig, ok := tlsConfigRegister[value]; ok {
|
||||
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
|
||||
host, _, err := net.SplitHostPort(cfg.addr)
|
||||
if err == nil {
|
||||
tlsConfig.ServerName = host
|
||||
}
|
||||
}
|
||||
|
||||
cfg.tls = tlsConfig
|
||||
} else {
|
||||
return fmt.Errorf("Invalid value / unknown config name: %s", value)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// lazy init
|
||||
if cfg.params == nil {
|
||||
cfg.params = make(map[string]string)
|
||||
}
|
||||
|
||||
if cfg.params[param[0]], err = url.QueryUnescape(value); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the bool value of the input.
|
||||
// The 2nd return value indicates if the input was a valid bool value
|
||||
func readBool(input string) (value bool, valid bool) {
|
||||
switch input {
|
||||
case "1", "true", "TRUE", "True":
|
||||
return true, true
|
||||
case "0", "false", "FALSE", "False":
|
||||
return false, true
|
||||
}
|
||||
|
||||
// Not a valid bool value
|
||||
return
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Authentication *
|
||||
******************************************************************************/
|
||||
|
||||
// Encrypt password using 4.1+ method
|
||||
func scramblePassword(scramble, password []byte) []byte {
|
||||
if len(password) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// stage1Hash = SHA1(password)
|
||||
crypt := sha1.New()
|
||||
crypt.Write(password)
|
||||
stage1 := crypt.Sum(nil)
|
||||
|
||||
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
||||
// inner Hash
|
||||
crypt.Reset()
|
||||
crypt.Write(stage1)
|
||||
hash := crypt.Sum(nil)
|
||||
|
||||
// outer Hash
|
||||
crypt.Reset()
|
||||
crypt.Write(scramble)
|
||||
crypt.Write(hash)
|
||||
scramble = crypt.Sum(nil)
|
||||
|
||||
// token = scrambleHash XOR stage1Hash
|
||||
for i := range scramble {
|
||||
scramble[i] ^= stage1[i]
|
||||
}
|
||||
return scramble
|
||||
}
|
||||
|
||||
// Encrypt password using pre 4.1 (old password) method
|
||||
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
||||
type myRnd struct {
|
||||
seed1, seed2 uint32
|
||||
}
|
||||
|
||||
const myRndMaxVal = 0x3FFFFFFF
|
||||
|
||||
// Pseudo random number generator
|
||||
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
||||
return &myRnd{
|
||||
seed1: seed1 % myRndMaxVal,
|
||||
seed2: seed2 % myRndMaxVal,
|
||||
}
|
||||
}
|
||||
|
||||
// Tested to be equivalent to MariaDB's floating point variant
|
||||
// http://play.golang.org/p/QHvhd4qved
|
||||
// http://play.golang.org/p/RG0q4ElWDx
|
||||
func (r *myRnd) NextByte() byte {
|
||||
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
||||
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
||||
|
||||
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
||||
}
|
||||
|
||||
// Generate binary hash from byte string using insecure pre 4.1 method
|
||||
func pwHash(password []byte) (result [2]uint32) {
|
||||
var add uint32 = 7
|
||||
var tmp uint32
|
||||
|
||||
result[0] = 1345345333
|
||||
result[1] = 0x12345671
|
||||
|
||||
for _, c := range password {
|
||||
// skip spaces and tabs in password
|
||||
if c == ' ' || c == '\t' {
|
||||
continue
|
||||
}
|
||||
|
||||
tmp = uint32(c)
|
||||
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
||||
result[1] += (result[1] << 8) ^ result[0]
|
||||
add += tmp
|
||||
}
|
||||
|
||||
// Remove sign bit (1<<31)-1)
|
||||
result[0] &= 0x7FFFFFFF
|
||||
result[1] &= 0x7FFFFFFF
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Encrypt password using insecure pre 4.1 method
|
||||
func scrambleOldPassword(scramble, password []byte) []byte {
|
||||
if len(password) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
scramble = scramble[:8]
|
||||
|
||||
hashPw := pwHash(password)
|
||||
hashSc := pwHash(scramble)
|
||||
|
||||
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
||||
|
||||
var out [8]byte
|
||||
for i := range out {
|
||||
out[i] = r.NextByte() + 64
|
||||
}
|
||||
|
||||
mask := r.NextByte()
|
||||
for i := range out {
|
||||
out[i] ^= mask
|
||||
}
|
||||
|
||||
return out[:]
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Time related utils *
|
||||
******************************************************************************/
|
||||
|
||||
// NullTime represents a time.Time that may be NULL.
|
||||
// NullTime implements the Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var nt NullTime
|
||||
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||
// ...
|
||||
// if nt.Valid {
|
||||
// // use nt.Time
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
// This NullTime implementation is not driver-specific
|
||||
type NullTime struct {
|
||||
Time time.Time
|
||||
Valid bool // Valid is true if Time is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
// The value type must be time.Time or string / []byte (formatted time-string),
|
||||
// otherwise Scan fails.
|
||||
func (nt *NullTime) Scan(value interface{}) (err error) {
|
||||
if value == nil {
|
||||
nt.Time, nt.Valid = time.Time{}, false
|
||||
return
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
nt.Time, nt.Valid = v, true
|
||||
return
|
||||
case []byte:
|
||||
nt.Time, err = parseDateTime(string(v), time.UTC)
|
||||
nt.Valid = (err == nil)
|
||||
return
|
||||
case string:
|
||||
nt.Time, err = parseDateTime(v, time.UTC)
|
||||
nt.Valid = (err == nil)
|
||||
return
|
||||
}
|
||||
|
||||
nt.Valid = false
|
||||
return fmt.Errorf("Can't convert %T to time.Time", value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nt NullTime) Value() (driver.Value, error) {
|
||||
if !nt.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return nt.Time, nil
|
||||
}
|
||||
|
||||
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
||||
base := "0000-00-00 00:00:00.0000000"
|
||||
switch len(str) {
|
||||
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
|
||||
if str == base[:len(str)] {
|
||||
return
|
||||
}
|
||||
t, err = time.Parse(timeFormat[:len(str)], str)
|
||||
default:
|
||||
err = fmt.Errorf("Invalid Time-String: %s", str)
|
||||
return
|
||||
}
|
||||
|
||||
// Adjust location
|
||||
if err == nil && loc != time.UTC {
|
||||
y, mo, d := t.Date()
|
||||
h, mi, s := t.Clock()
|
||||
t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
|
||||
switch num {
|
||||
case 0:
|
||||
return time.Time{}, nil
|
||||
case 4:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
0, 0, 0, 0,
|
||||
loc,
|
||||
), nil
|
||||
case 7:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
0,
|
||||
loc,
|
||||
), nil
|
||||
case 11:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
|
||||
loc,
|
||||
), nil
|
||||
}
|
||||
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
|
||||
}
|
||||
|
||||
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
|
||||
// if the DATE or DATETIME has the zero value.
|
||||
// It must never be changed.
|
||||
// The current behavior depends on database/sql copying the result.
|
||||
var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
|
||||
|
||||
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
|
||||
|
||||
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
|
||||
// length expects the deterministic length of the zero value,
|
||||
// negative time and 100+ hours are automatically added if needed
|
||||
if len(src) == 0 {
|
||||
if justTime {
|
||||
return zeroDateTime[11 : 11+length], nil
|
||||
}
|
||||
return zeroDateTime[:length], nil
|
||||
}
|
||||
var dst []byte // return value
|
||||
var pt, p1, p2, p3 byte // current digit pair
|
||||
var zOffs byte // offset of value in zeroDateTime
|
||||
if justTime {
|
||||
switch length {
|
||||
case
|
||||
8, // time (can be up to 10 when negative and 100+ hours)
|
||||
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
||||
default:
|
||||
return nil, fmt.Errorf("illegal TIME length %d", length)
|
||||
}
|
||||
switch len(src) {
|
||||
case 8, 12:
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid TIME-packet length %d", len(src))
|
||||
}
|
||||
// +2 to enable negative time and 100+ hours
|
||||
dst = make([]byte, 0, length+2)
|
||||
if src[0] == 1 {
|
||||
dst = append(dst, '-')
|
||||
}
|
||||
if src[1] != 0 {
|
||||
hour := uint16(src[1])*24 + uint16(src[5])
|
||||
pt = byte(hour / 100)
|
||||
p1 = byte(hour - 100*uint16(pt))
|
||||
dst = append(dst, digits01[pt])
|
||||
} else {
|
||||
p1 = src[5]
|
||||
}
|
||||
zOffs = 11
|
||||
src = src[6:]
|
||||
} else {
|
||||
switch length {
|
||||
case 10, 19, 21, 22, 23, 24, 25, 26:
|
||||
default:
|
||||
t := "DATE"
|
||||
if length > 10 {
|
||||
t += "TIME"
|
||||
}
|
||||
return nil, fmt.Errorf("illegal %s length %d", t, length)
|
||||
}
|
||||
switch len(src) {
|
||||
case 4, 7, 11:
|
||||
default:
|
||||
t := "DATE"
|
||||
if length > 10 {
|
||||
t += "TIME"
|
||||
}
|
||||
return nil, fmt.Errorf("illegal %s-packet length %d", t, len(src))
|
||||
}
|
||||
dst = make([]byte, 0, length)
|
||||
// start with the date
|
||||
year := binary.LittleEndian.Uint16(src[:2])
|
||||
pt = byte(year / 100)
|
||||
p1 = byte(year - 100*uint16(pt))
|
||||
p2, p3 = src[2], src[3]
|
||||
dst = append(dst,
|
||||
digits10[pt], digits01[pt],
|
||||
digits10[p1], digits01[p1], '-',
|
||||
digits10[p2], digits01[p2], '-',
|
||||
digits10[p3], digits01[p3],
|
||||
)
|
||||
if length == 10 {
|
||||
return dst, nil
|
||||
}
|
||||
if len(src) == 4 {
|
||||
return append(dst, zeroDateTime[10:length]...), nil
|
||||
}
|
||||
dst = append(dst, ' ')
|
||||
p1 = src[4] // hour
|
||||
src = src[5:]
|
||||
}
|
||||
// p1 is 2-digit hour, src is after hour
|
||||
p2, p3 = src[0], src[1]
|
||||
dst = append(dst,
|
||||
digits10[p1], digits01[p1], ':',
|
||||
digits10[p2], digits01[p2], ':',
|
||||
digits10[p3], digits01[p3],
|
||||
)
|
||||
if length <= byte(len(dst)) {
|
||||
return dst, nil
|
||||
}
|
||||
src = src[2:]
|
||||
if len(src) == 0 {
|
||||
return append(dst, zeroDateTime[19:zOffs+length]...), nil
|
||||
}
|
||||
microsecs := binary.LittleEndian.Uint32(src[:4])
|
||||
p1 = byte(microsecs / 10000)
|
||||
microsecs -= 10000 * uint32(p1)
|
||||
p2 = byte(microsecs / 100)
|
||||
microsecs -= 100 * uint32(p2)
|
||||
p3 = byte(microsecs)
|
||||
switch decimals := zOffs + length - 20; decimals {
|
||||
default:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
digits10[p3], digits01[p3],
|
||||
), nil
|
||||
case 1:
|
||||
return append(dst, '.',
|
||||
digits10[p1],
|
||||
), nil
|
||||
case 2:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
), nil
|
||||
case 3:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2],
|
||||
), nil
|
||||
case 4:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
), nil
|
||||
case 5:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
digits10[p3],
|
||||
), nil
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Convert from and to bytes *
|
||||
******************************************************************************/
|
||||
|
||||
func uint64ToBytes(n uint64) []byte {
|
||||
return []byte{
|
||||
byte(n),
|
||||
byte(n >> 8),
|
||||
byte(n >> 16),
|
||||
byte(n >> 24),
|
||||
byte(n >> 32),
|
||||
byte(n >> 40),
|
||||
byte(n >> 48),
|
||||
byte(n >> 56),
|
||||
}
|
||||
}
|
||||
|
||||
func uint64ToString(n uint64) []byte {
|
||||
var a [20]byte
|
||||
i := 20
|
||||
|
||||
// U+0030 = 0
|
||||
// ...
|
||||
// U+0039 = 9
|
||||
|
||||
var q uint64
|
||||
for n >= 10 {
|
||||
i--
|
||||
q = n / 10
|
||||
a[i] = uint8(n-q*10) + 0x30
|
||||
n = q
|
||||
}
|
||||
|
||||
i--
|
||||
a[i] = uint8(n) + 0x30
|
||||
|
||||
return a[i:]
|
||||
}
|
||||
|
||||
// treats string value as unsigned integer representation
|
||||
func stringToInt(b []byte) int {
|
||||
val := 0
|
||||
for i := range b {
|
||||
val *= 10
|
||||
val += int(b[i] - 0x30)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// returns the string read as a bytes slice, wheter the value is NULL,
|
||||
// the number of bytes read and an error, in case the string is longer than
|
||||
// the input slice
|
||||
func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
|
||||
// Get length
|
||||
num, isNull, n := readLengthEncodedInteger(b)
|
||||
if num < 1 {
|
||||
return b[n:n], isNull, n, nil
|
||||
}
|
||||
|
||||
n += int(num)
|
||||
|
||||
// Check data length
|
||||
if len(b) >= n {
|
||||
return b[n-int(num) : n], false, n, nil
|
||||
}
|
||||
return nil, false, n, io.EOF
|
||||
}
|
||||
|
||||
// returns the number of bytes skipped and an error, in case the string is
|
||||
// longer than the input slice
|
||||
func skipLengthEncodedString(b []byte) (int, error) {
|
||||
// Get length
|
||||
num, _, n := readLengthEncodedInteger(b)
|
||||
if num < 1 {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
n += int(num)
|
||||
|
||||
// Check data length
|
||||
if len(b) >= n {
|
||||
return n, nil
|
||||
}
|
||||
return n, io.EOF
|
||||
}
|
||||
|
||||
// returns the number read, whether the value is NULL and the number of bytes read
|
||||
func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
|
||||
switch b[0] {
|
||||
|
||||
// 251: NULL
|
||||
case 0xfb:
|
||||
return 0, true, 1
|
||||
|
||||
// 252: value of following 2
|
||||
case 0xfc:
|
||||
return uint64(b[1]) | uint64(b[2])<<8, false, 3
|
||||
|
||||
// 253: value of following 3
|
||||
case 0xfd:
|
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
|
||||
|
||||
// 254: value of following 8
|
||||
case 0xfe:
|
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
|
||||
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
|
||||
uint64(b[7])<<48 | uint64(b[8])<<56,
|
||||
false, 9
|
||||
}
|
||||
|
||||
// 0-250: value of first byte
|
||||
return uint64(b[0]), false, 1
|
||||
}
|
||||
|
||||
// encodes a uint64 value and appends it to the given bytes slice
|
||||
func appendLengthEncodedInteger(b []byte, n uint64) []byte {
|
||||
switch {
|
||||
case n <= 250:
|
||||
return append(b, byte(n))
|
||||
|
||||
case n <= 0xffff:
|
||||
return append(b, 0xfc, byte(n), byte(n>>8))
|
||||
|
||||
case n <= 0xffffff:
|
||||
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
|
||||
}
|
||||
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
|
||||
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
|
||||
}
|
||||
|
||||
// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
|
||||
// If cap(buf) is not enough, reallocate new buffer.
|
||||
func reserveBuffer(buf []byte, appendSize int) []byte {
|
||||
newSize := len(buf) + appendSize
|
||||
if cap(buf) < newSize {
|
||||
// Grow buffer exponentially
|
||||
newBuf := make([]byte, len(buf)*2+appendSize)
|
||||
copy(newBuf, buf)
|
||||
buf = newBuf
|
||||
}
|
||||
return buf[:newSize]
|
||||
}
|
||||
|
||||
// escapeBytesBackslash escapes []byte with backslashes (\)
|
||||
// This escapes the contents of a string (provided as []byte) by adding backslashes before special
|
||||
// characters, and turning others into specific escape sequences, such as
|
||||
// turning newlines into \n and null bytes into \0.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
|
||||
func escapeBytesBackslash(buf, v []byte) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for _, c := range v {
|
||||
switch c {
|
||||
case '\x00':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '0'
|
||||
pos += 2
|
||||
case '\n':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'n'
|
||||
pos += 2
|
||||
case '\r':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'r'
|
||||
pos += 2
|
||||
case '\x1a':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'Z'
|
||||
pos += 2
|
||||
case '\'':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
case '"':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '"'
|
||||
pos += 2
|
||||
case '\\':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\\'
|
||||
pos += 2
|
||||
default:
|
||||
buf[pos] = c
|
||||
pos += 1
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeStringBackslash is similar to escapeBytesBackslash but for string.
|
||||
func escapeStringBackslash(buf []byte, v string) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
switch c {
|
||||
case '\x00':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '0'
|
||||
pos += 2
|
||||
case '\n':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'n'
|
||||
pos += 2
|
||||
case '\r':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'r'
|
||||
pos += 2
|
||||
case '\x1a':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'Z'
|
||||
pos += 2
|
||||
case '\'':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
case '"':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '"'
|
||||
pos += 2
|
||||
case '\\':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\\'
|
||||
pos += 2
|
||||
default:
|
||||
buf[pos] = c
|
||||
pos += 1
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.
|
||||
// This escapes the contents of a string by doubling up any apostrophes that
|
||||
// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
|
||||
// effect on the server.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038
|
||||
func escapeBytesQuotes(buf, v []byte) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for _, c := range v {
|
||||
if c == '\'' {
|
||||
buf[pos] = '\''
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
} else {
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeStringQuotes is similar to escapeBytesQuotes but for string.
|
||||
func escapeStringQuotes(buf []byte, v string) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
if c == '\'' {
|
||||
buf[pos] = '\''
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
} else {
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
346
Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils_test.go
generated
vendored
Normal file
346
Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils_test.go
generated
vendored
Normal file
@@ -0,0 +1,346 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var testDSNs = []struct {
|
||||
in string
|
||||
out string
|
||||
loc *time.Location
|
||||
}{
|
||||
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:true interpolateParams:false}", time.UTC},
|
||||
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8mb4,utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{user:user passwd:p@ss(word) net:tcp addr:[de:ad:be:ef::ca:fe]:80 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.Local},
|
||||
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
{"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC},
|
||||
}
|
||||
|
||||
func TestDSNParser(t *testing.T) {
|
||||
var cfg *config
|
||||
var err error
|
||||
var res string
|
||||
|
||||
for i, tst := range testDSNs {
|
||||
cfg, err = parseDSN(tst.in)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
// pointer not static
|
||||
cfg.tls = nil
|
||||
|
||||
res = fmt.Sprintf("%+v", cfg)
|
||||
if res != fmt.Sprintf(tst.out, tst.loc) {
|
||||
t.Errorf("%d. parseDSN(%q) => %q, want %q", i, tst.in, res, fmt.Sprintf(tst.out, tst.loc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSNParserInvalid(t *testing.T) {
|
||||
var invalidDSNs = []string{
|
||||
"@net(addr/", // no closing brace
|
||||
"@tcp(/", // no closing brace
|
||||
"tcp(/", // no closing brace
|
||||
"(/", // no closing brace
|
||||
"net(addr)//", // unescaped
|
||||
"user:pass@tcp(1.2.3.4:3306)", // no trailing slash
|
||||
//"/dbname?arg=/some/unescaped/path",
|
||||
}
|
||||
|
||||
for i, tst := range invalidDSNs {
|
||||
if _, err := parseDSN(tst); err == nil {
|
||||
t.Errorf("invalid DSN #%d. (%s) didn't error!", i, tst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSNWithCustomTLS(t *testing.T) {
|
||||
baseDSN := "user:password@tcp(localhost:5555)/dbname?tls="
|
||||
tlsCfg := tls.Config{}
|
||||
|
||||
RegisterTLSConfig("utils_test", &tlsCfg)
|
||||
|
||||
// Custom TLS is missing
|
||||
tst := baseDSN + "invalid_tls"
|
||||
cfg, err := parseDSN(tst)
|
||||
if err == nil {
|
||||
t.Errorf("Invalid custom TLS in DSN (%s) but did not error. Got config: %#v", tst, cfg)
|
||||
}
|
||||
|
||||
tst = baseDSN + "utils_test"
|
||||
|
||||
// Custom TLS with a server name
|
||||
name := "foohost"
|
||||
tlsCfg.ServerName = name
|
||||
cfg, err = parseDSN(tst)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if cfg.tls.ServerName != name {
|
||||
t.Errorf("Did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, tst)
|
||||
}
|
||||
|
||||
// Custom TLS without a server name
|
||||
name = "localhost"
|
||||
tlsCfg.ServerName = ""
|
||||
cfg, err = parseDSN(tst)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if cfg.tls.ServerName != name {
|
||||
t.Errorf("Did not get the correct ServerName (%s) parsing DSN (%s).", name, tst)
|
||||
}
|
||||
|
||||
DeregisterTLSConfig("utils_test")
|
||||
}
|
||||
|
||||
func TestDSNUnsafeCollation(t *testing.T) {
|
||||
_, err := parseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=true")
|
||||
if err != errInvalidDSNUnsafeCollation {
|
||||
t.Error("Expected %v, Got %v", errInvalidDSNUnsafeCollation, err)
|
||||
}
|
||||
|
||||
_, err = parseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=false")
|
||||
if err != nil {
|
||||
t.Error("Expected %v, Got %v", nil, err)
|
||||
}
|
||||
|
||||
_, err = parseDSN("/dbname?collation=gbk_chinese_ci")
|
||||
if err != nil {
|
||||
t.Error("Expected %v, Got %v", nil, err)
|
||||
}
|
||||
|
||||
_, err = parseDSN("/dbname?collation=ascii_bin&interpolateParams=true")
|
||||
if err != nil {
|
||||
t.Error("Expected %v, Got %v", nil, err)
|
||||
}
|
||||
|
||||
_, err = parseDSN("/dbname?collation=latin1_german1_ci&interpolateParams=true")
|
||||
if err != nil {
|
||||
t.Error("Expected %v, Got %v", nil, err)
|
||||
}
|
||||
|
||||
_, err = parseDSN("/dbname?collation=utf8_general_ci&interpolateParams=true")
|
||||
if err != nil {
|
||||
t.Error("Expected %v, Got %v", nil, err)
|
||||
}
|
||||
|
||||
_, err = parseDSN("/dbname?collation=utf8mb4_general_ci&interpolateParams=true")
|
||||
if err != nil {
|
||||
t.Error("Expected %v, Got %v", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseDSN(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, tst := range testDSNs {
|
||||
if _, err := parseDSN(tst.in); err != nil {
|
||||
b.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanNullTime(t *testing.T) {
|
||||
var scanTests = []struct {
|
||||
in interface{}
|
||||
error bool
|
||||
valid bool
|
||||
time time.Time
|
||||
}{
|
||||
{tDate, false, true, tDate},
|
||||
{sDate, false, true, tDate},
|
||||
{[]byte(sDate), false, true, tDate},
|
||||
{tDateTime, false, true, tDateTime},
|
||||
{sDateTime, false, true, tDateTime},
|
||||
{[]byte(sDateTime), false, true, tDateTime},
|
||||
{tDate0, false, true, tDate0},
|
||||
{sDate0, false, true, tDate0},
|
||||
{[]byte(sDate0), false, true, tDate0},
|
||||
{sDateTime0, false, true, tDate0},
|
||||
{[]byte(sDateTime0), false, true, tDate0},
|
||||
{"", true, false, tDate0},
|
||||
{"1234", true, false, tDate0},
|
||||
{0, true, false, tDate0},
|
||||
}
|
||||
|
||||
var nt = NullTime{}
|
||||
var err error
|
||||
|
||||
for _, tst := range scanTests {
|
||||
err = nt.Scan(tst.in)
|
||||
if (err != nil) != tst.error {
|
||||
t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil))
|
||||
}
|
||||
if nt.Valid != tst.valid {
|
||||
t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid)
|
||||
}
|
||||
if nt.Time != tst.time {
|
||||
t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLengthEncodedInteger(t *testing.T) {
|
||||
var integerTests = []struct {
|
||||
num uint64
|
||||
encoded []byte
|
||||
}{
|
||||
{0x0000000000000000, []byte{0x00}},
|
||||
{0x0000000000000012, []byte{0x12}},
|
||||
{0x00000000000000fa, []byte{0xfa}},
|
||||
{0x0000000000000100, []byte{0xfc, 0x00, 0x01}},
|
||||
{0x0000000000001234, []byte{0xfc, 0x34, 0x12}},
|
||||
{0x000000000000ffff, []byte{0xfc, 0xff, 0xff}},
|
||||
{0x0000000000010000, []byte{0xfd, 0x00, 0x00, 0x01}},
|
||||
{0x0000000000123456, []byte{0xfd, 0x56, 0x34, 0x12}},
|
||||
{0x0000000000ffffff, []byte{0xfd, 0xff, 0xff, 0xff}},
|
||||
{0x0000000001000000, []byte{0xfe, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}},
|
||||
{0x123456789abcdef0, []byte{0xfe, 0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12}},
|
||||
{0xffffffffffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||
}
|
||||
|
||||
for _, tst := range integerTests {
|
||||
num, isNull, numLen := readLengthEncodedInteger(tst.encoded)
|
||||
if isNull {
|
||||
t.Errorf("%x: expected %d, got NULL", tst.encoded, tst.num)
|
||||
}
|
||||
if num != tst.num {
|
||||
t.Errorf("%x: expected %d, got %d", tst.encoded, tst.num, num)
|
||||
}
|
||||
if numLen != len(tst.encoded) {
|
||||
t.Errorf("%x: expected size %d, got %d", tst.encoded, len(tst.encoded), numLen)
|
||||
}
|
||||
encoded := appendLengthEncodedInteger(nil, num)
|
||||
if !bytes.Equal(encoded, tst.encoded) {
|
||||
t.Errorf("%v: expected %x, got %x", num, tst.encoded, encoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOldPass(t *testing.T) {
|
||||
scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2}
|
||||
vectors := []struct {
|
||||
pass string
|
||||
out string
|
||||
}{
|
||||
{" pass", "47575c5a435b4251"},
|
||||
{"pass ", "47575c5a435b4251"},
|
||||
{"123\t456", "575c47505b5b5559"},
|
||||
{"C0mpl!ca ted#PASS123", "5d5d554849584a45"},
|
||||
}
|
||||
for _, tuple := range vectors {
|
||||
ours := scrambleOldPassword(scramble, []byte(tuple.pass))
|
||||
if tuple.out != fmt.Sprintf("%x", ours) {
|
||||
t.Errorf("Failed old password %q", tuple.pass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatBinaryDateTime(t *testing.T) {
|
||||
rawDate := [11]byte{}
|
||||
binary.LittleEndian.PutUint16(rawDate[:2], 1978) // years
|
||||
rawDate[2] = 12 // months
|
||||
rawDate[3] = 30 // days
|
||||
rawDate[4] = 15 // hours
|
||||
rawDate[5] = 46 // minutes
|
||||
rawDate[6] = 23 // seconds
|
||||
binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
|
||||
expect := func(expected string, inlen, outlen uint8) {
|
||||
actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false)
|
||||
bytes, ok := actual.([]byte)
|
||||
if !ok {
|
||||
t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
|
||||
}
|
||||
if string(bytes) != expected {
|
||||
t.Errorf(
|
||||
"expected %q, got %q for length in %d, out %d",
|
||||
bytes, actual, inlen, outlen,
|
||||
)
|
||||
}
|
||||
}
|
||||
expect("0000-00-00", 0, 10)
|
||||
expect("0000-00-00 00:00:00", 0, 19)
|
||||
expect("1978-12-30", 4, 10)
|
||||
expect("1978-12-30 15:46:23", 7, 19)
|
||||
expect("1978-12-30 15:46:23.987654", 11, 26)
|
||||
}
|
||||
|
||||
func TestEscapeBackslash(t *testing.T) {
|
||||
expect := func(expected, value string) {
|
||||
actual := string(escapeBytesBackslash([]byte{}, []byte(value)))
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"expected %s, got %s",
|
||||
expected, actual,
|
||||
)
|
||||
}
|
||||
|
||||
actual = string(escapeStringBackslash([]byte{}, value))
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"expected %s, got %s",
|
||||
expected, actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
expect("foo\\0bar", "foo\x00bar")
|
||||
expect("foo\\nbar", "foo\nbar")
|
||||
expect("foo\\rbar", "foo\rbar")
|
||||
expect("foo\\Zbar", "foo\x1abar")
|
||||
expect("foo\\\"bar", "foo\"bar")
|
||||
expect("foo\\\\bar", "foo\\bar")
|
||||
expect("foo\\'bar", "foo'bar")
|
||||
}
|
||||
|
||||
func TestEscapeQuotes(t *testing.T) {
|
||||
expect := func(expected, value string) {
|
||||
actual := string(escapeBytesQuotes([]byte{}, []byte(value)))
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"expected %s, got %s",
|
||||
expected, actual,
|
||||
)
|
||||
}
|
||||
|
||||
actual = string(escapeStringQuotes([]byte{}, value))
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"expected %s, got %s",
|
||||
expected, actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
expect("foo\x00bar", "foo\x00bar") // not affected
|
||||
expect("foo\nbar", "foo\nbar") // not affected
|
||||
expect("foo\rbar", "foo\rbar") // not affected
|
||||
expect("foo\x1abar", "foo\x1abar") // not affected
|
||||
expect("foo''bar", "foo'bar") // affected
|
||||
expect("foo\"bar", "foo\"bar") // not affected
|
||||
}
|
||||
14
Godeps/_workspace/src/github.com/google/go-github/github/activity.go
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/google/go-github/github/activity.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
// ActivityService handles communication with the activity related
|
||||
// methods of the GitHub API.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/
|
||||
type ActivityService struct {
|
||||
client *Client
|
||||
}
|
||||
305
Godeps/_workspace/src/github.com/google/go-github/github/activity_events.go
generated
vendored
Normal file
305
Godeps/_workspace/src/github.com/google/go-github/github/activity_events.go
generated
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Event represents a GitHub event.
|
||||
type Event struct {
|
||||
Type *string `json:"type,omitempty"`
|
||||
Public *bool `json:"public"`
|
||||
RawPayload *json.RawMessage `json:"payload,omitempty"`
|
||||
Repo *Repository `json:"repo,omitempty"`
|
||||
Actor *User `json:"actor,omitempty"`
|
||||
Org *Organization `json:"org,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
ID *string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func (e Event) String() string {
|
||||
return Stringify(e)
|
||||
}
|
||||
|
||||
// Payload returns the parsed event payload. For recognized event types
|
||||
// (PushEvent), a value of the corresponding struct type will be returned.
|
||||
func (e *Event) Payload() (payload interface{}) {
|
||||
switch *e.Type {
|
||||
case "PushEvent":
|
||||
payload = &PushEvent{}
|
||||
}
|
||||
if err := json.Unmarshal(*e.RawPayload, &payload); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
// PushEvent represents a git push to a GitHub repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/events/types/#pushevent
|
||||
type PushEvent struct {
|
||||
PushID *int `json:"push_id,omitempty"`
|
||||
Head *string `json:"head,omitempty"`
|
||||
Ref *string `json:"ref,omitempty"`
|
||||
Size *int `json:"size,omitempty"`
|
||||
Commits []PushEventCommit `json:"commits,omitempty"`
|
||||
Repo *Repository `json:"repository,omitempty"`
|
||||
}
|
||||
|
||||
func (p PushEvent) String() string {
|
||||
return Stringify(p)
|
||||
}
|
||||
|
||||
// PushEventCommit represents a git commit in a GitHub PushEvent.
|
||||
type PushEventCommit struct {
|
||||
SHA *string `json:"sha,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
Author *CommitAuthor `json:"author,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Distinct *bool `json:"distinct,omitempty"`
|
||||
Added []string `json:"added,omitempty"`
|
||||
Removed []string `json:"removed,omitempty"`
|
||||
Modified []string `json:"modified,omitempty"`
|
||||
}
|
||||
|
||||
func (p PushEventCommit) String() string {
|
||||
return Stringify(p)
|
||||
}
|
||||
|
||||
//PullRequestEvent represents the payload delivered by PullRequestEvent webhook
|
||||
type PullRequestEvent struct {
|
||||
Action *string `json:"action,omitempty"`
|
||||
Number *int `json:"number,omitempty"`
|
||||
PullRequest *PullRequest `json:"pull_request,omitempty"`
|
||||
Repo *Repository `json:"repository,omitempty"`
|
||||
Sender *User `json:"sender,omitempty"`
|
||||
}
|
||||
|
||||
// IssueActivityEvent represents the payload delivered by Issue webhook
|
||||
type IssueActivityEvent struct {
|
||||
Action *string `json:"action,omitempty"`
|
||||
Issue *Issue `json:"issue,omitempty"`
|
||||
Repo *Repository `json:"repository,omitempty"`
|
||||
Sender *User `json:"sender,omitempty"`
|
||||
}
|
||||
|
||||
// IssueCommentEvent represents the payload delivered by IssueComment webhook
|
||||
//
|
||||
// This webhook also gets fired for comments on pull requests
|
||||
type IssueCommentEvent struct {
|
||||
Action *string `json:"action,omitempty"`
|
||||
Issue *Issue `json:"issue,omitempty"`
|
||||
Comment *IssueComment `json:"comment,omitempty"`
|
||||
Repo *Repository `json:"repository,omitempty"`
|
||||
Sender *User `json:"sender,omitempty"`
|
||||
}
|
||||
|
||||
// ListEvents drinks from the firehose of all public events across GitHub.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events
|
||||
func (s *ActivityService) ListEvents(opt *ListOptions) ([]Event, *Response, error) {
|
||||
u, err := addOptions("events", opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
events := new([]Event)
|
||||
resp, err := s.client.Do(req, events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *events, resp, err
|
||||
}
|
||||
|
||||
// ListRepositoryEvents lists events for a repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/events/#list-repository-events
|
||||
func (s *ActivityService) ListRepositoryEvents(owner, repo string, opt *ListOptions) ([]Event, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/events", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
events := new([]Event)
|
||||
resp, err := s.client.Do(req, events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *events, resp, err
|
||||
}
|
||||
|
||||
// ListIssueEventsForRepository lists issue events for a repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/events/#list-issue-events-for-a-repository
|
||||
func (s *ActivityService) ListIssueEventsForRepository(owner, repo string, opt *ListOptions) ([]Event, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
events := new([]Event)
|
||||
resp, err := s.client.Do(req, events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *events, resp, err
|
||||
}
|
||||
|
||||
// ListEventsForRepoNetwork lists public events for a network of repositories.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events-for-a-network-of-repositories
|
||||
func (s *ActivityService) ListEventsForRepoNetwork(owner, repo string, opt *ListOptions) ([]Event, *Response, error) {
|
||||
u := fmt.Sprintf("networks/%v/%v/events", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
events := new([]Event)
|
||||
resp, err := s.client.Do(req, events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *events, resp, err
|
||||
}
|
||||
|
||||
// ListEventsForOrganization lists public events for an organization.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events-for-an-organization
|
||||
func (s *ActivityService) ListEventsForOrganization(org string, opt *ListOptions) ([]Event, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/events", org)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
events := new([]Event)
|
||||
resp, err := s.client.Do(req, events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *events, resp, err
|
||||
}
|
||||
|
||||
// ListEventsPerformedByUser lists the events performed by a user. If publicOnly is
|
||||
// true, only public events will be returned.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/events/#list-events-performed-by-a-user
|
||||
func (s *ActivityService) ListEventsPerformedByUser(user string, publicOnly bool, opt *ListOptions) ([]Event, *Response, error) {
|
||||
var u string
|
||||
if publicOnly {
|
||||
u = fmt.Sprintf("users/%v/events/public", user)
|
||||
} else {
|
||||
u = fmt.Sprintf("users/%v/events", user)
|
||||
}
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
events := new([]Event)
|
||||
resp, err := s.client.Do(req, events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *events, resp, err
|
||||
}
|
||||
|
||||
// ListEventsRecievedByUser lists the events recieved by a user. If publicOnly is
|
||||
// true, only public events will be returned.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/events/#list-events-that-a-user-has-received
|
||||
func (s *ActivityService) ListEventsRecievedByUser(user string, publicOnly bool, opt *ListOptions) ([]Event, *Response, error) {
|
||||
var u string
|
||||
if publicOnly {
|
||||
u = fmt.Sprintf("users/%v/received_events/public", user)
|
||||
} else {
|
||||
u = fmt.Sprintf("users/%v/received_events", user)
|
||||
}
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
events := new([]Event)
|
||||
resp, err := s.client.Do(req, events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *events, resp, err
|
||||
}
|
||||
|
||||
// ListUserEventsForOrganization provides the user’s organization dashboard. You
|
||||
// must be authenticated as the user to view this.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/events/#list-events-for-an-organization
|
||||
func (s *ActivityService) ListUserEventsForOrganization(org, user string, opt *ListOptions) ([]Event, *Response, error) {
|
||||
u := fmt.Sprintf("users/%v/events/orgs/%v", user, org)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
events := new([]Event)
|
||||
resp, err := s.client.Do(req, events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *events, resp, err
|
||||
}
|
||||
305
Godeps/_workspace/src/github.com/google/go-github/github/activity_events_test.go
generated
vendored
Normal file
305
Godeps/_workspace/src/github.com/google/go-github/github/activity_events_test.go
generated
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestActivityService_ListEvents(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
events, _, err := client.Activity.ListEvents(opt)
|
||||
if err != nil {
|
||||
t.Errorf("Activities.ListEvents returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Activities.ListEvents returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListRepositoryEvents(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
events, _, err := client.Activity.ListRepositoryEvents("o", "r", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Activities.ListRepositoryEvents returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Activities.ListRepositoryEvents returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListRepositoryEvents_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Activity.ListRepositoryEvents("%", "%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestActivityService_ListIssueEventsForRepository(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
events, _, err := client.Activity.ListIssueEventsForRepository("o", "r", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Activities.ListIssueEventsForRepository returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Activities.ListIssueEventsForRepository returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListIssueEventsForRepository_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Activity.ListIssueEventsForRepository("%", "%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsForRepoNetwork(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/networks/o/r/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
events, _, err := client.Activity.ListEventsForRepoNetwork("o", "r", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Activities.ListEventsForRepoNetwork returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Activities.ListEventsForRepoNetwork returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsForRepoNetwork_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Activity.ListEventsForRepoNetwork("%", "%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsForOrganization(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
events, _, err := client.Activity.ListEventsForOrganization("o", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Activities.ListEventsForOrganization returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Activities.ListEventsForOrganization returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsForOrganization_invalidOrg(t *testing.T) {
|
||||
_, _, err := client.Activity.ListEventsForOrganization("%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsPerformedByUser_all(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/users/u/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
events, _, err := client.Activity.ListEventsPerformedByUser("u", false, opt)
|
||||
if err != nil {
|
||||
t.Errorf("Events.ListPerformedByUser returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Events.ListPerformedByUser returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsPerformedByUser_publicOnly(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/users/u/events/public", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
events, _, err := client.Activity.ListEventsPerformedByUser("u", true, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Events.ListPerformedByUser returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Events.ListPerformedByUser returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsPerformedByUser_invalidUser(t *testing.T) {
|
||||
_, _, err := client.Activity.ListEventsPerformedByUser("%", false, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsRecievedByUser_all(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/users/u/received_events", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
events, _, err := client.Activity.ListEventsRecievedByUser("u", false, opt)
|
||||
if err != nil {
|
||||
t.Errorf("Events.ListRecievedByUser returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Events.ListRecievedUser returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsRecievedByUser_publicOnly(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/users/u/received_events/public", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
events, _, err := client.Activity.ListEventsRecievedByUser("u", true, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Events.ListRecievedByUser returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Events.ListRecievedByUser returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListEventsRecievedByUser_invalidUser(t *testing.T) {
|
||||
_, _, err := client.Activity.ListEventsRecievedByUser("%", false, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestActivityService_ListUserEventsForOrganization(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/users/u/events/orgs/o", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
events, _, err := client.Activity.ListUserEventsForOrganization("o", "u", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Activities.ListUserEventsForOrganization returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Event{{ID: String("1")}, {ID: String("2")}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Activities.ListUserEventsForOrganization returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivity_EventPayload_typed(t *testing.T) {
|
||||
raw := []byte(`{"type": "PushEvent","payload":{"push_id": 1}}`)
|
||||
var event *Event
|
||||
if err := json.Unmarshal(raw, &event); err != nil {
|
||||
t.Fatalf("Unmarshal Event returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &PushEvent{PushID: Int(1)}
|
||||
if !reflect.DeepEqual(event.Payload(), want) {
|
||||
t.Errorf("Event Payload returned %+v, want %+v", event.Payload(), want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEvent_Payload_untyped checks that unrecognized events are parsed to an
|
||||
// interface{} value (instead of being discarded or throwing an error), for
|
||||
// forward compatibility with new event types.
|
||||
func TestActivity_EventPayload_untyped(t *testing.T) {
|
||||
raw := []byte(`{"type": "UnrecognizedEvent","payload":{"field": "val"}}`)
|
||||
var event *Event
|
||||
if err := json.Unmarshal(raw, &event); err != nil {
|
||||
t.Fatalf("Unmarshal Event returned error: %v", err)
|
||||
}
|
||||
|
||||
want := map[string]interface{}{"field": "val"}
|
||||
if !reflect.DeepEqual(event.Payload(), want) {
|
||||
t.Errorf("Event Payload returned %+v, want %+v", event.Payload(), want)
|
||||
}
|
||||
}
|
||||
224
Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications.go
generated
vendored
Normal file
224
Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications.go
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Notification identifies a GitHub notification for a user.
|
||||
type Notification struct {
|
||||
ID *string `json:"id,omitempty"`
|
||||
Repository *Repository `json:"repository,omitempty"`
|
||||
Subject *NotificationSubject `json:"subject,omitempty"`
|
||||
|
||||
// Reason identifies the event that triggered the notification.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#notification-reasons
|
||||
Reason *string `json:"reason,omitempty"`
|
||||
|
||||
Unread *bool `json:"unread,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
LastReadAt *time.Time `json:"last_read_at,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationSubject identifies the subject of a notification.
|
||||
type NotificationSubject struct {
|
||||
Title *string `json:"title,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
LatestCommentURL *string `json:"latest_comment_url,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationListOptions specifies the optional parameters to the
|
||||
// ActivityService.ListNotifications method.
|
||||
type NotificationListOptions struct {
|
||||
All bool `url:"all,omitempty"`
|
||||
Participating bool `url:"participating,omitempty"`
|
||||
Since time.Time `url:"since,omitempty"`
|
||||
}
|
||||
|
||||
// ListNotifications lists all notifications for the authenticated user.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications
|
||||
func (s *ActivityService) ListNotifications(opt *NotificationListOptions) ([]Notification, *Response, error) {
|
||||
u := fmt.Sprintf("notifications")
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var notifications []Notification
|
||||
resp, err := s.client.Do(req, ¬ifications)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return notifications, resp, err
|
||||
}
|
||||
|
||||
// ListRepositoryNotifications lists all notifications in a given repository
|
||||
// for the authenticated user.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications-in-a-repository
|
||||
func (s *ActivityService) ListRepositoryNotifications(owner, repo string, opt *NotificationListOptions) ([]Notification, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var notifications []Notification
|
||||
resp, err := s.client.Do(req, ¬ifications)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return notifications, resp, err
|
||||
}
|
||||
|
||||
type markReadOptions struct {
|
||||
LastReadAt time.Time `url:"last_read_at,omitempty"`
|
||||
}
|
||||
|
||||
// MarkNotificationsRead marks all notifications up to lastRead as read.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#mark-as-read
|
||||
func (s *ActivityService) MarkNotificationsRead(lastRead time.Time) (*Response, error) {
|
||||
u := fmt.Sprintf("notifications")
|
||||
u, err := addOptions(u, markReadOptions{lastRead})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("PUT", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// MarkRepositoryNotificationsRead marks all notifications up to lastRead in
|
||||
// the specified repository as read.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#mark-notifications-as-read-in-a-repository
|
||||
func (s *ActivityService) MarkRepositoryNotificationsRead(owner, repo string, lastRead time.Time) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo)
|
||||
u, err := addOptions(u, markReadOptions{lastRead})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("PUT", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// GetThread gets the specified notification thread.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#view-a-single-thread
|
||||
func (s *ActivityService) GetThread(id string) (*Notification, *Response, error) {
|
||||
u := fmt.Sprintf("notifications/threads/%v", id)
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
notification := new(Notification)
|
||||
resp, err := s.client.Do(req, notification)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return notification, resp, err
|
||||
}
|
||||
|
||||
// MarkThreadRead marks the specified thread as read.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read
|
||||
func (s *ActivityService) MarkThreadRead(id string) (*Response, error) {
|
||||
u := fmt.Sprintf("notifications/threads/%v", id)
|
||||
|
||||
req, err := s.client.NewRequest("PATCH", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// GetThreadSubscription checks to see if the authenticated user is subscribed
|
||||
// to a thread.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#get-a-thread-subscription
|
||||
func (s *ActivityService) GetThreadSubscription(id string) (*Subscription, *Response, error) {
|
||||
u := fmt.Sprintf("notifications/threads/%v/subscription", id)
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sub := new(Subscription)
|
||||
resp, err := s.client.Do(req, sub)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return sub, resp, err
|
||||
}
|
||||
|
||||
// SetThreadSubscription sets the subscription for the specified thread for the
|
||||
// authenticated user.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#set-a-thread-subscription
|
||||
func (s *ActivityService) SetThreadSubscription(id string, subscription *Subscription) (*Subscription, *Response, error) {
|
||||
u := fmt.Sprintf("notifications/threads/%v/subscription", id)
|
||||
|
||||
req, err := s.client.NewRequest("PUT", u, subscription)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sub := new(Subscription)
|
||||
resp, err := s.client.Do(req, sub)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return sub, resp, err
|
||||
}
|
||||
|
||||
// DeleteThreadSubscription deletes the subscription for the specified thread
|
||||
// for the authenticated user.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#delete-a-thread-subscription
|
||||
func (s *ActivityService) DeleteThreadSubscription(id string) (*Response, error) {
|
||||
u := fmt.Sprintf("notifications/threads/%v/subscription", id)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
203
Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications_test.go
generated
vendored
Normal file
203
Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications_test.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestActivityService_ListNotification(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/notifications", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"all": "true",
|
||||
"participating": "true",
|
||||
"since": "2006-01-02T15:04:05Z",
|
||||
})
|
||||
|
||||
fmt.Fprint(w, `[{"id":"1", "subject":{"title":"t"}}]`)
|
||||
})
|
||||
|
||||
opt := &NotificationListOptions{
|
||||
All: true,
|
||||
Participating: true,
|
||||
Since: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC),
|
||||
}
|
||||
notifications, _, err := client.Activity.ListNotifications(opt)
|
||||
if err != nil {
|
||||
t.Errorf("Activity.ListNotifications returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Notification{{ID: String("1"), Subject: &NotificationSubject{Title: String("t")}}}
|
||||
if !reflect.DeepEqual(notifications, want) {
|
||||
t.Errorf("Activity.ListNotifications returned %+v, want %+v", notifications, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListRepositoryNotification(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/notifications", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"id":"1"}]`)
|
||||
})
|
||||
|
||||
notifications, _, err := client.Activity.ListRepositoryNotifications("o", "r", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Activity.ListRepositoryNotifications returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Notification{{ID: String("1")}}
|
||||
if !reflect.DeepEqual(notifications, want) {
|
||||
t.Errorf("Activity.ListRepositoryNotifications returned %+v, want %+v", notifications, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_MarkNotificationsRead(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/notifications", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
testFormValues(t, r, values{
|
||||
"last_read_at": "2006-01-02T15:04:05Z",
|
||||
})
|
||||
|
||||
w.WriteHeader(http.StatusResetContent)
|
||||
})
|
||||
|
||||
_, err := client.Activity.MarkNotificationsRead(time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC))
|
||||
if err != nil {
|
||||
t.Errorf("Activity.MarkNotificationsRead returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_MarkRepositoryNotificationsRead(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/notifications", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
testFormValues(t, r, values{
|
||||
"last_read_at": "2006-01-02T15:04:05Z",
|
||||
})
|
||||
|
||||
w.WriteHeader(http.StatusResetContent)
|
||||
})
|
||||
|
||||
_, err := client.Activity.MarkRepositoryNotificationsRead("o", "r", time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC))
|
||||
if err != nil {
|
||||
t.Errorf("Activity.MarkRepositoryNotificationsRead returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_GetThread(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/notifications/threads/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"id":"1"}`)
|
||||
})
|
||||
|
||||
notification, _, err := client.Activity.GetThread("1")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.GetThread returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Notification{ID: String("1")}
|
||||
if !reflect.DeepEqual(notification, want) {
|
||||
t.Errorf("Activity.GetThread returned %+v, want %+v", notification, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_MarkThreadRead(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/notifications/threads/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PATCH")
|
||||
w.WriteHeader(http.StatusResetContent)
|
||||
})
|
||||
|
||||
_, err := client.Activity.MarkThreadRead("1")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.MarkThreadRead returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_GetThreadSubscription(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"subscribed":true}`)
|
||||
})
|
||||
|
||||
sub, _, err := client.Activity.GetThreadSubscription("1")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.GetThreadSubscription returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Subscription{Subscribed: Bool(true)}
|
||||
if !reflect.DeepEqual(sub, want) {
|
||||
t.Errorf("Activity.GetThreadSubscription returned %+v, want %+v", sub, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_SetThreadSubscription(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Subscription{Subscribed: Bool(true)}
|
||||
|
||||
mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Subscription)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PUT")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"ignored":true}`)
|
||||
})
|
||||
|
||||
sub, _, err := client.Activity.SetThreadSubscription("1", input)
|
||||
if err != nil {
|
||||
t.Errorf("Activity.SetThreadSubscription returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Subscription{Ignored: Bool(true)}
|
||||
if !reflect.DeepEqual(sub, want) {
|
||||
t.Errorf("Activity.SetThreadSubscription returned %+v, want %+v", sub, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_DeleteThreadSubscription(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
_, err := client.Activity.DeleteThreadSubscription("1")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.DeleteThreadSubscription returned error: %v", err)
|
||||
}
|
||||
}
|
||||
114
Godeps/_workspace/src/github.com/google/go-github/github/activity_star.go
generated
vendored
Normal file
114
Godeps/_workspace/src/github.com/google/go-github/github/activity_star.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ListStargazers lists people who have starred the specified repo.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/starring/#list-stargazers
|
||||
func (s *ActivityService) ListStargazers(owner, repo string, opt *ListOptions) ([]User, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%s/%s/stargazers", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
stargazers := new([]User)
|
||||
resp, err := s.client.Do(req, stargazers)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *stargazers, resp, err
|
||||
}
|
||||
|
||||
// ActivityListStarredOptions specifies the optional parameters to the
|
||||
// ActivityService.ListStarred method.
|
||||
type ActivityListStarredOptions struct {
|
||||
// How to sort the repository list. Possible values are: created, updated,
|
||||
// pushed, full_name. Default is "full_name".
|
||||
Sort string `url:"sort,omitempty"`
|
||||
|
||||
// Direction in which to sort repositories. Possible values are: asc, desc.
|
||||
// Default is "asc" when sort is "full_name", otherwise default is "desc".
|
||||
Direction string `url:"direction,omitempty"`
|
||||
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListStarred lists all the repos starred by a user. Passing the empty string
|
||||
// will list the starred repositories for the authenticated user.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/activity/starring/#list-repositories-being-starred
|
||||
func (s *ActivityService) ListStarred(user string, opt *ActivityListStarredOptions) ([]Repository, *Response, error) {
|
||||
var u string
|
||||
if user != "" {
|
||||
u = fmt.Sprintf("users/%v/starred", user)
|
||||
} else {
|
||||
u = "user/starred"
|
||||
}
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repos := new([]Repository)
|
||||
resp, err := s.client.Do(req, repos)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *repos, resp, err
|
||||
}
|
||||
|
||||
// IsStarred checks if a repository is starred by authenticated user.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/activity/starring/#check-if-you-are-starring-a-repository
|
||||
func (s *ActivityService) IsStarred(owner, repo string) (bool, *Response, error) {
|
||||
u := fmt.Sprintf("user/starred/%v/%v", owner, repo)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
resp, err := s.client.Do(req, nil)
|
||||
starred, err := parseBoolResponse(err)
|
||||
return starred, resp, err
|
||||
}
|
||||
|
||||
// Star a repository as the authenticated user.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/activity/starring/#star-a-repository
|
||||
func (s *ActivityService) Star(owner, repo string) (*Response, error) {
|
||||
u := fmt.Sprintf("user/starred/%v/%v", owner, repo)
|
||||
req, err := s.client.NewRequest("PUT", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// Unstar a repository as the authenticated user.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/activity/starring/#unstar-a-repository
|
||||
func (s *ActivityService) Unstar(owner, repo string) (*Response, error) {
|
||||
u := fmt.Sprintf("user/starred/%v/%v", owner, repo)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
167
Godeps/_workspace/src/github.com/google/go-github/github/activity_star_test.go
generated
vendored
Normal file
167
Godeps/_workspace/src/github.com/google/go-github/github/activity_star_test.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestActivityService_ListStargazers(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/stargazers", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
stargazers, _, err := client.Activity.ListStargazers("o", "r", &ListOptions{Page: 2})
|
||||
if err != nil {
|
||||
t.Errorf("Activity.ListStargazers returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []User{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(stargazers, want) {
|
||||
t.Errorf("Activity.ListStargazers returned %+v, want %+v", stargazers, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListStarred_authenticatedUser(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/user/starred", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
repos, _, err := client.Activity.ListStarred("", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Activity.ListStarred returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Repository{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(repos, want) {
|
||||
t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListStarred_specifiedUser(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/users/u/starred", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"sort": "created",
|
||||
"direction": "asc",
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":2}]`)
|
||||
})
|
||||
|
||||
opt := &ActivityListStarredOptions{"created", "asc", ListOptions{Page: 2}}
|
||||
repos, _, err := client.Activity.ListStarred("u", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Activity.ListStarred returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Repository{{ID: Int(2)}}
|
||||
if !reflect.DeepEqual(repos, want) {
|
||||
t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListStarred_invalidUser(t *testing.T) {
|
||||
_, _, err := client.Activity.ListStarred("%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestActivityService_IsStarred_hasStar(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
star, _, err := client.Activity.IsStarred("o", "r")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.IsStarred returned error: %v", err)
|
||||
}
|
||||
if want := true; star != want {
|
||||
t.Errorf("Activity.IsStarred returned %+v, want %+v", star, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_IsStarred_noStar(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
star, _, err := client.Activity.IsStarred("o", "r")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.IsStarred returned error: %v", err)
|
||||
}
|
||||
if want := false; star != want {
|
||||
t.Errorf("Activity.IsStarred returned %+v, want %+v", star, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_IsStarred_invalidID(t *testing.T) {
|
||||
_, _, err := client.Activity.IsStarred("%", "%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestActivityService_Star(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
})
|
||||
|
||||
_, err := client.Activity.Star("o", "r")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.Star returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_Star_invalidID(t *testing.T) {
|
||||
_, err := client.Activity.Star("%", "%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestActivityService_Unstar(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Activity.Unstar("o", "r")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.Unstar returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_Unstar_invalidID(t *testing.T) {
|
||||
_, err := client.Activity.Unstar("%", "%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
131
Godeps/_workspace/src/github.com/google/go-github/github/activity_watching.go
generated
vendored
Normal file
131
Godeps/_workspace/src/github.com/google/go-github/github/activity_watching.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Subscription identifies a repository or thread subscription.
|
||||
type Subscription struct {
|
||||
Subscribed *bool `json:"subscribed,omitempty"`
|
||||
Ignored *bool `json:"ignored,omitempty"`
|
||||
Reason *string `json:"reason,omitempty"`
|
||||
CreatedAt *Timestamp `json:"created_at,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
|
||||
// only populated for repository subscriptions
|
||||
RepositoryURL *string `json:"repository_url,omitempty"`
|
||||
|
||||
// only populated for thread subscriptions
|
||||
ThreadURL *string `json:"thread_url,omitempty"`
|
||||
}
|
||||
|
||||
// ListWatchers lists watchers of a particular repo.
|
||||
//
|
||||
// GitHub API Docs: http://developer.github.com/v3/activity/watching/#list-watchers
|
||||
func (s *ActivityService) ListWatchers(owner, repo string, opt *ListOptions) ([]User, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%s/%s/subscribers", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
watchers := new([]User)
|
||||
resp, err := s.client.Do(req, watchers)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *watchers, resp, err
|
||||
}
|
||||
|
||||
// ListWatched lists the repositories the specified user is watching. Passing
|
||||
// the empty string will fetch watched repos for the authenticated user.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/watching/#list-repositories-being-watched
|
||||
func (s *ActivityService) ListWatched(user string) ([]Repository, *Response, error) {
|
||||
var u string
|
||||
if user != "" {
|
||||
u = fmt.Sprintf("users/%v/subscriptions", user)
|
||||
} else {
|
||||
u = "user/subscriptions"
|
||||
}
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
watched := new([]Repository)
|
||||
resp, err := s.client.Do(req, watched)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *watched, resp, err
|
||||
}
|
||||
|
||||
// GetRepositorySubscription returns the subscription for the specified
|
||||
// repository for the authenticated user. If the authenticated user is not
|
||||
// watching the repository, a nil Subscription is returned.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/watching/#get-a-repository-subscription
|
||||
func (s *ActivityService) GetRepositorySubscription(owner, repo string) (*Subscription, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo)
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sub := new(Subscription)
|
||||
resp, err := s.client.Do(req, sub)
|
||||
if err != nil {
|
||||
// if it's just a 404, don't return that as an error
|
||||
_, err = parseBoolResponse(err)
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return sub, resp, err
|
||||
}
|
||||
|
||||
// SetRepositorySubscription sets the subscription for the specified repository
|
||||
// for the authenticated user.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/watching/#set-a-repository-subscription
|
||||
func (s *ActivityService) SetRepositorySubscription(owner, repo string, subscription *Subscription) (*Subscription, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo)
|
||||
|
||||
req, err := s.client.NewRequest("PUT", u, subscription)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sub := new(Subscription)
|
||||
resp, err := s.client.Do(req, sub)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return sub, resp, err
|
||||
}
|
||||
|
||||
// DeleteRepositorySubscription deletes the subscription for the specified
|
||||
// repository for the authenticated user.
|
||||
//
|
||||
// GitHub API Docs: https://developer.github.com/v3/activity/watching/#delete-a-repository-subscription
|
||||
func (s *ActivityService) DeleteRepositorySubscription(owner, repo string) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
177
Godeps/_workspace/src/github.com/google/go-github/github/activity_watching_test.go
generated
vendored
Normal file
177
Godeps/_workspace/src/github.com/google/go-github/github/activity_watching_test.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestActivityService_ListWatchers(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/subscribers", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "2",
|
||||
})
|
||||
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
watchers, _, err := client.Activity.ListWatchers("o", "r", &ListOptions{Page: 2})
|
||||
if err != nil {
|
||||
t.Errorf("Activity.ListWatchers returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []User{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(watchers, want) {
|
||||
t.Errorf("Activity.ListWatchers returned %+v, want %+v", watchers, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListWatched_authenticatedUser(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/user/subscriptions", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
watched, _, err := client.Activity.ListWatched("")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.ListWatched returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Repository{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(watched, want) {
|
||||
t.Errorf("Activity.ListWatched returned %+v, want %+v", watched, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_ListWatched_specifiedUser(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/users/u/subscriptions", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
watched, _, err := client.Activity.ListWatched("u")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.ListWatched returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Repository{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(watched, want) {
|
||||
t.Errorf("Activity.ListWatched returned %+v, want %+v", watched, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_GetRepositorySubscription_true(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"subscribed":true}`)
|
||||
})
|
||||
|
||||
sub, _, err := client.Activity.GetRepositorySubscription("o", "r")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.GetRepositorySubscription returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Subscription{Subscribed: Bool(true)}
|
||||
if !reflect.DeepEqual(sub, want) {
|
||||
t.Errorf("Activity.GetRepositorySubscription returned %+v, want %+v", sub, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_GetRepositorySubscription_false(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
sub, _, err := client.Activity.GetRepositorySubscription("o", "r")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.GetRepositorySubscription returned error: %v", err)
|
||||
}
|
||||
|
||||
var want *Subscription
|
||||
if !reflect.DeepEqual(sub, want) {
|
||||
t.Errorf("Activity.GetRepositorySubscription returned %+v, want %+v", sub, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_GetRepositorySubscription_error(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
})
|
||||
|
||||
_, _, err := client.Activity.GetRepositorySubscription("o", "r")
|
||||
if err == nil {
|
||||
t.Errorf("Expected HTTP 400 response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_SetRepositorySubscription(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Subscription{Subscribed: Bool(true)}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Subscription)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PUT")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"ignored":true}`)
|
||||
})
|
||||
|
||||
sub, _, err := client.Activity.SetRepositorySubscription("o", "r", input)
|
||||
if err != nil {
|
||||
t.Errorf("Activity.SetRepositorySubscription returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Subscription{Ignored: Bool(true)}
|
||||
if !reflect.DeepEqual(sub, want) {
|
||||
t.Errorf("Activity.SetRepositorySubscription returned %+v, want %+v", sub, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityService_DeleteRepositorySubscription(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
_, err := client.Activity.DeleteRepositorySubscription("o", "r")
|
||||
if err != nil {
|
||||
t.Errorf("Activity.DeleteRepositorySubscription returned error: %v", err)
|
||||
}
|
||||
}
|
||||
137
Godeps/_workspace/src/github.com/google/go-github/github/doc.go
generated
vendored
Normal file
137
Godeps/_workspace/src/github.com/google/go-github/github/doc.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package github provides a client for using the GitHub API.
|
||||
|
||||
Construct a new GitHub client, then use the various services on the client to
|
||||
access different parts of the GitHub API. For example:
|
||||
|
||||
client := github.NewClient(nil)
|
||||
|
||||
// list all organizations for user "willnorris"
|
||||
orgs, _, err := client.Organizations.List("willnorris", nil)
|
||||
|
||||
Set optional parameters for an API method by passing an Options object.
|
||||
|
||||
// list recently updated repositories for org "github"
|
||||
opt := &github.RepositoryListByOrgOptions{Sort: "updated"}
|
||||
repos, _, err := client.Repositories.ListByOrg("github", opt)
|
||||
|
||||
The services of a client divide the API into logical chunks and correspond to
|
||||
the structure of the GitHub API documentation at
|
||||
http://developer.github.com/v3/.
|
||||
|
||||
Authentication
|
||||
|
||||
The go-github library does not directly handle authentication. Instead, when
|
||||
creating a new client, pass an http.Client that can handle authentication for
|
||||
you. The easiest and recommended way to do this is using the golang.org/x/oauth2
|
||||
library, but you can always use any other library that provides an http.Client.
|
||||
If you have an OAuth2 access token (for example, a personal API token), you can
|
||||
use it with the oauth2 library using:
|
||||
|
||||
import "golang.org/x/oauth2"
|
||||
|
||||
// tokenSource is an oauth2.TokenSource which returns a static access token
|
||||
type tokenSource struct {
|
||||
token *oauth2.Token
|
||||
}
|
||||
|
||||
// Token implements the oauth2.TokenSource interface
|
||||
func (t *tokenSource) Token() (*oauth2.Token, error){
|
||||
return t.token, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
ts := &tokenSource{
|
||||
&oauth2.Token{AccessToken: "... your access token ..."},
|
||||
}
|
||||
|
||||
tc := oauth2.NewClient(oauth2.NoContext, ts)
|
||||
|
||||
client := github.NewClient(tc)
|
||||
|
||||
// list all repositories for the authenticated user
|
||||
repos, _, err := client.Repositories.List("", nil)
|
||||
}
|
||||
|
||||
Note that when using an authenticated Client, all calls made by the client will
|
||||
include the specified OAuth token. Therefore, authenticated clients should
|
||||
almost never be shared between different users.
|
||||
|
||||
Rate Limiting
|
||||
|
||||
GitHub imposes a rate limit on all API clients. Unauthenticated clients are
|
||||
limited to 60 requests per hour, while authenticated clients can make up to
|
||||
5,000 requests per hour. To receive the higher rate limit when making calls
|
||||
that are not issued on behalf of a user, use the
|
||||
UnauthenticatedRateLimitedTransport.
|
||||
|
||||
The Rate field on a client tracks the rate limit information based on the most
|
||||
recent API call. This is updated on every call, but may be out of date if it's
|
||||
been some time since the last API call and other clients have made subsequent
|
||||
requests since then. You can always call RateLimit() directly to get the most
|
||||
up-to-date rate limit data for the client.
|
||||
|
||||
Learn more about GitHub rate limiting at
|
||||
http://developer.github.com/v3/#rate-limiting.
|
||||
|
||||
Conditional Requests
|
||||
|
||||
The GitHub API has good support for conditional requests which will help
|
||||
prevent you from burning through your rate limit, as well as help speed up your
|
||||
application. go-github does not handle conditional requests directly, but is
|
||||
instead designed to work with a caching http.Transport. We recommend using
|
||||
https://github.com/gregjones/httpcache, which can be used in conjuction with
|
||||
https://github.com/sourcegraph/apiproxy to provide additional flexibility and
|
||||
control of caching rules.
|
||||
|
||||
Learn more about GitHub conditional requests at
|
||||
https://developer.github.com/v3/#conditional-requests.
|
||||
|
||||
Creating and Updating Resources
|
||||
|
||||
All structs for GitHub resources use pointer values for all non-repeated fields.
|
||||
This allows distinguishing between unset fields and those set to a zero-value.
|
||||
Helper functions have been provided to easily create these pointers for string,
|
||||
bool, and int values. For example:
|
||||
|
||||
// create a new private repository named "foo"
|
||||
repo := &github.Repository{
|
||||
Name: github.String("foo"),
|
||||
Private: github.Bool(true),
|
||||
}
|
||||
client.Repositories.Create("", repo)
|
||||
|
||||
Users who have worked with protocol buffers should find this pattern familiar.
|
||||
|
||||
Pagination
|
||||
|
||||
All requests for resource collections (repos, pull requests, issues, etc)
|
||||
support pagination. Pagination options are described in the
|
||||
ListOptions struct and passed to the list methods directly or as an
|
||||
embedded type of a more specific list options struct (for example
|
||||
PullRequestListOptions). Pages information is available via Response struct.
|
||||
|
||||
opt := &github.RepositoryListByOrgOptions{
|
||||
ListOptions: github.ListOptions{PerPage: 10},
|
||||
}
|
||||
// get all pages of results
|
||||
var allRepos []github.Repository
|
||||
for {
|
||||
repos, resp, err := client.Repositories.ListByOrg("github", opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allRepos = append(allRepos, repos...)
|
||||
if resp.NextPage == 0 {
|
||||
break
|
||||
}
|
||||
opt.ListOptions.Page = resp.NextPage
|
||||
}
|
||||
|
||||
*/
|
||||
package github
|
||||
263
Godeps/_workspace/src/github.com/google/go-github/github/gists.go
generated
vendored
Normal file
263
Godeps/_workspace/src/github.com/google/go-github/github/gists.go
generated
vendored
Normal file
@@ -0,0 +1,263 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GistsService handles communication with the Gist related
|
||||
// methods of the GitHub API.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/
|
||||
type GistsService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Gist represents a GitHub's gist.
|
||||
type Gist struct {
|
||||
ID *string `json:"id,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Public *bool `json:"public,omitempty"`
|
||||
Owner *User `json:"owner,omitempty"`
|
||||
Files map[GistFilename]GistFile `json:"files,omitempty"`
|
||||
Comments *int `json:"comments,omitempty"`
|
||||
HTMLURL *string `json:"html_url,omitempty"`
|
||||
GitPullURL *string `json:"git_pull_url,omitempty"`
|
||||
GitPushURL *string `json:"git_push_url,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
func (g Gist) String() string {
|
||||
return Stringify(g)
|
||||
}
|
||||
|
||||
// GistFilename represents filename on a gist.
|
||||
type GistFilename string
|
||||
|
||||
// GistFile represents a file on a gist.
|
||||
type GistFile struct {
|
||||
Size *int `json:"size,omitempty"`
|
||||
Filename *string `json:"filename,omitempty"`
|
||||
RawURL *string `json:"raw_url,omitempty"`
|
||||
Content *string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
func (g GistFile) String() string {
|
||||
return Stringify(g)
|
||||
}
|
||||
|
||||
// GistListOptions specifies the optional parameters to the
|
||||
// GistsService.List, GistsService.ListAll, and GistsService.ListStarred methods.
|
||||
type GistListOptions struct {
|
||||
// Since filters Gists by time.
|
||||
Since time.Time `url:"since,omitempty"`
|
||||
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// List gists for a user. Passing the empty string will list
|
||||
// all public gists if called anonymously. However, if the call
|
||||
// is authenticated, it will returns all gists for the authenticated
|
||||
// user.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#list-gists
|
||||
func (s *GistsService) List(user string, opt *GistListOptions) ([]Gist, *Response, error) {
|
||||
var u string
|
||||
if user != "" {
|
||||
u = fmt.Sprintf("users/%v/gists", user)
|
||||
} else {
|
||||
u = "gists"
|
||||
}
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
gists := new([]Gist)
|
||||
resp, err := s.client.Do(req, gists)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *gists, resp, err
|
||||
}
|
||||
|
||||
// ListAll lists all public gists.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#list-gists
|
||||
func (s *GistsService) ListAll(opt *GistListOptions) ([]Gist, *Response, error) {
|
||||
u, err := addOptions("gists/public", opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
gists := new([]Gist)
|
||||
resp, err := s.client.Do(req, gists)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *gists, resp, err
|
||||
}
|
||||
|
||||
// ListStarred lists starred gists of authenticated user.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#list-gists
|
||||
func (s *GistsService) ListStarred(opt *GistListOptions) ([]Gist, *Response, error) {
|
||||
u, err := addOptions("gists/starred", opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
gists := new([]Gist)
|
||||
resp, err := s.client.Do(req, gists)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *gists, resp, err
|
||||
}
|
||||
|
||||
// Get a single gist.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#get-a-single-gist
|
||||
func (s *GistsService) Get(id string) (*Gist, *Response, error) {
|
||||
u := fmt.Sprintf("gists/%v", id)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
gist := new(Gist)
|
||||
resp, err := s.client.Do(req, gist)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return gist, resp, err
|
||||
}
|
||||
|
||||
// Create a gist for authenticated user.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#create-a-gist
|
||||
func (s *GistsService) Create(gist *Gist) (*Gist, *Response, error) {
|
||||
u := "gists"
|
||||
req, err := s.client.NewRequest("POST", u, gist)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
g := new(Gist)
|
||||
resp, err := s.client.Do(req, g)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return g, resp, err
|
||||
}
|
||||
|
||||
// Edit a gist.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#edit-a-gist
|
||||
func (s *GistsService) Edit(id string, gist *Gist) (*Gist, *Response, error) {
|
||||
u := fmt.Sprintf("gists/%v", id)
|
||||
req, err := s.client.NewRequest("PATCH", u, gist)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
g := new(Gist)
|
||||
resp, err := s.client.Do(req, g)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return g, resp, err
|
||||
}
|
||||
|
||||
// Delete a gist.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#delete-a-gist
|
||||
func (s *GistsService) Delete(id string) (*Response, error) {
|
||||
u := fmt.Sprintf("gists/%v", id)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// Star a gist on behalf of authenticated user.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#star-a-gist
|
||||
func (s *GistsService) Star(id string) (*Response, error) {
|
||||
u := fmt.Sprintf("gists/%v/star", id)
|
||||
req, err := s.client.NewRequest("PUT", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// Unstar a gist on a behalf of authenticated user.
|
||||
//
|
||||
// Github API docs: http://developer.github.com/v3/gists/#unstar-a-gist
|
||||
func (s *GistsService) Unstar(id string) (*Response, error) {
|
||||
u := fmt.Sprintf("gists/%v/star", id)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// IsStarred checks if a gist is starred by authenticated user.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#check-if-a-gist-is-starred
|
||||
func (s *GistsService) IsStarred(id string) (bool, *Response, error) {
|
||||
u := fmt.Sprintf("gists/%v/star", id)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
resp, err := s.client.Do(req, nil)
|
||||
starred, err := parseBoolResponse(err)
|
||||
return starred, resp, err
|
||||
}
|
||||
|
||||
// Fork a gist.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/#fork-a-gist
|
||||
func (s *GistsService) Fork(id string) (*Gist, *Response, error) {
|
||||
u := fmt.Sprintf("gists/%v/forks", id)
|
||||
req, err := s.client.NewRequest("POST", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
g := new(Gist)
|
||||
resp, err := s.client.Do(req, g)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return g, resp, err
|
||||
}
|
||||
118
Godeps/_workspace/src/github.com/google/go-github/github/gists_comments.go
generated
vendored
Normal file
118
Godeps/_workspace/src/github.com/google/go-github/github/gists_comments.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GistComment represents a Gist comment.
|
||||
type GistComment struct {
|
||||
ID *int `json:"id,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Body *string `json:"body,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
}
|
||||
|
||||
func (g GistComment) String() string {
|
||||
return Stringify(g)
|
||||
}
|
||||
|
||||
// ListComments lists all comments for a gist.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/comments/#list-comments-on-a-gist
|
||||
func (s *GistsService) ListComments(gistID string, opt *ListOptions) ([]GistComment, *Response, error) {
|
||||
u := fmt.Sprintf("gists/%v/comments", gistID)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
comments := new([]GistComment)
|
||||
resp, err := s.client.Do(req, comments)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *comments, resp, err
|
||||
}
|
||||
|
||||
// GetComment retrieves a single comment from a gist.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/comments/#get-a-single-comment
|
||||
func (s *GistsService) GetComment(gistID string, commentID int) (*GistComment, *Response, error) {
|
||||
u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c := new(GistComment)
|
||||
resp, err := s.client.Do(req, c)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return c, resp, err
|
||||
}
|
||||
|
||||
// CreateComment creates a comment for a gist.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/comments/#create-a-comment
|
||||
func (s *GistsService) CreateComment(gistID string, comment *GistComment) (*GistComment, *Response, error) {
|
||||
u := fmt.Sprintf("gists/%v/comments", gistID)
|
||||
req, err := s.client.NewRequest("POST", u, comment)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c := new(GistComment)
|
||||
resp, err := s.client.Do(req, c)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return c, resp, err
|
||||
}
|
||||
|
||||
// EditComment edits an existing gist comment.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/comments/#edit-a-comment
|
||||
func (s *GistsService) EditComment(gistID string, commentID int, comment *GistComment) (*GistComment, *Response, error) {
|
||||
u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID)
|
||||
req, err := s.client.NewRequest("PATCH", u, comment)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c := new(GistComment)
|
||||
resp, err := s.client.Do(req, c)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return c, resp, err
|
||||
}
|
||||
|
||||
// DeleteComment deletes a gist comment.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gists/comments/#delete-a-comment
|
||||
func (s *GistsService) DeleteComment(gistID string, commentID int) (*Response, error) {
|
||||
u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
155
Godeps/_workspace/src/github.com/google/go-github/github/gists_comments_test.go
generated
vendored
Normal file
155
Godeps/_workspace/src/github.com/google/go-github/github/gists_comments_test.go
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGistsService_ListComments(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1/comments", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{"page": "2"})
|
||||
fmt.Fprint(w, `[{"id": 1}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
comments, _, err := client.Gists.ListComments("1", opt)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Comments returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []GistComment{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(comments, want) {
|
||||
t.Errorf("Gists.ListComments returned %+v, want %+v", comments, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_ListComments_invalidID(t *testing.T) {
|
||||
_, _, err := client.Gists.ListComments("%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_GetComment(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"id": 1}`)
|
||||
})
|
||||
|
||||
comment, _, err := client.Gists.GetComment("1", 2)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Gists.GetComment returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &GistComment{ID: Int(1)}
|
||||
if !reflect.DeepEqual(comment, want) {
|
||||
t.Errorf("Gists.GetComment returned %+v, want %+v", comment, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_GetComment_invalidID(t *testing.T) {
|
||||
_, _, err := client.Gists.GetComment("%", 1)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_CreateComment(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &GistComment{ID: Int(1), Body: String("b")}
|
||||
|
||||
mux.HandleFunc("/gists/1/comments", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(GistComment)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"id":1}`)
|
||||
})
|
||||
|
||||
comment, _, err := client.Gists.CreateComment("1", input)
|
||||
if err != nil {
|
||||
t.Errorf("Gists.CreateComment returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &GistComment{ID: Int(1)}
|
||||
if !reflect.DeepEqual(comment, want) {
|
||||
t.Errorf("Gists.CreateComment returned %+v, want %+v", comment, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_CreateComment_invalidID(t *testing.T) {
|
||||
_, _, err := client.Gists.CreateComment("%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_EditComment(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &GistComment{ID: Int(1), Body: String("b")}
|
||||
|
||||
mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(GistComment)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PATCH")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"id":1}`)
|
||||
})
|
||||
|
||||
comment, _, err := client.Gists.EditComment("1", 2, input)
|
||||
if err != nil {
|
||||
t.Errorf("Gists.EditComment returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &GistComment{ID: Int(1)}
|
||||
if !reflect.DeepEqual(comment, want) {
|
||||
t.Errorf("Gists.EditComment returned %+v, want %+v", comment, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_EditComment_invalidID(t *testing.T) {
|
||||
_, _, err := client.Gists.EditComment("%", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_DeleteComment(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Gists.DeleteComment("1", 2)
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Delete returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_DeleteComment_invalidID(t *testing.T) {
|
||||
_, err := client.Gists.DeleteComment("%", 1)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
385
Godeps/_workspace/src/github.com/google/go-github/github/gists_test.go
generated
vendored
Normal file
385
Godeps/_workspace/src/github.com/google/go-github/github/gists_test.go
generated
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGistsService_List_specifiedUser(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
since := "2013-01-01T00:00:00Z"
|
||||
|
||||
mux.HandleFunc("/users/u/gists", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"since": since,
|
||||
})
|
||||
fmt.Fprint(w, `[{"id": "1"}]`)
|
||||
})
|
||||
|
||||
opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)}
|
||||
gists, _, err := client.Gists.List("u", opt)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Gists.List returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Gist{{ID: String("1")}}
|
||||
if !reflect.DeepEqual(gists, want) {
|
||||
t.Errorf("Gists.List returned %+v, want %+v", gists, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_List_authenticatedUser(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"id": "1"}]`)
|
||||
})
|
||||
|
||||
gists, _, err := client.Gists.List("", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Gists.List returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Gist{{ID: String("1")}}
|
||||
if !reflect.DeepEqual(gists, want) {
|
||||
t.Errorf("Gists.List returned %+v, want %+v", gists, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_List_invalidUser(t *testing.T) {
|
||||
_, _, err := client.Gists.List("%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_ListAll(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
since := "2013-01-01T00:00:00Z"
|
||||
|
||||
mux.HandleFunc("/gists/public", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"since": since,
|
||||
})
|
||||
fmt.Fprint(w, `[{"id": "1"}]`)
|
||||
})
|
||||
|
||||
opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)}
|
||||
gists, _, err := client.Gists.ListAll(opt)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Gists.ListAll returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Gist{{ID: String("1")}}
|
||||
if !reflect.DeepEqual(gists, want) {
|
||||
t.Errorf("Gists.ListAll returned %+v, want %+v", gists, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_ListStarred(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
since := "2013-01-01T00:00:00Z"
|
||||
|
||||
mux.HandleFunc("/gists/starred", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"since": since,
|
||||
})
|
||||
fmt.Fprint(w, `[{"id": "1"}]`)
|
||||
})
|
||||
|
||||
opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)}
|
||||
gists, _, err := client.Gists.ListStarred(opt)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Gists.ListStarred returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Gist{{ID: String("1")}}
|
||||
if !reflect.DeepEqual(gists, want) {
|
||||
t.Errorf("Gists.ListStarred returned %+v, want %+v", gists, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_Get(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"id": "1"}`)
|
||||
})
|
||||
|
||||
gist, _, err := client.Gists.Get("1")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Get returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Gist{ID: String("1")}
|
||||
if !reflect.DeepEqual(gist, want) {
|
||||
t.Errorf("Gists.Get returned %+v, want %+v", gist, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_Get_invalidID(t *testing.T) {
|
||||
_, _, err := client.Gists.Get("%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_Create(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Gist{
|
||||
Description: String("Gist description"),
|
||||
Public: Bool(false),
|
||||
Files: map[GistFilename]GistFile{
|
||||
"test.txt": {Content: String("Gist file content")},
|
||||
},
|
||||
}
|
||||
|
||||
mux.HandleFunc("/gists", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Gist)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w,
|
||||
`
|
||||
{
|
||||
"id": "1",
|
||||
"description": "Gist description",
|
||||
"public": false,
|
||||
"files": {
|
||||
"test.txt": {
|
||||
"filename": "test.txt"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
})
|
||||
|
||||
gist, _, err := client.Gists.Create(input)
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Create returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Gist{
|
||||
ID: String("1"),
|
||||
Description: String("Gist description"),
|
||||
Public: Bool(false),
|
||||
Files: map[GistFilename]GistFile{
|
||||
"test.txt": {Filename: String("test.txt")},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(gist, want) {
|
||||
t.Errorf("Gists.Create returned %+v, want %+v", gist, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_Edit(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Gist{
|
||||
Description: String("New description"),
|
||||
Files: map[GistFilename]GistFile{
|
||||
"new.txt": {Content: String("new file content")},
|
||||
},
|
||||
}
|
||||
|
||||
mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Gist)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PATCH")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w,
|
||||
`
|
||||
{
|
||||
"id": "1",
|
||||
"description": "new description",
|
||||
"public": false,
|
||||
"files": {
|
||||
"test.txt": {
|
||||
"filename": "test.txt"
|
||||
},
|
||||
"new.txt": {
|
||||
"filename": "new.txt"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
})
|
||||
|
||||
gist, _, err := client.Gists.Edit("1", input)
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Edit returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Gist{
|
||||
ID: String("1"),
|
||||
Description: String("new description"),
|
||||
Public: Bool(false),
|
||||
Files: map[GistFilename]GistFile{
|
||||
"test.txt": {Filename: String("test.txt")},
|
||||
"new.txt": {Filename: String("new.txt")},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(gist, want) {
|
||||
t.Errorf("Gists.Edit returned %+v, want %+v", gist, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_Edit_invalidID(t *testing.T) {
|
||||
_, _, err := client.Gists.Edit("%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_Delete(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Gists.Delete("1")
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Delete returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_Delete_invalidID(t *testing.T) {
|
||||
_, err := client.Gists.Delete("%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_Star(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
})
|
||||
|
||||
_, err := client.Gists.Star("1")
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Star returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_Star_invalidID(t *testing.T) {
|
||||
_, err := client.Gists.Star("%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_Unstar(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Gists.Unstar("1")
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Unstar returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_Unstar_invalidID(t *testing.T) {
|
||||
_, err := client.Gists.Unstar("%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_IsStarred_hasStar(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
star, _, err := client.Gists.IsStarred("1")
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Starred returned error: %v", err)
|
||||
}
|
||||
if want := true; star != want {
|
||||
t.Errorf("Gists.Starred returned %+v, want %+v", star, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_IsStarred_noStar(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
star, _, err := client.Gists.IsStarred("1")
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Starred returned error: %v", err)
|
||||
}
|
||||
if want := false; star != want {
|
||||
t.Errorf("Gists.Starred returned %+v, want %+v", star, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_IsStarred_invalidID(t *testing.T) {
|
||||
_, _, err := client.Gists.IsStarred("%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGistsService_Fork(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gists/1/forks", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "POST")
|
||||
fmt.Fprint(w, `{"id": "2"}`)
|
||||
})
|
||||
|
||||
gist, _, err := client.Gists.Fork("1")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Gists.Fork returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Gist{ID: String("2")}
|
||||
if !reflect.DeepEqual(gist, want) {
|
||||
t.Errorf("Gists.Fork returned %+v, want %+v", gist, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGistsService_Fork_invalidID(t *testing.T) {
|
||||
_, _, err := client.Gists.Fork("%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
14
Godeps/_workspace/src/github.com/google/go-github/github/git.go
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/google/go-github/github/git.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
// GitService handles communication with the git data related
|
||||
// methods of the GitHub API.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/
|
||||
type GitService struct {
|
||||
client *Client
|
||||
}
|
||||
47
Godeps/_workspace/src/github.com/google/go-github/github/git_blobs.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/google/go-github/github/git_blobs.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Blob represents a blob object.
|
||||
type Blob struct {
|
||||
Content *string `json:"content,omitempty"`
|
||||
Encoding *string `json:"encoding,omitempty"`
|
||||
SHA *string `json:"sha,omitempty"`
|
||||
Size *int `json:"size,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// GetBlob fetchs a blob from a repo given a SHA.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/blobs/#get-a-blob
|
||||
func (s *GitService) GetBlob(owner string, repo string, sha string) (*Blob, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/git/blobs/%v", owner, repo, sha)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
blob := new(Blob)
|
||||
resp, err := s.client.Do(req, blob)
|
||||
return blob, resp, err
|
||||
}
|
||||
|
||||
// CreateBlob creates a blob object.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/blobs/#create-a-blob
|
||||
func (s *GitService) CreateBlob(owner string, repo string, blob *Blob) (*Blob, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/git/blobs", owner, repo)
|
||||
req, err := s.client.NewRequest("POST", u, blob)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
t := new(Blob)
|
||||
resp, err := s.client.Do(req, t)
|
||||
return t, resp, err
|
||||
}
|
||||
92
Godeps/_workspace/src/github.com/google/go-github/github/git_blobs_test.go
generated
vendored
Normal file
92
Godeps/_workspace/src/github.com/google/go-github/github/git_blobs_test.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGitService_GetBlob(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/blobs/s", func(w http.ResponseWriter, r *http.Request) {
|
||||
if m := "GET"; m != r.Method {
|
||||
t.Errorf("Request method = %v, want %v", r.Method, m)
|
||||
}
|
||||
fmt.Fprint(w, `{
|
||||
"sha": "s",
|
||||
"content": "blob content"
|
||||
}`)
|
||||
})
|
||||
|
||||
blob, _, err := client.Git.GetBlob("o", "r", "s")
|
||||
if err != nil {
|
||||
t.Errorf("Git.GetBlob returned error: %v", err)
|
||||
}
|
||||
|
||||
want := Blob{
|
||||
SHA: String("s"),
|
||||
Content: String("blob content"),
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(*blob, want) {
|
||||
t.Errorf("Blob.Get returned %+v, want %+v", *blob, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_GetBlob_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Git.GetBlob("%", "%", "%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGitService_CreateBlob(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Blob{
|
||||
SHA: String("s"),
|
||||
Content: String("blob content"),
|
||||
Encoding: String("utf-8"),
|
||||
Size: Int(12),
|
||||
}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/blobs", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Blob)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
if m := "POST"; m != r.Method {
|
||||
t.Errorf("Request method = %v, want %v", r.Method, m)
|
||||
}
|
||||
|
||||
want := input
|
||||
if !reflect.DeepEqual(v, want) {
|
||||
t.Errorf("Git.CreateBlob request body: %+v, want %+v", v, want)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{
|
||||
"sha": "s",
|
||||
"content": "blob content",
|
||||
"encoding": "utf-8",
|
||||
"size": 12
|
||||
}`)
|
||||
})
|
||||
|
||||
blob, _, err := client.Git.CreateBlob("o", "r", input)
|
||||
if err != nil {
|
||||
t.Errorf("Git.CreateBlob returned error: %v", err)
|
||||
}
|
||||
|
||||
want := input
|
||||
|
||||
if !reflect.DeepEqual(*blob, *want) {
|
||||
t.Errorf("Git.CreateBlob returned %+v, want %+v", *blob, *want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_CreateBlob_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Git.CreateBlob("%", "%", &Blob{})
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
112
Godeps/_workspace/src/github.com/google/go-github/github/git_commits.go
generated
vendored
Normal file
112
Godeps/_workspace/src/github.com/google/go-github/github/git_commits.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Commit represents a GitHub commit.
|
||||
type Commit struct {
|
||||
SHA *string `json:"sha,omitempty"`
|
||||
Author *CommitAuthor `json:"author,omitempty"`
|
||||
Committer *CommitAuthor `json:"committer,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
Tree *Tree `json:"tree,omitempty"`
|
||||
Parents []Commit `json:"parents,omitempty"`
|
||||
Stats *CommitStats `json:"stats,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
|
||||
// CommentCount is the number of GitHub comments on the commit. This
|
||||
// is only populated for requests that fetch GitHub data like
|
||||
// Pulls.ListCommits, Repositories.ListCommits, etc.
|
||||
CommentCount *int `json:"comment_count,omitempty"`
|
||||
}
|
||||
|
||||
func (c Commit) String() string {
|
||||
return Stringify(c)
|
||||
}
|
||||
|
||||
// CommitAuthor represents the author or committer of a commit. The commit
|
||||
// author may not correspond to a GitHub User.
|
||||
type CommitAuthor struct {
|
||||
Date *time.Time `json:"date,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
func (c CommitAuthor) String() string {
|
||||
return Stringify(c)
|
||||
}
|
||||
|
||||
// GetCommit fetchs the Commit object for a given SHA.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/commits/#get-a-commit
|
||||
func (s *GitService) GetCommit(owner string, repo string, sha string) (*Commit, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/git/commits/%v", owner, repo, sha)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c := new(Commit)
|
||||
resp, err := s.client.Do(req, c)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return c, resp, err
|
||||
}
|
||||
|
||||
// createCommit represents the body of a CreateCommit request.
|
||||
type createCommit struct {
|
||||
Author *CommitAuthor `json:"author,omitempty"`
|
||||
Committer *CommitAuthor `json:"committer,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
Tree *string `json:"tree,omitempty"`
|
||||
Parents []string `json:"parents,omitempty"`
|
||||
}
|
||||
|
||||
// CreateCommit creates a new commit in a repository.
|
||||
//
|
||||
// The commit.Committer is optional and will be filled with the commit.Author
|
||||
// data if omitted. If the commit.Author is omitted, it will be filled in with
|
||||
// the authenticated user’s information and the current date.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/commits/#create-a-commit
|
||||
func (s *GitService) CreateCommit(owner string, repo string, commit *Commit) (*Commit, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/git/commits", owner, repo)
|
||||
|
||||
body := &createCommit{}
|
||||
if commit != nil {
|
||||
parents := make([]string, len(commit.Parents))
|
||||
for i, parent := range commit.Parents {
|
||||
parents[i] = *parent.SHA
|
||||
}
|
||||
|
||||
body = &createCommit{
|
||||
Author: commit.Author,
|
||||
Committer: commit.Committer,
|
||||
Message: commit.Message,
|
||||
Tree: commit.Tree.SHA,
|
||||
Parents: parents,
|
||||
}
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("POST", u, body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c := new(Commit)
|
||||
resp, err := s.client.Do(req, c)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return c, resp, err
|
||||
}
|
||||
82
Godeps/_workspace/src/github.com/google/go-github/github/git_commits_test.go
generated
vendored
Normal file
82
Godeps/_workspace/src/github.com/google/go-github/github/git_commits_test.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGitService_GetCommit(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/commits/s", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"sha":"s","message":"m","author":{"name":"n"}}`)
|
||||
})
|
||||
|
||||
commit, _, err := client.Git.GetCommit("o", "r", "s")
|
||||
if err != nil {
|
||||
t.Errorf("Git.GetCommit returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Commit{SHA: String("s"), Message: String("m"), Author: &CommitAuthor{Name: String("n")}}
|
||||
if !reflect.DeepEqual(commit, want) {
|
||||
t.Errorf("Git.GetCommit returned %+v, want %+v", commit, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_GetCommit_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Git.GetCommit("%", "%", "%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGitService_CreateCommit(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Commit{
|
||||
Message: String("m"),
|
||||
Tree: &Tree{SHA: String("t")},
|
||||
Parents: []Commit{{SHA: String("p")}},
|
||||
}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/commits", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(createCommit)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
|
||||
want := &createCommit{
|
||||
Message: input.Message,
|
||||
Tree: String("t"),
|
||||
Parents: []string{"p"},
|
||||
}
|
||||
if !reflect.DeepEqual(v, want) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, want)
|
||||
}
|
||||
fmt.Fprint(w, `{"sha":"s"}`)
|
||||
})
|
||||
|
||||
commit, _, err := client.Git.CreateCommit("o", "r", input)
|
||||
if err != nil {
|
||||
t.Errorf("Git.CreateCommit returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Commit{SHA: String("s")}
|
||||
if !reflect.DeepEqual(commit, want) {
|
||||
t.Errorf("Git.CreateCommit returned %+v, want %+v", commit, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_CreateCommit_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Git.CreateCommit("%", "%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
162
Godeps/_workspace/src/github.com/google/go-github/github/git_refs.go
generated
vendored
Normal file
162
Godeps/_workspace/src/github.com/google/go-github/github/git_refs.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Reference represents a GitHub reference.
|
||||
type Reference struct {
|
||||
Ref *string `json:"ref"`
|
||||
URL *string `json:"url"`
|
||||
Object *GitObject `json:"object"`
|
||||
}
|
||||
|
||||
func (r Reference) String() string {
|
||||
return Stringify(r)
|
||||
}
|
||||
|
||||
// GitObject represents a Git object.
|
||||
type GitObject struct {
|
||||
Type *string `json:"type"`
|
||||
SHA *string `json:"sha"`
|
||||
URL *string `json:"url"`
|
||||
}
|
||||
|
||||
func (o GitObject) String() string {
|
||||
return Stringify(o)
|
||||
}
|
||||
|
||||
// createRefRequest represents the payload for creating a reference.
|
||||
type createRefRequest struct {
|
||||
Ref *string `json:"ref"`
|
||||
SHA *string `json:"sha"`
|
||||
}
|
||||
|
||||
// updateRefRequest represents the payload for updating a reference.
|
||||
type updateRefRequest struct {
|
||||
SHA *string `json:"sha"`
|
||||
Force *bool `json:"force"`
|
||||
}
|
||||
|
||||
// GetRef fetches the Reference object for a given Git ref.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/refs/#get-a-reference
|
||||
func (s *GitService) GetRef(owner string, repo string, ref string) (*Reference, *Response, error) {
|
||||
ref = strings.TrimPrefix(ref, "refs/")
|
||||
u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
r := new(Reference)
|
||||
resp, err := s.client.Do(req, r)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return r, resp, err
|
||||
}
|
||||
|
||||
// ReferenceListOptions specifies optional parameters to the
|
||||
// GitService.ListRefs method.
|
||||
type ReferenceListOptions struct {
|
||||
Type string `url:"-"`
|
||||
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListRefs lists all refs in a repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/refs/#get-all-references
|
||||
func (s *GitService) ListRefs(owner, repo string, opt *ReferenceListOptions) ([]Reference, *Response, error) {
|
||||
var u string
|
||||
if opt != nil && opt.Type != "" {
|
||||
u = fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, opt.Type)
|
||||
} else {
|
||||
u = fmt.Sprintf("repos/%v/%v/git/refs", owner, repo)
|
||||
}
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var rs []Reference
|
||||
resp, err := s.client.Do(req, &rs)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return rs, resp, err
|
||||
}
|
||||
|
||||
// CreateRef creates a new ref in a repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/refs/#create-a-reference
|
||||
func (s *GitService) CreateRef(owner string, repo string, ref *Reference) (*Reference, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/git/refs", owner, repo)
|
||||
req, err := s.client.NewRequest("POST", u, &createRefRequest{
|
||||
// back-compat with previous behavior that didn't require 'refs/' prefix
|
||||
Ref: String("refs/" + strings.TrimPrefix(*ref.Ref, "refs/")),
|
||||
SHA: ref.Object.SHA,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
r := new(Reference)
|
||||
resp, err := s.client.Do(req, r)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return r, resp, err
|
||||
}
|
||||
|
||||
// UpdateRef updates an existing ref in a repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/refs/#update-a-reference
|
||||
func (s *GitService) UpdateRef(owner string, repo string, ref *Reference, force bool) (*Reference, *Response, error) {
|
||||
refPath := strings.TrimPrefix(*ref.Ref, "refs/")
|
||||
u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, refPath)
|
||||
req, err := s.client.NewRequest("PATCH", u, &updateRefRequest{
|
||||
SHA: ref.Object.SHA,
|
||||
Force: &force,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
r := new(Reference)
|
||||
resp, err := s.client.Do(req, r)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return r, resp, err
|
||||
}
|
||||
|
||||
// DeleteRef deletes a ref from a repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/refs/#delete-a-reference
|
||||
func (s *GitService) DeleteRef(owner string, repo string, ref string) (*Response, error) {
|
||||
ref = strings.TrimPrefix(ref, "refs/")
|
||||
u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
280
Godeps/_workspace/src/github.com/google/go-github/github/git_refs_test.go
generated
vendored
Normal file
280
Godeps/_workspace/src/github.com/google/go-github/github/git_refs_test.go
generated
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGitService_GetRef(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `
|
||||
{
|
||||
"ref": "refs/heads/b",
|
||||
"url": "https://api.github.com/repos/o/r/git/refs/heads/b",
|
||||
"object": {
|
||||
"type": "commit",
|
||||
"sha": "aa218f56b14c9653891f9e74264a383fa43fefbd",
|
||||
"url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"
|
||||
}
|
||||
}`)
|
||||
})
|
||||
|
||||
ref, _, err := client.Git.GetRef("o", "r", "refs/heads/b")
|
||||
if err != nil {
|
||||
t.Errorf("Git.GetRef returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Reference{
|
||||
Ref: String("refs/heads/b"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"),
|
||||
Object: &GitObject{
|
||||
Type: String("commit"),
|
||||
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(ref, want) {
|
||||
t.Errorf("Git.GetRef returned %+v, want %+v", ref, want)
|
||||
}
|
||||
|
||||
// without 'refs/' prefix
|
||||
if _, _, err := client.Git.GetRef("o", "r", "heads/b"); err != nil {
|
||||
t.Errorf("Git.GetRef returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_ListRefs(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/refs", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `
|
||||
[
|
||||
{
|
||||
"ref": "refs/heads/branchA",
|
||||
"url": "https://api.github.com/repos/o/r/git/refs/heads/branchA",
|
||||
"object": {
|
||||
"type": "commit",
|
||||
"sha": "aa218f56b14c9653891f9e74264a383fa43fefbd",
|
||||
"url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ref": "refs/heads/branchB",
|
||||
"url": "https://api.github.com/repos/o/r/git/refs/heads/branchB",
|
||||
"object": {
|
||||
"type": "commit",
|
||||
"sha": "aa218f56b14c9653891f9e74264a383fa43fefbd",
|
||||
"url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"
|
||||
}
|
||||
}
|
||||
]`)
|
||||
})
|
||||
|
||||
refs, _, err := client.Git.ListRefs("o", "r", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Git.ListRefs returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Reference{
|
||||
{
|
||||
Ref: String("refs/heads/branchA"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/refs/heads/branchA"),
|
||||
Object: &GitObject{
|
||||
Type: String("commit"),
|
||||
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Ref: String("refs/heads/branchB"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/refs/heads/branchB"),
|
||||
Object: &GitObject{
|
||||
Type: String("commit"),
|
||||
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(refs, want) {
|
||||
t.Errorf("Git.ListRefs returned %+v, want %+v", refs, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_ListRefs_options(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/refs/t", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{"page": "2"})
|
||||
fmt.Fprint(w, `[{"ref": "r"}]`)
|
||||
})
|
||||
|
||||
opt := &ReferenceListOptions{Type: "t", ListOptions: ListOptions{Page: 2}}
|
||||
refs, _, err := client.Git.ListRefs("o", "r", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Git.ListRefs returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Reference{{Ref: String("r")}}
|
||||
if !reflect.DeepEqual(refs, want) {
|
||||
t.Errorf("Git.ListRefs returned %+v, want %+v", refs, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_CreateRef(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
args := &createRefRequest{
|
||||
Ref: String("refs/heads/b"),
|
||||
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/refs", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(createRefRequest)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(v, args) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, args)
|
||||
}
|
||||
fmt.Fprint(w, `
|
||||
{
|
||||
"ref": "refs/heads/b",
|
||||
"url": "https://api.github.com/repos/o/r/git/refs/heads/b",
|
||||
"object": {
|
||||
"type": "commit",
|
||||
"sha": "aa218f56b14c9653891f9e74264a383fa43fefbd",
|
||||
"url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"
|
||||
}
|
||||
}`)
|
||||
})
|
||||
|
||||
ref, _, err := client.Git.CreateRef("o", "r", &Reference{
|
||||
Ref: String("refs/heads/b"),
|
||||
Object: &GitObject{
|
||||
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Git.CreateRef returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Reference{
|
||||
Ref: String("refs/heads/b"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"),
|
||||
Object: &GitObject{
|
||||
Type: String("commit"),
|
||||
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(ref, want) {
|
||||
t.Errorf("Git.CreateRef returned %+v, want %+v", ref, want)
|
||||
}
|
||||
|
||||
// without 'refs/' prefix
|
||||
_, _, err = client.Git.CreateRef("o", "r", &Reference{
|
||||
Ref: String("heads/b"),
|
||||
Object: &GitObject{
|
||||
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Git.CreateRef returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_UpdateRef(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
args := &updateRefRequest{
|
||||
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
Force: Bool(true),
|
||||
}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(updateRefRequest)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PATCH")
|
||||
if !reflect.DeepEqual(v, args) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, args)
|
||||
}
|
||||
fmt.Fprint(w, `
|
||||
{
|
||||
"ref": "refs/heads/b",
|
||||
"url": "https://api.github.com/repos/o/r/git/refs/heads/b",
|
||||
"object": {
|
||||
"type": "commit",
|
||||
"sha": "aa218f56b14c9653891f9e74264a383fa43fefbd",
|
||||
"url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"
|
||||
}
|
||||
}`)
|
||||
})
|
||||
|
||||
ref, _, err := client.Git.UpdateRef("o", "r", &Reference{
|
||||
Ref: String("refs/heads/b"),
|
||||
Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")},
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Errorf("Git.UpdateRef returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Reference{
|
||||
Ref: String("refs/heads/b"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"),
|
||||
Object: &GitObject{
|
||||
Type: String("commit"),
|
||||
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"),
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(ref, want) {
|
||||
t.Errorf("Git.UpdateRef returned %+v, want %+v", ref, want)
|
||||
}
|
||||
|
||||
// without 'refs/' prefix
|
||||
_, _, err = client.Git.UpdateRef("o", "r", &Reference{
|
||||
Ref: String("heads/b"),
|
||||
Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")},
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Errorf("Git.UpdateRef returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_DeleteRef(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Git.DeleteRef("o", "r", "refs/heads/b")
|
||||
if err != nil {
|
||||
t.Errorf("Git.DeleteRef returned error: %v", err)
|
||||
}
|
||||
|
||||
// without 'refs/' prefix
|
||||
if _, err := client.Git.DeleteRef("o", "r", "heads/b"); err != nil {
|
||||
t.Errorf("Git.DeleteRef returned error: %v", err)
|
||||
}
|
||||
}
|
||||
73
Godeps/_workspace/src/github.com/google/go-github/github/git_tags.go
generated
vendored
Normal file
73
Godeps/_workspace/src/github.com/google/go-github/github/git_tags.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Tag represents a tag object.
|
||||
type Tag struct {
|
||||
Tag *string `json:"tag,omitempty"`
|
||||
SHA *string `json:"sha,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
Tagger *CommitAuthor `json:"tagger,omitempty"`
|
||||
Object *GitObject `json:"object,omitempty"`
|
||||
}
|
||||
|
||||
// createTagRequest represents the body of a CreateTag request. This is mostly
|
||||
// identical to Tag with the exception that the object SHA and Type are
|
||||
// top-level fields, rather than being nested inside a JSON object.
|
||||
type createTagRequest struct {
|
||||
Tag *string `json:"tag,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
Object *string `json:"object,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Tagger *CommitAuthor `json:"tagger,omitempty"`
|
||||
}
|
||||
|
||||
// GetTag fetchs a tag from a repo given a SHA.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/tags/#get-a-tag
|
||||
func (s *GitService) GetTag(owner string, repo string, sha string) (*Tag, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/git/tags/%v", owner, repo, sha)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tag := new(Tag)
|
||||
resp, err := s.client.Do(req, tag)
|
||||
return tag, resp, err
|
||||
}
|
||||
|
||||
// CreateTag creates a tag object.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/tags/#create-a-tag-object
|
||||
func (s *GitService) CreateTag(owner string, repo string, tag *Tag) (*Tag, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/git/tags", owner, repo)
|
||||
|
||||
// convert Tag into a createTagRequest
|
||||
tagRequest := &createTagRequest{
|
||||
Tag: tag.Tag,
|
||||
Message: tag.Message,
|
||||
Tagger: tag.Tagger,
|
||||
}
|
||||
if tag.Object != nil {
|
||||
tagRequest.Object = tag.Object.SHA
|
||||
tagRequest.Type = tag.Object.Type
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("POST", u, tagRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
t := new(Tag)
|
||||
resp, err := s.client.Do(req, t)
|
||||
return t, resp, err
|
||||
}
|
||||
68
Godeps/_workspace/src/github.com/google/go-github/github/git_tags_test.go
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/google/go-github/github/git_tags_test.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGitService_GetTag(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/tags/s", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
|
||||
fmt.Fprint(w, `{"tag": "t"}`)
|
||||
})
|
||||
|
||||
tag, _, err := client.Git.GetTag("o", "r", "s")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Git.GetTag returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Tag{Tag: String("t")}
|
||||
if !reflect.DeepEqual(tag, want) {
|
||||
t.Errorf("Git.GetTag returned %+v, want %+v", tag, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_CreateTag(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &createTagRequest{Tag: String("t"), Object: String("s")}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/tags", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(createTagRequest)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"tag": "t"}`)
|
||||
})
|
||||
|
||||
tag, _, err := client.Git.CreateTag("o", "r", &Tag{
|
||||
Tag: input.Tag,
|
||||
Object: &GitObject{SHA: input.Object},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Git.CreateTag returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Tag{Tag: String("t")}
|
||||
if !reflect.DeepEqual(tag, want) {
|
||||
t.Errorf("Git.GetTag returned %+v, want %+v", tag, want)
|
||||
}
|
||||
}
|
||||
89
Godeps/_workspace/src/github.com/google/go-github/github/git_trees.go
generated
vendored
Normal file
89
Godeps/_workspace/src/github.com/google/go-github/github/git_trees.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Tree represents a GitHub tree.
|
||||
type Tree struct {
|
||||
SHA *string `json:"sha,omitempty"`
|
||||
Entries []TreeEntry `json:"tree,omitempty"`
|
||||
}
|
||||
|
||||
func (t Tree) String() string {
|
||||
return Stringify(t)
|
||||
}
|
||||
|
||||
// TreeEntry represents the contents of a tree structure. TreeEntry can
|
||||
// represent either a blob, a commit (in the case of a submodule), or another
|
||||
// tree.
|
||||
type TreeEntry struct {
|
||||
SHA *string `json:"sha,omitempty"`
|
||||
Path *string `json:"path,omitempty"`
|
||||
Mode *string `json:"mode,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Size *int `json:"size,omitempty"`
|
||||
Content *string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
func (t TreeEntry) String() string {
|
||||
return Stringify(t)
|
||||
}
|
||||
|
||||
// GetTree fetches the Tree object for a given sha hash from a repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/trees/#get-a-tree
|
||||
func (s *GitService) GetTree(owner string, repo string, sha string, recursive bool) (*Tree, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/git/trees/%v", owner, repo, sha)
|
||||
if recursive {
|
||||
u += "?recursive=1"
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
t := new(Tree)
|
||||
resp, err := s.client.Do(req, t)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return t, resp, err
|
||||
}
|
||||
|
||||
// createTree represents the body of a CreateTree request.
|
||||
type createTree struct {
|
||||
BaseTree string `json:"base_tree,omitempty"`
|
||||
Entries []TreeEntry `json:"tree"`
|
||||
}
|
||||
|
||||
// CreateTree creates a new tree in a repository. If both a tree and a nested
|
||||
// path modifying that tree are specified, it will overwrite the contents of
|
||||
// that tree with the new path contents and write a new tree out.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/git/trees/#create-a-tree
|
||||
func (s *GitService) CreateTree(owner string, repo string, baseTree string, entries []TreeEntry) (*Tree, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/git/trees", owner, repo)
|
||||
|
||||
body := &createTree{
|
||||
BaseTree: baseTree,
|
||||
Entries: entries,
|
||||
}
|
||||
req, err := s.client.NewRequest("POST", u, body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
t := new(Tree)
|
||||
resp, err := s.client.Do(req, t)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return t, resp, err
|
||||
}
|
||||
189
Godeps/_workspace/src/github.com/google/go-github/github/git_trees_test.go
generated
vendored
Normal file
189
Godeps/_workspace/src/github.com/google/go-github/github/git_trees_test.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGitService_GetTree(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/trees/s", func(w http.ResponseWriter, r *http.Request) {
|
||||
if m := "GET"; m != r.Method {
|
||||
t.Errorf("Request method = %v, want %v", r.Method, m)
|
||||
}
|
||||
fmt.Fprint(w, `{
|
||||
"sha": "s",
|
||||
"tree": [ { "type": "blob" } ]
|
||||
}`)
|
||||
})
|
||||
|
||||
tree, _, err := client.Git.GetTree("o", "r", "s", true)
|
||||
if err != nil {
|
||||
t.Errorf("Git.GetTree returned error: %v", err)
|
||||
}
|
||||
|
||||
want := Tree{
|
||||
SHA: String("s"),
|
||||
Entries: []TreeEntry{
|
||||
{
|
||||
Type: String("blob"),
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(*tree, want) {
|
||||
t.Errorf("Tree.Get returned %+v, want %+v", *tree, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_GetTree_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Git.GetTree("%", "%", "%", false)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestGitService_CreateTree(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := []TreeEntry{
|
||||
{
|
||||
Path: String("file.rb"),
|
||||
Mode: String("100644"),
|
||||
Type: String("blob"),
|
||||
SHA: String("7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b"),
|
||||
},
|
||||
}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(createTree)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
if m := "POST"; m != r.Method {
|
||||
t.Errorf("Request method = %v, want %v", r.Method, m)
|
||||
}
|
||||
|
||||
want := &createTree{
|
||||
BaseTree: "b",
|
||||
Entries: input,
|
||||
}
|
||||
if !reflect.DeepEqual(v, want) {
|
||||
t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{
|
||||
"sha": "cd8274d15fa3ae2ab983129fb037999f264ba9a7",
|
||||
"tree": [
|
||||
{
|
||||
"path": "file.rb",
|
||||
"mode": "100644",
|
||||
"type": "blob",
|
||||
"size": 132,
|
||||
"sha": "7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
})
|
||||
|
||||
tree, _, err := client.Git.CreateTree("o", "r", "b", input)
|
||||
if err != nil {
|
||||
t.Errorf("Git.CreateTree returned error: %v", err)
|
||||
}
|
||||
|
||||
want := Tree{
|
||||
String("cd8274d15fa3ae2ab983129fb037999f264ba9a7"),
|
||||
[]TreeEntry{
|
||||
{
|
||||
Path: String("file.rb"),
|
||||
Mode: String("100644"),
|
||||
Type: String("blob"),
|
||||
Size: Int(132),
|
||||
SHA: String("7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(*tree, want) {
|
||||
t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_CreateTree_Content(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := []TreeEntry{
|
||||
{
|
||||
Path: String("content.md"),
|
||||
Mode: String("100644"),
|
||||
Content: String("file content"),
|
||||
},
|
||||
}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(createTree)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
if m := "POST"; m != r.Method {
|
||||
t.Errorf("Request method = %v, want %v", r.Method, m)
|
||||
}
|
||||
|
||||
want := &createTree{
|
||||
BaseTree: "b",
|
||||
Entries: input,
|
||||
}
|
||||
if !reflect.DeepEqual(v, want) {
|
||||
t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{
|
||||
"sha": "5c6780ad2c68743383b740fd1dab6f6a33202b11",
|
||||
"url": "https://api.github.com/repos/o/r/git/trees/5c6780ad2c68743383b740fd1dab6f6a33202b11",
|
||||
"tree": [
|
||||
{
|
||||
"mode": "100644",
|
||||
"type": "blob",
|
||||
"sha": "aad8feacf6f8063150476a7b2bd9770f2794c08b",
|
||||
"path": "content.md",
|
||||
"size": 12,
|
||||
"url": "https://api.github.com/repos/o/r/git/blobs/aad8feacf6f8063150476a7b2bd9770f2794c08b"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
})
|
||||
|
||||
tree, _, err := client.Git.CreateTree("o", "r", "b", input)
|
||||
if err != nil {
|
||||
t.Errorf("Git.CreateTree returned error: %v", err)
|
||||
}
|
||||
|
||||
want := Tree{
|
||||
String("5c6780ad2c68743383b740fd1dab6f6a33202b11"),
|
||||
[]TreeEntry{
|
||||
{
|
||||
Path: String("content.md"),
|
||||
Mode: String("100644"),
|
||||
Type: String("blob"),
|
||||
Size: Int(12),
|
||||
SHA: String("aad8feacf6f8063150476a7b2bd9770f2794c08b"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(*tree, want) {
|
||||
t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitService_CreateTree_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Git.CreateTree("%", "%", "", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
568
Godeps/_workspace/src/github.com/google/go-github/github/github.go
generated
vendored
Normal file
568
Godeps/_workspace/src/github.com/google/go-github/github/github.go
generated
vendored
Normal file
@@ -0,0 +1,568 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
const (
|
||||
libraryVersion = "0.1"
|
||||
defaultBaseURL = "https://api.github.com/"
|
||||
uploadBaseURL = "https://uploads.github.com/"
|
||||
userAgent = "go-github/" + libraryVersion
|
||||
|
||||
headerRateLimit = "X-RateLimit-Limit"
|
||||
headerRateRemaining = "X-RateLimit-Remaining"
|
||||
headerRateReset = "X-RateLimit-Reset"
|
||||
|
||||
mediaTypeV3 = "application/vnd.github.v3+json"
|
||||
defaultMediaType = "application/octet-stream"
|
||||
|
||||
// Media Type values to access preview APIs
|
||||
|
||||
// https://developer.github.com/changes/2014-08-05-team-memberships-api/
|
||||
mediaTypeMembershipPreview = "application/vnd.github.the-wasp-preview+json"
|
||||
|
||||
// https://developer.github.com/changes/2014-01-09-preview-the-new-deployments-api/
|
||||
mediaTypeDeploymentPreview = "application/vnd.github.cannonball-preview+json"
|
||||
)
|
||||
|
||||
// A Client manages communication with the GitHub API.
|
||||
type Client struct {
|
||||
// HTTP client used to communicate with the API.
|
||||
client *http.Client
|
||||
|
||||
// Base URL for API requests. Defaults to the public GitHub API, but can be
|
||||
// set to a domain endpoint to use with GitHub Enterprise. BaseURL should
|
||||
// always be specified with a trailing slash.
|
||||
BaseURL *url.URL
|
||||
|
||||
// Base URL for uploading files.
|
||||
UploadURL *url.URL
|
||||
|
||||
// User agent used when communicating with the GitHub API.
|
||||
UserAgent string
|
||||
|
||||
// Rate specifies the current rate limit for the client as determined by the
|
||||
// most recent API call. If the client is used in a multi-user application,
|
||||
// this rate may not always be up-to-date. Call RateLimit() to check the
|
||||
// current rate.
|
||||
Rate Rate
|
||||
|
||||
// Services used for talking to different parts of the GitHub API.
|
||||
Activity *ActivityService
|
||||
Gists *GistsService
|
||||
Git *GitService
|
||||
Gitignores *GitignoresService
|
||||
Issues *IssuesService
|
||||
Organizations *OrganizationsService
|
||||
PullRequests *PullRequestsService
|
||||
Repositories *RepositoriesService
|
||||
Search *SearchService
|
||||
Users *UsersService
|
||||
}
|
||||
|
||||
// ListOptions specifies the optional parameters to various List methods that
|
||||
// support pagination.
|
||||
type ListOptions struct {
|
||||
// For paginated result sets, page of results to retrieve.
|
||||
Page int `url:"page,omitempty"`
|
||||
|
||||
// For paginated result sets, the number of results to include per page.
|
||||
PerPage int `url:"per_page,omitempty"`
|
||||
}
|
||||
|
||||
// UploadOptions specifies the parameters to methods that support uploads.
|
||||
type UploadOptions struct {
|
||||
Name string `url:"name,omitempty"`
|
||||
}
|
||||
|
||||
// addOptions adds the parameters in opt as URL query parameters to s. opt
|
||||
// must be a struct whose fields may contain "url" tags.
|
||||
func addOptions(s string, opt interface{}) (string, error) {
|
||||
v := reflect.ValueOf(opt)
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
qs, err := query.Values(opt)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
u.RawQuery = qs.Encode()
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// NewClient returns a new GitHub API client. If a nil httpClient is
|
||||
// provided, http.DefaultClient will be used. To use API methods which require
|
||||
// authentication, provide an http.Client that will perform the authentication
|
||||
// for you (such as that provided by the golang.org/x/oauth2 library).
|
||||
func NewClient(httpClient *http.Client) *Client {
|
||||
if httpClient == nil {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
baseURL, _ := url.Parse(defaultBaseURL)
|
||||
uploadURL, _ := url.Parse(uploadBaseURL)
|
||||
|
||||
c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL}
|
||||
c.Activity = &ActivityService{client: c}
|
||||
c.Gists = &GistsService{client: c}
|
||||
c.Git = &GitService{client: c}
|
||||
c.Gitignores = &GitignoresService{client: c}
|
||||
c.Issues = &IssuesService{client: c}
|
||||
c.Organizations = &OrganizationsService{client: c}
|
||||
c.PullRequests = &PullRequestsService{client: c}
|
||||
c.Repositories = &RepositoriesService{client: c}
|
||||
c.Search = &SearchService{client: c}
|
||||
c.Users = &UsersService{client: c}
|
||||
return c
|
||||
}
|
||||
|
||||
// NewRequest creates an API request. A relative URL can be provided in urlStr,
|
||||
// in which case it is resolved relative to the BaseURL of the Client.
|
||||
// Relative URLs should always be specified without a preceding slash. If
|
||||
// specified, the value pointed to by body is JSON encoded and included as the
|
||||
// request body.
|
||||
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
|
||||
rel, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := c.BaseURL.ResolveReference(rel)
|
||||
|
||||
var buf io.ReadWriter
|
||||
if body != nil {
|
||||
buf = new(bytes.Buffer)
|
||||
err := json.NewEncoder(buf).Encode(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Accept", mediaTypeV3)
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Add("User-Agent", c.UserAgent)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewUploadRequest creates an upload request. A relative URL can be provided in
|
||||
// urlStr, in which case it is resolved relative to the UploadURL of the Client.
|
||||
// Relative URLs should always be specified without a preceding slash.
|
||||
func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) {
|
||||
rel, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := c.UploadURL.ResolveReference(rel)
|
||||
req, err := http.NewRequest("POST", u.String(), reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.ContentLength = size
|
||||
|
||||
if len(mediaType) == 0 {
|
||||
mediaType = defaultMediaType
|
||||
}
|
||||
req.Header.Add("Content-Type", mediaType)
|
||||
req.Header.Add("Accept", mediaTypeV3)
|
||||
req.Header.Add("User-Agent", c.UserAgent)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Response is a GitHub API response. This wraps the standard http.Response
|
||||
// returned from GitHub and provides convenient access to things like
|
||||
// pagination links.
|
||||
type Response struct {
|
||||
*http.Response
|
||||
|
||||
// These fields provide the page values for paginating through a set of
|
||||
// results. Any or all of these may be set to the zero value for
|
||||
// responses that are not part of a paginated set, or for which there
|
||||
// are no additional pages.
|
||||
|
||||
NextPage int
|
||||
PrevPage int
|
||||
FirstPage int
|
||||
LastPage int
|
||||
|
||||
Rate
|
||||
}
|
||||
|
||||
// newResponse creats a new Response for the provided http.Response.
|
||||
func newResponse(r *http.Response) *Response {
|
||||
response := &Response{Response: r}
|
||||
response.populatePageValues()
|
||||
response.populateRate()
|
||||
return response
|
||||
}
|
||||
|
||||
// populatePageValues parses the HTTP Link response headers and populates the
|
||||
// various pagination link values in the Reponse.
|
||||
func (r *Response) populatePageValues() {
|
||||
if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
|
||||
for _, link := range strings.Split(links[0], ",") {
|
||||
segments := strings.Split(strings.TrimSpace(link), ";")
|
||||
|
||||
// link must at least have href and rel
|
||||
if len(segments) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// ensure href is properly formatted
|
||||
if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
|
||||
continue
|
||||
}
|
||||
|
||||
// try to pull out page parameter
|
||||
url, err := url.Parse(segments[0][1 : len(segments[0])-1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
page := url.Query().Get("page")
|
||||
if page == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, segment := range segments[1:] {
|
||||
switch strings.TrimSpace(segment) {
|
||||
case `rel="next"`:
|
||||
r.NextPage, _ = strconv.Atoi(page)
|
||||
case `rel="prev"`:
|
||||
r.PrevPage, _ = strconv.Atoi(page)
|
||||
case `rel="first"`:
|
||||
r.FirstPage, _ = strconv.Atoi(page)
|
||||
case `rel="last"`:
|
||||
r.LastPage, _ = strconv.Atoi(page)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// populateRate parses the rate related headers and populates the response Rate.
|
||||
func (r *Response) populateRate() {
|
||||
if limit := r.Header.Get(headerRateLimit); limit != "" {
|
||||
r.Rate.Limit, _ = strconv.Atoi(limit)
|
||||
}
|
||||
if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
|
||||
r.Rate.Remaining, _ = strconv.Atoi(remaining)
|
||||
}
|
||||
if reset := r.Header.Get(headerRateReset); reset != "" {
|
||||
if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
|
||||
r.Rate.Reset = Timestamp{time.Unix(v, 0)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do sends an API request and returns the API response. The API response is
|
||||
// JSON decoded and stored in the value pointed to by v, or returned as an
|
||||
// error if an API error has occurred. If v implements the io.Writer
|
||||
// interface, the raw response body will be written to v, without attempting to
|
||||
// first decode it.
|
||||
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
response := newResponse(resp)
|
||||
|
||||
c.Rate = response.Rate
|
||||
|
||||
err = CheckResponse(resp)
|
||||
if err != nil {
|
||||
// even though there was an error, we still return the response
|
||||
// in case the caller wants to inspect it further
|
||||
return response, err
|
||||
}
|
||||
|
||||
if v != nil {
|
||||
if w, ok := v.(io.Writer); ok {
|
||||
io.Copy(w, resp.Body)
|
||||
} else {
|
||||
err = json.NewDecoder(resp.Body).Decode(v)
|
||||
}
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
/*
|
||||
An ErrorResponse reports one or more errors caused by an API request.
|
||||
|
||||
GitHub API docs: http://developer.github.com/v3/#client-errors
|
||||
*/
|
||||
type ErrorResponse struct {
|
||||
Response *http.Response // HTTP response that caused this error
|
||||
Message string `json:"message"` // error message
|
||||
Errors []Error `json:"errors"` // more detail on individual errors
|
||||
}
|
||||
|
||||
func (r *ErrorResponse) Error() string {
|
||||
return fmt.Sprintf("%v %v: %d %v %+v",
|
||||
r.Response.Request.Method, r.Response.Request.URL,
|
||||
r.Response.StatusCode, r.Message, r.Errors)
|
||||
}
|
||||
|
||||
/*
|
||||
An Error reports more details on an individual error in an ErrorResponse.
|
||||
These are the possible validation error codes:
|
||||
|
||||
missing:
|
||||
resource does not exist
|
||||
missing_field:
|
||||
a required field on a resource has not been set
|
||||
invalid:
|
||||
the formatting of a field is invalid
|
||||
already_exists:
|
||||
another resource has the same valid as this field
|
||||
|
||||
GitHub API docs: http://developer.github.com/v3/#client-errors
|
||||
*/
|
||||
type Error struct {
|
||||
Resource string `json:"resource"` // resource on which the error occurred
|
||||
Field string `json:"field"` // field on which the error occurred
|
||||
Code string `json:"code"` // validation error code
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%v error caused by %v field on %v resource",
|
||||
e.Code, e.Field, e.Resource)
|
||||
}
|
||||
|
||||
// CheckResponse checks the API response for errors, and returns them if
|
||||
// present. A response is considered an error if it has a status code outside
|
||||
// the 200 range. API error responses are expected to have either no response
|
||||
// body, or a JSON response body that maps to ErrorResponse. Any other
|
||||
// response body will be silently ignored.
|
||||
func CheckResponse(r *http.Response) error {
|
||||
if c := r.StatusCode; 200 <= c && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
errorResponse := &ErrorResponse{Response: r}
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err == nil && data != nil {
|
||||
json.Unmarshal(data, errorResponse)
|
||||
}
|
||||
return errorResponse
|
||||
}
|
||||
|
||||
// parseBoolResponse determines the boolean result from a GitHub API response.
|
||||
// Several GitHub API methods return boolean responses indicated by the HTTP
|
||||
// status code in the response (true indicated by a 204, false indicated by a
|
||||
// 404). This helper function will determine that result and hide the 404
|
||||
// error if present. Any other error will be returned through as-is.
|
||||
func parseBoolResponse(err error) (bool, error) {
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
|
||||
// Simply false. In this one case, we do not pass the error through.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// some other real error occurred
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Rate represents the rate limit for the current client.
|
||||
type Rate struct {
|
||||
// The number of requests per hour the client is currently limited to.
|
||||
Limit int `json:"limit"`
|
||||
|
||||
// The number of remaining requests the client can make this hour.
|
||||
Remaining int `json:"remaining"`
|
||||
|
||||
// The time at which the current rate limit will reset.
|
||||
Reset Timestamp `json:"reset"`
|
||||
}
|
||||
|
||||
func (r Rate) String() string {
|
||||
return Stringify(r)
|
||||
}
|
||||
|
||||
// RateLimits represents the rate limits for the current client.
|
||||
type RateLimits struct {
|
||||
// The rate limit for non-search API requests. Unauthenticated
|
||||
// requests are limited to 60 per hour. Authenticated requests are
|
||||
// limited to 5,000 per hour.
|
||||
Core *Rate `json:"core"`
|
||||
|
||||
// The rate limit for search API requests. Unauthenticated requests
|
||||
// are limited to 5 requests per minutes. Authenticated requests are
|
||||
// limited to 20 per minute.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/search/#rate-limit
|
||||
Search *Rate `json:"search"`
|
||||
}
|
||||
|
||||
func (r RateLimits) String() string {
|
||||
return Stringify(r)
|
||||
}
|
||||
|
||||
// RateLimit is deprecated. Use RateLimits instead.
|
||||
func (c *Client) RateLimit() (*Rate, *Response, error) {
|
||||
limits, resp, err := c.RateLimits()
|
||||
if limits == nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return limits.Core, resp, err
|
||||
}
|
||||
|
||||
// RateLimits returns the rate limits for the current client.
|
||||
func (c *Client) RateLimits() (*RateLimits, *Response, error) {
|
||||
req, err := c.NewRequest("GET", "rate_limit", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
response := new(struct {
|
||||
Resources *RateLimits `json:"resources"`
|
||||
})
|
||||
resp, err := c.Do(req, response)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return response.Resources, resp, err
|
||||
}
|
||||
|
||||
/*
|
||||
UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
|
||||
that need to use a higher rate limit associated with your OAuth application.
|
||||
|
||||
t := &github.UnauthenticatedRateLimitedTransport{
|
||||
ClientID: "your app's client ID",
|
||||
ClientSecret: "your app's client secret",
|
||||
}
|
||||
client := github.NewClient(t.Client())
|
||||
|
||||
This will append the querystring params client_id=xxx&client_secret=yyy to all
|
||||
requests.
|
||||
|
||||
See http://developer.github.com/v3/#unauthenticated-rate-limited-requests for
|
||||
more information.
|
||||
*/
|
||||
type UnauthenticatedRateLimitedTransport struct {
|
||||
// ClientID is the GitHub OAuth client ID of the current application, which
|
||||
// can be found by selecting its entry in the list at
|
||||
// https://github.com/settings/applications.
|
||||
ClientID string
|
||||
|
||||
// ClientSecret is the GitHub OAuth client secret of the current
|
||||
// application.
|
||||
ClientSecret string
|
||||
|
||||
// Transport is the underlying HTTP transport to use when making requests.
|
||||
// It will default to http.DefaultTransport if nil.
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip implements the RoundTripper interface.
|
||||
func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if t.ClientID == "" {
|
||||
return nil, errors.New("t.ClientID is empty")
|
||||
}
|
||||
if t.ClientSecret == "" {
|
||||
return nil, errors.New("t.ClientSecret is empty")
|
||||
}
|
||||
|
||||
// To set extra querystring params, we must make a copy of the Request so
|
||||
// that we don't modify the Request we were given. This is required by the
|
||||
// specification of http.RoundTripper.
|
||||
req = cloneRequest(req)
|
||||
q := req.URL.Query()
|
||||
q.Set("client_id", t.ClientID)
|
||||
q.Set("client_secret", t.ClientSecret)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
// Make the HTTP request.
|
||||
return t.transport().RoundTrip(req)
|
||||
}
|
||||
|
||||
// Client returns an *http.Client that makes requests which are subject to the
|
||||
// rate limit of your OAuth application.
|
||||
func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client {
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
|
||||
if t.Transport != nil {
|
||||
return t.Transport
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request. The clone is a
|
||||
// shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
// shallow copy of the struct
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
// deep copy of the Header
|
||||
r2.Header = make(http.Header)
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = s
|
||||
}
|
||||
return r2
|
||||
}
|
||||
|
||||
// Bool is a helper routine that allocates a new bool value
|
||||
// to store v and returns a pointer to it.
|
||||
func Bool(v bool) *bool {
|
||||
p := new(bool)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// Int is a helper routine that allocates a new int32 value
|
||||
// to store v and returns a pointer to it, but unlike Int32
|
||||
// its argument value is an int.
|
||||
func Int(v int) *int {
|
||||
p := new(int)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// String is a helper routine that allocates a new string value
|
||||
// to store v and returns a pointer to it.
|
||||
func String(v string) *string {
|
||||
p := new(string)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
660
Godeps/_workspace/src/github.com/google/go-github/github/github_test.go
generated
vendored
Normal file
660
Godeps/_workspace/src/github.com/google/go-github/github/github_test.go
generated
vendored
Normal file
@@ -0,0 +1,660 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// mux is the HTTP request multiplexer used with the test server.
|
||||
mux *http.ServeMux
|
||||
|
||||
// client is the GitHub client being tested.
|
||||
client *Client
|
||||
|
||||
// server is a test HTTP server used to provide mock API responses.
|
||||
server *httptest.Server
|
||||
)
|
||||
|
||||
// setup sets up a test HTTP server along with a github.Client that is
|
||||
// configured to talk to that test server. Tests should register handlers on
|
||||
// mux which provide mock responses for the API method being tested.
|
||||
func setup() {
|
||||
// test server
|
||||
mux = http.NewServeMux()
|
||||
server = httptest.NewServer(mux)
|
||||
|
||||
// github client configured to use test server
|
||||
client = NewClient(nil)
|
||||
url, _ := url.Parse(server.URL)
|
||||
client.BaseURL = url
|
||||
client.UploadURL = url
|
||||
}
|
||||
|
||||
// teardown closes the test HTTP server.
|
||||
func teardown() {
|
||||
server.Close()
|
||||
}
|
||||
|
||||
// openTestFile creates a new file with the given name and content for testing.
|
||||
// In order to ensure the exact file name, this function will create a new temp
|
||||
// directory, and create the file in that directory. It is the caller's
|
||||
// responsibility to remove the directy and its contents when no longer needed.
|
||||
func openTestFile(name, content string) (file *os.File, dir string, err error) {
|
||||
dir, err = ioutil.TempDir("", "go-github")
|
||||
if err != nil {
|
||||
return nil, dir, err
|
||||
}
|
||||
|
||||
file, err = os.OpenFile(path.Join(dir, name), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
return nil, dir, err
|
||||
}
|
||||
|
||||
fmt.Fprint(file, content)
|
||||
|
||||
// close and re-open the file to keep file.Stat() happy
|
||||
file.Close()
|
||||
file, err = os.Open(file.Name())
|
||||
if err != nil {
|
||||
return nil, dir, err
|
||||
}
|
||||
|
||||
return file, dir, err
|
||||
}
|
||||
|
||||
func testMethod(t *testing.T, r *http.Request, want string) {
|
||||
if got := r.Method; got != want {
|
||||
t.Errorf("Request method: %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type values map[string]string
|
||||
|
||||
func testFormValues(t *testing.T, r *http.Request, values values) {
|
||||
want := url.Values{}
|
||||
for k, v := range values {
|
||||
want.Add(k, v)
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
if got := r.Form; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Request parameters: %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testHeader(t *testing.T, r *http.Request, header string, want string) {
|
||||
if got := r.Header.Get(header); got != want {
|
||||
t.Errorf("Header.Get(%q) returned %s, want %s", header, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testURLParseError(t *testing.T, err error) {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error to be returned")
|
||||
}
|
||||
if err, ok := err.(*url.Error); !ok || err.Op != "parse" {
|
||||
t.Errorf("Expected URL parse error, got %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testBody(t *testing.T, r *http.Request, want string) {
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Error reading request body: %v", err)
|
||||
}
|
||||
if got := string(b); got != want {
|
||||
t.Errorf("request Body is %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to test that a value is marshalled to JSON as expected.
|
||||
func testJSONMarshal(t *testing.T, v interface{}, want string) {
|
||||
j, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Errorf("Unable to marshal JSON for %v", v)
|
||||
}
|
||||
|
||||
w := new(bytes.Buffer)
|
||||
err = json.Compact(w, []byte(want))
|
||||
if err != nil {
|
||||
t.Errorf("String is not valid json: %s", want)
|
||||
}
|
||||
|
||||
if w.String() != string(j) {
|
||||
t.Errorf("json.Marshal(%q) returned %s, want %s", v, j, w)
|
||||
}
|
||||
|
||||
// now go the other direction and make sure things unmarshal as expected
|
||||
u := reflect.ValueOf(v).Interface()
|
||||
if err := json.Unmarshal([]byte(want), u); err != nil {
|
||||
t.Errorf("Unable to unmarshal JSON for %v", want)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(v, u) {
|
||||
t.Errorf("json.Unmarshal(%q) returned %s, want %s", want, u, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
|
||||
if got, want := c.BaseURL.String(), defaultBaseURL; got != want {
|
||||
t.Errorf("NewClient BaseURL is %v, want %v", got, want)
|
||||
}
|
||||
if got, want := c.UserAgent, userAgent; got != want {
|
||||
t.Errorf("NewClient UserAgent is %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRequest(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
|
||||
inURL, outURL := "/foo", defaultBaseURL+"foo"
|
||||
inBody, outBody := &User{Login: String("l")}, `{"login":"l"}`+"\n"
|
||||
req, _ := c.NewRequest("GET", inURL, inBody)
|
||||
|
||||
// test that relative URL was expanded
|
||||
if got, want := req.URL.String(), outURL; got != want {
|
||||
t.Errorf("NewRequest(%q) URL is %v, want %v", inURL, got, want)
|
||||
}
|
||||
|
||||
// test that body was JSON encoded
|
||||
body, _ := ioutil.ReadAll(req.Body)
|
||||
if got, want := string(body), outBody; got != want {
|
||||
t.Errorf("NewRequest(%q) Body is %v, want %v", inBody, got, want)
|
||||
}
|
||||
|
||||
// test that default user-agent is attached to the request
|
||||
if got, want := req.Header.Get("User-Agent"), c.UserAgent; got != want {
|
||||
t.Errorf("NewRequest() User-Agent is %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRequest_invalidJSON(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
|
||||
type T struct {
|
||||
A map[int]interface{}
|
||||
}
|
||||
_, err := c.NewRequest("GET", "/", &T{})
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected error to be returned.")
|
||||
}
|
||||
if err, ok := err.(*json.UnsupportedTypeError); !ok {
|
||||
t.Errorf("Expected a JSON error; got %#v.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRequest_badURL(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
_, err := c.NewRequest("GET", ":", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
// ensure that no User-Agent header is set if the client's UserAgent is empty.
|
||||
// This caused a problem with Google's internal http client.
|
||||
func TestNewRequest_emptyUserAgent(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
c.UserAgent = ""
|
||||
req, err := c.NewRequest("GET", "/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRequest returned unexpected error: %v", err)
|
||||
}
|
||||
if _, ok := req.Header["User-Agent"]; ok {
|
||||
t.Fatal("constructed request contains unexpected User-Agent header")
|
||||
}
|
||||
}
|
||||
|
||||
// If a nil body is passed to github.NewRequest, make sure that nil is also
|
||||
// passed to http.NewRequest. In most cases, passing an io.Reader that returns
|
||||
// no content is fine, since there is no difference between an HTTP request
|
||||
// body that is an empty string versus one that is not set at all. However in
|
||||
// certain cases, intermediate systems may treat these differently resulting in
|
||||
// subtle errors.
|
||||
func TestNewRequest_emptyBody(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
req, err := c.NewRequest("GET", "/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRequest returned unexpected error: %v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
t.Fatalf("constructed request contains a non-nil Body")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponse_populatePageValues(t *testing.T) {
|
||||
r := http.Response{
|
||||
Header: http.Header{
|
||||
"Link": {`<https://api.github.com/?page=1>; rel="first",` +
|
||||
` <https://api.github.com/?page=2>; rel="prev",` +
|
||||
` <https://api.github.com/?page=4>; rel="next",` +
|
||||
` <https://api.github.com/?page=5>; rel="last"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response := newResponse(&r)
|
||||
if got, want := response.FirstPage, 1; got != want {
|
||||
t.Errorf("response.FirstPage: %v, want %v", got, want)
|
||||
}
|
||||
if got, want := response.PrevPage, 2; want != got {
|
||||
t.Errorf("response.PrevPage: %v, want %v", got, want)
|
||||
}
|
||||
if got, want := response.NextPage, 4; want != got {
|
||||
t.Errorf("response.NextPage: %v, want %v", got, want)
|
||||
}
|
||||
if got, want := response.LastPage, 5; want != got {
|
||||
t.Errorf("response.LastPage: %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponse_populatePageValues_invalid(t *testing.T) {
|
||||
r := http.Response{
|
||||
Header: http.Header{
|
||||
"Link": {`<https://api.github.com/?page=1>,` +
|
||||
`<https://api.github.com/?page=abc>; rel="first",` +
|
||||
`https://api.github.com/?page=2; rel="prev",` +
|
||||
`<https://api.github.com/>; rel="next",` +
|
||||
`<https://api.github.com/?page=>; rel="last"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response := newResponse(&r)
|
||||
if got, want := response.FirstPage, 0; got != want {
|
||||
t.Errorf("response.FirstPage: %v, want %v", got, want)
|
||||
}
|
||||
if got, want := response.PrevPage, 0; got != want {
|
||||
t.Errorf("response.PrevPage: %v, want %v", got, want)
|
||||
}
|
||||
if got, want := response.NextPage, 0; got != want {
|
||||
t.Errorf("response.NextPage: %v, want %v", got, want)
|
||||
}
|
||||
if got, want := response.LastPage, 0; got != want {
|
||||
t.Errorf("response.LastPage: %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// more invalid URLs
|
||||
r = http.Response{
|
||||
Header: http.Header{
|
||||
"Link": {`<https://api.github.com/%?page=2>; rel="first"`},
|
||||
},
|
||||
}
|
||||
|
||||
response = newResponse(&r)
|
||||
if got, want := response.FirstPage, 0; got != want {
|
||||
t.Errorf("response.FirstPage: %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDo(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
type foo struct {
|
||||
A string
|
||||
}
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if m := "GET"; m != r.Method {
|
||||
t.Errorf("Request method = %v, want %v", r.Method, m)
|
||||
}
|
||||
fmt.Fprint(w, `{"A":"a"}`)
|
||||
})
|
||||
|
||||
req, _ := client.NewRequest("GET", "/", nil)
|
||||
body := new(foo)
|
||||
client.Do(req, body)
|
||||
|
||||
want := &foo{"a"}
|
||||
if !reflect.DeepEqual(body, want) {
|
||||
t.Errorf("Response body = %v, want %v", body, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDo_httpError(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Bad Request", 400)
|
||||
})
|
||||
|
||||
req, _ := client.NewRequest("GET", "/", nil)
|
||||
_, err := client.Do(req, nil)
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected HTTP 400 error.")
|
||||
}
|
||||
}
|
||||
|
||||
// Test handling of an error caused by the internal http client's Do()
|
||||
// function. A redirect loop is pretty unlikely to occur within the GitHub
|
||||
// API, but does allow us to exercise the right code path.
|
||||
func TestDo_redirectLoop(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
})
|
||||
|
||||
req, _ := client.NewRequest("GET", "/", nil)
|
||||
_, err := client.Do(req, nil)
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected error to be returned.")
|
||||
}
|
||||
if err, ok := err.(*url.Error); !ok {
|
||||
t.Errorf("Expected a URL error; got %#v.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDo_rateLimit(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add(headerRateLimit, "60")
|
||||
w.Header().Add(headerRateRemaining, "59")
|
||||
w.Header().Add(headerRateReset, "1372700873")
|
||||
})
|
||||
|
||||
if got, want := client.Rate.Limit, 0; got != want {
|
||||
t.Errorf("Client rate limit = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := client.Rate.Limit, 0; got != want {
|
||||
t.Errorf("Client rate remaining = %v, got %v", got, want)
|
||||
}
|
||||
if !client.Rate.Reset.IsZero() {
|
||||
t.Errorf("Client rate reset not initialized to zero value")
|
||||
}
|
||||
|
||||
req, _ := client.NewRequest("GET", "/", nil)
|
||||
client.Do(req, nil)
|
||||
|
||||
if got, want := client.Rate.Limit, 60; got != want {
|
||||
t.Errorf("Client rate limit = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := client.Rate.Remaining, 59; got != want {
|
||||
t.Errorf("Client rate remaining = %v, want %v", got, want)
|
||||
}
|
||||
reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC)
|
||||
if client.Rate.Reset.UTC() != reset {
|
||||
t.Errorf("Client rate reset = %v, want %v", client.Rate.Reset, reset)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure rate limit is still parsed, even for error responses
|
||||
func TestDo_rateLimit_errorResponse(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add(headerRateLimit, "60")
|
||||
w.Header().Add(headerRateRemaining, "59")
|
||||
w.Header().Add(headerRateReset, "1372700873")
|
||||
http.Error(w, "Bad Request", 400)
|
||||
})
|
||||
|
||||
req, _ := client.NewRequest("GET", "/", nil)
|
||||
client.Do(req, nil)
|
||||
|
||||
if got, want := client.Rate.Limit, 60; got != want {
|
||||
t.Errorf("Client rate limit = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := client.Rate.Remaining, 59; got != want {
|
||||
t.Errorf("Client rate remaining = %v, want %v", got, want)
|
||||
}
|
||||
reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC)
|
||||
if client.Rate.Reset.UTC() != reset {
|
||||
t.Errorf("Client rate reset = %v, want %v", client.Rate.Reset, reset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckResponse(t *testing.T) {
|
||||
res := &http.Response{
|
||||
Request: &http.Request{},
|
||||
StatusCode: http.StatusBadRequest,
|
||||
Body: ioutil.NopCloser(strings.NewReader(`{"message":"m",
|
||||
"errors": [{"resource": "r", "field": "f", "code": "c"}]}`)),
|
||||
}
|
||||
err := CheckResponse(res).(*ErrorResponse)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error response.")
|
||||
}
|
||||
|
||||
want := &ErrorResponse{
|
||||
Response: res,
|
||||
Message: "m",
|
||||
Errors: []Error{{Resource: "r", Field: "f", Code: "c"}},
|
||||
}
|
||||
if !reflect.DeepEqual(err, want) {
|
||||
t.Errorf("Error = %#v, want %#v", err, want)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that we properly handle API errors that do not contain a response body
|
||||
func TestCheckResponse_noBody(t *testing.T) {
|
||||
res := &http.Response{
|
||||
Request: &http.Request{},
|
||||
StatusCode: http.StatusBadRequest,
|
||||
Body: ioutil.NopCloser(strings.NewReader("")),
|
||||
}
|
||||
err := CheckResponse(res).(*ErrorResponse)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error response.")
|
||||
}
|
||||
|
||||
want := &ErrorResponse{
|
||||
Response: res,
|
||||
}
|
||||
if !reflect.DeepEqual(err, want) {
|
||||
t.Errorf("Error = %#v, want %#v", err, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBooleanResponse_true(t *testing.T) {
|
||||
result, err := parseBoolResponse(nil)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("parseBoolResponse returned error: %+v", err)
|
||||
}
|
||||
|
||||
if want := true; result != want {
|
||||
t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBooleanResponse_false(t *testing.T) {
|
||||
v := &ErrorResponse{Response: &http.Response{StatusCode: http.StatusNotFound}}
|
||||
result, err := parseBoolResponse(v)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("parseBoolResponse returned error: %+v", err)
|
||||
}
|
||||
|
||||
if want := false; result != want {
|
||||
t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBooleanResponse_error(t *testing.T) {
|
||||
v := &ErrorResponse{Response: &http.Response{StatusCode: http.StatusBadRequest}}
|
||||
result, err := parseBoolResponse(v)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error to be returned.")
|
||||
}
|
||||
|
||||
if want := false; result != want {
|
||||
t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorResponse_Error(t *testing.T) {
|
||||
res := &http.Response{Request: &http.Request{}}
|
||||
err := ErrorResponse{Message: "m", Response: res}
|
||||
if err.Error() == "" {
|
||||
t.Errorf("Expected non-empty ErrorResponse.Error()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestError_Error(t *testing.T) {
|
||||
err := Error{}
|
||||
if err.Error() == "" {
|
||||
t.Errorf("Expected non-empty Error.Error()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimit(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/rate_limit", func(w http.ResponseWriter, r *http.Request) {
|
||||
if m := "GET"; m != r.Method {
|
||||
t.Errorf("Request method = %v, want %v", r.Method, m)
|
||||
}
|
||||
//fmt.Fprint(w, `{"resources":{"core": {"limit":2,"remaining":1,"reset":1372700873}}}`)
|
||||
fmt.Fprint(w, `{"resources":{
|
||||
"core": {"limit":2,"remaining":1,"reset":1372700873},
|
||||
"search": {"limit":3,"remaining":2,"reset":1372700874}
|
||||
}}`)
|
||||
})
|
||||
|
||||
rate, _, err := client.RateLimit()
|
||||
if err != nil {
|
||||
t.Errorf("Rate limit returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Rate{
|
||||
Limit: 2,
|
||||
Remaining: 1,
|
||||
Reset: Timestamp{time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC).Local()},
|
||||
}
|
||||
if !reflect.DeepEqual(rate, want) {
|
||||
t.Errorf("RateLimit returned %+v, want %+v", rate, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimits(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/rate_limit", func(w http.ResponseWriter, r *http.Request) {
|
||||
if m := "GET"; m != r.Method {
|
||||
t.Errorf("Request method = %v, want %v", r.Method, m)
|
||||
}
|
||||
fmt.Fprint(w, `{"resources":{
|
||||
"core": {"limit":2,"remaining":1,"reset":1372700873},
|
||||
"search": {"limit":3,"remaining":2,"reset":1372700874}
|
||||
}}`)
|
||||
})
|
||||
|
||||
rate, _, err := client.RateLimits()
|
||||
if err != nil {
|
||||
t.Errorf("RateLimits returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &RateLimits{
|
||||
Core: &Rate{
|
||||
Limit: 2,
|
||||
Remaining: 1,
|
||||
Reset: Timestamp{time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC).Local()},
|
||||
},
|
||||
Search: &Rate{
|
||||
Limit: 3,
|
||||
Remaining: 2,
|
||||
Reset: Timestamp{time.Date(2013, 7, 1, 17, 47, 54, 0, time.UTC).Local()},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(rate, want) {
|
||||
t.Errorf("RateLimits returned %+v, want %+v", rate, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnauthenticatedRateLimitedTransport(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
var v, want string
|
||||
q := r.URL.Query()
|
||||
if v, want = q.Get("client_id"), "id"; v != want {
|
||||
t.Errorf("OAuth Client ID = %v, want %v", v, want)
|
||||
}
|
||||
if v, want = q.Get("client_secret"), "secret"; v != want {
|
||||
t.Errorf("OAuth Client Secret = %v, want %v", v, want)
|
||||
}
|
||||
})
|
||||
|
||||
tp := &UnauthenticatedRateLimitedTransport{
|
||||
ClientID: "id",
|
||||
ClientSecret: "secret",
|
||||
}
|
||||
unauthedClient := NewClient(tp.Client())
|
||||
unauthedClient.BaseURL = client.BaseURL
|
||||
req, _ := unauthedClient.NewRequest("GET", "/", nil)
|
||||
unauthedClient.Do(req, nil)
|
||||
}
|
||||
|
||||
func TestUnauthenticatedRateLimitedTransport_missingFields(t *testing.T) {
|
||||
// missing ClientID
|
||||
tp := &UnauthenticatedRateLimitedTransport{
|
||||
ClientSecret: "secret",
|
||||
}
|
||||
_, err := tp.RoundTrip(nil)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error to be returned")
|
||||
}
|
||||
|
||||
// missing ClientSecret
|
||||
tp = &UnauthenticatedRateLimitedTransport{
|
||||
ClientID: "id",
|
||||
}
|
||||
_, err = tp.RoundTrip(nil)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnauthenticatedRateLimitedTransport_transport(t *testing.T) {
|
||||
// default transport
|
||||
tp := &UnauthenticatedRateLimitedTransport{
|
||||
ClientID: "id",
|
||||
ClientSecret: "secret",
|
||||
}
|
||||
if tp.transport() != http.DefaultTransport {
|
||||
t.Errorf("Expected http.DefaultTransport to be used.")
|
||||
}
|
||||
|
||||
// custom transport
|
||||
tp = &UnauthenticatedRateLimitedTransport{
|
||||
ClientID: "id",
|
||||
ClientSecret: "secret",
|
||||
Transport: &http.Transport{},
|
||||
}
|
||||
if tp.transport() == http.DefaultTransport {
|
||||
t.Errorf("Expected custom transport to be used.")
|
||||
}
|
||||
}
|
||||
63
Godeps/_workspace/src/github.com/google/go-github/github/gitignore.go
generated
vendored
Normal file
63
Godeps/_workspace/src/github.com/google/go-github/github/gitignore.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import "fmt"
|
||||
|
||||
// GitignoresService provides access to the gitignore related functions in the
|
||||
// GitHub API.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/gitignore/
|
||||
type GitignoresService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Gitignore represents a .gitignore file as returned by the GitHub API.
|
||||
type Gitignore struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Source *string `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
func (g Gitignore) String() string {
|
||||
return Stringify(g)
|
||||
}
|
||||
|
||||
// List all available Gitignore templates.
|
||||
//
|
||||
// http://developer.github.com/v3/gitignore/#listing-available-templates
|
||||
func (s GitignoresService) List() ([]string, *Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "gitignore/templates", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
availableTemplates := new([]string)
|
||||
resp, err := s.client.Do(req, availableTemplates)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *availableTemplates, resp, err
|
||||
}
|
||||
|
||||
// Get a Gitignore by name.
|
||||
//
|
||||
// http://developer.github.com/v3/gitignore/#get-a-single-template
|
||||
func (s GitignoresService) Get(name string) (*Gitignore, *Response, error) {
|
||||
u := fmt.Sprintf("gitignore/templates/%v", name)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
gitignore := new(Gitignore)
|
||||
resp, err := s.client.Do(req, gitignore)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return gitignore, resp, err
|
||||
}
|
||||
58
Godeps/_workspace/src/github.com/google/go-github/github/gitignore_test.go
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/google/go-github/github/gitignore_test.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGitignoresService_List(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gitignore/templates", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `["C", "Go"]`)
|
||||
})
|
||||
|
||||
available, _, err := client.Gitignores.List()
|
||||
if err != nil {
|
||||
t.Errorf("Gitignores.List returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"C", "Go"}
|
||||
if !reflect.DeepEqual(available, want) {
|
||||
t.Errorf("Gitignores.List returned %+v, want %+v", available, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitignoresService_Get(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/gitignore/templates/name", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"name":"Name","source":"template source"}`)
|
||||
})
|
||||
|
||||
gitignore, _, err := client.Gitignores.Get("name")
|
||||
if err != nil {
|
||||
t.Errorf("Gitignores.List returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Gitignore{Name: String("Name"), Source: String("template source")}
|
||||
if !reflect.DeepEqual(gitignore, want) {
|
||||
t.Errorf("Gitignores.Get returned %+v, want %+v", gitignore, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitignoresService_Get_invalidTemplate(t *testing.T) {
|
||||
_, _, err := client.Gitignores.Get("%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
261
Godeps/_workspace/src/github.com/google/go-github/github/issues.go
generated
vendored
Normal file
261
Godeps/_workspace/src/github.com/google/go-github/github/issues.go
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IssuesService handles communication with the issue related
|
||||
// methods of the GitHub API.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/
|
||||
type IssuesService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Issue represents a GitHub issue on a repository.
|
||||
type Issue struct {
|
||||
Number *int `json:"number,omitempty"`
|
||||
State *string `json:"state,omitempty"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Body *string `json:"body,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Labels []Label `json:"labels,omitempty"`
|
||||
Assignee *User `json:"assignee,omitempty"`
|
||||
Comments *int `json:"comments,omitempty"`
|
||||
ClosedAt *time.Time `json:"closed_at,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
HTMLURL *string `json:"html_url,omitempty"`
|
||||
Milestone *Milestone `json:"milestone,omitempty"`
|
||||
PullRequestLinks *PullRequestLinks `json:"pull_request,omitempty"`
|
||||
|
||||
// TextMatches is only populated from search results that request text matches
|
||||
// See: search.go and https://developer.github.com/v3/search/#text-match-metadata
|
||||
TextMatches []TextMatch `json:"text_matches,omitempty"`
|
||||
}
|
||||
|
||||
func (i Issue) String() string {
|
||||
return Stringify(i)
|
||||
}
|
||||
|
||||
// IssueRequest represents a request to create/edit an issue.
|
||||
// It is separate from Issue above because otherwise Labels
|
||||
// and Assignee fail to serialize to the correct JSON.
|
||||
type IssueRequest struct {
|
||||
Title *string `json:"title,omitempty"`
|
||||
Body *string `json:"body,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
Assignee *string `json:"assignee,omitempty"`
|
||||
State *string `json:"state,omitempty"`
|
||||
Milestone *int `json:"milestone,omitempty"`
|
||||
}
|
||||
|
||||
// IssueListOptions specifies the optional parameters to the IssuesService.List
|
||||
// and IssuesService.ListByOrg methods.
|
||||
type IssueListOptions struct {
|
||||
// Filter specifies which issues to list. Possible values are: assigned,
|
||||
// created, mentioned, subscribed, all. Default is "assigned".
|
||||
Filter string `url:"filter,omitempty"`
|
||||
|
||||
// State filters issues based on their state. Possible values are: open,
|
||||
// closed. Default is "open".
|
||||
State string `url:"state,omitempty"`
|
||||
|
||||
// Labels filters issues based on their label.
|
||||
Labels []string `url:"labels,comma,omitempty"`
|
||||
|
||||
// Sort specifies how to sort issues. Possible values are: created, updated,
|
||||
// and comments. Default value is "assigned".
|
||||
Sort string `url:"sort,omitempty"`
|
||||
|
||||
// Direction in which to sort issues. Possible values are: asc, desc.
|
||||
// Default is "asc".
|
||||
Direction string `url:"direction,omitempty"`
|
||||
|
||||
// Since filters issues by time.
|
||||
Since time.Time `url:"since,omitempty"`
|
||||
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// PullRequestLinks object is added to the Issue object when it's an issue included
|
||||
// in the IssueCommentEvent webhook payload, if the webhooks is fired by a comment on a PR
|
||||
type PullRequestLinks struct {
|
||||
URL *string `json:"url,omitempty"`
|
||||
HTMLURL *string `json:"html_url,omitempty"`
|
||||
DiffURL *string `json:"diff_url,omitempty"`
|
||||
PatchURL *string `json:"patch_url,omitempty"`
|
||||
}
|
||||
|
||||
// List the issues for the authenticated user. If all is true, list issues
|
||||
// across all the user's visible repositories including owned, member, and
|
||||
// organization repositories; if false, list only owned and member
|
||||
// repositories.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/#list-issues
|
||||
func (s *IssuesService) List(all bool, opt *IssueListOptions) ([]Issue, *Response, error) {
|
||||
var u string
|
||||
if all {
|
||||
u = "issues"
|
||||
} else {
|
||||
u = "user/issues"
|
||||
}
|
||||
return s.listIssues(u, opt)
|
||||
}
|
||||
|
||||
// ListByOrg fetches the issues in the specified organization for the
|
||||
// authenticated user.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/#list-issues
|
||||
func (s *IssuesService) ListByOrg(org string, opt *IssueListOptions) ([]Issue, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/issues", org)
|
||||
return s.listIssues(u, opt)
|
||||
}
|
||||
|
||||
func (s *IssuesService) listIssues(u string, opt *IssueListOptions) ([]Issue, *Response, error) {
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
issues := new([]Issue)
|
||||
resp, err := s.client.Do(req, issues)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *issues, resp, err
|
||||
}
|
||||
|
||||
// IssueListByRepoOptions specifies the optional parameters to the
|
||||
// IssuesService.ListByRepo method.
|
||||
type IssueListByRepoOptions struct {
|
||||
// Milestone limits issues for the specified milestone. Possible values are
|
||||
// a milestone number, "none" for issues with no milestone, "*" for issues
|
||||
// with any milestone.
|
||||
Milestone string `url:"milestone,omitempty"`
|
||||
|
||||
// State filters issues based on their state. Possible values are: open,
|
||||
// closed. Default is "open".
|
||||
State string `url:"state,omitempty"`
|
||||
|
||||
// Assignee filters issues based on their assignee. Possible values are a
|
||||
// user name, "none" for issues that are not assigned, "*" for issues with
|
||||
// any assigned user.
|
||||
Assignee string `url:"assignee,omitempty"`
|
||||
|
||||
// Assignee filters issues based on their creator.
|
||||
Creator string `url:"creator,omitempty"`
|
||||
|
||||
// Assignee filters issues to those mentioned a specific user.
|
||||
Mentioned string `url:"mentioned,omitempty"`
|
||||
|
||||
// Labels filters issues based on their label.
|
||||
Labels []string `url:"labels,omitempty,comma"`
|
||||
|
||||
// Sort specifies how to sort issues. Possible values are: created, updated,
|
||||
// and comments. Default value is "assigned".
|
||||
Sort string `url:"sort,omitempty"`
|
||||
|
||||
// Direction in which to sort issues. Possible values are: asc, desc.
|
||||
// Default is "asc".
|
||||
Direction string `url:"direction,omitempty"`
|
||||
|
||||
// Since filters issues by time.
|
||||
Since time.Time `url:"since,omitempty"`
|
||||
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListByRepo lists the issues for the specified repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/#list-issues-for-a-repository
|
||||
func (s *IssuesService) ListByRepo(owner string, repo string, opt *IssueListByRepoOptions) ([]Issue, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
issues := new([]Issue)
|
||||
resp, err := s.client.Do(req, issues)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *issues, resp, err
|
||||
}
|
||||
|
||||
// Get a single issue.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/#get-a-single-issue
|
||||
func (s *IssuesService) Get(owner string, repo string, number int) (*Issue, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
issue := new(Issue)
|
||||
resp, err := s.client.Do(req, issue)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return issue, resp, err
|
||||
}
|
||||
|
||||
// Create a new issue on the specified repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/#create-an-issue
|
||||
func (s *IssuesService) Create(owner string, repo string, issue *IssueRequest) (*Issue, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues", owner, repo)
|
||||
req, err := s.client.NewRequest("POST", u, issue)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
i := new(Issue)
|
||||
resp, err := s.client.Do(req, i)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return i, resp, err
|
||||
}
|
||||
|
||||
// Edit an issue.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/#edit-an-issue
|
||||
func (s *IssuesService) Edit(owner string, repo string, number int, issue *IssueRequest) (*Issue, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number)
|
||||
req, err := s.client.NewRequest("PATCH", u, issue)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
i := new(Issue)
|
||||
resp, err := s.client.Do(req, i)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return i, resp, err
|
||||
}
|
||||
46
Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ListAssignees fetches all available assignees (owners and collaborators) to
|
||||
// which issues may be assigned.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/assignees/#list-assignees
|
||||
func (s *IssuesService) ListAssignees(owner string, repo string, opt *ListOptions) ([]User, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/assignees", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
assignees := new([]User)
|
||||
resp, err := s.client.Do(req, assignees)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *assignees, resp, err
|
||||
}
|
||||
|
||||
// IsAssignee checks if a user is an assignee for the specified repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/assignees/#check-assignee
|
||||
func (s *IssuesService) IsAssignee(owner string, repo string, user string) (bool, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/assignees/%v", owner, repo, user)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
resp, err := s.client.Do(req, nil)
|
||||
assignee, err := parseBoolResponse(err)
|
||||
return assignee, resp, err
|
||||
}
|
||||
98
Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees_test.go
generated
vendored
Normal file
98
Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees_test.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIssuesService_ListAssignees(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/assignees", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{"page": "2"})
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
assignees, _, err := client.Issues.ListAssignees("o", "r", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.List returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []User{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(assignees, want) {
|
||||
t.Errorf("Issues.ListAssignees returned %+v, want %+v", assignees, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListAssignees_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.ListAssignees("%", "r", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_IsAssignee_true(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
})
|
||||
|
||||
assignee, _, err := client.Issues.IsAssignee("o", "r", "u")
|
||||
if err != nil {
|
||||
t.Errorf("Issues.IsAssignee returned error: %v", err)
|
||||
}
|
||||
if want := true; assignee != want {
|
||||
t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_IsAssignee_false(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
assignee, _, err := client.Issues.IsAssignee("o", "r", "u")
|
||||
if err != nil {
|
||||
t.Errorf("Issues.IsAssignee returned error: %v", err)
|
||||
}
|
||||
if want := false; assignee != want {
|
||||
t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_IsAssignee_error(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
http.Error(w, "BadRequest", http.StatusBadRequest)
|
||||
})
|
||||
|
||||
assignee, _, err := client.Issues.IsAssignee("o", "r", "u")
|
||||
if err == nil {
|
||||
t.Errorf("Expected HTTP 400 response")
|
||||
}
|
||||
if want := false; assignee != want {
|
||||
t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_IsAssignee_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.IsAssignee("%", "r", "u")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
138
Godeps/_workspace/src/github.com/google/go-github/github/issues_comments.go
generated
vendored
Normal file
138
Godeps/_workspace/src/github.com/google/go-github/github/issues_comments.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IssueComment represents a comment left on an issue.
|
||||
type IssueComment struct {
|
||||
ID *int `json:"id,omitempty"`
|
||||
Body *string `json:"body,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
HTMLURL *string `json:"html_url,omitempty"`
|
||||
IssueURL *string `json:"issue_url,omitempty"`
|
||||
}
|
||||
|
||||
func (i IssueComment) String() string {
|
||||
return Stringify(i)
|
||||
}
|
||||
|
||||
// IssueListCommentsOptions specifies the optional parameters to the
|
||||
// IssuesService.ListComments method.
|
||||
type IssueListCommentsOptions struct {
|
||||
// Sort specifies how to sort comments. Possible values are: created, updated.
|
||||
Sort string `url:"sort,omitempty"`
|
||||
|
||||
// Direction in which to sort comments. Possible values are: asc, desc.
|
||||
Direction string `url:"direction,omitempty"`
|
||||
|
||||
// Since filters comments by time.
|
||||
Since time.Time `url:"since,omitempty"`
|
||||
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListComments lists all comments on the specified issue. Specifying an issue
|
||||
// number of 0 will return all comments on all issues for the repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
|
||||
func (s *IssuesService) ListComments(owner string, repo string, number int, opt *IssueListCommentsOptions) ([]IssueComment, *Response, error) {
|
||||
var u string
|
||||
if number == 0 {
|
||||
u = fmt.Sprintf("repos/%v/%v/issues/comments", owner, repo)
|
||||
} else {
|
||||
u = fmt.Sprintf("repos/%v/%v/issues/%d/comments", owner, repo, number)
|
||||
}
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
comments := new([]IssueComment)
|
||||
resp, err := s.client.Do(req, comments)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *comments, resp, err
|
||||
}
|
||||
|
||||
// GetComment fetches the specified issue comment.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/comments/#get-a-single-comment
|
||||
func (s *IssuesService) GetComment(owner string, repo string, id int) (*IssueComment, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, id)
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
comment := new(IssueComment)
|
||||
resp, err := s.client.Do(req, comment)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return comment, resp, err
|
||||
}
|
||||
|
||||
// CreateComment creates a new comment on the specified issue.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/comments/#create-a-comment
|
||||
func (s *IssuesService) CreateComment(owner string, repo string, number int, comment *IssueComment) (*IssueComment, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/%d/comments", owner, repo, number)
|
||||
req, err := s.client.NewRequest("POST", u, comment)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c := new(IssueComment)
|
||||
resp, err := s.client.Do(req, c)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return c, resp, err
|
||||
}
|
||||
|
||||
// EditComment updates an issue comment.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/comments/#edit-a-comment
|
||||
func (s *IssuesService) EditComment(owner string, repo string, id int, comment *IssueComment) (*IssueComment, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, id)
|
||||
req, err := s.client.NewRequest("PATCH", u, comment)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c := new(IssueComment)
|
||||
resp, err := s.client.Do(req, c)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return c, resp, err
|
||||
}
|
||||
|
||||
// DeleteComment deletes an issue comment.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/comments/#delete-a-comment
|
||||
func (s *IssuesService) DeleteComment(owner string, repo string, id int) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, id)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
184
Godeps/_workspace/src/github.com/google/go-github/github/issues_comments_test.go
generated
vendored
Normal file
184
Godeps/_workspace/src/github.com/google/go-github/github/issues_comments_test.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIssuesService_ListComments_allIssues(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/comments", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"sort": "updated",
|
||||
"direction": "desc",
|
||||
"since": "2002-02-10T15:30:00Z",
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
opt := &IssueListCommentsOptions{
|
||||
Sort: "updated",
|
||||
Direction: "desc",
|
||||
Since: time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
|
||||
ListOptions: ListOptions{Page: 2},
|
||||
}
|
||||
comments, _, err := client.Issues.ListComments("o", "r", 0, opt)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ListComments returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []IssueComment{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(comments, want) {
|
||||
t.Errorf("Issues.ListComments returned %+v, want %+v", comments, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListComments_specificIssue(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1/comments", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
comments, _, err := client.Issues.ListComments("o", "r", 1, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ListComments returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []IssueComment{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(comments, want) {
|
||||
t.Errorf("Issues.ListComments returned %+v, want %+v", comments, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListComments_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.ListComments("%", "r", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_GetComment(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"id":1}`)
|
||||
})
|
||||
|
||||
comment, _, err := client.Issues.GetComment("o", "r", 1)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.GetComment returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &IssueComment{ID: Int(1)}
|
||||
if !reflect.DeepEqual(comment, want) {
|
||||
t.Errorf("Issues.GetComment returned %+v, want %+v", comment, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_GetComment_invalidOrg(t *testing.T) {
|
||||
_, _, err := client.Issues.GetComment("%", "r", 1)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_CreateComment(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &IssueComment{Body: String("b")}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1/comments", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(IssueComment)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"id":1}`)
|
||||
})
|
||||
|
||||
comment, _, err := client.Issues.CreateComment("o", "r", 1, input)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.CreateComment returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &IssueComment{ID: Int(1)}
|
||||
if !reflect.DeepEqual(comment, want) {
|
||||
t.Errorf("Issues.CreateComment returned %+v, want %+v", comment, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_CreateComment_invalidOrg(t *testing.T) {
|
||||
_, _, err := client.Issues.CreateComment("%", "r", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_EditComment(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &IssueComment{Body: String("b")}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(IssueComment)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PATCH")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"id":1}`)
|
||||
})
|
||||
|
||||
comment, _, err := client.Issues.EditComment("o", "r", 1, input)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.EditComment returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &IssueComment{ID: Int(1)}
|
||||
if !reflect.DeepEqual(comment, want) {
|
||||
t.Errorf("Issues.EditComment returned %+v, want %+v", comment, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_EditComment_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.EditComment("%", "r", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_DeleteComment(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Issues.DeleteComment("o", "r", 1)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.DeleteComments returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_DeleteComment_invalidOwner(t *testing.T) {
|
||||
_, err := client.Issues.DeleteComment("%", "r", 1)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
127
Godeps/_workspace/src/github.com/google/go-github/github/issues_events.go
generated
vendored
Normal file
127
Godeps/_workspace/src/github.com/google/go-github/github/issues_events.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IssueEvent represents an event that occurred around an Issue or Pull Request.
|
||||
type IssueEvent struct {
|
||||
ID *int `json:"id,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
|
||||
// The User that generated this event.
|
||||
Actor *User `json:"actor,omitempty"`
|
||||
|
||||
// Event identifies the actual type of Event that occurred. Possible
|
||||
// values are:
|
||||
//
|
||||
// closed
|
||||
// The issue was closed by the actor. When the commit_id is
|
||||
// present, it identifies the commit that closed the issue using
|
||||
// “closes / fixes #NN” syntax.
|
||||
//
|
||||
// reopened
|
||||
// The issue was reopened by the actor.
|
||||
//
|
||||
// subscribed
|
||||
// The actor subscribed to receive notifications for an issue.
|
||||
//
|
||||
// merged
|
||||
// The issue was merged by the actor. The commit_id attribute is the SHA1 of the HEAD commit that was merged.
|
||||
//
|
||||
// referenced
|
||||
// The issue was referenced from a commit message. The commit_id attribute is the commit SHA1 of where that happened.
|
||||
//
|
||||
// mentioned
|
||||
// The actor was @mentioned in an issue body.
|
||||
//
|
||||
// assigned
|
||||
// The issue was assigned to the actor.
|
||||
//
|
||||
// head_ref_deleted
|
||||
// The pull request’s branch was deleted.
|
||||
//
|
||||
// head_ref_restored
|
||||
// The pull request’s branch was restored.
|
||||
Event *string `json:"event,omitempty"`
|
||||
|
||||
// The SHA of the commit that referenced this commit, if applicable.
|
||||
CommitID *string `json:"commit_id,omitempty"`
|
||||
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
Issue *Issue `json:"issue,omitempty"`
|
||||
}
|
||||
|
||||
// ListIssueEvents lists events for the specified issue.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/issues/events/#list-events-for-an-issue
|
||||
func (s *IssuesService) ListIssueEvents(owner, repo string, number int, opt *ListOptions) ([]IssueEvent, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/%v/events", owner, repo, number)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var events []IssueEvent
|
||||
resp, err := s.client.Do(req, &events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return events, resp, err
|
||||
}
|
||||
|
||||
// ListRepositoryEvents lists events for the specified repository.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/issues/events/#list-events-for-a-repository
|
||||
func (s *IssuesService) ListRepositoryEvents(owner, repo string, opt *ListOptions) ([]IssueEvent, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var events []IssueEvent
|
||||
resp, err := s.client.Do(req, &events)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return events, resp, err
|
||||
}
|
||||
|
||||
// GetEvent returns the specified issue event.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/issues/events/#get-a-single-event
|
||||
func (s *IssuesService) GetEvent(owner, repo string, id int) (*IssueEvent, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/events/%v", owner, repo, id)
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
event := new(IssueEvent)
|
||||
resp, err := s.client.Do(req, event)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return event, resp, err
|
||||
}
|
||||
86
Godeps/_workspace/src/github.com/google/go-github/github/issues_events_test.go
generated
vendored
Normal file
86
Godeps/_workspace/src/github.com/google/go-github/github/issues_events_test.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIssuesService_ListIssueEvents(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "1",
|
||||
"per_page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 1, PerPage: 2}
|
||||
events, _, err := client.Issues.ListIssueEvents("o", "r", 1, opt)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ListIssueEvents returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []IssueEvent{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Issues.ListIssueEvents returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListRepositoryEvents(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"page": "1",
|
||||
"per_page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 1, PerPage: 2}
|
||||
events, _, err := client.Issues.ListRepositoryEvents("o", "r", opt)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ListRepositoryEvents returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []IssueEvent{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("Issues.ListRepositoryEvents returned %+v, want %+v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_GetEvent(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/events/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"id":1}`)
|
||||
})
|
||||
|
||||
event, _, err := client.Issues.GetEvent("o", "r", 1)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Issues.GetEvent returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &IssueEvent{ID: Int(1)}
|
||||
if !reflect.DeepEqual(event, want) {
|
||||
t.Errorf("Issues.GetEvent returned %+v, want %+v", event, want)
|
||||
}
|
||||
}
|
||||
222
Godeps/_workspace/src/github.com/google/go-github/github/issues_labels.go
generated
vendored
Normal file
222
Godeps/_workspace/src/github.com/google/go-github/github/issues_labels.go
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Label represents a GitHib label on an Issue
|
||||
type Label struct {
|
||||
URL *string `json:"url,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Color *string `json:"color,omitempty"`
|
||||
}
|
||||
|
||||
func (l Label) String() string {
|
||||
return fmt.Sprint(*l.Name)
|
||||
}
|
||||
|
||||
// ListLabels lists all labels for a repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
|
||||
func (s *IssuesService) ListLabels(owner string, repo string, opt *ListOptions) ([]Label, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/labels", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
labels := new([]Label)
|
||||
resp, err := s.client.Do(req, labels)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *labels, resp, err
|
||||
}
|
||||
|
||||
// GetLabel gets a single label.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#get-a-single-label
|
||||
func (s *IssuesService) GetLabel(owner string, repo string, name string) (*Label, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
label := new(Label)
|
||||
resp, err := s.client.Do(req, label)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return label, resp, err
|
||||
}
|
||||
|
||||
// CreateLabel creates a new label on the specified repository.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#create-a-label
|
||||
func (s *IssuesService) CreateLabel(owner string, repo string, label *Label) (*Label, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/labels", owner, repo)
|
||||
req, err := s.client.NewRequest("POST", u, label)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
l := new(Label)
|
||||
resp, err := s.client.Do(req, l)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return l, resp, err
|
||||
}
|
||||
|
||||
// EditLabel edits a label.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#update-a-label
|
||||
func (s *IssuesService) EditLabel(owner string, repo string, name string, label *Label) (*Label, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name)
|
||||
req, err := s.client.NewRequest("PATCH", u, label)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
l := new(Label)
|
||||
resp, err := s.client.Do(req, l)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return l, resp, err
|
||||
}
|
||||
|
||||
// DeleteLabel deletes a label.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#delete-a-label
|
||||
func (s *IssuesService) DeleteLabel(owner string, repo string, name string) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// ListLabelsByIssue lists all labels for an issue.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
|
||||
func (s *IssuesService) ListLabelsByIssue(owner string, repo string, number int, opt *ListOptions) ([]Label, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
labels := new([]Label)
|
||||
resp, err := s.client.Do(req, labels)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *labels, resp, err
|
||||
}
|
||||
|
||||
// AddLabelsToIssue adds labels to an issue.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
|
||||
func (s *IssuesService) AddLabelsToIssue(owner string, repo string, number int, labels []string) ([]Label, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number)
|
||||
req, err := s.client.NewRequest("POST", u, labels)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
l := new([]Label)
|
||||
resp, err := s.client.Do(req, l)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *l, resp, err
|
||||
}
|
||||
|
||||
// RemoveLabelForIssue removes a label for an issue.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue
|
||||
func (s *IssuesService) RemoveLabelForIssue(owner string, repo string, number int, label string) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/%d/labels/%v", owner, repo, number, label)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// ReplaceLabelsForIssue replaces all labels for an issue.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue
|
||||
func (s *IssuesService) ReplaceLabelsForIssue(owner string, repo string, number int, labels []string) ([]Label, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number)
|
||||
req, err := s.client.NewRequest("PUT", u, labels)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
l := new([]Label)
|
||||
resp, err := s.client.Do(req, l)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *l, resp, err
|
||||
}
|
||||
|
||||
// RemoveLabelsForIssue removes all labels for an issue.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#remove-all-labels-from-an-issue
|
||||
func (s *IssuesService) RemoveLabelsForIssue(owner string, repo string, number int) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// ListLabelsForMilestone lists labels for every issue in a milestone.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone
|
||||
func (s *IssuesService) ListLabelsForMilestone(owner string, repo string, number int, opt *ListOptions) ([]Label, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/milestones/%d/labels", owner, repo, number)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
labels := new([]Label)
|
||||
resp, err := s.client.Do(req, labels)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *labels, resp, err
|
||||
}
|
||||
313
Godeps/_workspace/src/github.com/google/go-github/github/issues_labels_test.go
generated
vendored
Normal file
313
Godeps/_workspace/src/github.com/google/go-github/github/issues_labels_test.go
generated
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIssuesService_ListLabels(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/labels", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{"page": "2"})
|
||||
fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
labels, _, err := client.Issues.ListLabels("o", "r", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ListLabels returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Label{{Name: String("a")}, {Name: String("b")}}
|
||||
if !reflect.DeepEqual(labels, want) {
|
||||
t.Errorf("Issues.ListLabels returned %+v, want %+v", labels, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListLabels_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.ListLabels("%", "%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_GetLabel(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"url":"u", "name": "n", "color": "c"}`)
|
||||
})
|
||||
|
||||
label, _, err := client.Issues.GetLabel("o", "r", "n")
|
||||
if err != nil {
|
||||
t.Errorf("Issues.GetLabel returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Label{URL: String("u"), Name: String("n"), Color: String("c")}
|
||||
if !reflect.DeepEqual(label, want) {
|
||||
t.Errorf("Issues.GetLabel returned %+v, want %+v", label, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_GetLabel_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.GetLabel("%", "%", "%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_CreateLabel(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Label{Name: String("n")}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/labels", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Label)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"url":"u"}`)
|
||||
})
|
||||
|
||||
label, _, err := client.Issues.CreateLabel("o", "r", input)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.CreateLabel returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Label{URL: String("u")}
|
||||
if !reflect.DeepEqual(label, want) {
|
||||
t.Errorf("Issues.CreateLabel returned %+v, want %+v", label, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_CreateLabel_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.CreateLabel("%", "%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_EditLabel(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Label{Name: String("z")}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Label)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PATCH")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"url":"u"}`)
|
||||
})
|
||||
|
||||
label, _, err := client.Issues.EditLabel("o", "r", "n", input)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.EditLabel returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Label{URL: String("u")}
|
||||
if !reflect.DeepEqual(label, want) {
|
||||
t.Errorf("Issues.EditLabel returned %+v, want %+v", label, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_EditLabel_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.EditLabel("%", "%", "%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_DeleteLabel(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Issues.DeleteLabel("o", "r", "n")
|
||||
if err != nil {
|
||||
t.Errorf("Issues.DeleteLabel returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_DeleteLabel_invalidOwner(t *testing.T) {
|
||||
_, err := client.Issues.DeleteLabel("%", "%", "%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_ListLabelsByIssue(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{"page": "2"})
|
||||
fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
labels, _, err := client.Issues.ListLabelsByIssue("o", "r", 1, opt)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ListLabelsByIssue returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Label{{Name: String("a")}, {Name: String("b")}}
|
||||
if !reflect.DeepEqual(labels, want) {
|
||||
t.Errorf("Issues.ListLabelsByIssue returned %+v, want %+v", labels, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListLabelsByIssue_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.ListLabelsByIssue("%", "%", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_AddLabelsToIssue(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := []string{"a", "b"}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new([]string)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(*v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `[{"url":"u"}]`)
|
||||
})
|
||||
|
||||
labels, _, err := client.Issues.AddLabelsToIssue("o", "r", 1, input)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.AddLabelsToIssue returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Label{{URL: String("u")}}
|
||||
if !reflect.DeepEqual(labels, want) {
|
||||
t.Errorf("Issues.AddLabelsToIssue returned %+v, want %+v", labels, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_AddLabelsToIssue_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.AddLabelsToIssue("%", "%", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_RemoveLabelForIssue(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1/labels/l", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Issues.RemoveLabelForIssue("o", "r", 1, "l")
|
||||
if err != nil {
|
||||
t.Errorf("Issues.RemoveLabelForIssue returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_RemoveLabelForIssue_invalidOwner(t *testing.T) {
|
||||
_, err := client.Issues.RemoveLabelForIssue("%", "%", 1, "%")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_ReplaceLabelsForIssue(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := []string{"a", "b"}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new([]string)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PUT")
|
||||
if !reflect.DeepEqual(*v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `[{"url":"u"}]`)
|
||||
})
|
||||
|
||||
labels, _, err := client.Issues.ReplaceLabelsForIssue("o", "r", 1, input)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ReplaceLabelsForIssue returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Label{{URL: String("u")}}
|
||||
if !reflect.DeepEqual(labels, want) {
|
||||
t.Errorf("Issues.ReplaceLabelsForIssue returned %+v, want %+v", labels, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ReplaceLabelsForIssue_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.ReplaceLabelsForIssue("%", "%", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_RemoveLabelsForIssue(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Issues.RemoveLabelsForIssue("o", "r", 1)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.RemoveLabelsForIssue returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_RemoveLabelsForIssue_invalidOwner(t *testing.T) {
|
||||
_, err := client.Issues.RemoveLabelsForIssue("%", "%", 1)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_ListLabelsForMilestone(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/milestones/1/labels", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{"page": "2"})
|
||||
fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOptions{Page: 2}
|
||||
labels, _, err := client.Issues.ListLabelsForMilestone("o", "r", 1, opt)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ListLabelsForMilestone returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Label{{Name: String("a")}, {Name: String("b")}}
|
||||
if !reflect.DeepEqual(labels, want) {
|
||||
t.Errorf("Issues.ListLabelsForMilestone returned %+v, want %+v", labels, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListLabelsForMilestone_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.ListLabelsForMilestone("%", "%", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
140
Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones.go
generated
vendored
Normal file
140
Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Milestone represents a Github repository milestone.
|
||||
type Milestone struct {
|
||||
URL *string `json:"url,omitempty"`
|
||||
Number *int `json:"number,omitempty"`
|
||||
State *string `json:"state,omitempty"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Creator *User `json:"creator,omitempty"`
|
||||
OpenIssues *int `json:"open_issues,omitempty"`
|
||||
ClosedIssues *int `json:"closed_issues,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
DueOn *time.Time `json:"due_on,omitempty"`
|
||||
}
|
||||
|
||||
func (m Milestone) String() string {
|
||||
return Stringify(m)
|
||||
}
|
||||
|
||||
// MilestoneListOptions specifies the optional parameters to the
|
||||
// IssuesService.ListMilestones method.
|
||||
type MilestoneListOptions struct {
|
||||
// State filters milestones based on their state. Possible values are:
|
||||
// open, closed. Default is "open".
|
||||
State string `url:"state,omitempty"`
|
||||
|
||||
// Sort specifies how to sort milestones. Possible values are: due_date, completeness.
|
||||
// Default value is "due_date".
|
||||
Sort string `url:"sort,omitempty"`
|
||||
|
||||
// Direction in which to sort milestones. Possible values are: asc, desc.
|
||||
// Default is "asc".
|
||||
Direction string `url:"direction,omitempty"`
|
||||
}
|
||||
|
||||
// ListMilestones lists all milestones for a repository.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository
|
||||
func (s *IssuesService) ListMilestones(owner string, repo string, opt *MilestoneListOptions) ([]Milestone, *Response, error) {
|
||||
u := fmt.Sprintf("/repos/%v/%v/milestones", owner, repo)
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
milestones := new([]Milestone)
|
||||
resp, err := s.client.Do(req, milestones)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *milestones, resp, err
|
||||
}
|
||||
|
||||
// GetMilestone gets a single milestone.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/issues/milestones/#get-a-single-milestone
|
||||
func (s *IssuesService) GetMilestone(owner string, repo string, number int) (*Milestone, *Response, error) {
|
||||
u := fmt.Sprintf("/repos/%v/%v/milestones/%d", owner, repo, number)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
milestone := new(Milestone)
|
||||
resp, err := s.client.Do(req, milestone)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return milestone, resp, err
|
||||
}
|
||||
|
||||
// CreateMilestone creates a new milestone on the specified repository.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/issues/milestones/#create-a-milestone
|
||||
func (s *IssuesService) CreateMilestone(owner string, repo string, milestone *Milestone) (*Milestone, *Response, error) {
|
||||
u := fmt.Sprintf("/repos/%v/%v/milestones", owner, repo)
|
||||
req, err := s.client.NewRequest("POST", u, milestone)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
m := new(Milestone)
|
||||
resp, err := s.client.Do(req, m)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return m, resp, err
|
||||
}
|
||||
|
||||
// EditMilestone edits a milestone.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/issues/milestones/#update-a-milestone
|
||||
func (s *IssuesService) EditMilestone(owner string, repo string, number int, milestone *Milestone) (*Milestone, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number)
|
||||
req, err := s.client.NewRequest("PATCH", u, milestone)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
m := new(Milestone)
|
||||
resp, err := s.client.Do(req, m)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return m, resp, err
|
||||
}
|
||||
|
||||
// DeleteMilestone deletes a milestone.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/issues/milestones/#delete-a-milestone
|
||||
func (s *IssuesService) DeleteMilestone(owner string, repo string, number int) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
157
Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones_test.go
generated
vendored
Normal file
157
Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones_test.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIssuesService_ListMilestones(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/milestones", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"state": "closed",
|
||||
"sort": "due_date",
|
||||
"direction": "asc",
|
||||
})
|
||||
fmt.Fprint(w, `[{"number":1}]`)
|
||||
})
|
||||
|
||||
opt := &MilestoneListOptions{"closed", "due_date", "asc"}
|
||||
milestones, _, err := client.Issues.ListMilestones("o", "r", opt)
|
||||
if err != nil {
|
||||
t.Errorf("IssuesService.ListMilestones returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Milestone{{Number: Int(1)}}
|
||||
if !reflect.DeepEqual(milestones, want) {
|
||||
t.Errorf("IssuesService.ListMilestones returned %+v, want %+v", milestones, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListMilestones_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.ListMilestones("%", "r", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_GetMilestone(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"number":1}`)
|
||||
})
|
||||
|
||||
milestone, _, err := client.Issues.GetMilestone("o", "r", 1)
|
||||
if err != nil {
|
||||
t.Errorf("IssuesService.GetMilestone returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Milestone{Number: Int(1)}
|
||||
if !reflect.DeepEqual(milestone, want) {
|
||||
t.Errorf("IssuesService.GetMilestone returned %+v, want %+v", milestone, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_GetMilestone_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.GetMilestone("%", "r", 1)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_CreateMilestone(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Milestone{Title: String("t")}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/milestones", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Milestone)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"number":1}`)
|
||||
})
|
||||
|
||||
milestone, _, err := client.Issues.CreateMilestone("o", "r", input)
|
||||
if err != nil {
|
||||
t.Errorf("IssuesService.CreateMilestone returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Milestone{Number: Int(1)}
|
||||
if !reflect.DeepEqual(milestone, want) {
|
||||
t.Errorf("IssuesService.CreateMilestone returned %+v, want %+v", milestone, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_CreateMilestone_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.CreateMilestone("%", "r", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_EditMilestone(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Milestone{Title: String("t")}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Milestone)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PATCH")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"number":1}`)
|
||||
})
|
||||
|
||||
milestone, _, err := client.Issues.EditMilestone("o", "r", 1, input)
|
||||
if err != nil {
|
||||
t.Errorf("IssuesService.EditMilestone returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Milestone{Number: Int(1)}
|
||||
if !reflect.DeepEqual(milestone, want) {
|
||||
t.Errorf("IssuesService.EditMilestone returned %+v, want %+v", milestone, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_EditMilestone_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.EditMilestone("%", "r", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_DeleteMilestone(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Issues.DeleteMilestone("o", "r", 1)
|
||||
if err != nil {
|
||||
t.Errorf("IssuesService.DeleteMilestone returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_DeleteMilestone_invalidOwner(t *testing.T) {
|
||||
_, err := client.Issues.DeleteMilestone("%", "r", 1)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
242
Godeps/_workspace/src/github.com/google/go-github/github/issues_test.go
generated
vendored
Normal file
242
Godeps/_workspace/src/github.com/google/go-github/github/issues_test.go
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIssuesService_List_all(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/issues", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"filter": "all",
|
||||
"state": "closed",
|
||||
"labels": "a,b",
|
||||
"sort": "updated",
|
||||
"direction": "asc",
|
||||
"since": "2002-02-10T15:30:00Z",
|
||||
"page": "1",
|
||||
"per_page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"number":1}]`)
|
||||
})
|
||||
|
||||
opt := &IssueListOptions{
|
||||
"all", "closed", []string{"a", "b"}, "updated", "asc",
|
||||
time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
|
||||
ListOptions{Page: 1, PerPage: 2},
|
||||
}
|
||||
issues, _, err := client.Issues.List(true, opt)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Issues.List returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Issue{{Number: Int(1)}}
|
||||
if !reflect.DeepEqual(issues, want) {
|
||||
t.Errorf("Issues.List returned %+v, want %+v", issues, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_List_owned(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/user/issues", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"number":1}]`)
|
||||
})
|
||||
|
||||
issues, _, err := client.Issues.List(false, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.List returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Issue{{Number: Int(1)}}
|
||||
if !reflect.DeepEqual(issues, want) {
|
||||
t.Errorf("Issues.List returned %+v, want %+v", issues, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListByOrg(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/issues", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"number":1}]`)
|
||||
})
|
||||
|
||||
issues, _, err := client.Issues.ListByOrg("o", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ListByOrg returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Issue{{Number: Int(1)}}
|
||||
if !reflect.DeepEqual(issues, want) {
|
||||
t.Errorf("Issues.List returned %+v, want %+v", issues, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListByOrg_invalidOrg(t *testing.T) {
|
||||
_, _, err := client.Issues.ListByOrg("%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_ListByRepo(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"milestone": "*",
|
||||
"state": "closed",
|
||||
"assignee": "a",
|
||||
"creator": "c",
|
||||
"mentioned": "m",
|
||||
"labels": "a,b",
|
||||
"sort": "updated",
|
||||
"direction": "asc",
|
||||
"since": "2002-02-10T15:30:00Z",
|
||||
})
|
||||
fmt.Fprint(w, `[{"number":1}]`)
|
||||
})
|
||||
|
||||
opt := &IssueListByRepoOptions{
|
||||
"*", "closed", "a", "c", "m", []string{"a", "b"}, "updated", "asc",
|
||||
time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
|
||||
ListOptions{0, 0},
|
||||
}
|
||||
issues, _, err := client.Issues.ListByRepo("o", "r", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.ListByOrg returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Issue{{Number: Int(1)}}
|
||||
if !reflect.DeepEqual(issues, want) {
|
||||
t.Errorf("Issues.List returned %+v, want %+v", issues, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_ListByRepo_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.ListByRepo("%", "r", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_Get(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"number":1, "labels": [{"url": "u", "name": "n", "color": "c"}]}`)
|
||||
})
|
||||
|
||||
issue, _, err := client.Issues.Get("o", "r", 1)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.Get returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Issue{
|
||||
Number: Int(1),
|
||||
Labels: []Label{{
|
||||
URL: String("u"),
|
||||
Name: String("n"),
|
||||
Color: String("c"),
|
||||
}},
|
||||
}
|
||||
if !reflect.DeepEqual(issue, want) {
|
||||
t.Errorf("Issues.Get returned %+v, want %+v", issue, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_Get_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.Get("%", "r", 1)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_Create(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &IssueRequest{
|
||||
Title: String("t"),
|
||||
Body: String("b"),
|
||||
Assignee: String("a"),
|
||||
Labels: []string{"l1", "l2"},
|
||||
}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(IssueRequest)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"number":1}`)
|
||||
})
|
||||
|
||||
issue, _, err := client.Issues.Create("o", "r", input)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.Create returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Issue{Number: Int(1)}
|
||||
if !reflect.DeepEqual(issue, want) {
|
||||
t.Errorf("Issues.Create returned %+v, want %+v", issue, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_Create_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.Create("%", "r", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestIssuesService_Edit(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &IssueRequest{Title: String("t")}
|
||||
|
||||
mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(IssueRequest)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PATCH")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"number":1}`)
|
||||
})
|
||||
|
||||
issue, _, err := client.Issues.Edit("o", "r", 1, input)
|
||||
if err != nil {
|
||||
t.Errorf("Issues.Edit returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Issue{Number: Int(1)}
|
||||
if !reflect.DeepEqual(issue, want) {
|
||||
t.Errorf("Issues.Edit returned %+v, want %+v", issue, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuesService_Edit_invalidOwner(t *testing.T) {
|
||||
_, _, err := client.Issues.Edit("%", "r", 1, nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
161
Godeps/_workspace/src/github.com/google/go-github/github/misc.go
generated
vendored
Normal file
161
Godeps/_workspace/src/github.com/google/go-github/github/misc.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// MarkdownOptions specifies optional parameters to the Markdown method.
|
||||
type MarkdownOptions struct {
|
||||
// Mode identifies the rendering mode. Possible values are:
|
||||
// markdown - render a document as plain Markdown, just like
|
||||
// README files are rendered.
|
||||
//
|
||||
// gfm - to render a document as user-content, e.g. like user
|
||||
// comments or issues are rendered. In GFM mode, hard line breaks are
|
||||
// always taken into account, and issue and user mentions are linked
|
||||
// accordingly.
|
||||
//
|
||||
// Default is "markdown".
|
||||
Mode string
|
||||
|
||||
// Context identifies the repository context. Only taken into account
|
||||
// when rendering as "gfm".
|
||||
Context string
|
||||
}
|
||||
|
||||
type markdownRequest struct {
|
||||
Text *string `json:"text,omitempty"`
|
||||
Mode *string `json:"mode,omitempty"`
|
||||
Context *string `json:"context,omitempty"`
|
||||
}
|
||||
|
||||
// Markdown renders an arbitrary Markdown document.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/markdown/
|
||||
func (c *Client) Markdown(text string, opt *MarkdownOptions) (string, *Response, error) {
|
||||
request := &markdownRequest{Text: String(text)}
|
||||
if opt != nil {
|
||||
if opt.Mode != "" {
|
||||
request.Mode = String(opt.Mode)
|
||||
}
|
||||
if opt.Context != "" {
|
||||
request.Context = String(opt.Context)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := c.NewRequest("POST", "markdown", request)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
resp, err := c.Do(req, buf)
|
||||
if err != nil {
|
||||
return "", resp, err
|
||||
}
|
||||
|
||||
return buf.String(), resp, nil
|
||||
}
|
||||
|
||||
// ListEmojis returns the emojis available to use on GitHub.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/emojis/
|
||||
func (c *Client) ListEmojis() (map[string]string, *Response, error) {
|
||||
req, err := c.NewRequest("GET", "emojis", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var emoji map[string]string
|
||||
resp, err := c.Do(req, &emoji)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return emoji, resp, nil
|
||||
}
|
||||
|
||||
// APIMeta represents metadata about the GitHub API.
|
||||
type APIMeta struct {
|
||||
// An Array of IP addresses in CIDR format specifying the addresses
|
||||
// that incoming service hooks will originate from on GitHub.com.
|
||||
Hooks []string `json:"hooks,omitempty"`
|
||||
|
||||
// An Array of IP addresses in CIDR format specifying the Git servers
|
||||
// for GitHub.com.
|
||||
Git []string `json:"git,omitempty"`
|
||||
|
||||
// Whether authentication with username and password is supported.
|
||||
// (GitHub Enterprise instances using CAS or OAuth for authentication
|
||||
// will return false. Features like Basic Authentication with a
|
||||
// username and password, sudo mode, and two-factor authentication are
|
||||
// not supported on these servers.)
|
||||
VerifiablePasswordAuthentication *bool `json:"verifiable_password_authentication,omitempty"`
|
||||
}
|
||||
|
||||
// APIMeta returns information about GitHub.com, the service. Or, if you access
|
||||
// this endpoint on your organization’s GitHub Enterprise installation, this
|
||||
// endpoint provides information about that installation.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/meta/
|
||||
func (c *Client) APIMeta() (*APIMeta, *Response, error) {
|
||||
req, err := c.NewRequest("GET", "meta", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
meta := new(APIMeta)
|
||||
resp, err := c.Do(req, meta)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return meta, resp, nil
|
||||
}
|
||||
|
||||
// Octocat returns an ASCII art octocat with the specified message in a speech
|
||||
// bubble. If message is empty, a random zen phrase is used.
|
||||
func (c *Client) Octocat(message string) (string, *Response, error) {
|
||||
u := "octocat"
|
||||
if message != "" {
|
||||
u = fmt.Sprintf("%s?s=%s", u, url.QueryEscape(message))
|
||||
}
|
||||
|
||||
req, err := c.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
resp, err := c.Do(req, buf)
|
||||
if err != nil {
|
||||
return "", resp, err
|
||||
}
|
||||
|
||||
return buf.String(), resp, nil
|
||||
}
|
||||
|
||||
// Zen returns a random line from The Zen of GitHub.
|
||||
//
|
||||
// see also: http://warpspire.com/posts/taste/
|
||||
func (c *Client) Zen() (string, *Response, error) {
|
||||
req, err := c.NewRequest("GET", "zen", nil)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
resp, err := c.Do(req, buf)
|
||||
if err != nil {
|
||||
return "", resp, err
|
||||
}
|
||||
|
||||
return buf.String(), resp, nil
|
||||
}
|
||||
137
Godeps/_workspace/src/github.com/google/go-github/github/misc_test.go
generated
vendored
Normal file
137
Godeps/_workspace/src/github.com/google/go-github/github/misc_test.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMarkdown(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &markdownRequest{
|
||||
Text: String("# text #"),
|
||||
Mode: String("gfm"),
|
||||
Context: String("google/go-github"),
|
||||
}
|
||||
mux.HandleFunc("/markdown", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(markdownRequest)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "POST")
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
fmt.Fprint(w, `<h1>text</h1>`)
|
||||
})
|
||||
|
||||
md, _, err := client.Markdown("# text #", &MarkdownOptions{
|
||||
Mode: "gfm",
|
||||
Context: "google/go-github",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Markdown returned error: %v", err)
|
||||
}
|
||||
|
||||
if want := "<h1>text</h1>"; want != md {
|
||||
t.Errorf("Markdown returned %+v, want %+v", md, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEmojis(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/emojis", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"+1": "+1.png"}`)
|
||||
})
|
||||
|
||||
emoji, _, err := client.ListEmojis()
|
||||
if err != nil {
|
||||
t.Errorf("ListEmojis returned error: %v", err)
|
||||
}
|
||||
|
||||
want := map[string]string{"+1": "+1.png"}
|
||||
if !reflect.DeepEqual(want, emoji) {
|
||||
t.Errorf("ListEmojis returned %+v, want %+v", emoji, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIMeta(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/meta", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"hooks":["h"], "git":["g"], "verifiable_password_authentication": true}`)
|
||||
})
|
||||
|
||||
meta, _, err := client.APIMeta()
|
||||
if err != nil {
|
||||
t.Errorf("APIMeta returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &APIMeta{
|
||||
Hooks: []string{"h"},
|
||||
Git: []string{"g"},
|
||||
VerifiablePasswordAuthentication: Bool(true),
|
||||
}
|
||||
if !reflect.DeepEqual(want, meta) {
|
||||
t.Errorf("APIMeta returned %+v, want %+v", meta, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOctocat(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := "input"
|
||||
output := "sample text"
|
||||
|
||||
mux.HandleFunc("/octocat", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{"s": input})
|
||||
w.Header().Set("Content-Type", "application/octocat-stream")
|
||||
fmt.Fprint(w, output)
|
||||
})
|
||||
|
||||
got, _, err := client.Octocat(input)
|
||||
if err != nil {
|
||||
t.Errorf("Octocat returned error: %v", err)
|
||||
}
|
||||
|
||||
if want := output; got != want {
|
||||
t.Errorf("Octocat returned %+v, want %+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZen(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
output := "sample text"
|
||||
|
||||
mux.HandleFunc("/zen", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||
fmt.Fprint(w, output)
|
||||
})
|
||||
|
||||
got, _, err := client.Zen()
|
||||
if err != nil {
|
||||
t.Errorf("Zen returned error: %v", err)
|
||||
}
|
||||
|
||||
if want := output; got != want {
|
||||
t.Errorf("Zen returned %+v, want %+v", got, want)
|
||||
}
|
||||
}
|
||||
137
Godeps/_workspace/src/github.com/google/go-github/github/orgs.go
generated
vendored
Normal file
137
Godeps/_workspace/src/github.com/google/go-github/github/orgs.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OrganizationsService provides access to the organization related functions
|
||||
// in the GitHub API.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/
|
||||
type OrganizationsService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Organization represents a GitHub organization account.
|
||||
type Organization struct {
|
||||
Login *string `json:"login,omitempty"`
|
||||
ID *int `json:"id,omitempty"`
|
||||
AvatarURL *string `json:"avatar_url,omitempty"`
|
||||
HTMLURL *string `json:"html_url,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Company *string `json:"company,omitempty"`
|
||||
Blog *string `json:"blog,omitempty"`
|
||||
Location *string `json:"location,omitempty"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
PublicRepos *int `json:"public_repos,omitempty"`
|
||||
PublicGists *int `json:"public_gists,omitempty"`
|
||||
Followers *int `json:"followers,omitempty"`
|
||||
Following *int `json:"following,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
TotalPrivateRepos *int `json:"total_private_repos,omitempty"`
|
||||
OwnedPrivateRepos *int `json:"owned_private_repos,omitempty"`
|
||||
PrivateGists *int `json:"private_gists,omitempty"`
|
||||
DiskUsage *int `json:"disk_usage,omitempty"`
|
||||
Collaborators *int `json:"collaborators,omitempty"`
|
||||
BillingEmail *string `json:"billing_email,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Plan *Plan `json:"plan,omitempty"`
|
||||
|
||||
// API URLs
|
||||
URL *string `json:"url,omitempty"`
|
||||
EventsURL *string `json:"events_url,omitempty"`
|
||||
MembersURL *string `json:"members_url,omitempty"`
|
||||
PublicMembersURL *string `json:"public_members_url,omitempty"`
|
||||
ReposURL *string `json:"repos_url,omitempty"`
|
||||
}
|
||||
|
||||
func (o Organization) String() string {
|
||||
return Stringify(o)
|
||||
}
|
||||
|
||||
// Plan represents the payment plan for an account. See plans at https://github.com/plans.
|
||||
type Plan struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Space *int `json:"space,omitempty"`
|
||||
Collaborators *int `json:"collaborators,omitempty"`
|
||||
PrivateRepos *int `json:"private_repos,omitempty"`
|
||||
}
|
||||
|
||||
func (p Plan) String() string {
|
||||
return Stringify(p)
|
||||
}
|
||||
|
||||
// List the organizations for a user. Passing the empty string will list
|
||||
// organizations for the authenticated user.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/#list-user-organizations
|
||||
func (s *OrganizationsService) List(user string, opt *ListOptions) ([]Organization, *Response, error) {
|
||||
var u string
|
||||
if user != "" {
|
||||
u = fmt.Sprintf("users/%v/orgs", user)
|
||||
} else {
|
||||
u = "user/orgs"
|
||||
}
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
orgs := new([]Organization)
|
||||
resp, err := s.client.Do(req, orgs)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *orgs, resp, err
|
||||
}
|
||||
|
||||
// Get fetches an organization by name.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/#get-an-organization
|
||||
func (s *OrganizationsService) Get(org string) (*Organization, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v", org)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
organization := new(Organization)
|
||||
resp, err := s.client.Do(req, organization)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return organization, resp, err
|
||||
}
|
||||
|
||||
// Edit an organization.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/#edit-an-organization
|
||||
func (s *OrganizationsService) Edit(name string, org *Organization) (*Organization, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v", name)
|
||||
req, err := s.client.NewRequest("PATCH", u, org)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
o := new(Organization)
|
||||
resp, err := s.client.Do(req, o)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return o, resp, err
|
||||
}
|
||||
230
Godeps/_workspace/src/github.com/google/go-github/github/orgs_members.go
generated
vendored
Normal file
230
Godeps/_workspace/src/github.com/google/go-github/github/orgs_members.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Membership represents the status of a user's membership in an organization or team.
|
||||
type Membership struct {
|
||||
URL *string `json:"url,omitempty"`
|
||||
|
||||
// State is the user's status within the organization or team.
|
||||
// Possible values are: "active", "pending"
|
||||
State *string `json:"state,omitempty"`
|
||||
|
||||
// TODO(willnorris): add docs
|
||||
Role *string `json:"role,omitempty"`
|
||||
|
||||
// For organization membership, the API URL of the organization.
|
||||
OrganizationURL *string `json:"organization_url,omitempty"`
|
||||
|
||||
// For organization membership, the organization the membership is for.
|
||||
Organization *Organization `json:"organization,omitempty"`
|
||||
|
||||
// For organization membership, the user the membership is for.
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
func (m Membership) String() string {
|
||||
return Stringify(m)
|
||||
}
|
||||
|
||||
// ListMembersOptions specifies optional parameters to the
|
||||
// OrganizationsService.ListMembers method.
|
||||
type ListMembersOptions struct {
|
||||
// If true (or if the authenticated user is not an owner of the
|
||||
// organization), list only publicly visible members.
|
||||
PublicOnly bool `url:"-"`
|
||||
|
||||
// Filter members returned in the list. Possible values are:
|
||||
// 2fa_disabled, all. Default is "all".
|
||||
Filter string `url:"filter,omitempty"`
|
||||
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListMembers lists the members for an organization. If the authenticated
|
||||
// user is an owner of the organization, this will return both concealed and
|
||||
// public members, otherwise it will only return public members.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/members/#members-list
|
||||
func (s *OrganizationsService) ListMembers(org string, opt *ListMembersOptions) ([]User, *Response, error) {
|
||||
var u string
|
||||
if opt != nil && opt.PublicOnly {
|
||||
u = fmt.Sprintf("orgs/%v/public_members", org)
|
||||
} else {
|
||||
u = fmt.Sprintf("orgs/%v/members", org)
|
||||
}
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
members := new([]User)
|
||||
resp, err := s.client.Do(req, members)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return *members, resp, err
|
||||
}
|
||||
|
||||
// IsMember checks if a user is a member of an organization.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/members/#check-membership
|
||||
func (s *OrganizationsService) IsMember(org, user string) (bool, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/members/%v", org, user)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
member, err := parseBoolResponse(err)
|
||||
return member, resp, err
|
||||
}
|
||||
|
||||
// IsPublicMember checks if a user is a public member of an organization.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/members/#check-public-membership
|
||||
func (s *OrganizationsService) IsPublicMember(org, user string) (bool, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/public_members/%v", org, user)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
member, err := parseBoolResponse(err)
|
||||
return member, resp, err
|
||||
}
|
||||
|
||||
// RemoveMember removes a user from all teams of an organization.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/members/#remove-a-member
|
||||
func (s *OrganizationsService) RemoveMember(org, user string) (*Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/members/%v", org, user)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// PublicizeMembership publicizes a user's membership in an organization.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/members/#publicize-a-users-membership
|
||||
func (s *OrganizationsService) PublicizeMembership(org, user string) (*Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/public_members/%v", org, user)
|
||||
req, err := s.client.NewRequest("PUT", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// ConcealMembership conceals a user's membership in an organization.
|
||||
//
|
||||
// GitHub API docs: http://developer.github.com/v3/orgs/members/#conceal-a-users-membership
|
||||
func (s *OrganizationsService) ConcealMembership(org, user string) (*Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/public_members/%v", org, user)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// ListOrgMembershipsOptions specifies optional parameters to the
|
||||
// OrganizationsService.ListOrgMemberships method.
|
||||
type ListOrgMembershipsOptions struct {
|
||||
// Filter memberships to include only those withe the specified state.
|
||||
// Possible values are: "active", "pending".
|
||||
State string `url:"state,omitempty"`
|
||||
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListOrgMemberships lists the organization memberships for the authenticated user.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/orgs/members/#list-your-organization-memberships
|
||||
func (s *OrganizationsService) ListOrgMemberships(opt *ListOrgMembershipsOptions) ([]Membership, *Response, error) {
|
||||
u := "user/memberships/orgs"
|
||||
u, err := addOptions(u, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// TODO: remove custom Accept header when this API fully launches
|
||||
req.Header.Set("Accept", mediaTypeMembershipPreview)
|
||||
|
||||
var memberships []Membership
|
||||
resp, err := s.client.Do(req, &memberships)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return memberships, resp, err
|
||||
}
|
||||
|
||||
// GetOrgMembership gets the membership for the authenticated user for the
|
||||
// specified organization.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-your-organization-membership
|
||||
func (s *OrganizationsService) GetOrgMembership(org string) (*Membership, *Response, error) {
|
||||
u := fmt.Sprintf("user/memberships/orgs/%v", org)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// TODO: remove custom Accept header when this API fully launches
|
||||
req.Header.Set("Accept", mediaTypeMembershipPreview)
|
||||
|
||||
membership := new(Membership)
|
||||
resp, err := s.client.Do(req, membership)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return membership, resp, err
|
||||
}
|
||||
|
||||
// EditOrgMembership edits the membership for the authenticated user for the
|
||||
// specified organization.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/orgs/members/#edit-your-organization-membership
|
||||
func (s *OrganizationsService) EditOrgMembership(org string, membership *Membership) (*Membership, *Response, error) {
|
||||
u := fmt.Sprintf("user/memberships/orgs/%v", org)
|
||||
req, err := s.client.NewRequest("PATCH", u, membership)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// TODO: remove custom Accept header when this API fully launches
|
||||
req.Header.Set("Accept", mediaTypeMembershipPreview)
|
||||
|
||||
m := new(Membership)
|
||||
resp, err := s.client.Do(req, m)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return m, resp, err
|
||||
}
|
||||
292
Godeps/_workspace/src/github.com/google/go-github/github/orgs_members_test.go
generated
vendored
Normal file
292
Godeps/_workspace/src/github.com/google/go-github/github/orgs_members_test.go
generated
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOrganizationsService_ListMembers(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/members", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testFormValues(t, r, values{
|
||||
"filter": "2fa_disabled",
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
opt := &ListMembersOptions{
|
||||
PublicOnly: false,
|
||||
Filter: "2fa_disabled",
|
||||
ListOptions: ListOptions{Page: 2},
|
||||
}
|
||||
members, _, err := client.Organizations.ListMembers("o", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.ListMembers returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []User{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(members, want) {
|
||||
t.Errorf("Organizations.ListMembers returned %+v, want %+v", members, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizationsService_ListMembers_invalidOrg(t *testing.T) {
|
||||
_, _, err := client.Organizations.ListMembers("%", nil)
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestOrganizationsService_ListMembers_public(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/public_members", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `[{"id":1}]`)
|
||||
})
|
||||
|
||||
opt := &ListMembersOptions{PublicOnly: true}
|
||||
members, _, err := client.Organizations.ListMembers("o", opt)
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.ListMembers returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []User{{ID: Int(1)}}
|
||||
if !reflect.DeepEqual(members, want) {
|
||||
t.Errorf("Organizations.ListMembers returned %+v, want %+v", members, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizationsService_IsMember(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
member, _, err := client.Organizations.IsMember("o", "u")
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.IsMember returned error: %v", err)
|
||||
}
|
||||
if want := true; member != want {
|
||||
t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that a 404 response is interpreted as "false" and not an error
|
||||
func TestOrganizationsService_IsMember_notMember(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
member, _, err := client.Organizations.IsMember("o", "u")
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.IsMember returned error: %+v", err)
|
||||
}
|
||||
if want := false; member != want {
|
||||
t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that a 400 response is interpreted as an actual error, and not simply
|
||||
// as "false" like the above case of a 404
|
||||
func TestOrganizationsService_IsMember_error(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
http.Error(w, "BadRequest", http.StatusBadRequest)
|
||||
})
|
||||
|
||||
member, _, err := client.Organizations.IsMember("o", "u")
|
||||
if err == nil {
|
||||
t.Errorf("Expected HTTP 400 response")
|
||||
}
|
||||
if want := false; member != want {
|
||||
t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizationsService_IsMember_invalidOrg(t *testing.T) {
|
||||
_, _, err := client.Organizations.IsMember("%", "u")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestOrganizationsService_IsPublicMember(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
member, _, err := client.Organizations.IsPublicMember("o", "u")
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.IsPublicMember returned error: %v", err)
|
||||
}
|
||||
if want := true; member != want {
|
||||
t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that a 404 response is interpreted as "false" and not an error
|
||||
func TestOrganizationsService_IsPublicMember_notMember(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
member, _, err := client.Organizations.IsPublicMember("o", "u")
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.IsPublicMember returned error: %v", err)
|
||||
}
|
||||
if want := false; member != want {
|
||||
t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that a 400 response is interpreted as an actual error, and not simply
|
||||
// as "false" like the above case of a 404
|
||||
func TestOrganizationsService_IsPublicMember_error(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
http.Error(w, "BadRequest", http.StatusBadRequest)
|
||||
})
|
||||
|
||||
member, _, err := client.Organizations.IsPublicMember("o", "u")
|
||||
if err == nil {
|
||||
t.Errorf("Expected HTTP 400 response")
|
||||
}
|
||||
if want := false; member != want {
|
||||
t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizationsService_IsPublicMember_invalidOrg(t *testing.T) {
|
||||
_, _, err := client.Organizations.IsPublicMember("%", "u")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestOrganizationsService_RemoveMember(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "DELETE")
|
||||
})
|
||||
|
||||
_, err := client.Organizations.RemoveMember("o", "u")
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.RemoveMember returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizationsService_RemoveMember_invalidOrg(t *testing.T) {
|
||||
_, err := client.Organizations.RemoveMember("%", "u")
|
||||
testURLParseError(t, err)
|
||||
}
|
||||
|
||||
func TestOrganizationsService_ListOrgMemberships(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/user/memberships/orgs", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
|
||||
testFormValues(t, r, values{
|
||||
"state": "active",
|
||||
"page": "2",
|
||||
})
|
||||
fmt.Fprint(w, `[{"url":"u"}]`)
|
||||
})
|
||||
|
||||
opt := &ListOrgMembershipsOptions{
|
||||
State: "active",
|
||||
ListOptions: ListOptions{Page: 2},
|
||||
}
|
||||
memberships, _, err := client.Organizations.ListOrgMemberships(opt)
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.ListOrgMemberships returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []Membership{{URL: String("u")}}
|
||||
if !reflect.DeepEqual(memberships, want) {
|
||||
t.Errorf("Organizations.ListOrgMemberships returned %+v, want %+v", memberships, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizationsService_GetOrgMembership(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
|
||||
fmt.Fprint(w, `{"url":"u"}`)
|
||||
})
|
||||
|
||||
membership, _, err := client.Organizations.GetOrgMembership("o")
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.GetOrgMembership returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Membership{URL: String("u")}
|
||||
if !reflect.DeepEqual(membership, want) {
|
||||
t.Errorf("Organizations.GetOrgMembership returned %+v, want %+v", membership, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizationsService_EditOrgMembership(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
input := &Membership{State: String("active")}
|
||||
|
||||
mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) {
|
||||
v := new(Membership)
|
||||
json.NewDecoder(r.Body).Decode(v)
|
||||
|
||||
testMethod(t, r, "PATCH")
|
||||
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
|
||||
if !reflect.DeepEqual(v, input) {
|
||||
t.Errorf("Request body = %+v, want %+v", v, input)
|
||||
}
|
||||
|
||||
fmt.Fprint(w, `{"url":"u"}`)
|
||||
})
|
||||
|
||||
membership, _, err := client.Organizations.EditOrgMembership("o", input)
|
||||
if err != nil {
|
||||
t.Errorf("Organizations.EditOrgMembership returned error: %v", err)
|
||||
}
|
||||
|
||||
want := &Membership{URL: String("u")}
|
||||
if !reflect.DeepEqual(membership, want) {
|
||||
t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user