mirror of
https://github.com/documize/community.git
synced 2025-07-18 12:49:42 +02:00
Bump version to 5.11.0
This commit is contained in:
parent
a32510b8e6
commit
510e1bd0bd
370 changed files with 18825 additions and 5454 deletions
|
@ -4,7 +4,7 @@ COPY ./gui /go/src/github.com/documize/community/gui
|
|||
RUN npm --network-timeout=100000 install
|
||||
RUN npm run build -- --environment=production --output-path dist-prod --suppress-sizes true
|
||||
|
||||
FROM golang:1.19-alpine as builder
|
||||
FROM golang:1.21-alpine as builder
|
||||
WORKDIR /go/src/github.com/documize/community
|
||||
COPY . /go/src/github.com/documize/community
|
||||
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/assets /go/src/github.com/documize/community/edition/static/public/assets
|
||||
|
|
|
@ -40,9 +40,9 @@ func main() {
|
|||
// Specify the product edition.
|
||||
rt.Product = domain.Product{}
|
||||
rt.Product.Major = "5"
|
||||
rt.Product.Minor = "10"
|
||||
rt.Product.Minor = "11"
|
||||
rt.Product.Patch = "0"
|
||||
rt.Product.Revision = "1695054225"
|
||||
rt.Product.Revision = "1704915883"
|
||||
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
||||
rt.Product.Edition = domain.CommunityEdition
|
||||
rt.Product.Title = "Community"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "documize",
|
||||
"version": "5.10.0",
|
||||
"version": "5.11.0",
|
||||
"private": true,
|
||||
"description": "Documize Community",
|
||||
"repository": "",
|
||||
|
|
|
@ -6,21 +6,5 @@
|
|||
"theme_color": "#280A42",
|
||||
"description": "Organized documents",
|
||||
"start_url": "/?utm_source=homescreen",
|
||||
"icons": [{
|
||||
"src": "assets/img/icon.png",
|
||||
"sizes": "64x64",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "assets/img/icon.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "assets/img/icon.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "assets/img/icon.png",
|
||||
"sizes": "1024x1024",
|
||||
"type": "image/png"
|
||||
}]
|
||||
"icons": []
|
||||
}
|
||||
|
|
41
vendor/github.com/Azure/go-ntlmssp/SECURITY.md
generated
vendored
Normal file
41
vendor/github.com/Azure/go-ntlmssp/SECURITY.md
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->
|
||||
|
||||
## Security
|
||||
|
||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
||||
|
||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
||||
|
||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
||||
|
||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
||||
|
||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||
|
||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||
* Full paths of source file(s) related to the manifestation of the issue
|
||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||
* Any special configuration required to reproduce the issue
|
||||
* Step-by-step instructions to reproduce the issue
|
||||
* Proof-of-concept or exploit code (if possible)
|
||||
* Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
||||
|
||||
## Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
|
||||
## Policy
|
||||
|
||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
||||
|
||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
8
vendor/github.com/Azure/go-ntlmssp/authenticate_message.go
generated
vendored
8
vendor/github.com/Azure/go-ntlmssp/authenticate_message.go
generated
vendored
|
@ -42,7 +42,7 @@ func (m authenicateMessage) MarshalBinary() ([]byte, error) {
|
|||
}
|
||||
|
||||
target, user := toUnicode(m.TargetName), toUnicode(m.UserName)
|
||||
workstation := toUnicode("go-ntlmssp")
|
||||
workstation := toUnicode("")
|
||||
|
||||
ptr := binary.Size(&authenticateMessageFields{})
|
||||
f := authenticateMessageFields{
|
||||
|
@ -82,7 +82,7 @@ func (m authenicateMessage) MarshalBinary() ([]byte, error) {
|
|||
|
||||
//ProcessChallenge crafts an AUTHENTICATE message in response to the CHALLENGE message
|
||||
//that was received from the server
|
||||
func ProcessChallenge(challengeMessageData []byte, user, password string) ([]byte, error) {
|
||||
func ProcessChallenge(challengeMessageData []byte, user, password string, domainNeeded bool) ([]byte, error) {
|
||||
if user == "" && password == "" {
|
||||
return nil, errors.New("Anonymous authentication not supported")
|
||||
}
|
||||
|
@ -98,6 +98,10 @@ func ProcessChallenge(challengeMessageData []byte, user, password string) ([]byt
|
|||
if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEKEYEXCH) {
|
||||
return nil, errors.New("Key exchange requested but not supported (NTLMSSP_NEGOTIATE_KEY_EXCH)")
|
||||
}
|
||||
|
||||
if !domainNeeded {
|
||||
cm.TargetName = ""
|
||||
}
|
||||
|
||||
am := authenicateMessage{
|
||||
UserName: user,
|
||||
|
|
45
vendor/github.com/Azure/go-ntlmssp/authheader.go
generated
vendored
45
vendor/github.com/Azure/go-ntlmssp/authheader.go
generated
vendored
|
@ -5,26 +5,55 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type authheader string
|
||||
type authheader []string
|
||||
|
||||
func (h authheader) IsBasic() bool {
|
||||
return strings.HasPrefix(string(h), "Basic ")
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "Basic ") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h authheader) Basic() string {
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "Basic ") {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h authheader) IsNegotiate() bool {
|
||||
return strings.HasPrefix(string(h), "Negotiate")
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "Negotiate") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h authheader) IsNTLM() bool {
|
||||
return strings.HasPrefix(string(h), "NTLM")
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "NTLM") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h authheader) GetData() ([]byte, error) {
|
||||
p := strings.Split(string(h), " ")
|
||||
if len(p) < 2 {
|
||||
return nil, nil
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "NTLM") || strings.HasPrefix(string(s), "Negotiate") || strings.HasPrefix(string(s), "Basic ") {
|
||||
p := strings.Split(string(s), " ")
|
||||
if len(p) < 2 {
|
||||
return nil, nil
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(string(p[1]))
|
||||
}
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(string(p[1]))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h authheader) GetBasicCreds() (username, password string, err error) {
|
||||
|
|
27
vendor/github.com/Azure/go-ntlmssp/negotiator.go
generated
vendored
27
vendor/github.com/Azure/go-ntlmssp/negotiator.go
generated
vendored
|
@ -10,15 +10,22 @@ import (
|
|||
)
|
||||
|
||||
// GetDomain : parse domain name from based on slashes in the input
|
||||
func GetDomain(user string) (string, string) {
|
||||
// Need to check for upn as well
|
||||
func GetDomain(user string) (string, string, bool) {
|
||||
domain := ""
|
||||
domainNeeded := false
|
||||
|
||||
if strings.Contains(user, "\\") {
|
||||
ucomponents := strings.SplitN(user, "\\", 2)
|
||||
domain = ucomponents[0]
|
||||
user = ucomponents[1]
|
||||
domainNeeded = true
|
||||
} else if strings.Contains(user, "@") {
|
||||
domainNeeded = false
|
||||
} else {
|
||||
domainNeeded = true
|
||||
}
|
||||
return user, domain
|
||||
return user, domain, domainNeeded
|
||||
}
|
||||
|
||||
//Negotiator is a http.Roundtripper decorator that automatically
|
||||
|
@ -34,10 +41,11 @@ func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error)
|
|||
rt = http.DefaultTransport
|
||||
}
|
||||
// If it is not basic auth, just round trip the request as usual
|
||||
reqauth := authheader(req.Header.Get("Authorization"))
|
||||
reqauth := authheader(req.Header.Values("Authorization"))
|
||||
if !reqauth.IsBasic() {
|
||||
return rt.RoundTrip(req)
|
||||
}
|
||||
reqauthBasic := reqauth.Basic()
|
||||
// Save request body
|
||||
body := bytes.Buffer{}
|
||||
if req.Body != nil {
|
||||
|
@ -59,11 +67,10 @@ func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error)
|
|||
if res.StatusCode != http.StatusUnauthorized {
|
||||
return res, err
|
||||
}
|
||||
|
||||
resauth := authheader(res.Header.Get("Www-Authenticate"))
|
||||
resauth := authheader(res.Header.Values("Www-Authenticate"))
|
||||
if !resauth.IsNegotiate() && !resauth.IsNTLM() {
|
||||
// Unauthorized, Negotiate not requested, let's try with basic auth
|
||||
req.Header.Set("Authorization", string(reqauth))
|
||||
req.Header.Set("Authorization", string(reqauthBasic))
|
||||
io.Copy(ioutil.Discard, res.Body)
|
||||
res.Body.Close()
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||
|
@ -75,7 +82,7 @@ func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error)
|
|||
if res.StatusCode != http.StatusUnauthorized {
|
||||
return res, err
|
||||
}
|
||||
resauth = authheader(res.Header.Get("Www-Authenticate"))
|
||||
resauth = authheader(res.Header.Values("Www-Authenticate"))
|
||||
}
|
||||
|
||||
if resauth.IsNegotiate() || resauth.IsNTLM() {
|
||||
|
@ -91,7 +98,7 @@ func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error)
|
|||
|
||||
// get domain from username
|
||||
domain := ""
|
||||
u, domain = GetDomain(u)
|
||||
u, domain, domainNeeded := GetDomain(u)
|
||||
|
||||
// send negotiate
|
||||
negotiateMessage, err := NewNegotiateMessage(domain, "")
|
||||
|
@ -112,7 +119,7 @@ func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error)
|
|||
}
|
||||
|
||||
// receive challenge?
|
||||
resauth = authheader(res.Header.Get("Www-Authenticate"))
|
||||
resauth = authheader(res.Header.Values("Www-Authenticate"))
|
||||
challengeMessage, err := resauth.GetData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -125,7 +132,7 @@ func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error)
|
|||
res.Body.Close()
|
||||
|
||||
// send authenticate
|
||||
authenticateMessage, err := ProcessChallenge(challengeMessage, u, p)
|
||||
authenticateMessage, err := ProcessChallenge(challengeMessage, u, p, domainNeeded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
7
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
7
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
|
@ -1,5 +1,2 @@
|
|||
TAGS
|
||||
tags
|
||||
.*.swp
|
||||
tomlcheck/tomlcheck
|
||||
toml.test
|
||||
/toml.test
|
||||
/toml-test
|
||||
|
|
15
vendor/github.com/BurntSushi/toml/.travis.yml
generated
vendored
15
vendor/github.com/BurntSushi/toml/.travis.yml
generated
vendored
|
@ -1,15 +0,0 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- tip
|
||||
install:
|
||||
- go install ./...
|
||||
- go get github.com/BurntSushi/toml-test
|
||||
script:
|
||||
- export PATH="$PATH:$HOME/gopath/bin"
|
||||
- make test
|
3
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
3
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
|
@ -1,3 +0,0 @@
|
|||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)
|
||||
|
19
vendor/github.com/BurntSushi/toml/Makefile
generated
vendored
19
vendor/github.com/BurntSushi/toml/Makefile
generated
vendored
|
@ -1,19 +0,0 @@
|
|||
install:
|
||||
go install ./...
|
||||
|
||||
test: install
|
||||
go test -v
|
||||
toml-test toml-test-decoder
|
||||
toml-test -encoder toml-test-encoder
|
||||
|
||||
fmt:
|
||||
gofmt -w *.go */*.go
|
||||
colcheck *.go */*.go
|
||||
|
||||
tags:
|
||||
find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
|
||||
|
||||
push:
|
||||
git push origin master
|
||||
git push github master
|
||||
|
228
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
228
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
|
@ -1,46 +1,26 @@
|
|||
## TOML parser and encoder for Go with reflection
|
||||
|
||||
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||
reflection interface similar to Go's standard library `json` and `xml`
|
||||
packages. This package also supports the `encoding.TextUnmarshaler` and
|
||||
`encoding.TextMarshaler` interfaces so that you can define custom data
|
||||
representations. (There is an example of this below.)
|
||||
reflection interface similar to Go's standard library `json` and `xml` packages.
|
||||
|
||||
Spec: https://github.com/toml-lang/toml
|
||||
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
Documentation: https://godocs.io/github.com/BurntSushi/toml
|
||||
|
||||
Documentation: https://godoc.org/github.com/BurntSushi/toml
|
||||
See the [releases page](https://github.com/BurntSushi/toml/releases) for a
|
||||
changelog; this information is also in the git tag annotations (e.g. `git show
|
||||
v0.4.0`).
|
||||
|
||||
Installation:
|
||||
This library requires Go 1.13 or newer; add it to your go.mod with:
|
||||
|
||||
```bash
|
||||
go get github.com/BurntSushi/toml
|
||||
```
|
||||
% go get github.com/BurntSushi/toml@latest
|
||||
|
||||
Try the toml validator:
|
||||
It also comes with a TOML validator CLI tool:
|
||||
|
||||
```bash
|
||||
go get github.com/BurntSushi/toml/cmd/tomlv
|
||||
tomlv some-toml-file.toml
|
||||
```
|
||||
|
||||
[](https://travis-ci.org/BurntSushi/toml) [](https://godoc.org/github.com/BurntSushi/toml)
|
||||
|
||||
### Testing
|
||||
|
||||
This package passes all tests in
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
|
||||
and the encoder.
|
||||
% go install github.com/BurntSushi/toml/cmd/tomlv@latest
|
||||
% tomlv some-toml-file.toml
|
||||
|
||||
### Examples
|
||||
|
||||
This package works similarly to how the Go standard library handles `XML`
|
||||
and `JSON`. Namely, data is loaded into Go values via reflection.
|
||||
|
||||
For the simplest example, consider some TOML file as just a list of keys
|
||||
and values:
|
||||
For the simplest example, consider some TOML file as just a list of keys and
|
||||
values:
|
||||
|
||||
```toml
|
||||
Age = 25
|
||||
|
@ -50,29 +30,23 @@ Perfection = [ 6, 28, 496, 8128 ]
|
|||
DOB = 1987-07-05T05:45:00Z
|
||||
```
|
||||
|
||||
Which could be defined in Go as:
|
||||
Which can be decoded with:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Age int
|
||||
Cats []string
|
||||
Pi float64
|
||||
Perfection []int
|
||||
DOB time.Time // requires `import time`
|
||||
Age int
|
||||
Cats []string
|
||||
Pi float64
|
||||
Perfection []int
|
||||
DOB time.Time
|
||||
}
|
||||
```
|
||||
|
||||
And then decoded with:
|
||||
|
||||
```go
|
||||
var conf Config
|
||||
if _, err := toml.Decode(tomlData, &conf); err != nil {
|
||||
// handle error
|
||||
}
|
||||
_, err := toml.Decode(tomlData, &conf)
|
||||
```
|
||||
|
||||
You can also use struct tags if your struct field name doesn't map to a TOML
|
||||
key value directly:
|
||||
You can also use struct tags if your struct field name doesn't map to a TOML key
|
||||
value directly:
|
||||
|
||||
```toml
|
||||
some_key_NAME = "wat"
|
||||
|
@ -80,139 +54,67 @@ some_key_NAME = "wat"
|
|||
|
||||
```go
|
||||
type TOML struct {
|
||||
ObscureKey string `toml:"some_key_NAME"`
|
||||
ObscureKey string `toml:"some_key_NAME"`
|
||||
}
|
||||
```
|
||||
|
||||
### Using the `encoding.TextUnmarshaler` interface
|
||||
Beware that like other decoders **only exported fields** are considered when
|
||||
encoding and decoding; private fields are silently ignored.
|
||||
|
||||
Here's an example that automatically parses duration strings into
|
||||
`time.Duration` values:
|
||||
### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces
|
||||
Here's an example that automatically parses values in a `mail.Address`:
|
||||
|
||||
```toml
|
||||
[[song]]
|
||||
name = "Thunder Road"
|
||||
duration = "4m49s"
|
||||
|
||||
[[song]]
|
||||
name = "Stairway to Heaven"
|
||||
duration = "8m03s"
|
||||
```
|
||||
|
||||
Which can be decoded with:
|
||||
|
||||
```go
|
||||
type song struct {
|
||||
Name string
|
||||
Duration duration
|
||||
}
|
||||
type songs struct {
|
||||
Song []song
|
||||
}
|
||||
var favorites songs
|
||||
if _, err := toml.Decode(blob, &favorites); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, s := range favorites.Song {
|
||||
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
|
||||
}
|
||||
```
|
||||
|
||||
And you'll also need a `duration` type that satisfies the
|
||||
`encoding.TextUnmarshaler` interface:
|
||||
|
||||
```go
|
||||
type duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (d *duration) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
d.Duration, err = time.ParseDuration(string(text))
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
### More complex usage
|
||||
|
||||
Here's an example of how to load the example from the official spec page:
|
||||
|
||||
```toml
|
||||
# This is a TOML document. Boom.
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
|
||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
contacts = [
|
||||
"Donald Duck <donald@duckburg.com>",
|
||||
"Scrooge McDuck <scrooge@duckburg.com>",
|
||||
]
|
||||
```
|
||||
|
||||
And the corresponding Go types are:
|
||||
Can be decoded with:
|
||||
|
||||
```go
|
||||
type tomlConfig struct {
|
||||
Title string
|
||||
Owner ownerInfo
|
||||
DB database `toml:"database"`
|
||||
Servers map[string]server
|
||||
Clients clients
|
||||
// Create address type which satisfies the encoding.TextUnmarshaler interface.
|
||||
type address struct {
|
||||
*mail.Address
|
||||
}
|
||||
|
||||
type ownerInfo struct {
|
||||
Name string
|
||||
Org string `toml:"organization"`
|
||||
Bio string
|
||||
DOB time.Time
|
||||
func (a *address) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
a.Address, err = mail.ParseAddress(string(text))
|
||||
return err
|
||||
}
|
||||
|
||||
type database struct {
|
||||
Server string
|
||||
Ports []int
|
||||
ConnMax int `toml:"connection_max"`
|
||||
Enabled bool
|
||||
}
|
||||
// Decode it.
|
||||
func decode() {
|
||||
blob := `
|
||||
contacts = [
|
||||
"Donald Duck <donald@duckburg.com>",
|
||||
"Scrooge McDuck <scrooge@duckburg.com>",
|
||||
]
|
||||
`
|
||||
|
||||
type server struct {
|
||||
IP string
|
||||
DC string
|
||||
}
|
||||
var contacts struct {
|
||||
Contacts []address
|
||||
}
|
||||
|
||||
type clients struct {
|
||||
Data [][]interface{}
|
||||
Hosts []string
|
||||
_, err := toml.Decode(blob, &contacts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, c := range contacts.Contacts {
|
||||
fmt.Printf("%#v\n", c.Address)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// &mail.Address{Name:"Donald Duck", Address:"donald@duckburg.com"}
|
||||
// &mail.Address{Name:"Scrooge McDuck", Address:"scrooge@duckburg.com"}
|
||||
}
|
||||
```
|
||||
|
||||
Note that a case insensitive match will be tried if an exact match can't be
|
||||
found.
|
||||
To target TOML specifically you can implement `UnmarshalTOML` TOML interface in
|
||||
a similar way.
|
||||
|
||||
A working example of the above can be found in `_examples/example.{go,toml}`.
|
||||
### More complex usage
|
||||
See the [`_example/`](/_example) directory for a more complex example.
|
||||
|
|
539
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
539
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
|
@ -1,157 +1,199 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func e(format string, args ...interface{}) error {
|
||||
return fmt.Errorf("toml: "+format, args...)
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||
// TOML description of themselves.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalTOML(interface{}) error
|
||||
}
|
||||
|
||||
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
|
||||
func Unmarshal(p []byte, v interface{}) error {
|
||||
_, err := Decode(string(p), v)
|
||||
// Unmarshal decodes the contents of data in TOML format into a pointer v.
|
||||
//
|
||||
// See [Decoder] for a description of the decoding process.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
_, err := NewDecoder(bytes.NewReader(data)).Decode(v)
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode the TOML data in to the pointer v.
|
||||
//
|
||||
// See [Decoder] for a description of the decoding process.
|
||||
func Decode(data string, v interface{}) (MetaData, error) {
|
||||
return NewDecoder(strings.NewReader(data)).Decode(v)
|
||||
}
|
||||
|
||||
// DecodeFile reads the contents of a file and decodes it with [Decode].
|
||||
func DecodeFile(path string, v interface{}) (MetaData, error) {
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
defer fp.Close()
|
||||
return NewDecoder(fp).Decode(v)
|
||||
}
|
||||
|
||||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
||||
// When using the various `Decode*` functions, the type `Primitive` may
|
||||
// be given to any value, and its decoding will be delayed.
|
||||
//
|
||||
// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
|
||||
// This type can be used for any value, which will cause decoding to be delayed.
|
||||
// You can use [PrimitiveDecode] to "manually" decode these values.
|
||||
//
|
||||
// The underlying representation of a `Primitive` value is subject to change.
|
||||
// Do not rely on it.
|
||||
// NOTE: The underlying representation of a `Primitive` value is subject to
|
||||
// change. Do not rely on it.
|
||||
//
|
||||
// N.B. Primitive values are still parsed, so using them will only avoid
|
||||
// the overhead of reflection. They can be useful when you don't know the
|
||||
// exact type of TOML data until run time.
|
||||
// NOTE: Primitive values are still parsed, so using them will only avoid the
|
||||
// overhead of reflection. They can be useful when you don't know the exact type
|
||||
// of TOML data until runtime.
|
||||
type Primitive struct {
|
||||
undecoded interface{}
|
||||
context Key
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
// The significand precision for float32 and float64 is 24 and 53 bits; this is
|
||||
// the range a natural number can be stored in a float without loss of data.
|
||||
const (
|
||||
maxSafeFloat32Int = 16777215 // 2^24-1
|
||||
maxSafeFloat64Int = int64(9007199254740991) // 2^53-1
|
||||
)
|
||||
|
||||
// Decoder decodes TOML data.
|
||||
//
|
||||
// Use MetaData.PrimitiveDecode instead.
|
||||
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md := MetaData{decoded: make(map[string]bool)}
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
// TOML tables correspond to Go structs or maps; they can be used
|
||||
// interchangeably, but structs offer better type safety.
|
||||
//
|
||||
// TOML table arrays correspond to either a slice of structs or a slice of maps.
|
||||
//
|
||||
// TOML datetimes correspond to [time.Time]. Local datetimes are parsed in the
|
||||
// local timezone.
|
||||
//
|
||||
// [time.Duration] types are treated as nanoseconds if the TOML value is an
|
||||
// integer, or they're parsed with time.ParseDuration() if they're strings.
|
||||
//
|
||||
// All other TOML types (float, string, int, bool and array) correspond to the
|
||||
// obvious Go types.
|
||||
//
|
||||
// An exception to the above rules is if a type implements the TextUnmarshaler
|
||||
// interface, in which case any primitive TOML value (floats, strings, integers,
|
||||
// booleans, datetimes) will be converted to a []byte and given to the value's
|
||||
// UnmarshalText method. See the Unmarshaler example for a demonstration with
|
||||
// email addresses.
|
||||
//
|
||||
// # Key mapping
|
||||
//
|
||||
// TOML keys can map to either keys in a Go map or field names in a Go struct.
|
||||
// The special `toml` struct tag can be used to map TOML keys to struct fields
|
||||
// that don't match the key name exactly (see the example). A case insensitive
|
||||
// match to struct names will be tried if an exact match can't be found.
|
||||
//
|
||||
// The mapping between TOML values and Go values is loose. That is, there may
|
||||
// exist TOML values that cannot be placed into your representation, and there
|
||||
// may be parts of your representation that do not correspond to TOML values.
|
||||
// This loose mapping can be made stricter by using the IsDefined and/or
|
||||
// Undecoded methods on the MetaData returned.
|
||||
//
|
||||
// This decoder does not handle cyclic types. Decode will not terminate if a
|
||||
// cyclic type is passed.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
||||
// decodes a TOML value that has already been parsed. Valid primitive values
|
||||
// can *only* be obtained from values filled by the decoder functions,
|
||||
// including this method. (i.e., `v` may contain more `Primitive`
|
||||
// values.)
|
||||
// NewDecoder creates a new Decoder.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: r}
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
primitiveType = reflect.TypeOf((*Primitive)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Decode TOML data in to the pointer `v`.
|
||||
func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
s := "%q"
|
||||
if reflect.TypeOf(v) == nil {
|
||||
s = "%v"
|
||||
}
|
||||
|
||||
return MetaData{}, fmt.Errorf("toml: cannot decode to non-pointer "+s, reflect.TypeOf(v))
|
||||
}
|
||||
if rv.IsNil() {
|
||||
return MetaData{}, fmt.Errorf("toml: cannot decode to nil value of %q", reflect.TypeOf(v))
|
||||
}
|
||||
|
||||
// Check if this is a supported type: struct, map, interface{}, or something
|
||||
// that implements UnmarshalTOML or UnmarshalText.
|
||||
rv = indirect(rv)
|
||||
rt := rv.Type()
|
||||
if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map &&
|
||||
!(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) &&
|
||||
!rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) {
|
||||
return MetaData{}, fmt.Errorf("toml: cannot decode to type %s", rt)
|
||||
}
|
||||
|
||||
// TODO: parser should read from io.Reader? Or at the very least, make it
|
||||
// read from []byte rather than string
|
||||
data, err := ioutil.ReadAll(dec.r)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
|
||||
p, err := parse(string(data))
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
|
||||
md := MetaData{
|
||||
mapping: p.mapping,
|
||||
keyInfo: p.keyInfo,
|
||||
keys: p.ordered,
|
||||
decoded: make(map[string]struct{}, len(p.ordered)),
|
||||
context: nil,
|
||||
data: data,
|
||||
}
|
||||
return md, md.unify(p.mapping, rv)
|
||||
}
|
||||
|
||||
// PrimitiveDecode is just like the other Decode* functions, except it decodes a
|
||||
// TOML value that has already been parsed. Valid primitive values can *only* be
|
||||
// obtained from values filled by the decoder functions, including this method.
|
||||
// (i.e., v may contain more [Primitive] values.)
|
||||
//
|
||||
// Meta data for primitive values is included in the meta data returned by
|
||||
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
||||
// method will only reflect keys that were decoded. Namely, any keys hidden
|
||||
// behind a Primitive will be considered undecoded. Executing this method will
|
||||
// update the undecoded keys in the meta data. (See the example.)
|
||||
// Meta data for primitive values is included in the meta data returned by the
|
||||
// Decode* functions with one exception: keys returned by the Undecoded method
|
||||
// will only reflect keys that were decoded. Namely, any keys hidden behind a
|
||||
// Primitive will be considered undecoded. Executing this method will update the
|
||||
// undecoded keys in the meta data. (See the example.)
|
||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md.context = primValue.context
|
||||
defer func() { md.context = nil }()
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// Decode will decode the contents of `data` in TOML format into a pointer
|
||||
// `v`.
|
||||
//
|
||||
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
|
||||
// used interchangeably.)
|
||||
//
|
||||
// TOML arrays of tables correspond to either a slice of structs or a slice
|
||||
// of maps.
|
||||
//
|
||||
// TOML datetimes correspond to Go `time.Time` values.
|
||||
//
|
||||
// All other TOML types (float, string, int, bool and array) correspond
|
||||
// to the obvious Go types.
|
||||
//
|
||||
// An exception to the above rules is if a type implements the
|
||||
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
|
||||
// (floats, strings, integers, booleans and datetimes) will be converted to
|
||||
// a byte string and given to the value's UnmarshalText method. See the
|
||||
// Unmarshaler example for a demonstration with time duration strings.
|
||||
//
|
||||
// Key mapping
|
||||
//
|
||||
// TOML keys can map to either keys in a Go map or field names in a Go
|
||||
// struct. The special `toml` struct tag may be used to map TOML keys to
|
||||
// struct fields that don't match the key name exactly. (See the example.)
|
||||
// A case insensitive match to struct names will be tried if an exact match
|
||||
// can't be found.
|
||||
//
|
||||
// The mapping between TOML values and Go values is loose. That is, there
|
||||
// may exist TOML values that cannot be placed into your representation, and
|
||||
// there may be parts of your representation that do not correspond to
|
||||
// TOML values. This loose mapping can be made stricter by using the IsDefined
|
||||
// and/or Undecoded methods on the MetaData returned.
|
||||
//
|
||||
// This decoder will not handle cyclic types. If a cyclic type is passed,
|
||||
// `Decode` will not terminate.
|
||||
func Decode(data string, v interface{}) (MetaData, error) {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
||||
}
|
||||
if rv.IsNil() {
|
||||
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
||||
}
|
||||
p, err := parse(data)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
md := MetaData{
|
||||
p.mapping, p.types, p.ordered,
|
||||
make(map[string]bool, len(p.ordered)), nil,
|
||||
}
|
||||
return md, md.unify(p.mapping, indirect(rv))
|
||||
}
|
||||
|
||||
// DecodeFile is just like Decode, except it will automatically read the
|
||||
// contents of the file at `fpath` and decode it for you.
|
||||
func DecodeFile(fpath string, v interface{}) (MetaData, error) {
|
||||
bs, err := ioutil.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
return Decode(string(bs), v)
|
||||
}
|
||||
|
||||
// DecodeReader is just like Decode, except it will consume all bytes
|
||||
// from the reader and decode it for you.
|
||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
|
||||
bs, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
return Decode(string(bs), v)
|
||||
}
|
||||
|
||||
// unify performs a sort of type unification based on the structure of `rv`,
|
||||
// which is the client representation.
|
||||
//
|
||||
// Any type mismatch produces an error. Finding a type that we don't know
|
||||
// how to handle produces an unsupported type error.
|
||||
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||||
|
||||
// Special case. Look for a `Primitive` value.
|
||||
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
|
||||
// TODO: #76 would make this superfluous after implemented.
|
||||
if rv.Type() == primitiveType {
|
||||
// Save the undecoded data and the key context into the primitive
|
||||
// value.
|
||||
context := make(Key, len(md.context))
|
||||
|
@ -163,36 +205,24 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Special case. Unmarshaler Interface support.
|
||||
if rv.CanAddr() {
|
||||
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
|
||||
return v.UnmarshalTOML(data)
|
||||
}
|
||||
rvi := rv.Interface()
|
||||
if v, ok := rvi.(Unmarshaler); ok {
|
||||
return v.UnmarshalTOML(data)
|
||||
}
|
||||
|
||||
// Special case. Handle time.Time values specifically.
|
||||
// TODO: Remove this code when we decide to drop support for Go 1.1.
|
||||
// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
|
||||
// interfaces.
|
||||
if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
|
||||
return md.unifyDatetime(data, rv)
|
||||
}
|
||||
|
||||
// Special case. Look for a value satisfying the TextUnmarshaler interface.
|
||||
if v, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||
if v, ok := rvi.(encoding.TextUnmarshaler); ok {
|
||||
return md.unifyText(data, v)
|
||||
}
|
||||
// BUG(burntsushi)
|
||||
|
||||
// TODO:
|
||||
// The behavior here is incorrect whenever a Go type satisfies the
|
||||
// encoding.TextUnmarshaler interface but also corresponds to a TOML
|
||||
// hash or array. In particular, the unmarshaler should only be applied
|
||||
// to primitive TOML values. But at this point, it will be applied to
|
||||
// all kinds of values and produce an incorrect error whenever those values
|
||||
// are hashes or arrays (including arrays of tables).
|
||||
// encoding.TextUnmarshaler interface but also corresponds to a TOML hash or
|
||||
// array. In particular, the unmarshaler should only be applied to primitive
|
||||
// TOML values. But at this point, it will be applied to all kinds of values
|
||||
// and produce an incorrect error whenever those values are hashes or arrays
|
||||
// (including arrays of tables).
|
||||
|
||||
k := rv.Kind()
|
||||
|
||||
// laziness
|
||||
if k >= reflect.Int && k <= reflect.Uint64 {
|
||||
return md.unifyInt(data, rv)
|
||||
}
|
||||
|
@ -218,17 +248,14 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
|||
case reflect.Bool:
|
||||
return md.unifyBool(data, rv)
|
||||
case reflect.Interface:
|
||||
// we only support empty interfaces.
|
||||
if rv.NumMethod() > 0 {
|
||||
return e("unsupported type %s", rv.Type())
|
||||
if rv.NumMethod() > 0 { /// Only empty interfaces are supported.
|
||||
return md.e("unsupported type %s", rv.Type())
|
||||
}
|
||||
return md.unifyAnything(data, rv)
|
||||
case reflect.Float32:
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return md.unifyFloat64(data, rv)
|
||||
}
|
||||
return e("unsupported type %s", rv.Kind())
|
||||
return md.e("unsupported type %s", rv.Kind())
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||
|
@ -237,7 +264,7 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
|||
if mapping == nil {
|
||||
return nil
|
||||
}
|
||||
return e("type mismatch for %s: expected table but found %T",
|
||||
return md.e("type mismatch for %s: expected table but found %T",
|
||||
rv.Type().String(), mapping)
|
||||
}
|
||||
|
||||
|
@ -259,17 +286,18 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
|||
for _, i := range f.index {
|
||||
subv = indirect(subv.Field(i))
|
||||
}
|
||||
|
||||
if isUnifiable(subv) {
|
||||
md.decoded[md.context.add(key).String()] = true
|
||||
md.decoded[md.context.add(key).String()] = struct{}{}
|
||||
md.context = append(md.context, key)
|
||||
if err := md.unify(datum, subv); err != nil {
|
||||
|
||||
err := md.unify(datum, subv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
} else if f.name != "" {
|
||||
// Bad user! No soup for you!
|
||||
return e("cannot write unexported field %s.%s",
|
||||
rv.Type().String(), f.name)
|
||||
return md.e("cannot write unexported field %s.%s", rv.Type().String(), f.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -277,28 +305,43 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
|||
}
|
||||
|
||||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||
keyType := rv.Type().Key().Kind()
|
||||
if keyType != reflect.String && keyType != reflect.Interface {
|
||||
return fmt.Errorf("toml: cannot decode to a map with non-string key type (%s in %q)",
|
||||
keyType, rv.Type())
|
||||
}
|
||||
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
if !ok {
|
||||
if tmap == nil {
|
||||
return nil
|
||||
}
|
||||
return badtype("map", mapping)
|
||||
return md.badtype("map", mapping)
|
||||
}
|
||||
if rv.IsNil() {
|
||||
rv.Set(reflect.MakeMap(rv.Type()))
|
||||
}
|
||||
for k, v := range tmap {
|
||||
md.decoded[md.context.add(k).String()] = true
|
||||
md.decoded[md.context.add(k).String()] = struct{}{}
|
||||
md.context = append(md.context, k)
|
||||
|
||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||||
if err := md.unify(v, rvval); err != nil {
|
||||
|
||||
err := md.unify(v, indirect(rvval))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
|
||||
rvkey.SetString(k)
|
||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||
|
||||
switch keyType {
|
||||
case reflect.Interface:
|
||||
rvkey.Set(reflect.ValueOf(k))
|
||||
case reflect.String:
|
||||
rvkey.SetString(k)
|
||||
}
|
||||
|
||||
rv.SetMapIndex(rvkey, rvval)
|
||||
}
|
||||
return nil
|
||||
|
@ -310,12 +353,10 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
|||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
return md.badtype("slice", data)
|
||||
}
|
||||
sliceLen := datav.Len()
|
||||
if sliceLen != rv.Len() {
|
||||
return e("expected array length %d; got TOML array of length %d",
|
||||
rv.Len(), sliceLen)
|
||||
if l := datav.Len(); l != rv.Len() {
|
||||
return md.e("expected array length %d; got TOML array of length %d", rv.Len(), l)
|
||||
}
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
@ -326,7 +367,7 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
|||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
return md.badtype("slice", data)
|
||||
}
|
||||
n := datav.Len()
|
||||
if rv.IsNil() || rv.Cap() < n {
|
||||
|
@ -337,37 +378,45 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
|||
}
|
||||
|
||||
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||||
sliceLen := data.Len()
|
||||
for i := 0; i < sliceLen; i++ {
|
||||
v := data.Index(i).Interface()
|
||||
sliceval := indirect(rv.Index(i))
|
||||
if err := md.unify(v, sliceval); err != nil {
|
||||
l := data.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
||||
if _, ok := data.(time.Time); ok {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||||
_, ok := rv.Interface().(json.Number)
|
||||
if ok {
|
||||
if i, ok := data.(int64); ok {
|
||||
rv.SetString(strconv.FormatInt(i, 10))
|
||||
} else if f, ok := data.(float64); ok {
|
||||
rv.SetString(strconv.FormatFloat(f, 'f', -1, 64))
|
||||
} else {
|
||||
return md.badtype("string", data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("time.Time", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||||
if s, ok := data.(string); ok {
|
||||
rv.SetString(s)
|
||||
return nil
|
||||
}
|
||||
return badtype("string", data)
|
||||
return md.badtype("string", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||||
rvk := rv.Kind()
|
||||
|
||||
if num, ok := data.(float64); ok {
|
||||
switch rv.Kind() {
|
||||
switch rvk {
|
||||
case reflect.Float32:
|
||||
if num < -math.MaxFloat32 || num > math.MaxFloat32 {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
rv.SetFloat(num)
|
||||
|
@ -376,54 +425,60 @@ func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("float", data)
|
||||
|
||||
if num, ok := data.(int64); ok {
|
||||
if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) ||
|
||||
(rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetFloat(float64(num))
|
||||
return nil
|
||||
}
|
||||
|
||||
return md.badtype("float", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||||
if num, ok := data.(int64); ok {
|
||||
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int64:
|
||||
// No bounds checking necessary.
|
||||
case reflect.Int8:
|
||||
if num < math.MinInt8 || num > math.MaxInt8 {
|
||||
return e("value %d is out of range for int8", num)
|
||||
}
|
||||
case reflect.Int16:
|
||||
if num < math.MinInt16 || num > math.MaxInt16 {
|
||||
return e("value %d is out of range for int16", num)
|
||||
}
|
||||
case reflect.Int32:
|
||||
if num < math.MinInt32 || num > math.MaxInt32 {
|
||||
return e("value %d is out of range for int32", num)
|
||||
}
|
||||
_, ok := rv.Interface().(time.Duration)
|
||||
if ok {
|
||||
// Parse as string duration, and fall back to regular integer parsing
|
||||
// (as nanosecond) if this is not a string.
|
||||
if s, ok := data.(string); ok {
|
||||
dur, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return md.parseErr(errParseDuration{s})
|
||||
}
|
||||
rv.SetInt(num)
|
||||
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
|
||||
unum := uint64(num)
|
||||
switch rv.Kind() {
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
// No bounds checking necessary.
|
||||
case reflect.Uint8:
|
||||
if num < 0 || unum > math.MaxUint8 {
|
||||
return e("value %d is out of range for uint8", num)
|
||||
}
|
||||
case reflect.Uint16:
|
||||
if num < 0 || unum > math.MaxUint16 {
|
||||
return e("value %d is out of range for uint16", num)
|
||||
}
|
||||
case reflect.Uint32:
|
||||
if num < 0 || unum > math.MaxUint32 {
|
||||
return e("value %d is out of range for uint32", num)
|
||||
}
|
||||
}
|
||||
rv.SetUint(unum)
|
||||
} else {
|
||||
panic("unreachable")
|
||||
rv.SetInt(int64(dur))
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("integer", data)
|
||||
|
||||
num, ok := data.(int64)
|
||||
if !ok {
|
||||
return md.badtype("integer", data)
|
||||
}
|
||||
|
||||
rvk := rv.Kind()
|
||||
switch {
|
||||
case rvk >= reflect.Int && rvk <= reflect.Int64:
|
||||
if (rvk == reflect.Int8 && (num < math.MinInt8 || num > math.MaxInt8)) ||
|
||||
(rvk == reflect.Int16 && (num < math.MinInt16 || num > math.MaxInt16)) ||
|
||||
(rvk == reflect.Int32 && (num < math.MinInt32 || num > math.MaxInt32)) {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetInt(num)
|
||||
case rvk >= reflect.Uint && rvk <= reflect.Uint64:
|
||||
unum := uint64(num)
|
||||
if rvk == reflect.Uint8 && (num < 0 || unum > math.MaxUint8) ||
|
||||
rvk == reflect.Uint16 && (num < 0 || unum > math.MaxUint16) ||
|
||||
rvk == reflect.Uint32 && (num < 0 || unum > math.MaxUint32) {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetUint(unum)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||
|
@ -431,7 +486,7 @@ func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
|||
rv.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
return badtype("boolean", data)
|
||||
return md.badtype("boolean", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||
|
@ -439,10 +494,16 @@ func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
||||
func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) error {
|
||||
var s string
|
||||
switch sdata := data.(type) {
|
||||
case TextMarshaler:
|
||||
case Marshaler:
|
||||
text, err := sdata.MarshalTOML()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s = string(text)
|
||||
case encoding.TextMarshaler:
|
||||
text, err := sdata.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -459,7 +520,7 @@ func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
|||
case float64:
|
||||
s = fmt.Sprintf("%f", sdata)
|
||||
default:
|
||||
return badtype("primitive (string-like)", data)
|
||||
return md.badtype("primitive (string-like)", data)
|
||||
}
|
||||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||||
return err
|
||||
|
@ -467,22 +528,54 @@ func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) badtype(dst string, data interface{}) error {
|
||||
return md.e("incompatible types: TOML value has type %T; destination has type %s", data, dst)
|
||||
}
|
||||
|
||||
func (md *MetaData) parseErr(err error) error {
|
||||
k := md.context.String()
|
||||
return ParseError{
|
||||
LastKey: k,
|
||||
Position: md.keyInfo[k].pos,
|
||||
Line: md.keyInfo[k].pos.Line,
|
||||
err: err,
|
||||
input: string(md.data),
|
||||
}
|
||||
}
|
||||
|
||||
func (md *MetaData) e(format string, args ...interface{}) error {
|
||||
f := "toml: "
|
||||
if len(md.context) > 0 {
|
||||
f = fmt.Sprintf("toml: (last key %q): ", md.context)
|
||||
p := md.keyInfo[md.context.String()].pos
|
||||
if p.Line > 0 {
|
||||
f = fmt.Sprintf("toml: line %d (last key %q): ", p.Line, md.context)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf(f+format, args...)
|
||||
}
|
||||
|
||||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||||
func rvalue(v interface{}) reflect.Value {
|
||||
return indirect(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// indirect returns the value pointed to by a pointer.
|
||||
// Pointers are followed until the value is not a pointer.
|
||||
// New values are allocated for each nil pointer.
|
||||
//
|
||||
// An exception to this rule is if the value satisfies an interface of
|
||||
// interest to us (like encoding.TextUnmarshaler).
|
||||
// Pointers are followed until the value is not a pointer. New values are
|
||||
// allocated for each nil pointer.
|
||||
//
|
||||
// An exception to this rule is if the value satisfies an interface of interest
|
||||
// to us (like encoding.TextUnmarshaler).
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
if v.CanSet() {
|
||||
pv := v.Addr()
|
||||
if _, ok := pv.Interface().(TextUnmarshaler); ok {
|
||||
pvi := pv.Interface()
|
||||
if _, ok := pvi.(encoding.TextUnmarshaler); ok {
|
||||
return pv
|
||||
}
|
||||
if _, ok := pvi.(Unmarshaler); ok {
|
||||
return pv
|
||||
}
|
||||
}
|
||||
|
@ -498,12 +591,12 @@ func isUnifiable(rv reflect.Value) bool {
|
|||
if rv.CanSet() {
|
||||
return true
|
||||
}
|
||||
if _, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||
rvi := rv.Interface()
|
||||
if _, ok := rvi.(encoding.TextUnmarshaler); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rvi.(Unmarshaler); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func badtype(expected string, data interface{}) error {
|
||||
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
||||
}
|
||||
|
|
19
vendor/github.com/BurntSushi/toml/decode_go116.go
generated
vendored
Normal file
19
vendor/github.com/BurntSushi/toml/decode_go116.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package toml
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
// DecodeFS reads the contents of a file from [fs.FS] and decodes it with
|
||||
// [Decode].
|
||||
func DecodeFS(fsys fs.FS, path string, v interface{}) (MetaData, error) {
|
||||
fp, err := fsys.Open(path)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
defer fp.Close()
|
||||
return NewDecoder(fp).Decode(v)
|
||||
}
|
29
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
Normal file
29
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"io"
|
||||
)
|
||||
|
||||
// TextMarshaler is an alias for encoding.TextMarshaler.
|
||||
//
|
||||
// Deprecated: use encoding.TextMarshaler
|
||||
type TextMarshaler encoding.TextMarshaler
|
||||
|
||||
// TextUnmarshaler is an alias for encoding.TextUnmarshaler.
|
||||
//
|
||||
// Deprecated: use encoding.TextUnmarshaler
|
||||
type TextUnmarshaler encoding.TextUnmarshaler
|
||||
|
||||
// PrimitiveDecode is an alias for MetaData.PrimitiveDecode().
|
||||
//
|
||||
// Deprecated: use MetaData.PrimitiveDecode.
|
||||
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md := MetaData{decoded: make(map[string]struct{})}
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// DecodeReader is an alias for NewDecoder(r).Decode(v).
|
||||
//
|
||||
// Deprecated: use NewDecoder(reader).Decode(&value).
|
||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { return NewDecoder(r).Decode(v) }
|
36
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
36
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
|
@ -1,27 +1,11 @@
|
|||
/*
|
||||
Package toml provides facilities for decoding and encoding TOML configuration
|
||||
files via reflection. There is also support for delaying decoding with
|
||||
the Primitive type, and querying the set of keys in a TOML document with the
|
||||
MetaData type.
|
||||
|
||||
The specification implemented: https://github.com/toml-lang/toml
|
||||
|
||||
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
|
||||
whether a file is a valid TOML document. It can also be used to print the
|
||||
type of each key in a TOML document.
|
||||
|
||||
Testing
|
||||
|
||||
There are two important types of tests used for this package. The first is
|
||||
contained inside '*_test.go' files and uses the standard Go unit testing
|
||||
framework. These tests are primarily devoted to holistically testing the
|
||||
decoder and encoder.
|
||||
|
||||
The second type of testing is used to verify the implementation's adherence
|
||||
to the TOML specification. These tests have been factored into their own
|
||||
project: https://github.com/BurntSushi/toml-test
|
||||
|
||||
The reason the tests are in a separate project is so that they can be used by
|
||||
any implementation of TOML. Namely, it is language agnostic.
|
||||
*/
|
||||
// Package toml implements decoding and encoding of TOML files.
|
||||
//
|
||||
// This package supports TOML v1.0.0, as specified at https://toml.io
|
||||
//
|
||||
// There is also support for delaying decoding with the Primitive type, and
|
||||
// querying the set of keys in a TOML document with the MetaData type.
|
||||
//
|
||||
// The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator,
|
||||
// and can be used to verify if TOML document is valid. It can also be used to
|
||||
// print the type of each key.
|
||||
package toml
|
||||
|
|
651
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
651
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
|
@ -2,57 +2,127 @@ package toml
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml/internal"
|
||||
)
|
||||
|
||||
type tomlEncodeError struct{ error }
|
||||
|
||||
var (
|
||||
errArrayMixedElementTypes = errors.New(
|
||||
"toml: cannot encode array with mixed element types")
|
||||
errArrayNilElement = errors.New(
|
||||
"toml: cannot encode array with nil element")
|
||||
errNonString = errors.New(
|
||||
"toml: cannot encode a map with non-string key type")
|
||||
errAnonNonStruct = errors.New(
|
||||
"toml: cannot encode an anonymous field that is not a struct")
|
||||
errArrayNoTable = errors.New(
|
||||
"toml: TOML array element cannot contain a table")
|
||||
errNoKey = errors.New(
|
||||
"toml: top-level values must be Go maps or structs")
|
||||
errAnything = errors.New("") // used in testing
|
||||
errArrayNilElement = errors.New("toml: cannot encode array with nil element")
|
||||
errNonString = errors.New("toml: cannot encode a map with non-string key type")
|
||||
errNoKey = errors.New("toml: top-level values must be Go maps or structs")
|
||||
errAnything = errors.New("") // used in testing
|
||||
)
|
||||
|
||||
var quotedReplacer = strings.NewReplacer(
|
||||
"\t", "\\t",
|
||||
"\n", "\\n",
|
||||
"\r", "\\r",
|
||||
var dblQuotedReplacer = strings.NewReplacer(
|
||||
"\"", "\\\"",
|
||||
"\\", "\\\\",
|
||||
"\x00", `\u0000`,
|
||||
"\x01", `\u0001`,
|
||||
"\x02", `\u0002`,
|
||||
"\x03", `\u0003`,
|
||||
"\x04", `\u0004`,
|
||||
"\x05", `\u0005`,
|
||||
"\x06", `\u0006`,
|
||||
"\x07", `\u0007`,
|
||||
"\b", `\b`,
|
||||
"\t", `\t`,
|
||||
"\n", `\n`,
|
||||
"\x0b", `\u000b`,
|
||||
"\f", `\f`,
|
||||
"\r", `\r`,
|
||||
"\x0e", `\u000e`,
|
||||
"\x0f", `\u000f`,
|
||||
"\x10", `\u0010`,
|
||||
"\x11", `\u0011`,
|
||||
"\x12", `\u0012`,
|
||||
"\x13", `\u0013`,
|
||||
"\x14", `\u0014`,
|
||||
"\x15", `\u0015`,
|
||||
"\x16", `\u0016`,
|
||||
"\x17", `\u0017`,
|
||||
"\x18", `\u0018`,
|
||||
"\x19", `\u0019`,
|
||||
"\x1a", `\u001a`,
|
||||
"\x1b", `\u001b`,
|
||||
"\x1c", `\u001c`,
|
||||
"\x1d", `\u001d`,
|
||||
"\x1e", `\u001e`,
|
||||
"\x1f", `\u001f`,
|
||||
"\x7f", `\u007f`,
|
||||
)
|
||||
|
||||
// Encoder controls the encoding of Go values to a TOML document to some
|
||||
// io.Writer.
|
||||
//
|
||||
// The indentation level can be controlled with the Indent field.
|
||||
type Encoder struct {
|
||||
// A single indentation level. By default it is two spaces.
|
||||
Indent string
|
||||
var (
|
||||
marshalToml = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
marshalText = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||
)
|
||||
|
||||
// hasWritten is whether we have written any output to w yet.
|
||||
hasWritten bool
|
||||
w *bufio.Writer
|
||||
// Marshaler is the interface implemented by types that can marshal themselves
|
||||
// into valid TOML.
|
||||
type Marshaler interface {
|
||||
MarshalTOML() ([]byte, error)
|
||||
}
|
||||
|
||||
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
|
||||
// given. By default, a single indentation level is 2 spaces.
|
||||
// Encoder encodes a Go to a TOML document.
|
||||
//
|
||||
// The mapping between Go values and TOML values should be precisely the same as
|
||||
// for [Decode].
|
||||
//
|
||||
// time.Time is encoded as a RFC 3339 string, and time.Duration as its string
|
||||
// representation.
|
||||
//
|
||||
// The [Marshaler] and [encoding.TextMarshaler] interfaces are supported to
|
||||
// encoding the value as custom TOML.
|
||||
//
|
||||
// If you want to write arbitrary binary data then you will need to use
|
||||
// something like base64 since TOML does not have any binary types.
|
||||
//
|
||||
// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes
|
||||
// are encoded first.
|
||||
//
|
||||
// Go maps will be sorted alphabetically by key for deterministic output.
|
||||
//
|
||||
// The toml struct tag can be used to provide the key name; if omitted the
|
||||
// struct field name will be used. If the "omitempty" option is present the
|
||||
// following value will be skipped:
|
||||
//
|
||||
// - arrays, slices, maps, and string with len of 0
|
||||
// - struct with all zero values
|
||||
// - bool false
|
||||
//
|
||||
// If omitzero is given all int and float types with a value of 0 will be
|
||||
// skipped.
|
||||
//
|
||||
// Encoding Go values without a corresponding TOML representation will return an
|
||||
// error. Examples of this includes maps with non-string keys, slices with nil
|
||||
// elements, embedded non-struct types, and nested slices containing maps or
|
||||
// structs. (e.g. [][]map[string]string is not allowed but []map[string]string
|
||||
// is okay, as is []map[string][]string).
|
||||
//
|
||||
// NOTE: only exported keys are encoded due to the use of reflection. Unexported
|
||||
// keys are silently discarded.
|
||||
type Encoder struct {
|
||||
// String to use for a single indentation level; default is two spaces.
|
||||
Indent string
|
||||
|
||||
w *bufio.Writer
|
||||
hasWritten bool // written any output to w yet?
|
||||
}
|
||||
|
||||
// NewEncoder create a new Encoder.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: bufio.NewWriter(w),
|
||||
|
@ -60,32 +130,14 @@ func NewEncoder(w io.Writer) *Encoder {
|
|||
}
|
||||
}
|
||||
|
||||
// Encode writes a TOML representation of the Go value to the underlying
|
||||
// io.Writer. If the value given cannot be encoded to a valid TOML document,
|
||||
// then an error is returned.
|
||||
// Encode writes a TOML representation of the Go value to the [Encoder]'s writer.
|
||||
//
|
||||
// The mapping between Go values and TOML values should be precisely the same
|
||||
// as for the Decode* functions. Similarly, the TextMarshaler interface is
|
||||
// supported by encoding the resulting bytes as strings. (If you want to write
|
||||
// arbitrary binary data then you will need to use something like base64 since
|
||||
// TOML does not have any binary types.)
|
||||
//
|
||||
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
|
||||
// sub-hashes are encoded first.
|
||||
//
|
||||
// If a Go map is encoded, then its keys are sorted alphabetically for
|
||||
// deterministic output. More control over this behavior may be provided if
|
||||
// there is demand for it.
|
||||
//
|
||||
// Encoding Go values without a corresponding TOML representation---like map
|
||||
// types with non-string keys---will cause an error to be returned. Similarly
|
||||
// for mixed arrays/slices, arrays/slices with nil elements, embedded
|
||||
// non-struct types and nested slices containing maps or structs.
|
||||
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
|
||||
// and so is []map[string][]string.)
|
||||
// An error is returned if the value given cannot be encoded to a valid TOML
|
||||
// document.
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
rv := eindirect(reflect.ValueOf(v))
|
||||
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
|
||||
err := enc.safeEncode(Key([]string{}), rv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return enc.w.Flush()
|
||||
|
@ -106,13 +158,15 @@ func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
|||
}
|
||||
|
||||
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
||||
// Special case. Time needs to be in ISO8601 format.
|
||||
// Special case. If we can marshal the type to text, then we used that.
|
||||
// Basically, this prevents the encoder for handling these types as
|
||||
// generic structs (or whatever the underlying type of a TextMarshaler is).
|
||||
switch rv.Interface().(type) {
|
||||
case time.Time, TextMarshaler:
|
||||
enc.keyEqElement(key, rv)
|
||||
// If we can marshal the type to text, then we use that. This prevents the
|
||||
// encoder for handling these types as generic structs (or whatever the
|
||||
// underlying type of a TextMarshaler is).
|
||||
switch {
|
||||
case isMarshaler(rv):
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
return
|
||||
case rv.Type() == primitiveType: // TODO: #76 would make this superfluous after implemented.
|
||||
enc.encode(key, reflect.ValueOf(rv.Interface().(Primitive).undecoded))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -123,12 +177,12 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
|||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
||||
enc.keyEqElement(key, rv)
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
||||
enc.eArrayOfTables(key, rv)
|
||||
} else {
|
||||
enc.keyEqElement(key, rv)
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
}
|
||||
case reflect.Interface:
|
||||
if rv.IsNil() {
|
||||
|
@ -148,55 +202,114 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
|||
case reflect.Struct:
|
||||
enc.eTable(key, rv)
|
||||
default:
|
||||
panic(e("unsupported type for key '%s': %s", key, k))
|
||||
encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k))
|
||||
}
|
||||
}
|
||||
|
||||
// eElement encodes any value that can be an array element (primitives and
|
||||
// arrays).
|
||||
// eElement encodes any value that can be an array element.
|
||||
func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
switch v := rv.Interface().(type) {
|
||||
case time.Time:
|
||||
// Special case time.Time as a primitive. Has to come before
|
||||
// TextMarshaler below because time.Time implements
|
||||
// encoding.TextMarshaler, but we need to always use UTC.
|
||||
enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
|
||||
return
|
||||
case TextMarshaler:
|
||||
// Special case. Use text marshaler if it's available for this value.
|
||||
if s, err := v.MarshalText(); err != nil {
|
||||
encPanic(err)
|
||||
} else {
|
||||
enc.writeQuoted(string(s))
|
||||
case time.Time: // Using TextMarshaler adds extra quotes, which we don't want.
|
||||
format := time.RFC3339Nano
|
||||
switch v.Location() {
|
||||
case internal.LocalDatetime:
|
||||
format = "2006-01-02T15:04:05.999999999"
|
||||
case internal.LocalDate:
|
||||
format = "2006-01-02"
|
||||
case internal.LocalTime:
|
||||
format = "15:04:05.999999999"
|
||||
}
|
||||
switch v.Location() {
|
||||
default:
|
||||
enc.wf(v.Format(format))
|
||||
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
|
||||
enc.wf(v.In(time.UTC).Format(format))
|
||||
}
|
||||
return
|
||||
case Marshaler:
|
||||
s, err := v.MarshalTOML()
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
if s == nil {
|
||||
encPanic(errors.New("MarshalTOML returned nil and no error"))
|
||||
}
|
||||
enc.w.Write(s)
|
||||
return
|
||||
case encoding.TextMarshaler:
|
||||
s, err := v.MarshalText()
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
if s == nil {
|
||||
encPanic(errors.New("MarshalText returned nil and no error"))
|
||||
}
|
||||
enc.writeQuoted(string(s))
|
||||
return
|
||||
case time.Duration:
|
||||
enc.writeQuoted(v.String())
|
||||
return
|
||||
case json.Number:
|
||||
n, _ := rv.Interface().(json.Number)
|
||||
|
||||
if n == "" { /// Useful zero value.
|
||||
enc.w.WriteByte('0')
|
||||
return
|
||||
} else if v, err := n.Int64(); err == nil {
|
||||
enc.eElement(reflect.ValueOf(v))
|
||||
return
|
||||
} else if v, err := n.Float64(); err == nil {
|
||||
enc.eElement(reflect.ValueOf(v))
|
||||
return
|
||||
}
|
||||
encPanic(fmt.Errorf("unable to convert %q to int64 or float64", n))
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64:
|
||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||
reflect.Uint32, reflect.Uint64:
|
||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||
case reflect.Float32:
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
|
||||
case reflect.Float64:
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
|
||||
case reflect.Array, reflect.Slice:
|
||||
enc.eArrayOrSliceElement(rv)
|
||||
case reflect.Interface:
|
||||
case reflect.Ptr:
|
||||
enc.eElement(rv.Elem())
|
||||
return
|
||||
case reflect.String:
|
||||
enc.writeQuoted(rv.String())
|
||||
case reflect.Bool:
|
||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||
case reflect.Float32:
|
||||
f := rv.Float()
|
||||
if math.IsNaN(f) {
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
|
||||
} else {
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
|
||||
}
|
||||
case reflect.Float64:
|
||||
f := rv.Float()
|
||||
if math.IsNaN(f) {
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
|
||||
} else {
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64)))
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
enc.eArrayOrSliceElement(rv)
|
||||
case reflect.Struct:
|
||||
enc.eStruct(nil, rv, true)
|
||||
case reflect.Map:
|
||||
enc.eMap(nil, rv, true)
|
||||
case reflect.Interface:
|
||||
enc.eElement(rv.Elem())
|
||||
default:
|
||||
panic(e("unexpected primitive type: %s", rv.Kind()))
|
||||
encPanic(fmt.Errorf("unexpected type: %T", rv.Interface()))
|
||||
}
|
||||
}
|
||||
|
||||
// By the TOML spec, all floats must have a decimal with at least one
|
||||
// number on either side.
|
||||
// By the TOML spec, all floats must have a decimal with at least one number on
|
||||
// either side.
|
||||
func floatAddDecimal(fstr string) string {
|
||||
if !strings.Contains(fstr, ".") {
|
||||
return fstr + ".0"
|
||||
|
@ -205,14 +318,14 @@ func floatAddDecimal(fstr string) string {
|
|||
}
|
||||
|
||||
func (enc *Encoder) writeQuoted(s string) {
|
||||
enc.wf("\"%s\"", quotedReplacer.Replace(s))
|
||||
enc.wf("\"%s\"", dblQuotedReplacer.Replace(s))
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||
length := rv.Len()
|
||||
enc.wf("[")
|
||||
for i := 0; i < length; i++ {
|
||||
elem := rv.Index(i)
|
||||
elem := eindirect(rv.Index(i))
|
||||
enc.eElement(elem)
|
||||
if i != length-1 {
|
||||
enc.wf(", ")
|
||||
|
@ -226,44 +339,43 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
|||
encPanic(errNoKey)
|
||||
}
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
trv := rv.Index(i)
|
||||
trv := eindirect(rv.Index(i))
|
||||
if isNil(trv) {
|
||||
continue
|
||||
}
|
||||
panicIfInvalidKey(key)
|
||||
enc.newline()
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
enc.eMapOrStruct(key, trv)
|
||||
enc.eMapOrStruct(key, trv, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||
panicIfInvalidKey(key)
|
||||
if len(key) == 1 {
|
||||
// Output an extra newline between top-level tables.
|
||||
// (The newline isn't written if nothing else has been written though.)
|
||||
enc.newline()
|
||||
}
|
||||
if len(key) > 0 {
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
}
|
||||
enc.eMapOrStruct(key, rv)
|
||||
enc.eMapOrStruct(key, rv, false)
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
|
||||
switch rv := eindirect(rv); rv.Kind() {
|
||||
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) {
|
||||
switch rv.Kind() {
|
||||
case reflect.Map:
|
||||
enc.eMap(key, rv)
|
||||
enc.eMap(key, rv, inline)
|
||||
case reflect.Struct:
|
||||
enc.eStruct(key, rv)
|
||||
enc.eStruct(key, rv, inline)
|
||||
default:
|
||||
// Should never happen?
|
||||
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
|
||||
func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
|
||||
rt := rv.Type()
|
||||
if rt.Key().Kind() != reflect.String {
|
||||
encPanic(errNonString)
|
||||
|
@ -274,68 +386,100 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value) {
|
|||
var mapKeysDirect, mapKeysSub []string
|
||||
for _, mapKey := range rv.MapKeys() {
|
||||
k := mapKey.String()
|
||||
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
||||
if typeIsTable(tomlTypeOfGo(eindirect(rv.MapIndex(mapKey)))) {
|
||||
mapKeysSub = append(mapKeysSub, k)
|
||||
} else {
|
||||
mapKeysDirect = append(mapKeysDirect, k)
|
||||
}
|
||||
}
|
||||
|
||||
var writeMapKeys = func(mapKeys []string) {
|
||||
var writeMapKeys = func(mapKeys []string, trailC bool) {
|
||||
sort.Strings(mapKeys)
|
||||
for _, mapKey := range mapKeys {
|
||||
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
|
||||
if isNil(mrv) {
|
||||
// Don't write anything for nil fields.
|
||||
for i, mapKey := range mapKeys {
|
||||
val := eindirect(rv.MapIndex(reflect.ValueOf(mapKey)))
|
||||
if isNil(val) {
|
||||
continue
|
||||
}
|
||||
enc.encode(key.add(mapKey), mrv)
|
||||
|
||||
if inline {
|
||||
enc.writeKeyValue(Key{mapKey}, val, true)
|
||||
if trailC || i != len(mapKeys)-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
} else {
|
||||
enc.encode(key.add(mapKey), val)
|
||||
}
|
||||
}
|
||||
}
|
||||
writeMapKeys(mapKeysDirect)
|
||||
writeMapKeys(mapKeysSub)
|
||||
|
||||
if inline {
|
||||
enc.wf("{")
|
||||
}
|
||||
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
|
||||
writeMapKeys(mapKeysSub, false)
|
||||
if inline {
|
||||
enc.wf("}")
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
||||
const is32Bit = (32 << (^uint(0) >> 63)) == 32
|
||||
|
||||
func pointerTo(t reflect.Type) reflect.Type {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
return pointerTo(t.Elem())
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||
// Write keys for fields directly under this key first, because if we write
|
||||
// a field that creates a new table, then all keys under it will be in that
|
||||
// a field that creates a new table then all keys under it will be in that
|
||||
// table (not the one we're writing here).
|
||||
rt := rv.Type()
|
||||
var fieldsDirect, fieldsSub [][]int
|
||||
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
||||
//
|
||||
// Fields is a [][]int: for fieldsDirect this always has one entry (the
|
||||
// struct index). For fieldsSub it contains two entries: the parent field
|
||||
// index from tv, and the field indexes for the fields of the sub.
|
||||
var (
|
||||
rt = rv.Type()
|
||||
fieldsDirect, fieldsSub [][]int
|
||||
addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
||||
)
|
||||
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
f := rt.Field(i)
|
||||
// skip unexported fields
|
||||
if f.PkgPath != "" && !f.Anonymous {
|
||||
isEmbed := f.Anonymous && pointerTo(f.Type).Kind() == reflect.Struct
|
||||
if f.PkgPath != "" && !isEmbed { /// Skip unexported fields.
|
||||
continue
|
||||
}
|
||||
frv := rv.Field(i)
|
||||
if f.Anonymous {
|
||||
t := f.Type
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
// Treat anonymous struct fields with
|
||||
// tag names as though they are not
|
||||
// anonymous, like encoding/json does.
|
||||
if getOptions(f.Tag).name == "" {
|
||||
addFields(t, frv, f.Index)
|
||||
continue
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if t.Elem().Kind() == reflect.Struct &&
|
||||
getOptions(f.Tag).name == "" {
|
||||
if !frv.IsNil() {
|
||||
addFields(t.Elem(), frv.Elem(), f.Index)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Fall through to the normal field encoding logic below
|
||||
// for non-struct anonymous fields.
|
||||
opts := getOptions(f.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
|
||||
frv := eindirect(rv.Field(i))
|
||||
|
||||
if is32Bit {
|
||||
// Copy so it works correct on 32bit archs; not clear why this
|
||||
// is needed. See #314, and https://www.reddit.com/r/golang/comments/pnx8v4
|
||||
// This also works fine on 64bit, but 32bit archs are somewhat
|
||||
// rare and this is a wee bit faster.
|
||||
copyStart := make([]int, len(start))
|
||||
copy(copyStart, start)
|
||||
start = copyStart
|
||||
}
|
||||
|
||||
// Treat anonymous struct fields with tag names as though they are
|
||||
// not anonymous, like encoding/json does.
|
||||
//
|
||||
// Non-struct anonymous fields use the normal encoding logic.
|
||||
if isEmbed {
|
||||
if getOptions(f.Tag).name == "" && frv.Kind() == reflect.Struct {
|
||||
addFields(frv.Type(), frv, append(start, f.Index...))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if typeIsHash(tomlTypeOfGo(frv)) {
|
||||
if typeIsTable(tomlTypeOfGo(frv)) {
|
||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||
} else {
|
||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||
|
@ -344,48 +488,81 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
|||
}
|
||||
addFields(rt, rv, nil)
|
||||
|
||||
var writeFields = func(fields [][]int) {
|
||||
writeFields := func(fields [][]int) {
|
||||
for _, fieldIndex := range fields {
|
||||
sft := rt.FieldByIndex(fieldIndex)
|
||||
sf := rv.FieldByIndex(fieldIndex)
|
||||
if isNil(sf) {
|
||||
// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
fieldType := rt.FieldByIndex(fieldIndex)
|
||||
fieldVal := rv.FieldByIndex(fieldIndex)
|
||||
|
||||
opts := getOptions(sft.Tag)
|
||||
opts := getOptions(fieldType.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
keyName := sft.Name
|
||||
if opts.omitempty && isEmpty(fieldVal) {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldVal = eindirect(fieldVal)
|
||||
|
||||
if isNil(fieldVal) { /// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
|
||||
keyName := fieldType.Name
|
||||
if opts.name != "" {
|
||||
keyName = opts.name
|
||||
}
|
||||
if opts.omitempty && isEmpty(sf) {
|
||||
continue
|
||||
}
|
||||
if opts.omitzero && isZero(sf) {
|
||||
|
||||
if opts.omitzero && isZero(fieldVal) {
|
||||
continue
|
||||
}
|
||||
|
||||
enc.encode(key.add(keyName), sf)
|
||||
if inline {
|
||||
enc.writeKeyValue(Key{keyName}, fieldVal, true)
|
||||
if fieldIndex[0] != len(fields)-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
} else {
|
||||
enc.encode(key.add(keyName), fieldVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.wf("{")
|
||||
}
|
||||
writeFields(fieldsDirect)
|
||||
writeFields(fieldsSub)
|
||||
if inline {
|
||||
enc.wf("}")
|
||||
}
|
||||
}
|
||||
|
||||
// tomlTypeName returns the TOML type name of the Go value's type. It is
|
||||
// used to determine whether the types of array elements are mixed (which is
|
||||
// forbidden). If the Go value is nil, then it is illegal for it to be an array
|
||||
// element, and valueIsNil is returned as true.
|
||||
|
||||
// Returns the TOML type of a Go value. The type may be `nil`, which means
|
||||
// no concrete TOML type could be found.
|
||||
// tomlTypeOfGo returns the TOML type name of the Go value's type.
|
||||
//
|
||||
// It is used to determine whether the types of array elements are mixed (which
|
||||
// is forbidden). If the Go value is nil, then it is illegal for it to be an
|
||||
// array element, and valueIsNil is returned as true.
|
||||
//
|
||||
// The type may be `nil`, which means no concrete TOML type could be found.
|
||||
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if rv.Kind() == reflect.Struct {
|
||||
if rv.Type() == timeType {
|
||||
return tomlDatetime
|
||||
}
|
||||
if isMarshaler(rv) {
|
||||
return tomlString
|
||||
}
|
||||
return tomlHash
|
||||
}
|
||||
|
||||
if isMarshaler(rv) {
|
||||
return tomlString
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
return tomlBool
|
||||
|
@ -397,7 +574,7 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
|
|||
case reflect.Float32, reflect.Float64:
|
||||
return tomlFloat
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlHash, tomlArrayType(rv)) {
|
||||
if isTableArray(rv) {
|
||||
return tomlArrayHash
|
||||
}
|
||||
return tomlArray
|
||||
|
@ -407,54 +584,35 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
|
|||
return tomlString
|
||||
case reflect.Map:
|
||||
return tomlHash
|
||||
case reflect.Struct:
|
||||
switch rv.Interface().(type) {
|
||||
case time.Time:
|
||||
return tomlDatetime
|
||||
case TextMarshaler:
|
||||
return tomlString
|
||||
default:
|
||||
return tomlHash
|
||||
}
|
||||
default:
|
||||
panic("unexpected reflect.Kind: " + rv.Kind().String())
|
||||
encPanic(errors.New("unsupported type: " + rv.Kind().String()))
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// tomlArrayType returns the element type of a TOML array. The type returned
|
||||
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
|
||||
// slize). This function may also panic if it finds a type that cannot be
|
||||
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
|
||||
// nested arrays of tables).
|
||||
func tomlArrayType(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
firstType := tomlTypeOfGo(rv.Index(0))
|
||||
if firstType == nil {
|
||||
encPanic(errArrayNilElement)
|
||||
func isMarshaler(rv reflect.Value) bool {
|
||||
return rv.Type().Implements(marshalText) || rv.Type().Implements(marshalToml)
|
||||
}
|
||||
|
||||
// isTableArray reports if all entries in the array or slice are a table.
|
||||
func isTableArray(arr reflect.Value) bool {
|
||||
if isNil(arr) || !arr.IsValid() || arr.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
rvlen := rv.Len()
|
||||
for i := 1; i < rvlen; i++ {
|
||||
elem := rv.Index(i)
|
||||
switch elemType := tomlTypeOfGo(elem); {
|
||||
case elemType == nil:
|
||||
ret := true
|
||||
for i := 0; i < arr.Len(); i++ {
|
||||
tt := tomlTypeOfGo(eindirect(arr.Index(i)))
|
||||
// Don't allow nil.
|
||||
if tt == nil {
|
||||
encPanic(errArrayNilElement)
|
||||
case !typeEqual(firstType, elemType):
|
||||
encPanic(errArrayMixedElementTypes)
|
||||
}
|
||||
|
||||
if ret && !typeEqual(tomlHash, tt) {
|
||||
ret = false
|
||||
}
|
||||
}
|
||||
// If we have a nested array, then we must make sure that the nested
|
||||
// array contains ONLY primitives.
|
||||
// This checks arbitrarily nested arrays.
|
||||
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
|
||||
nest := tomlArrayType(eindirect(rv.Index(0)))
|
||||
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
|
||||
encPanic(errArrayNoTable)
|
||||
}
|
||||
}
|
||||
return firstType
|
||||
return ret
|
||||
}
|
||||
|
||||
type tagOptions struct {
|
||||
|
@ -499,8 +657,26 @@ func isEmpty(rv reflect.Value) bool {
|
|||
switch rv.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
return rv.Len() == 0
|
||||
case reflect.Struct:
|
||||
if rv.Type().Comparable() {
|
||||
return reflect.Zero(rv.Type()).Interface() == rv.Interface()
|
||||
}
|
||||
// Need to also check if all the fields are empty, otherwise something
|
||||
// like this with uncomparable types will always return true:
|
||||
//
|
||||
// type a struct{ field b }
|
||||
// type b struct{ s []string }
|
||||
// s := a{field: b{s: []string{"AAA"}}}
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
if !isEmpty(rv.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Bool:
|
||||
return !rv.Bool()
|
||||
case reflect.Ptr:
|
||||
return rv.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -511,18 +687,34 @@ func (enc *Encoder) newline() {
|
|||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
|
||||
// Write a key/value pair:
|
||||
//
|
||||
// key = <any value>
|
||||
//
|
||||
// This is also used for "k = v" in inline tables; so something like this will
|
||||
// be written in three calls:
|
||||
//
|
||||
// ┌───────────────────┐
|
||||
// │ ┌───┐ ┌────┐│
|
||||
// v v v v vv
|
||||
// key = {k = 1, k2 = 2}
|
||||
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
|
||||
/// Marshaler used on top-level document; call eElement() to just call
|
||||
/// Marshal{TOML,Text}.
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
enc.eElement(val)
|
||||
return
|
||||
}
|
||||
panicIfInvalidKey(key)
|
||||
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||
enc.eElement(val)
|
||||
enc.newline()
|
||||
if !inline {
|
||||
enc.newline()
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) wf(format string, v ...interface{}) {
|
||||
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
|
||||
_, err := fmt.Fprintf(enc.w, format, v...)
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.hasWritten = true
|
||||
|
@ -536,13 +728,25 @@ func encPanic(err error) {
|
|||
panic(tomlEncodeError{err})
|
||||
}
|
||||
|
||||
// Resolve any level of pointers to the actual value (e.g. **string → string).
|
||||
func eindirect(v reflect.Value) reflect.Value {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return eindirect(v.Elem())
|
||||
default:
|
||||
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
|
||||
if isMarshaler(v) {
|
||||
return v
|
||||
}
|
||||
if v.CanAddr() { /// Special case for marshalers; see #358.
|
||||
if pv := v.Addr(); isMarshaler(pv) {
|
||||
return pv
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
return v
|
||||
}
|
||||
|
||||
return eindirect(v.Elem())
|
||||
}
|
||||
|
||||
func isNil(rv reflect.Value) bool {
|
||||
|
@ -553,16 +757,3 @@ func isNil(rv reflect.Value) bool {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func panicIfInvalidKey(key Key) {
|
||||
for _, k := range key {
|
||||
if len(k) == 0 {
|
||||
encPanic(e("Key '%s' is not a valid table name. Key names "+
|
||||
"cannot be empty.", key.maybeQuotedAll()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isValidKeyName(s string) bool {
|
||||
return len(s) != 0
|
||||
}
|
||||
|
|
19
vendor/github.com/BurntSushi/toml/encoding_types.go
generated
vendored
19
vendor/github.com/BurntSushi/toml/encoding_types.go
generated
vendored
|
@ -1,19 +0,0 @@
|
|||
// +build go1.2
|
||||
|
||||
package toml
|
||||
|
||||
// In order to support Go 1.1, we define our own TextMarshaler and
|
||||
// TextUnmarshaler types. For Go 1.2+, we just alias them with the
|
||||
// standard library interfaces.
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
)
|
||||
|
||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||
// so that Go 1.1 can be supported.
|
||||
type TextMarshaler encoding.TextMarshaler
|
||||
|
||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||
// here so that Go 1.1 can be supported.
|
||||
type TextUnmarshaler encoding.TextUnmarshaler
|
18
vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
18
vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
|
@ -1,18 +0,0 @@
|
|||
// +build !go1.2
|
||||
|
||||
package toml
|
||||
|
||||
// These interfaces were introduced in Go 1.2, so we add them manually when
|
||||
// compiling for Go 1.1.
|
||||
|
||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||
// so that Go 1.1 can be supported.
|
||||
type TextMarshaler interface {
|
||||
MarshalText() (text []byte, err error)
|
||||
}
|
||||
|
||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||
// here so that Go 1.1 can be supported.
|
||||
type TextUnmarshaler interface {
|
||||
UnmarshalText(text []byte) error
|
||||
}
|
279
vendor/github.com/BurntSushi/toml/error.go
generated
vendored
Normal file
279
vendor/github.com/BurntSushi/toml/error.go
generated
vendored
Normal file
|
@ -0,0 +1,279 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseError is returned when there is an error parsing the TOML syntax such as
|
||||
// invalid syntax, duplicate keys, etc.
|
||||
//
|
||||
// In addition to the error message itself, you can also print detailed location
|
||||
// information with context by using [ErrorWithPosition]:
|
||||
//
|
||||
// toml: error: Key 'fruit' was already created and cannot be used as an array.
|
||||
//
|
||||
// At line 4, column 2-7:
|
||||
//
|
||||
// 2 | fruit = []
|
||||
// 3 |
|
||||
// 4 | [[fruit]] # Not allowed
|
||||
// ^^^^^
|
||||
//
|
||||
// [ErrorWithUsage] can be used to print the above with some more detailed usage
|
||||
// guidance:
|
||||
//
|
||||
// toml: error: newlines not allowed within inline tables
|
||||
//
|
||||
// At line 1, column 18:
|
||||
//
|
||||
// 1 | x = [{ key = 42 #
|
||||
// ^
|
||||
//
|
||||
// Error help:
|
||||
//
|
||||
// Inline tables must always be on a single line:
|
||||
//
|
||||
// table = {key = 42, second = 43}
|
||||
//
|
||||
// It is invalid to split them over multiple lines like so:
|
||||
//
|
||||
// # INVALID
|
||||
// table = {
|
||||
// key = 42,
|
||||
// second = 43
|
||||
// }
|
||||
//
|
||||
// Use regular for this:
|
||||
//
|
||||
// [table]
|
||||
// key = 42
|
||||
// second = 43
|
||||
type ParseError struct {
|
||||
Message string // Short technical message.
|
||||
Usage string // Longer message with usage guidance; may be blank.
|
||||
Position Position // Position of the error
|
||||
LastKey string // Last parsed key, may be blank.
|
||||
|
||||
// Line the error occurred.
|
||||
//
|
||||
// Deprecated: use [Position].
|
||||
Line int
|
||||
|
||||
err error
|
||||
input string
|
||||
}
|
||||
|
||||
// Position of an error.
|
||||
type Position struct {
|
||||
Line int // Line number, starting at 1.
|
||||
Start int // Start of error, as byte offset starting at 0.
|
||||
Len int // Lenght in bytes.
|
||||
}
|
||||
|
||||
func (pe ParseError) Error() string {
|
||||
msg := pe.Message
|
||||
if msg == "" { // Error from errorf()
|
||||
msg = pe.err.Error()
|
||||
}
|
||||
|
||||
if pe.LastKey == "" {
|
||||
return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, msg)
|
||||
}
|
||||
return fmt.Sprintf("toml: line %d (last key %q): %s",
|
||||
pe.Position.Line, pe.LastKey, msg)
|
||||
}
|
||||
|
||||
// ErrorWithPosition returns the error with detailed location context.
|
||||
//
|
||||
// See the documentation on [ParseError].
|
||||
func (pe ParseError) ErrorWithPosition() string {
|
||||
if pe.input == "" { // Should never happen, but just in case.
|
||||
return pe.Error()
|
||||
}
|
||||
|
||||
var (
|
||||
lines = strings.Split(pe.input, "\n")
|
||||
col = pe.column(lines)
|
||||
b = new(strings.Builder)
|
||||
)
|
||||
|
||||
msg := pe.Message
|
||||
if msg == "" {
|
||||
msg = pe.err.Error()
|
||||
}
|
||||
|
||||
// TODO: don't show control characters as literals? This may not show up
|
||||
// well everywhere.
|
||||
|
||||
if pe.Position.Len == 1 {
|
||||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
|
||||
msg, pe.Position.Line, col+1)
|
||||
} else {
|
||||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
|
||||
msg, pe.Position.Line, col, col+pe.Position.Len)
|
||||
}
|
||||
if pe.Position.Line > 2 {
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, lines[pe.Position.Line-3])
|
||||
}
|
||||
if pe.Position.Line > 1 {
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, lines[pe.Position.Line-2])
|
||||
}
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, lines[pe.Position.Line-1])
|
||||
fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col), strings.Repeat("^", pe.Position.Len))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ErrorWithUsage returns the error with detailed location context and usage
|
||||
// guidance.
|
||||
//
|
||||
// See the documentation on [ParseError].
|
||||
func (pe ParseError) ErrorWithUsage() string {
|
||||
m := pe.ErrorWithPosition()
|
||||
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
|
||||
lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
|
||||
for i := range lines {
|
||||
if lines[i] != "" {
|
||||
lines[i] = " " + lines[i]
|
||||
}
|
||||
}
|
||||
return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (pe ParseError) column(lines []string) int {
|
||||
var pos, col int
|
||||
for i := range lines {
|
||||
ll := len(lines[i]) + 1 // +1 for the removed newline
|
||||
if pos+ll >= pe.Position.Start {
|
||||
col = pe.Position.Start - pos
|
||||
if col < 0 { // Should never happen, but just in case.
|
||||
col = 0
|
||||
}
|
||||
break
|
||||
}
|
||||
pos += ll
|
||||
}
|
||||
|
||||
return col
|
||||
}
|
||||
|
||||
type (
|
||||
errLexControl struct{ r rune }
|
||||
errLexEscape struct{ r rune }
|
||||
errLexUTF8 struct{ b byte }
|
||||
errLexInvalidNum struct{ v string }
|
||||
errLexInvalidDate struct{ v string }
|
||||
errLexInlineTableNL struct{}
|
||||
errLexStringNL struct{}
|
||||
errParseRange struct {
|
||||
i interface{} // int or float
|
||||
size string // "int64", "uint16", etc.
|
||||
}
|
||||
errParseDuration struct{ d string }
|
||||
)
|
||||
|
||||
func (e errLexControl) Error() string {
|
||||
return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
|
||||
}
|
||||
func (e errLexControl) Usage() string { return "" }
|
||||
|
||||
func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
|
||||
func (e errLexEscape) Usage() string { return usageEscape }
|
||||
func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
|
||||
func (e errLexUTF8) Usage() string { return "" }
|
||||
func (e errLexInvalidNum) Error() string { return fmt.Sprintf("invalid number: %q", e.v) }
|
||||
func (e errLexInvalidNum) Usage() string { return "" }
|
||||
func (e errLexInvalidDate) Error() string { return fmt.Sprintf("invalid date: %q", e.v) }
|
||||
func (e errLexInvalidDate) Usage() string { return "" }
|
||||
func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
|
||||
func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
|
||||
func (e errLexStringNL) Error() string { return "strings cannot contain newlines" }
|
||||
func (e errLexStringNL) Usage() string { return usageStringNewline }
|
||||
func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
|
||||
func (e errParseRange) Usage() string { return usageIntOverflow }
|
||||
func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
|
||||
func (e errParseDuration) Usage() string { return usageDuration }
|
||||
|
||||
const usageEscape = `
|
||||
A '\' inside a "-delimited string is interpreted as an escape character.
|
||||
|
||||
The following escape sequences are supported:
|
||||
\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
|
||||
|
||||
To prevent a '\' from being recognized as an escape character, use either:
|
||||
|
||||
- a ' or '''-delimited string; escape characters aren't processed in them; or
|
||||
- write two backslashes to get a single backslash: '\\'.
|
||||
|
||||
If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
|
||||
instead of '\' will usually also work: "C:/Users/martin".
|
||||
`
|
||||
|
||||
const usageInlineNewline = `
|
||||
Inline tables must always be on a single line:
|
||||
|
||||
table = {key = 42, second = 43}
|
||||
|
||||
It is invalid to split them over multiple lines like so:
|
||||
|
||||
# INVALID
|
||||
table = {
|
||||
key = 42,
|
||||
second = 43
|
||||
}
|
||||
|
||||
Use regular for this:
|
||||
|
||||
[table]
|
||||
key = 42
|
||||
second = 43
|
||||
`
|
||||
|
||||
const usageStringNewline = `
|
||||
Strings must always be on a single line, and cannot span more than one line:
|
||||
|
||||
# INVALID
|
||||
string = "Hello,
|
||||
world!"
|
||||
|
||||
Instead use """ or ''' to split strings over multiple lines:
|
||||
|
||||
string = """Hello,
|
||||
world!"""
|
||||
`
|
||||
|
||||
const usageIntOverflow = `
|
||||
This number is too large; this may be an error in the TOML, but it can also be a
|
||||
bug in the program that uses too small of an integer.
|
||||
|
||||
The maximum and minimum values are:
|
||||
|
||||
size │ lowest │ highest
|
||||
───────┼────────────────┼──────────
|
||||
int8 │ -128 │ 127
|
||||
int16 │ -32,768 │ 32,767
|
||||
int32 │ -2,147,483,648 │ 2,147,483,647
|
||||
int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
|
||||
uint8 │ 0 │ 255
|
||||
uint16 │ 0 │ 65535
|
||||
uint32 │ 0 │ 4294967295
|
||||
uint64 │ 0 │ 1.8 × 10¹⁸
|
||||
|
||||
int refers to int32 on 32-bit systems and int64 on 64-bit systems.
|
||||
`
|
||||
|
||||
const usageDuration = `
|
||||
A duration must be as "number<unit>", without any spaces. Valid units are:
|
||||
|
||||
ns nanoseconds (billionth of a second)
|
||||
us, µs microseconds (millionth of a second)
|
||||
ms milliseconds (thousands of a second)
|
||||
s seconds
|
||||
m minutes
|
||||
h hours
|
||||
|
||||
You can combine multiple units; for example "5m10s" for 5 minutes and 10
|
||||
seconds.
|
||||
`
|
36
vendor/github.com/BurntSushi/toml/internal/tz.go
generated
vendored
Normal file
36
vendor/github.com/BurntSushi/toml/internal/tz.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
package internal
|
||||
|
||||
import "time"
|
||||
|
||||
// Timezones used for local datetime, date, and time TOML types.
|
||||
//
|
||||
// The exact way times and dates without a timezone should be interpreted is not
|
||||
// well-defined in the TOML specification and left to the implementation. These
|
||||
// defaults to current local timezone offset of the computer, but this can be
|
||||
// changed by changing these variables before decoding.
|
||||
//
|
||||
// TODO:
|
||||
// Ideally we'd like to offer people the ability to configure the used timezone
|
||||
// by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit
|
||||
// tricky: the reason we use three different variables for this is to support
|
||||
// round-tripping – without these specific TZ names we wouldn't know which
|
||||
// format to use.
|
||||
//
|
||||
// There isn't a good way to encode this right now though, and passing this sort
|
||||
// of information also ties in to various related issues such as string format
|
||||
// encoding, encoding of comments, etc.
|
||||
//
|
||||
// So, for the time being, just put this in internal until we can write a good
|
||||
// comprehensive API for doing all of this.
|
||||
//
|
||||
// The reason they're exported is because they're referred from in e.g.
|
||||
// internal/tag.
|
||||
//
|
||||
// Note that this behaviour is valid according to the TOML spec as the exact
|
||||
// behaviour is left up to implementations.
|
||||
var (
|
||||
localOffset = func() int { _, o := time.Now().Zone(); return o }()
|
||||
LocalDatetime = time.FixedZone("datetime-local", localOffset)
|
||||
LocalDate = time.FixedZone("date-local", localOffset)
|
||||
LocalTime = time.FixedZone("time-local", localOffset)
|
||||
)
|
866
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
866
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
File diff suppressed because it is too large
Load diff
126
vendor/github.com/BurntSushi/toml/decode_meta.go → vendor/github.com/BurntSushi/toml/meta.go
generated
vendored
126
vendor/github.com/BurntSushi/toml/decode_meta.go → vendor/github.com/BurntSushi/toml/meta.go
generated
vendored
|
@ -1,33 +1,40 @@
|
|||
package toml
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MetaData allows access to meta information about TOML data that may not
|
||||
// be inferrable via reflection. In particular, whether a key has been defined
|
||||
// and the TOML type of a key.
|
||||
// MetaData allows access to meta information about TOML data that's not
|
||||
// accessible otherwise.
|
||||
//
|
||||
// It allows checking if a key is defined in the TOML data, whether any keys
|
||||
// were undecoded, and the TOML type of a key.
|
||||
type MetaData struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
keys []Key
|
||||
decoded map[string]bool
|
||||
context Key // Used only during decoding.
|
||||
|
||||
keyInfo map[string]keyInfo
|
||||
mapping map[string]interface{}
|
||||
keys []Key
|
||||
decoded map[string]struct{}
|
||||
data []byte // Input file; for errors.
|
||||
}
|
||||
|
||||
// IsDefined returns true if the key given exists in the TOML data. The key
|
||||
// should be specified hierarchially. e.g.,
|
||||
// IsDefined reports if the key exists in the TOML data.
|
||||
//
|
||||
// // access the TOML key 'a.b.c'
|
||||
// IsDefined("a", "b", "c")
|
||||
// The key should be specified hierarchically, for example to access the TOML
|
||||
// key "a.b.c" you would use IsDefined("a", "b", "c"). Keys are case sensitive.
|
||||
//
|
||||
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
||||
// Returns false for an empty key.
|
||||
func (md *MetaData) IsDefined(key ...string) bool {
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var hash map[string]interface{}
|
||||
var ok bool
|
||||
var hashOrVal interface{} = md.mapping
|
||||
var (
|
||||
hash map[string]interface{}
|
||||
ok bool
|
||||
hashOrVal interface{} = md.mapping
|
||||
)
|
||||
for _, k := range key {
|
||||
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
||||
return false
|
||||
|
@ -41,58 +48,20 @@ func (md *MetaData) IsDefined(key ...string) bool {
|
|||
|
||||
// Type returns a string representation of the type of the key specified.
|
||||
//
|
||||
// Type will return the empty string if given an empty key or a key that
|
||||
// does not exist. Keys are case sensitive.
|
||||
// Type will return the empty string if given an empty key or a key that does
|
||||
// not exist. Keys are case sensitive.
|
||||
func (md *MetaData) Type(key ...string) string {
|
||||
fullkey := strings.Join(key, ".")
|
||||
if typ, ok := md.types[fullkey]; ok {
|
||||
return typ.typeString()
|
||||
if ki, ok := md.keyInfo[Key(key).String()]; ok {
|
||||
return ki.tomlType.typeString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
|
||||
// to get values of this type.
|
||||
type Key []string
|
||||
|
||||
func (k Key) String() string {
|
||||
return strings.Join(k, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuotedAll() string {
|
||||
var ss []string
|
||||
for i := range k {
|
||||
ss = append(ss, k.maybeQuoted(i))
|
||||
}
|
||||
return strings.Join(ss, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
quote := false
|
||||
for _, c := range k[i] {
|
||||
if !isBareKeyChar(c) {
|
||||
quote = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if quote {
|
||||
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
func (k Key) add(piece string) Key {
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
||||
|
||||
// Keys returns a slice of every key in the TOML data, including key groups.
|
||||
// Each key is itself a slice, where the first element is the top of the
|
||||
// hierarchy and the last is the most specific.
|
||||
//
|
||||
// The list will have the same order as the keys appeared in the TOML data.
|
||||
// Each key is itself a slice, where the first element is the top of the
|
||||
// hierarchy and the last is the most specific. The list will have the same
|
||||
// order as the keys appeared in the TOML data.
|
||||
//
|
||||
// All keys returned are non-empty.
|
||||
func (md *MetaData) Keys() []Key {
|
||||
|
@ -102,7 +71,7 @@ func (md *MetaData) Keys() []Key {
|
|||
// Undecoded returns all keys that have not been decoded in the order in which
|
||||
// they appear in the original TOML document.
|
||||
//
|
||||
// This includes keys that haven't been decoded because of a Primitive value.
|
||||
// This includes keys that haven't been decoded because of a [Primitive] value.
|
||||
// Once the Primitive value is decoded, the keys will be considered decoded.
|
||||
//
|
||||
// Also note that decoding into an empty interface will result in no decoding,
|
||||
|
@ -113,9 +82,40 @@ func (md *MetaData) Keys() []Key {
|
|||
func (md *MetaData) Undecoded() []Key {
|
||||
undecoded := make([]Key, 0, len(md.keys))
|
||||
for _, key := range md.keys {
|
||||
if !md.decoded[key.String()] {
|
||||
if _, ok := md.decoded[key.String()]; !ok {
|
||||
undecoded = append(undecoded, key)
|
||||
}
|
||||
}
|
||||
return undecoded
|
||||
}
|
||||
|
||||
// Key represents any TOML key, including key groups. Use [MetaData.Keys] to get
|
||||
// values of this type.
|
||||
type Key []string
|
||||
|
||||
func (k Key) String() string {
|
||||
ss := make([]string, len(k))
|
||||
for i := range k {
|
||||
ss[i] = k.maybeQuoted(i)
|
||||
}
|
||||
return strings.Join(ss, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
if k[i] == "" {
|
||||
return `""`
|
||||
}
|
||||
for _, c := range k[i] {
|
||||
if !isBareKeyChar(c, false) {
|
||||
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
|
||||
}
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
func (k Key) add(piece string) Key {
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
729
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
729
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
|
@ -2,57 +2,80 @@ package toml
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/BurntSushi/toml/internal"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
lx *lexer
|
||||
lx *lexer
|
||||
context Key // Full key for the current hash in scope.
|
||||
currentKey string // Base key name for everything except hashes.
|
||||
pos Position // Current position in the TOML file.
|
||||
tomlNext bool
|
||||
|
||||
// A list of keys in the order that they appear in the TOML data.
|
||||
ordered []Key
|
||||
ordered []Key // List of keys in the order that they appear in the TOML data.
|
||||
|
||||
// the full key for the current hash in scope
|
||||
context Key
|
||||
|
||||
// the base key name for everything except hashes
|
||||
currentKey string
|
||||
|
||||
// rough approximation of line number
|
||||
approxLine int
|
||||
|
||||
// A map of 'key.group.names' to whether they were created implicitly.
|
||||
implicits map[string]bool
|
||||
keyInfo map[string]keyInfo // Map keyname → info about the TOML key.
|
||||
mapping map[string]interface{} // Map keyname → key value.
|
||||
implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names").
|
||||
}
|
||||
|
||||
type parseError string
|
||||
|
||||
func (pe parseError) Error() string {
|
||||
return string(pe)
|
||||
type keyInfo struct {
|
||||
pos Position
|
||||
tomlType tomlType
|
||||
}
|
||||
|
||||
func parse(data string) (p *parser, err error) {
|
||||
_, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
if err, ok = r.(parseError); ok {
|
||||
if pErr, ok := r.(ParseError); ok {
|
||||
pErr.input = data
|
||||
err = pErr
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString()
|
||||
// which mangles stuff. UTF-16 BOM isn't strictly valid, but some tools add
|
||||
// it anyway.
|
||||
if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { // UTF-16
|
||||
data = data[2:]
|
||||
} else if strings.HasPrefix(data, "\xef\xbb\xbf") { // UTF-8
|
||||
data = data[3:]
|
||||
}
|
||||
|
||||
// Examine first few bytes for NULL bytes; this probably means it's a UTF-16
|
||||
// file (second byte in surrogate pair being NULL). Again, do this here to
|
||||
// avoid having to deal with UTF-8/16 stuff in the lexer.
|
||||
ex := 6
|
||||
if len(data) < 6 {
|
||||
ex = len(data)
|
||||
}
|
||||
if i := strings.IndexRune(data[:ex], 0); i > -1 {
|
||||
return nil, ParseError{
|
||||
Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8",
|
||||
Position: Position{Line: 1, Start: i, Len: 1},
|
||||
Line: 1,
|
||||
input: data,
|
||||
}
|
||||
}
|
||||
|
||||
p = &parser{
|
||||
keyInfo: make(map[string]keyInfo),
|
||||
mapping: make(map[string]interface{}),
|
||||
types: make(map[string]tomlType),
|
||||
lx: lex(data),
|
||||
lx: lex(data, tomlNext),
|
||||
ordered: make([]Key, 0),
|
||||
implicits: make(map[string]bool),
|
||||
implicits: make(map[string]struct{}),
|
||||
tomlNext: tomlNext,
|
||||
}
|
||||
for {
|
||||
item := p.next()
|
||||
|
@ -65,20 +88,57 @@ func parse(data string) (p *parser, err error) {
|
|||
return p, nil
|
||||
}
|
||||
|
||||
func (p *parser) panicErr(it item, err error) {
|
||||
panic(ParseError{
|
||||
err: err,
|
||||
Position: it.pos,
|
||||
Line: it.pos.Len,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) panicItemf(it item, format string, v ...interface{}) {
|
||||
panic(ParseError{
|
||||
Message: fmt.Sprintf(format, v...),
|
||||
Position: it.pos,
|
||||
Line: it.pos.Len,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) panicf(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
|
||||
p.approxLine, p.current(), fmt.Sprintf(format, v...))
|
||||
panic(parseError(msg))
|
||||
panic(ParseError{
|
||||
Message: fmt.Sprintf(format, v...),
|
||||
Position: p.pos,
|
||||
Line: p.pos.Line,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) next() item {
|
||||
it := p.lx.nextItem()
|
||||
//fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.pos.Line, it.val)
|
||||
if it.typ == itemError {
|
||||
p.panicf("%s", it.val)
|
||||
if it.err != nil {
|
||||
panic(ParseError{
|
||||
Position: it.pos,
|
||||
Line: it.pos.Line,
|
||||
LastKey: p.current(),
|
||||
err: it.err,
|
||||
})
|
||||
}
|
||||
|
||||
p.panicItemf(it, "%s", it.val)
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) nextPos() item {
|
||||
it := p.next()
|
||||
p.pos = it.pos
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) bug(format string, v ...interface{}) {
|
||||
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
||||
}
|
||||
|
@ -97,44 +157,60 @@ func (p *parser) assertEqual(expected, got itemType) {
|
|||
|
||||
func (p *parser) topLevel(item item) {
|
||||
switch item.typ {
|
||||
case itemCommentStart:
|
||||
p.approxLine = item.line
|
||||
case itemCommentStart: // # ..
|
||||
p.expect(itemText)
|
||||
case itemTableStart:
|
||||
kg := p.next()
|
||||
p.approxLine = kg.line
|
||||
case itemTableStart: // [ .. ]
|
||||
name := p.nextPos()
|
||||
|
||||
var key Key
|
||||
for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||
key = append(key, p.keyString(kg))
|
||||
for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() {
|
||||
key = append(key, p.keyString(name))
|
||||
}
|
||||
p.assertEqual(itemTableEnd, kg.typ)
|
||||
p.assertEqual(itemTableEnd, name.typ)
|
||||
|
||||
p.establishContext(key, false)
|
||||
p.setType("", tomlHash)
|
||||
p.addContext(key, false)
|
||||
p.setType("", tomlHash, item.pos)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemArrayTableStart:
|
||||
kg := p.next()
|
||||
p.approxLine = kg.line
|
||||
case itemArrayTableStart: // [[ .. ]]
|
||||
name := p.nextPos()
|
||||
|
||||
var key Key
|
||||
for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||
key = append(key, p.keyString(kg))
|
||||
for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() {
|
||||
key = append(key, p.keyString(name))
|
||||
}
|
||||
p.assertEqual(itemArrayTableEnd, kg.typ)
|
||||
p.assertEqual(itemArrayTableEnd, name.typ)
|
||||
|
||||
p.establishContext(key, true)
|
||||
p.setType("", tomlArrayHash)
|
||||
p.addContext(key, true)
|
||||
p.setType("", tomlArrayHash, item.pos)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemKeyStart:
|
||||
kname := p.next()
|
||||
p.approxLine = kname.line
|
||||
p.currentKey = p.keyString(kname)
|
||||
case itemKeyStart: // key = ..
|
||||
outerContext := p.context
|
||||
/// Read all the key parts (e.g. 'a' and 'b' in 'a.b')
|
||||
k := p.nextPos()
|
||||
var key Key
|
||||
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||
key = append(key, p.keyString(k))
|
||||
}
|
||||
p.assertEqual(itemKeyEnd, k.typ)
|
||||
|
||||
val, typ := p.value(p.next())
|
||||
p.setValue(p.currentKey, val)
|
||||
p.setType(p.currentKey, typ)
|
||||
/// The current key is the last part.
|
||||
p.currentKey = key[len(key)-1]
|
||||
|
||||
/// All the other parts (if any) are the context; need to set each part
|
||||
/// as implicit.
|
||||
context := key[:len(key)-1]
|
||||
for i := range context {
|
||||
p.addImplicitContext(append(p.context, context[i:i+1]...))
|
||||
}
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
|
||||
/// Set value.
|
||||
vItem := p.next()
|
||||
val, typ := p.value(vItem, false)
|
||||
p.set(p.currentKey, val, typ, vItem.pos)
|
||||
|
||||
/// Remove the context we added (preserving any context from [tbl] lines).
|
||||
p.context = outerContext
|
||||
p.currentKey = ""
|
||||
default:
|
||||
p.bug("Unexpected type at top level: %s", item.typ)
|
||||
|
@ -148,180 +224,271 @@ func (p *parser) keyString(it item) string {
|
|||
return it.val
|
||||
case itemString, itemMultilineString,
|
||||
itemRawString, itemRawMultilineString:
|
||||
s, _ := p.value(it)
|
||||
s, _ := p.value(it, false)
|
||||
return s.(string)
|
||||
default:
|
||||
p.bug("Unexpected key type: %s", it.typ)
|
||||
panic("unreachable")
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var datetimeRepl = strings.NewReplacer(
|
||||
"z", "Z",
|
||||
"t", "T",
|
||||
" ", "T")
|
||||
|
||||
// value translates an expected value from the lexer into a Go value wrapped
|
||||
// as an empty interface.
|
||||
func (p *parser) value(it item) (interface{}, tomlType) {
|
||||
func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) {
|
||||
switch it.typ {
|
||||
case itemString:
|
||||
return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
|
||||
return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it)
|
||||
case itemMultilineString:
|
||||
trimmed := stripFirstNewline(stripEscapedWhitespace(it.val))
|
||||
return p.replaceEscapes(trimmed), p.typeOfPrimitive(it)
|
||||
return p.replaceEscapes(it, p.stripEscapedNewlines(stripFirstNewline(it.val))), p.typeOfPrimitive(it)
|
||||
case itemRawString:
|
||||
return it.val, p.typeOfPrimitive(it)
|
||||
case itemRawMultilineString:
|
||||
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
|
||||
case itemInteger:
|
||||
return p.valueInteger(it)
|
||||
case itemFloat:
|
||||
return p.valueFloat(it)
|
||||
case itemBool:
|
||||
switch it.val {
|
||||
case "true":
|
||||
return true, p.typeOfPrimitive(it)
|
||||
case "false":
|
||||
return false, p.typeOfPrimitive(it)
|
||||
default:
|
||||
p.bug("Expected boolean value, but got '%s'.", it.val)
|
||||
}
|
||||
p.bug("Expected boolean value, but got '%s'.", it.val)
|
||||
case itemInteger:
|
||||
if !numUnderscoresOK(it.val) {
|
||||
p.panicf("Invalid integer %q: underscores must be surrounded by digits",
|
||||
it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
num, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
||||
// provides an invalid integer, but it's possible that the number is
|
||||
// out of range of valid values (which the lexer cannot determine).
|
||||
// So mark the former as a bug but the latter as a legitimate user
|
||||
// error.
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
|
||||
p.panicf("Integer '%s' is out of the range of 64-bit "+
|
||||
"signed integers.", it.val)
|
||||
} else {
|
||||
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
case itemFloat:
|
||||
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
||||
switch r {
|
||||
case '.', 'e', 'E':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, part := range parts {
|
||||
if !numUnderscoresOK(part) {
|
||||
p.panicf("Invalid float %q: underscores must be "+
|
||||
"surrounded by digits", it.val)
|
||||
}
|
||||
}
|
||||
if !numPeriodsOK(it.val) {
|
||||
// As a special case, numbers like '123.' or '1.e2',
|
||||
// which are valid as far as Go/strconv are concerned,
|
||||
// must be rejected because TOML says that a fractional
|
||||
// part consists of '.' followed by 1+ digits.
|
||||
p.panicf("Invalid float %q: '.' must be followed "+
|
||||
"by one or more digits", it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
num, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
|
||||
p.panicf("Float '%s' is out of the range of 64-bit "+
|
||||
"IEEE-754 floating-point numbers.", it.val)
|
||||
} else {
|
||||
p.panicf("Invalid float value: %q", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
case itemDatetime:
|
||||
var t time.Time
|
||||
var ok bool
|
||||
var err error
|
||||
for _, format := range []string{
|
||||
"2006-01-02T15:04:05Z07:00",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02",
|
||||
} {
|
||||
t, err = time.ParseInLocation(format, it.val, time.Local)
|
||||
if err == nil {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
p.panicf("Invalid TOML Datetime: %q.", it.val)
|
||||
}
|
||||
return t, p.typeOfPrimitive(it)
|
||||
return p.valueDatetime(it)
|
||||
case itemArray:
|
||||
array := make([]interface{}, 0)
|
||||
types := make([]tomlType, 0)
|
||||
|
||||
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
val, typ := p.value(it)
|
||||
array = append(array, val)
|
||||
types = append(types, typ)
|
||||
}
|
||||
return array, p.typeOfArray(types)
|
||||
return p.valueArray(it)
|
||||
case itemInlineTableStart:
|
||||
var (
|
||||
hash = make(map[string]interface{})
|
||||
outerContext = p.context
|
||||
outerKey = p.currentKey
|
||||
)
|
||||
|
||||
p.context = append(p.context, p.currentKey)
|
||||
p.currentKey = ""
|
||||
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
||||
if it.typ != itemKeyStart {
|
||||
p.bug("Expected key start but instead found %q, around line %d",
|
||||
it.val, p.approxLine)
|
||||
}
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
// retrieve key
|
||||
k := p.next()
|
||||
p.approxLine = k.line
|
||||
kname := p.keyString(k)
|
||||
|
||||
// retrieve value
|
||||
p.currentKey = kname
|
||||
val, typ := p.value(p.next())
|
||||
// make sure we keep metadata up to date
|
||||
p.setType(kname, typ)
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
hash[kname] = val
|
||||
}
|
||||
p.context = outerContext
|
||||
p.currentKey = outerKey
|
||||
return hash, tomlHash
|
||||
return p.valueInlineTable(it, parentIsArray)
|
||||
default:
|
||||
p.bug("Unexpected value type: %s", it.typ)
|
||||
}
|
||||
p.bug("Unexpected value type: %s", it.typ)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (p *parser) valueInteger(it item) (interface{}, tomlType) {
|
||||
if !numUnderscoresOK(it.val) {
|
||||
p.panicItemf(it, "Invalid integer %q: underscores must be surrounded by digits", it.val)
|
||||
}
|
||||
if numHasLeadingZero(it.val) {
|
||||
p.panicItemf(it, "Invalid integer %q: cannot have leading zeroes", it.val)
|
||||
}
|
||||
|
||||
num, err := strconv.ParseInt(it.val, 0, 64)
|
||||
if err != nil {
|
||||
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
||||
// provides an invalid integer, but it's possible that the number is
|
||||
// out of range of valid values (which the lexer cannot determine).
|
||||
// So mark the former as a bug but the latter as a legitimate user
|
||||
// error.
|
||||
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||
p.panicErr(it, errParseRange{i: it.val, size: "int64"})
|
||||
} else {
|
||||
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
}
|
||||
|
||||
func (p *parser) valueFloat(it item) (interface{}, tomlType) {
|
||||
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
||||
switch r {
|
||||
case '.', 'e', 'E':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, part := range parts {
|
||||
if !numUnderscoresOK(part) {
|
||||
p.panicItemf(it, "Invalid float %q: underscores must be surrounded by digits", it.val)
|
||||
}
|
||||
}
|
||||
if len(parts) > 0 && numHasLeadingZero(parts[0]) {
|
||||
p.panicItemf(it, "Invalid float %q: cannot have leading zeroes", it.val)
|
||||
}
|
||||
if !numPeriodsOK(it.val) {
|
||||
// As a special case, numbers like '123.' or '1.e2',
|
||||
// which are valid as far as Go/strconv are concerned,
|
||||
// must be rejected because TOML says that a fractional
|
||||
// part consists of '.' followed by 1+ digits.
|
||||
p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
if val == "+nan" || val == "-nan" { // Go doesn't support this, but TOML spec does.
|
||||
val = "nan"
|
||||
}
|
||||
num, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||
p.panicErr(it, errParseRange{i: it.val, size: "float64"})
|
||||
} else {
|
||||
p.panicItemf(it, "Invalid float value: %q", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
}
|
||||
|
||||
var dtTypes = []struct {
|
||||
fmt string
|
||||
zone *time.Location
|
||||
next bool
|
||||
}{
|
||||
{time.RFC3339Nano, time.Local, false},
|
||||
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false},
|
||||
{"2006-01-02", internal.LocalDate, false},
|
||||
{"15:04:05.999999999", internal.LocalTime, false},
|
||||
|
||||
// tomlNext
|
||||
{"2006-01-02T15:04Z07:00", time.Local, true},
|
||||
{"2006-01-02T15:04", internal.LocalDatetime, true},
|
||||
{"15:04", internal.LocalTime, true},
|
||||
}
|
||||
|
||||
func (p *parser) valueDatetime(it item) (interface{}, tomlType) {
|
||||
it.val = datetimeRepl.Replace(it.val)
|
||||
var (
|
||||
t time.Time
|
||||
ok bool
|
||||
err error
|
||||
)
|
||||
for _, dt := range dtTypes {
|
||||
if dt.next && !p.tomlNext {
|
||||
continue
|
||||
}
|
||||
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
|
||||
if err == nil {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
p.panicItemf(it, "Invalid TOML Datetime: %q.", it.val)
|
||||
}
|
||||
return t, p.typeOfPrimitive(it)
|
||||
}
|
||||
|
||||
func (p *parser) valueArray(it item) (interface{}, tomlType) {
|
||||
p.setType(p.currentKey, tomlArray, it.pos)
|
||||
|
||||
var (
|
||||
types []tomlType
|
||||
|
||||
// Initialize to a non-nil empty slice. This makes it consistent with
|
||||
// how S = [] decodes into a non-nil slice inside something like struct
|
||||
// { S []string }. See #338
|
||||
array = []interface{}{}
|
||||
)
|
||||
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
val, typ := p.value(it, true)
|
||||
array = append(array, val)
|
||||
types = append(types, typ)
|
||||
|
||||
// XXX: types isn't used here, we need it to record the accurate type
|
||||
// information.
|
||||
//
|
||||
// Not entirely sure how to best store this; could use "key[0]",
|
||||
// "key[1]" notation, or maybe store it on the Array type?
|
||||
_ = types
|
||||
}
|
||||
return array, tomlArray
|
||||
}
|
||||
|
||||
func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tomlType) {
|
||||
var (
|
||||
hash = make(map[string]interface{})
|
||||
outerContext = p.context
|
||||
outerKey = p.currentKey
|
||||
)
|
||||
|
||||
p.context = append(p.context, p.currentKey)
|
||||
prevContext := p.context
|
||||
p.currentKey = ""
|
||||
|
||||
p.addImplicit(p.context)
|
||||
p.addContext(p.context, parentIsArray)
|
||||
|
||||
/// Loop over all table key/value pairs.
|
||||
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
/// Read all key parts.
|
||||
k := p.nextPos()
|
||||
var key Key
|
||||
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||
key = append(key, p.keyString(k))
|
||||
}
|
||||
p.assertEqual(itemKeyEnd, k.typ)
|
||||
|
||||
/// The current key is the last part.
|
||||
p.currentKey = key[len(key)-1]
|
||||
|
||||
/// All the other parts (if any) are the context; need to set each part
|
||||
/// as implicit.
|
||||
context := key[:len(key)-1]
|
||||
for i := range context {
|
||||
p.addImplicitContext(append(p.context, context[i:i+1]...))
|
||||
}
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
|
||||
/// Set the value.
|
||||
val, typ := p.value(p.next(), false)
|
||||
p.set(p.currentKey, val, typ, it.pos)
|
||||
hash[p.currentKey] = val
|
||||
|
||||
/// Restore context.
|
||||
p.context = prevContext
|
||||
}
|
||||
p.context = outerContext
|
||||
p.currentKey = outerKey
|
||||
return hash, tomlHash
|
||||
}
|
||||
|
||||
// numHasLeadingZero checks if this number has leading zeroes, allowing for '0',
|
||||
// +/- signs, and base prefixes.
|
||||
func numHasLeadingZero(s string) bool {
|
||||
if len(s) > 1 && s[0] == '0' && !(s[1] == 'b' || s[1] == 'o' || s[1] == 'x') { // Allow 0b, 0o, 0x
|
||||
return true
|
||||
}
|
||||
if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// numUnderscoresOK checks whether each underscore in s is surrounded by
|
||||
// characters that are not underscores.
|
||||
func numUnderscoresOK(s string) bool {
|
||||
switch s {
|
||||
case "nan", "+nan", "-nan", "inf", "-inf", "+inf":
|
||||
return true
|
||||
}
|
||||
accept := false
|
||||
for _, r := range s {
|
||||
if r == '_' {
|
||||
if !accept {
|
||||
return false
|
||||
}
|
||||
accept = false
|
||||
continue
|
||||
}
|
||||
accept = true
|
||||
|
||||
// isHexadecimal is a superset of all the permissable characters
|
||||
// surrounding an underscore.
|
||||
accept = isHexadecimal(r)
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
@ -338,13 +505,12 @@ func numPeriodsOK(s string) bool {
|
|||
return !period
|
||||
}
|
||||
|
||||
// establishContext sets the current context of the parser,
|
||||
// where the context is either a hash or an array of hashes. Which one is
|
||||
// set depends on the value of the `array` parameter.
|
||||
// Set the current context of the parser, where the context is either a hash or
|
||||
// an array of hashes, depending on the value of the `array` parameter.
|
||||
//
|
||||
// Establishing the context also makes sure that the key isn't a duplicate, and
|
||||
// will create implicit hashes automatically.
|
||||
func (p *parser) establishContext(key Key, array bool) {
|
||||
func (p *parser) addContext(key Key, array bool) {
|
||||
var ok bool
|
||||
|
||||
// Always start at the top level and drill down for our context.
|
||||
|
@ -383,7 +549,7 @@ func (p *parser) establishContext(key Key, array bool) {
|
|||
// list of tables for it.
|
||||
k := key[len(key)-1]
|
||||
if _, ok := hashContext[k]; !ok {
|
||||
hashContext[k] = make([]map[string]interface{}, 0, 5)
|
||||
hashContext[k] = make([]map[string]interface{}, 0, 4)
|
||||
}
|
||||
|
||||
// Add a new table. But make sure the key hasn't already been used
|
||||
|
@ -391,8 +557,7 @@ func (p *parser) establishContext(key Key, array bool) {
|
|||
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
|
||||
hashContext[k] = append(hash, make(map[string]interface{}))
|
||||
} else {
|
||||
p.panicf("Key '%s' was already created and cannot be used as "+
|
||||
"an array.", keyContext)
|
||||
p.panicf("Key '%s' was already created and cannot be used as an array.", key)
|
||||
}
|
||||
} else {
|
||||
p.setValue(key[len(key)-1], make(map[string]interface{}))
|
||||
|
@ -400,15 +565,22 @@ func (p *parser) establishContext(key Key, array bool) {
|
|||
p.context = append(p.context, key[len(key)-1])
|
||||
}
|
||||
|
||||
// set calls setValue and setType.
|
||||
func (p *parser) set(key string, val interface{}, typ tomlType, pos Position) {
|
||||
p.setValue(key, val)
|
||||
p.setType(key, typ, pos)
|
||||
}
|
||||
|
||||
// setValue sets the given key to the given value in the current context.
|
||||
// It will make sure that the key hasn't already been defined, account for
|
||||
// implicit key groups.
|
||||
func (p *parser) setValue(key string, value interface{}) {
|
||||
var tmpHash interface{}
|
||||
var ok bool
|
||||
|
||||
hash := p.mapping
|
||||
keyContext := make(Key, 0)
|
||||
var (
|
||||
tmpHash interface{}
|
||||
ok bool
|
||||
hash = p.mapping
|
||||
keyContext Key
|
||||
)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
if tmpHash, ok = hash[k]; !ok {
|
||||
|
@ -422,24 +594,26 @@ func (p *parser) setValue(key string, value interface{}) {
|
|||
case map[string]interface{}:
|
||||
hash = t
|
||||
default:
|
||||
p.bug("Expected hash to have type 'map[string]interface{}', but "+
|
||||
"it has '%T' instead.", tmpHash)
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
}
|
||||
keyContext = append(keyContext, key)
|
||||
|
||||
if _, ok := hash[key]; ok {
|
||||
// Typically, if the given key has already been set, then we have
|
||||
// to raise an error since duplicate keys are disallowed. However,
|
||||
// it's possible that a key was previously defined implicitly. In this
|
||||
// case, it is allowed to be redefined concretely. (See the
|
||||
// `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
|
||||
// Normally redefining keys isn't allowed, but the key could have been
|
||||
// defined implicitly and it's allowed to be redefined concretely. (See
|
||||
// the `valid/implicit-and-explicit-after.toml` in toml-test)
|
||||
//
|
||||
// But we have to make sure to stop marking it as an implicit. (So that
|
||||
// another redefinition provokes an error.)
|
||||
//
|
||||
// Note that since it has already been defined (as a hash), we don't
|
||||
// want to overwrite it. So our business is done.
|
||||
if p.isArray(keyContext) {
|
||||
p.removeImplicit(keyContext)
|
||||
hash[key] = value
|
||||
return
|
||||
}
|
||||
if p.isImplicit(keyContext) {
|
||||
p.removeImplicit(keyContext)
|
||||
return
|
||||
|
@ -449,41 +623,37 @@ func (p *parser) setValue(key string, value interface{}) {
|
|||
// key, which is *always* wrong.
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
|
||||
hash[key] = value
|
||||
}
|
||||
|
||||
// setType sets the type of a particular value at a given key.
|
||||
// It should be called immediately AFTER setValue.
|
||||
// setType sets the type of a particular value at a given key. It should be
|
||||
// called immediately AFTER setValue.
|
||||
//
|
||||
// Note that if `key` is empty, then the type given will be applied to the
|
||||
// current context (which is either a table or an array of tables).
|
||||
func (p *parser) setType(key string, typ tomlType) {
|
||||
func (p *parser) setType(key string, typ tomlType, pos Position) {
|
||||
keyContext := make(Key, 0, len(p.context)+1)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
}
|
||||
keyContext = append(keyContext, p.context...)
|
||||
if len(key) > 0 { // allow type setting for hashes
|
||||
keyContext = append(keyContext, key)
|
||||
}
|
||||
p.types[keyContext.String()] = typ
|
||||
// Special case to make empty keys ("" = 1) work.
|
||||
// Without it it will set "" rather than `""`.
|
||||
// TODO: why is this needed? And why is this only needed here?
|
||||
if len(keyContext) == 0 {
|
||||
keyContext = Key{""}
|
||||
}
|
||||
p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos}
|
||||
}
|
||||
|
||||
// addImplicit sets the given Key as having been created implicitly.
|
||||
func (p *parser) addImplicit(key Key) {
|
||||
p.implicits[key.String()] = true
|
||||
}
|
||||
|
||||
// removeImplicit stops tagging the given key as having been implicitly
|
||||
// created.
|
||||
func (p *parser) removeImplicit(key Key) {
|
||||
p.implicits[key.String()] = false
|
||||
}
|
||||
|
||||
// isImplicit returns true if the key group pointed to by the key was created
|
||||
// implicitly.
|
||||
func (p *parser) isImplicit(key Key) bool {
|
||||
return p.implicits[key.String()]
|
||||
}
|
||||
// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
|
||||
// "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly).
|
||||
func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} }
|
||||
func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) }
|
||||
func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok }
|
||||
func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray }
|
||||
func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) }
|
||||
|
||||
// current returns the full key name of the current context.
|
||||
func (p *parser) current() string {
|
||||
|
@ -497,24 +667,67 @@ func (p *parser) current() string {
|
|||
}
|
||||
|
||||
func stripFirstNewline(s string) string {
|
||||
if len(s) == 0 || s[0] != '\n' {
|
||||
return s
|
||||
if len(s) > 0 && s[0] == '\n' {
|
||||
return s[1:]
|
||||
}
|
||||
return s[1:]
|
||||
if len(s) > 1 && s[0] == '\r' && s[1] == '\n' {
|
||||
return s[2:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func stripEscapedWhitespace(s string) string {
|
||||
esc := strings.Split(s, "\\\n")
|
||||
if len(esc) > 1 {
|
||||
for i := 1; i < len(esc); i++ {
|
||||
esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace)
|
||||
// stripEscapedNewlines removes whitespace after line-ending backslashes in
|
||||
// multiline strings.
|
||||
//
|
||||
// A line-ending backslash is an unescaped \ followed only by whitespace until
|
||||
// the next newline. After a line-ending backslash, all whitespace is removed
|
||||
// until the next non-whitespace character.
|
||||
func (p *parser) stripEscapedNewlines(s string) string {
|
||||
var b strings.Builder
|
||||
var i int
|
||||
for {
|
||||
ix := strings.Index(s[i:], `\`)
|
||||
if ix < 0 {
|
||||
b.WriteString(s)
|
||||
return b.String()
|
||||
}
|
||||
i += ix
|
||||
|
||||
if len(s) > i+1 && s[i+1] == '\\' {
|
||||
// Escaped backslash.
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
// Scan until the next non-whitespace.
|
||||
j := i + 1
|
||||
whitespaceLoop:
|
||||
for ; j < len(s); j++ {
|
||||
switch s[j] {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
default:
|
||||
break whitespaceLoop
|
||||
}
|
||||
}
|
||||
if j == i+1 {
|
||||
// Not a whitespace escape.
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(s[i:j], "\n") {
|
||||
// This is not a line-ending backslash.
|
||||
// (It's a bad escape sequence, but we can let
|
||||
// replaceEscapes catch it.)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
b.WriteString(s[:i])
|
||||
s = s[j:]
|
||||
i = 0
|
||||
}
|
||||
return strings.Join(esc, "")
|
||||
}
|
||||
|
||||
func (p *parser) replaceEscapes(str string) string {
|
||||
var replaced []rune
|
||||
func (p *parser) replaceEscapes(it item, str string) string {
|
||||
replaced := make([]rune, 0, len(str))
|
||||
s := []byte(str)
|
||||
r := 0
|
||||
for r < len(s) {
|
||||
|
@ -532,7 +745,8 @@ func (p *parser) replaceEscapes(str string) string {
|
|||
switch s[r] {
|
||||
default:
|
||||
p.bug("Expected valid escape code after \\, but got %q.", s[r])
|
||||
return ""
|
||||
case ' ', '\t':
|
||||
p.panicItemf(it, "invalid escape: '\\%c'", s[r])
|
||||
case 'b':
|
||||
replaced = append(replaced, rune(0x0008))
|
||||
r += 1
|
||||
|
@ -548,24 +762,35 @@ func (p *parser) replaceEscapes(str string) string {
|
|||
case 'r':
|
||||
replaced = append(replaced, rune(0x000D))
|
||||
r += 1
|
||||
case 'e':
|
||||
if p.tomlNext {
|
||||
replaced = append(replaced, rune(0x001B))
|
||||
r += 1
|
||||
}
|
||||
case '"':
|
||||
replaced = append(replaced, rune(0x0022))
|
||||
r += 1
|
||||
case '\\':
|
||||
replaced = append(replaced, rune(0x005C))
|
||||
r += 1
|
||||
case 'x':
|
||||
if p.tomlNext {
|
||||
escaped := p.asciiEscapeToUnicode(it, s[r+1:r+3])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 3
|
||||
}
|
||||
case 'u':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
|
||||
escaped := p.asciiEscapeToUnicode(it, s[r+1:r+5])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 5
|
||||
case 'U':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
|
||||
escaped := p.asciiEscapeToUnicode(it, s[r+1:r+9])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 9
|
||||
}
|
||||
|
@ -573,20 +798,14 @@ func (p *parser) replaceEscapes(str string) string {
|
|||
return string(replaced)
|
||||
}
|
||||
|
||||
func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
||||
func (p *parser) asciiEscapeToUnicode(it item, bs []byte) rune {
|
||||
s := string(bs)
|
||||
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
||||
if err != nil {
|
||||
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
||||
"lexer claims it's OK: %s", s, err)
|
||||
p.bug("Could not parse '%s' as a hexadecimal number, but the lexer claims it's OK: %s", s, err)
|
||||
}
|
||||
if !utf8.ValidRune(rune(hex)) {
|
||||
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||
p.panicItemf(it, "Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||
}
|
||||
return rune(hex)
|
||||
}
|
||||
|
||||
func isStringType(ty itemType) bool {
|
||||
return ty == itemString || ty == itemMultilineString ||
|
||||
ty == itemRawString || ty == itemRawMultilineString
|
||||
}
|
||||
|
|
1
vendor/github.com/BurntSushi/toml/session.vim
generated
vendored
1
vendor/github.com/BurntSushi/toml/session.vim
generated
vendored
|
@ -1 +0,0 @@
|
|||
au BufWritePost *.go silent!make tags > /dev/null 2>&1
|
4
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
4
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
|
@ -70,8 +70,8 @@ func typeFields(t reflect.Type) []field {
|
|||
next := []field{{typ: t}}
|
||||
|
||||
// Count of queued names for current level and the next.
|
||||
count := map[reflect.Type]int{}
|
||||
nextCount := map[reflect.Type]int{}
|
||||
var count map[reflect.Type]int
|
||||
var nextCount map[reflect.Type]int
|
||||
|
||||
// Types already visited at an earlier level.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
|
|
@ -16,7 +16,7 @@ func typeEqual(t1, t2 tomlType) bool {
|
|||
return t1.typeString() == t2.typeString()
|
||||
}
|
||||
|
||||
func typeIsHash(t tomlType) bool {
|
||||
func typeIsTable(t tomlType) bool {
|
||||
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||
}
|
||||
|
||||
|
@ -68,24 +68,3 @@ func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
|||
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// typeOfArray returns a tomlType for an array given a list of types of its
|
||||
// values.
|
||||
//
|
||||
// In the current spec, if an array is homogeneous, then its type is always
|
||||
// "Array". If the array is not homogeneous, an error is generated.
|
||||
func (p *parser) typeOfArray(types []tomlType) tomlType {
|
||||
// Empty arrays are cool.
|
||||
if len(types) == 0 {
|
||||
return tomlArray
|
||||
}
|
||||
|
||||
theType := types[0]
|
||||
for _, t := range types[1:] {
|
||||
if !typeEqual(theType, t) {
|
||||
p.panicf("Array contains values of type '%s' and '%s', but "+
|
||||
"arrays must be homogeneous.", theType, t)
|
||||
}
|
||||
}
|
||||
return tomlArray
|
||||
}
|
1
vendor/github.com/andygrunwald/go-jira/.gitignore
generated
vendored
1
vendor/github.com/andygrunwald/go-jira/.gitignore
generated
vendored
|
@ -27,3 +27,4 @@ _testmain.go
|
|||
*.prof
|
||||
*.iml
|
||||
.idea
|
||||
.DS_Store
|
20
vendor/github.com/andygrunwald/go-jira/.travis.yml
generated
vendored
20
vendor/github.com/andygrunwald/go-jira/.travis.yml
generated
vendored
|
@ -1,20 +0,0 @@
|
|||
language: go
|
||||
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
|
||||
before_install:
|
||||
- go get -t ./...
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: 1.13.x
|
||||
|
||||
script:
|
||||
- GOMAXPROCS=4 GORACE="halt_on_error=1" go test -race -v ./...
|
29
vendor/github.com/andygrunwald/go-jira/CHANGELOG.md
generated
vendored
29
vendor/github.com/andygrunwald/go-jira/CHANGELOG.md
generated
vendored
|
@ -2,6 +2,32 @@
|
|||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [1.13.0](https://github.com/andygrunwald/go-jira/compare/v1.11.1...v1.13.0) (2020-10-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add AddRemoteLink method ([f200e15](https://github.com/andygrunwald/go-jira/commit/f200e158b997a303db081cbbc5a9d8ad5d89566d)), closes [/developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2](https://github.com/andygrunwald//developer.atlassian.com/cloud/jira/platform/rest/v2//issues/api-rest-api-2)
|
||||
* Add Names support on Issue struct ([#278](https://github.com/andygrunwald/go-jira/issues/278)) ([1fc10e0](https://github.com/andygrunwald/go-jira/commit/1fc10e0606784f745673ccc4d8d706c36f385a7a))
|
||||
* Extend Makefile for more source code quality targets ([5e52236](https://github.com/andygrunwald/go-jira/commit/5e5223631a29d10a13e598318a6abe47384e2982))
|
||||
* **context:** Add support for context package ([e1f4265](https://github.com/andygrunwald/go-jira/commit/e1f4265e2b467b938fe0c095caf6d36f3136d2ff))
|
||||
* **issues:** Add GetEditMeta on issue ([a783764](https://github.com/andygrunwald/go-jira/commit/a783764b52dc890773658ddd0483a9d0393e385d)), closes [/docs.atlassian.com/DAC/rest/jira/6.1.html#d2e1364](https://github.com/andygrunwald//docs.atlassian.com/DAC/rest/jira/6.1.html/issues/d2e1364)
|
||||
* **IssueService:** allow empty JQL ([#268](https://github.com/andygrunwald/go-jira/issues/268)) ([4b91cf2](https://github.com/andygrunwald/go-jira/commit/4b91cf2b135355de7ecee41727c3e65f4e7067bc))
|
||||
* **project:** Add cronjob to check for stale issues ([#287](https://github.com/andygrunwald/go-jira/issues/287)) ([2096b04](https://github.com/andygrunwald/go-jira/commit/2096b04e52b434c1fb1c841bab487a94674a271e))
|
||||
* **project:** Add GitHub Actions testing workflow ([#289](https://github.com/andygrunwald/go-jira/issues/289)) ([80c0282](https://github.com/andygrunwald/go-jira/commit/80c02828ca9e4eb0e4a1877275baae14d330a2d9)), closes [#290](https://github.com/andygrunwald/go-jira/issues/290)
|
||||
* **project:** Add workflow to greet new contributors ([#288](https://github.com/andygrunwald/go-jira/issues/288)) ([c357b61](https://github.com/andygrunwald/go-jira/commit/c357b61a40f62a919ebd94a555390958f99c8db7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* change millisecond time format ([8c77107](https://github.com/andygrunwald/go-jira/commit/8c77107df3757c4ec5eae6e9d7c018618e708bfa))
|
||||
* paging with load balancer going to endless loop ([19d3fc0](https://github.com/andygrunwald/go-jira/commit/19d3fc0aecde547ffe1ab547c5ffb6c7972d387c)), closes [#260](https://github.com/andygrunwald/go-jira/issues/260)
|
||||
* **issue:** IssueService.Search() with a not empty JQL triggers 400 bad request ([#292](https://github.com/andygrunwald/go-jira/issues/292)) ([8b64c7f](https://github.com/andygrunwald/go-jira/commit/8b64c7f005fbceb11fa43a7aff3de61eb3166fca)), closes [#291](https://github.com/andygrunwald/go-jira/issues/291)
|
||||
* **IssueService.GetWatchers:** UserService.GetByAccountID support accountId params ([436469b](https://github.com/andygrunwald/go-jira/commit/436469b62d4d62037f380b38c918a13f4a5f0ab2))
|
||||
* **product:** Make product naming consistent, rename JIRA to Jira ([#286](https://github.com/andygrunwald/go-jira/issues/286)) ([146229d](https://github.com/andygrunwald/go-jira/commit/146229d2ab58a3fb128ddc8dcbe03aff72e20857)), closes [#284](https://github.com/andygrunwald/go-jira/issues/284)
|
||||
* **tests:** Fix TestIssueService_PostAttachment unit test ([f6b1dca](https://github.com/andygrunwald/go-jira/commit/f6b1dcafcfdd8fe69f842b1053c4030da6c97c7f))
|
||||
* removing the use of username field in searching for users ([#297](https://github.com/andygrunwald/go-jira/issues/297)) ([f50cb07](https://github.com/andygrunwald/go-jira/commit/f50cb07b297d79138b13e5ab49ea33965d32f5c1))
|
||||
|
||||
## [1.12.0](https://github.com/andygrunwald/go-jira/compare/v1.11.1...v1.12.0) (2019-12-14)
|
||||
|
||||
|
||||
|
@ -76,6 +102,3 @@ All notable changes to this project will be documented in this file. See [standa
|
|||
* Add ResolutionService to retrieve resolutions ([fb1ce22](https://github.com/andygrunwald/go-jira/commit/fb1ce22))
|
||||
* Add status category constants ([6223ddd](https://github.com/andygrunwald/go-jira/commit/6223ddd))
|
||||
* Add StatusCategory GetList ([049a756](https://github.com/andygrunwald/go-jira/commit/049a756))
|
||||
|
||||
|
||||
|
||||
|
|
27
vendor/github.com/andygrunwald/go-jira/Makefile
generated
vendored
27
vendor/github.com/andygrunwald/go-jira/Makefile
generated
vendored
|
@ -1,2 +1,25 @@
|
|||
test:
|
||||
go test -v ./...
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
.PHONY: help
|
||||
help: ## Outputs the help.
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: test
|
||||
test: ## Runs all unit, integration and example tests.
|
||||
go test -race -v ./...
|
||||
|
||||
.PHONY: vet
|
||||
vet: ## Runs go vet (to detect suspicious constructs).
|
||||
go vet ./...
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: ## Runs go fmt (to check for go coding guidelines).
|
||||
gofmt -d -s .
|
||||
|
||||
.PHONY: staticcheck
|
||||
staticcheck: ## Runs static analysis to prevend bugs, foster code simplicity, performance and editor integration.
|
||||
go install honnef.co/go/tools/cmd/staticcheck@2022.1
|
||||
staticcheck ./...
|
||||
|
||||
.PHONY: all
|
||||
all: test vet fmt staticcheck ## Runs all source code quality targets (like test, vet, fmt, staticcheck)
|
||||
|
|
15
vendor/github.com/andygrunwald/go-jira/PULL_REQUEST_TEMPLATE.md
generated
vendored
15
vendor/github.com/andygrunwald/go-jira/PULL_REQUEST_TEMPLATE.md
generated
vendored
|
@ -1,15 +0,0 @@
|
|||
# PR Description
|
||||
|
||||
_What does this fix or add?_
|
||||
|
||||
# Checklist
|
||||
|
||||
* [ ] Tests added
|
||||
* [ ] Good Path
|
||||
* [ ] Error Path
|
||||
* [ ] Commits follow conventions described here:
|
||||
* [ ] [https://conventionalcommits.org/en/v1.0.0-beta.4/#summary](https://conventionalcommits.org/en/v1.0.0-beta.4/#summary)
|
||||
* [ ] [https://chris.beams.io/posts/git-commit/#seven-rules](https://chris.beams.io/posts/git-commit/#seven-rules)
|
||||
* [ ] Commits are squashed such that
|
||||
* [ ] There is 1 commit per isolated change
|
||||
* [ ] I've not made extraneous commits/changes that are unrelated to my change.
|
97
vendor/github.com/andygrunwald/go-jira/README.md
generated
vendored
97
vendor/github.com/andygrunwald/go-jira/README.md
generated
vendored
|
@ -1,26 +1,29 @@
|
|||
# go-jira
|
||||
|
||||
[](https://godoc.org/github.com/andygrunwald/go-jira)
|
||||
[](https://travis-ci.org/andygrunwald/go-jira)
|
||||
[](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml)
|
||||
[](https://goreportcard.com/report/github.com/andygrunwald/go-jira)
|
||||
|
||||
[Go](https://golang.org/) client library for [Atlassian JIRA](https://www.atlassian.com/software/jira).
|
||||
[Go](https://golang.org/) client library for [Atlassian Jira](https://www.atlassian.com/software/jira).
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
* Authentication (HTTP Basic, OAuth, Session Cookie)
|
||||
* Create and retrieve issues
|
||||
* Create and retrieve issue transitions (status updates)
|
||||
* Call every API endpoint of the JIRA, even if it is not directly implemented in this library
|
||||
* Call every API endpoint of the Jira, even if it is not directly implemented in this library
|
||||
|
||||
This package is not JIRA API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of JIRA have a look at [latest JIRA REST API documentation](https://docs.atlassian.com/jira/REST/latest/).
|
||||
This package is not Jira API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of Jira have a look at [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/).
|
||||
|
||||
## Requirements
|
||||
|
||||
* Go >= 1.8
|
||||
* JIRA v6.3.4 & v7.1.2.
|
||||
* Go >= 1.14
|
||||
* Jira v6.3.4 & v7.1.2.
|
||||
|
||||
Note that we also run our tests against 1.13, though only the last two versions
|
||||
of Go are officially supported.
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -52,7 +55,7 @@ go test -v ./...
|
|||
|
||||
Please have a look at the [GoDoc documentation](https://godoc.org/github.com/andygrunwald/go-jira) for a detailed API description.
|
||||
|
||||
The [latest JIRA REST API documentation](https://docs.atlassian.com/jira/REST/latest/) was the base document for this package.
|
||||
The [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/) was the base document for this package.
|
||||
|
||||
## Examples
|
||||
|
||||
|
@ -68,7 +71,7 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/andygrunwald/go-jira"
|
||||
jira "github.com/andygrunwald/go-jira"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -92,9 +95,11 @@ an `http.Client`. That client can then be passed into the `NewClient` function
|
|||
|
||||
For convenience, capability for basic and cookie-based authentication is included in the main library.
|
||||
|
||||
#### Basic auth example
|
||||
#### Token (Jira on Atlassian Cloud)
|
||||
|
||||
A more thorough, [runnable example](examples/basicauth/main.go) is provided in the examples directory. **It's worth noting that using passwords in basic auth is now deprecated and will be removed.** Jira gives you the ability to [create tokens now.](https://confluence.atlassian.com/cloud/api-tokens-938839638.html)
|
||||
Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://confluence.atlassian.com/cloud/api-tokens-938839638.html).
|
||||
|
||||
A more thorough, [runnable example](examples/basicauth/main.go) is provided in the examples directory.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
|
@ -111,14 +116,15 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
#### Authenticate with session cookie [DEPRECATED]
|
||||
#### Basic (self-hosted Jira)
|
||||
|
||||
JIRA [deprecated this authentication method.](https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth/) It's not longer available for use.
|
||||
Password-based API authentication works for self-hosted Jira **only**, and has been [deprecated for users of Atlassian Cloud](https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth/).
|
||||
|
||||
The above token authentication example may be used, substituting a user's password for a generated token.
|
||||
|
||||
#### Authenticate with OAuth
|
||||
|
||||
If you want to connect via OAuth to your JIRA Cloud instance checkout the [example of using OAuth authentication with JIRA in Go](https://gist.github.com/Lupus/edafe9a7c5c6b13407293d795442fe67) by [@Lupus](https://github.com/Lupus).
|
||||
If you want to connect via OAuth to your Jira Cloud instance checkout the [example of using OAuth authentication with Jira in Go](https://gist.github.com/Lupus/edafe9a7c5c6b13407293d795442fe67) by [@Lupus](https://github.com/Lupus).
|
||||
|
||||
For more details have a look at the [issue #56](https://github.com/andygrunwald/go-jira/issues/56).
|
||||
|
||||
|
@ -173,11 +179,62 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
### Change an issue status
|
||||
|
||||
This is how one can change an issue status. In this example, we change the issue from "To Do" to "In Progress."
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/andygrunwald/go-jira"
|
||||
)
|
||||
|
||||
func main() {
|
||||
base := "https://my.jira.com"
|
||||
tp := jira.BasicAuthTransport{
|
||||
Username: "username",
|
||||
Password: "token",
|
||||
}
|
||||
|
||||
jiraClient, err := jira.NewClient(tp.Client(), base)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
issue, _, _ := jiraClient.Issue.Get("FART-1", nil)
|
||||
currentStatus := issue.Fields.Status.Name
|
||||
fmt.Printf("Current status: %s\n", currentStatus)
|
||||
|
||||
var transitionID string
|
||||
possibleTransitions, _, _ := jiraClient.Issue.GetTransitions("FART-1")
|
||||
for _, v := range possibleTransitions {
|
||||
if v.Name == "In Progress" {
|
||||
transitionID = v.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
jiraClient.Issue.DoTransition("FART-1", transitionID)
|
||||
issue, _, _ = jiraClient.Issue.Get(testIssueID, nil)
|
||||
fmt.Printf("Status after transition: %+v\n", issue.Fields.Status.Name)
|
||||
}
|
||||
```
|
||||
### Get all the issues for JQL with Pagination
|
||||
Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL.
|
||||
This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL
|
||||
|
||||
please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/examples/pagination/main.go)
|
||||
|
||||
|
||||
|
||||
|
||||
### Call a not implemented API endpoint
|
||||
|
||||
Not all API endpoints of the JIRA API are implemented into *go-jira*.
|
||||
Not all API endpoints of the Jira API are implemented into *go-jira*.
|
||||
But you can call them anyway:
|
||||
Lets get all public projects of [Atlassian`s JIRA instance](https://jira.atlassian.com/).
|
||||
Lets get all public projects of [Atlassian`s Jira instance](https://jira.atlassian.com/).
|
||||
|
||||
```go
|
||||
package main
|
||||
|
@ -209,7 +266,7 @@ func main() {
|
|||
|
||||
// ...
|
||||
// BAM: Bamboo
|
||||
// BAMJ: Bamboo JIRA Plugin
|
||||
// BAMJ: Bamboo Jira Plugin
|
||||
// CLOV: Clover
|
||||
// CONF: Confluence
|
||||
// ...
|
||||
|
@ -218,7 +275,7 @@ func main() {
|
|||
|
||||
## Implementations
|
||||
|
||||
* [andygrunwald/jitic](https://github.com/andygrunwald/jitic) - The JIRA Ticket Checker
|
||||
* [andygrunwald/jitic](https://github.com/andygrunwald/jitic) - The Jira Ticket Checker
|
||||
|
||||
## Code structure
|
||||
|
||||
|
@ -226,7 +283,7 @@ The code structure of this package was inspired by [google/go-github](https://gi
|
|||
|
||||
There is one main part (the client).
|
||||
Based on this main client the other endpoints, like Issues or Authentication are extracted in services. E.g. `IssueService` or `AuthenticationService`.
|
||||
These services own a responsibility of the single endpoints / usecases of JIRA.
|
||||
These services own a responsibility of the single endpoints / usecases of Jira.
|
||||
|
||||
## Contribution
|
||||
|
||||
|
@ -258,7 +315,7 @@ You can read more about them at https://developer.atlassian.com/blog/2016/04/clo
|
|||
|
||||
## Releasing
|
||||
|
||||
Install `standard-version`
|
||||
Install [standard-version](https://github.com/conventional-changelog/standard-version)
|
||||
```bash
|
||||
npm i -g standard-version
|
||||
```
|
||||
|
|
89
vendor/github.com/andygrunwald/go-jira/authentication.go
generated
vendored
89
vendor/github.com/andygrunwald/go-jira/authentication.go
generated
vendored
|
@ -1,6 +1,7 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -14,9 +15,9 @@ const (
|
|||
authTypeSession = 2
|
||||
)
|
||||
|
||||
// AuthenticationService handles authentication for the JIRA instance / API.
|
||||
// AuthenticationService handles authentication for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#authentication
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#authentication
|
||||
type AuthenticationService struct {
|
||||
client *Client
|
||||
|
||||
|
@ -30,7 +31,7 @@ type AuthenticationService struct {
|
|||
password string
|
||||
}
|
||||
|
||||
// Session represents a Session JSON response by the JIRA API.
|
||||
// Session represents a Session JSON response by the Jira API.
|
||||
type Session struct {
|
||||
Self string `json:"self,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
|
@ -47,16 +48,16 @@ type Session struct {
|
|||
Cookies []*http.Cookie
|
||||
}
|
||||
|
||||
// AcquireSessionCookie creates a new session for a user in JIRA.
|
||||
// Once a session has been successfully created it can be used to access any of JIRA's remote APIs and also the web UI by passing the appropriate HTTP Cookie header.
|
||||
// AcquireSessionCookieWithContext creates a new session for a user in Jira.
|
||||
// Once a session has been successfully created it can be used to access any of Jira's remote APIs and also the web UI by passing the appropriate HTTP Cookie header.
|
||||
// The header will by automatically applied to every API request.
|
||||
// Note that it is generally preferrable to use HTTP BASIC authentication with the REST API.
|
||||
// However, this resource may be used to mimic the behaviour of JIRA's log-in page (e.g. to display log-in errors to a user).
|
||||
// However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user).
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
|
||||
//
|
||||
// Deprecated: Use CookieAuthTransport instead
|
||||
func (s *AuthenticationService) AcquireSessionCookie(username, password string) (bool, error) {
|
||||
func (s *AuthenticationService) AcquireSessionCookieWithContext(ctx context.Context, username, password string) (bool, error) {
|
||||
apiEndpoint := "rest/auth/1/session"
|
||||
body := struct {
|
||||
Username string `json:"username"`
|
||||
|
@ -66,7 +67,7 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
|
|||
password,
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, body)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -79,10 +80,10 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Auth at JIRA instance failed (HTTP(S) request). %s", err)
|
||||
return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). %s", err)
|
||||
}
|
||||
if resp != nil && resp.StatusCode != 200 {
|
||||
return false, fmt.Errorf("Auth at JIRA instance failed (HTTP(S) request). Status code: %d", resp.StatusCode)
|
||||
return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). Status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
s.client.session = session
|
||||
|
@ -91,7 +92,14 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
|
|||
return true, nil
|
||||
}
|
||||
|
||||
// SetBasicAuth sets username and password for the basic auth against the JIRA instance.
|
||||
// AcquireSessionCookie wraps AcquireSessionCookieWithContext using the background context.
|
||||
//
|
||||
// Deprecated: Use CookieAuthTransport instead
|
||||
func (s *AuthenticationService) AcquireSessionCookie(username, password string) (bool, error) {
|
||||
return s.AcquireSessionCookieWithContext(context.Background(), username, password)
|
||||
}
|
||||
|
||||
// SetBasicAuth sets username and password for the basic auth against the Jira instance.
|
||||
//
|
||||
// Deprecated: Use BasicAuthTransport instead
|
||||
func (s *AuthenticationService) SetBasicAuth(username, password string) {
|
||||
|
@ -100,7 +108,7 @@ func (s *AuthenticationService) SetBasicAuth(username, password string) {
|
|||
s.authType = authTypeBasic
|
||||
}
|
||||
|
||||
// Authenticated reports if the current Client has authentication details for JIRA
|
||||
// Authenticated reports if the current Client has authentication details for Jira
|
||||
func (s *AuthenticationService) Authenticated() bool {
|
||||
if s != nil {
|
||||
if s.authType == authTypeSession {
|
||||
|
@ -113,29 +121,30 @@ func (s *AuthenticationService) Authenticated() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Logout logs out the current user that has been authenticated and the session in the client is destroyed.
|
||||
// LogoutWithContext logs out the current user that has been authenticated and the session in the client is destroyed.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
|
||||
//
|
||||
// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the
|
||||
// client anymore
|
||||
func (s *AuthenticationService) Logout() error {
|
||||
func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error {
|
||||
if s.authType != authTypeSession || s.client.session == nil {
|
||||
return fmt.Errorf("no user is authenticated")
|
||||
}
|
||||
|
||||
apiEndpoint := "rest/auth/1/session"
|
||||
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Creating the request to log the user out failed : %s", err)
|
||||
return fmt.Errorf("creating the request to log the user out failed : %s", err)
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error sending the logout request: %s", err)
|
||||
return fmt.Errorf("error sending the logout request: %s", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 204 {
|
||||
return fmt.Errorf("The logout was unsuccessful with status %d", resp.StatusCode)
|
||||
return fmt.Errorf("the logout was unsuccessful with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// If logout successful, delete session
|
||||
|
@ -145,43 +154,55 @@ func (s *AuthenticationService) Logout() error {
|
|||
|
||||
}
|
||||
|
||||
// GetCurrentUser gets the details of the current user.
|
||||
// Logout wraps LogoutWithContext using the background context.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
|
||||
func (s *AuthenticationService) GetCurrentUser() (*Session, error) {
|
||||
// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the
|
||||
// client anymore
|
||||
func (s *AuthenticationService) Logout() error {
|
||||
return s.LogoutWithContext(context.Background())
|
||||
}
|
||||
|
||||
// GetCurrentUserWithContext gets the details of the current user.
|
||||
//
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
|
||||
func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) (*Session, error) {
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("AUthenticaiton Service is not instantiated")
|
||||
return nil, fmt.Errorf("authentication Service is not instantiated")
|
||||
}
|
||||
if s.authType != authTypeSession || s.client.session == nil {
|
||||
return nil, fmt.Errorf("No user is authenticated yet")
|
||||
return nil, fmt.Errorf("no user is authenticated yet")
|
||||
}
|
||||
|
||||
apiEndpoint := "rest/auth/1/session"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not create request for getting user info : %s", err)
|
||||
return nil, fmt.Errorf("could not create request for getting user info : %s", err)
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error sending request to get user info : %s", err)
|
||||
return nil, fmt.Errorf("error sending request to get user info : %s", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("Getting user info failed with status : %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("getting user info failed with status : %d", resp.StatusCode)
|
||||
}
|
||||
ret := new(Session)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't read body from the response : %s", err)
|
||||
return nil, fmt.Errorf("couldn't read body from the response : %s", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &ret)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not unmarshall received user info : %s", err)
|
||||
return nil, fmt.Errorf("could not unmarshall received user info : %s", err)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetCurrentUser wraps GetCurrentUserWithContext using the background context.
|
||||
func (s *AuthenticationService) GetCurrentUser() (*Session, error) {
|
||||
return s.GetCurrentUserWithContext(context.Background())
|
||||
}
|
||||
|
|
100
vendor/github.com/andygrunwald/go-jira/board.go
generated
vendored
100
vendor/github.com/andygrunwald/go-jira/board.go
generated
vendored
|
@ -1,14 +1,15 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BoardService handles Agile Boards for the JIRA instance / API.
|
||||
// BoardService handles Agile Boards for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/server/
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/server/
|
||||
type BoardService struct {
|
||||
client *Client
|
||||
}
|
||||
|
@ -22,7 +23,7 @@ type BoardsList struct {
|
|||
Values []Board `json:"values" structs:"values"`
|
||||
}
|
||||
|
||||
// Board represents a JIRA agile board
|
||||
// Board represents a Jira agile board
|
||||
type Board struct {
|
||||
ID int `json:"id,omitempty" structs:"id,omitempty"`
|
||||
Self string `json:"self,omitempty" structs:"self,omitempty"`
|
||||
|
@ -62,7 +63,7 @@ type SprintsList struct {
|
|||
Values []Sprint `json:"values" structs:"values"`
|
||||
}
|
||||
|
||||
// Sprint represents a sprint on JIRA agile board
|
||||
// Sprint represents a sprint on Jira agile board
|
||||
type Sprint struct {
|
||||
ID int `json:"id" structs:"id"`
|
||||
Name string `json:"name" structs:"name"`
|
||||
|
@ -116,6 +117,8 @@ type BoardConfigurationColumnConfig struct {
|
|||
type BoardConfigurationColumn struct {
|
||||
Name string `json:"name"`
|
||||
Status []BoardConfigurationColumnStatus `json:"statuses"`
|
||||
Min int `json:"min,omitempty"`
|
||||
Max int `json:"max,omitempty"`
|
||||
}
|
||||
|
||||
// BoardConfigurationColumnStatus represents a status in the column configuration
|
||||
|
@ -124,16 +127,16 @@ type BoardConfigurationColumnStatus struct {
|
|||
Self string `json:"self"`
|
||||
}
|
||||
|
||||
// GetAllBoards will returns all boards. This only includes boards that the user has permission to view.
|
||||
// GetAllBoardsWithContext will returns all boards. This only includes boards that the user has permission to view.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards
|
||||
func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards
|
||||
func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) {
|
||||
apiEndpoint := "rest/agile/1.0/board"
|
||||
url, err := addOptions(apiEndpoint, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := s.client.NewRequest("GET", url, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -148,13 +151,18 @@ func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Respon
|
|||
return boards, resp, err
|
||||
}
|
||||
|
||||
// GetBoard will returns the board for the given boardID.
|
||||
// GetAllBoards wraps GetAllBoardsWithContext using the background context.
|
||||
func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Response, error) {
|
||||
return s.GetAllBoardsWithContext(context.Background(), opt)
|
||||
}
|
||||
|
||||
// GetBoardWithContext will returns the board for the given boardID.
|
||||
// This board will only be returned if the user has permission to view it.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard
|
||||
func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard
|
||||
func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -169,17 +177,22 @@ func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) {
|
|||
return board, resp, nil
|
||||
}
|
||||
|
||||
// CreateBoard creates a new board. Board name, type and filter Id is required.
|
||||
// GetBoard wraps GetBoardWithContext using the background context.
|
||||
func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) {
|
||||
return s.GetBoardWithContext(context.Background(), boardID)
|
||||
}
|
||||
|
||||
// CreateBoardWithContext creates a new board. Board name, type and filter Id is required.
|
||||
// name - Must be less than 255 characters.
|
||||
// type - Valid values: scrum, kanban
|
||||
// filterId - Id of a filter that the user has permissions to view.
|
||||
// Note, if the user does not have the 'Create shared objects' permission and tries to create a shared board, a private
|
||||
// board will be created instead (remember that board sharing depends on the filter sharing).
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard
|
||||
func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard
|
||||
func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) (*Board, *Response, error) {
|
||||
apiEndpoint := "rest/agile/1.0/board"
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, board)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, board)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -194,12 +207,18 @@ func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) {
|
|||
return responseBoard, resp, nil
|
||||
}
|
||||
|
||||
// DeleteBoard will delete an agile board.
|
||||
// CreateBoard wraps CreateBoardWithContext using the background context.
|
||||
func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) {
|
||||
return s.CreateBoardWithContext(context.Background(), board)
|
||||
}
|
||||
|
||||
// DeleteBoardWithContext will delete an agile board.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard
|
||||
func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard
|
||||
// Caller must close resp.Body
|
||||
func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID)
|
||||
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -211,11 +230,17 @@ func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) {
|
|||
return nil, resp, err
|
||||
}
|
||||
|
||||
// GetAllSprints will return all sprints from a board, for a given board Id.
|
||||
// DeleteBoard wraps DeleteBoardWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) {
|
||||
return s.DeleteBoardWithContext(context.Background(), boardID)
|
||||
}
|
||||
|
||||
// GetAllSprintsWithContext will return all sprints from a board, for a given board Id.
|
||||
// This only includes sprints that the user has permission to view.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
|
||||
func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
|
||||
func (s *BoardService) GetAllSprintsWithContext(ctx context.Context, boardID string) ([]Sprint, *Response, error) {
|
||||
id, err := strconv.Atoi(boardID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -229,17 +254,22 @@ func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error
|
|||
return result.Values, response, nil
|
||||
}
|
||||
|
||||
// GetAllSprintsWithOptions will return sprints from a board, for a given board Id and filtering options
|
||||
// GetAllSprints wraps GetAllSprintsWithContext using the background context.
|
||||
func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error) {
|
||||
return s.GetAllSprintsWithContext(context.Background(), boardID)
|
||||
}
|
||||
|
||||
// GetAllSprintsWithOptionsWithContext will return sprints from a board, for a given board Id and filtering options
|
||||
// This only includes sprints that the user has permission to view.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
|
||||
func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
|
||||
func (s *BoardService) GetAllSprintsWithOptionsWithContext(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID)
|
||||
url, err := addOptions(apiEndpoint, options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := s.client.NewRequest("GET", url, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -253,12 +283,17 @@ func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSpri
|
|||
return result, resp, err
|
||||
}
|
||||
|
||||
// GetBoardConfiguration will return a board configuration for a given board Id
|
||||
// GetAllSprintsWithOptions wraps GetAllSprintsWithOptionsWithContext using the background context.
|
||||
func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) {
|
||||
return s.GetAllSprintsWithOptionsWithContext(context.Background(), boardID, options)
|
||||
}
|
||||
|
||||
// GetBoardConfigurationWithContext will return a board configuration for a given board Id
|
||||
// Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get
|
||||
func (s *BoardService) GetBoardConfiguration(boardID int) (*BoardConfiguration, *Response, error) {
|
||||
func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID)
|
||||
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -273,3 +308,8 @@ func (s *BoardService) GetBoardConfiguration(boardID int) (*BoardConfiguration,
|
|||
return result, resp, err
|
||||
|
||||
}
|
||||
|
||||
// GetBoardConfiguration wraps GetBoardConfigurationWithContext using the background context.
|
||||
func (s *BoardService) GetBoardConfiguration(boardID int) (*BoardConfiguration, *Response, error) {
|
||||
return s.GetBoardConfigurationWithContext(context.Background(), boardID)
|
||||
}
|
||||
|
|
20
vendor/github.com/andygrunwald/go-jira/component.go
generated
vendored
20
vendor/github.com/andygrunwald/go-jira/component.go
generated
vendored
|
@ -1,13 +1,14 @@
|
|||
package jira
|
||||
|
||||
// ComponentService handles components for the JIRA instance / API.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component
|
||||
import "context"
|
||||
|
||||
// ComponentService handles components for the Jira instance / API.//
|
||||
// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component
|
||||
type ComponentService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// CreateComponentOptions are passed to the ComponentService.Create function to create a new JIRA component
|
||||
// CreateComponentOptions are passed to the ComponentService.Create function to create a new Jira component
|
||||
type CreateComponentOptions struct {
|
||||
Name string `json:"name,omitempty" structs:"name,omitempty"`
|
||||
Description string `json:"description,omitempty" structs:"description,omitempty"`
|
||||
|
@ -19,10 +20,10 @@ type CreateComponentOptions struct {
|
|||
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"`
|
||||
}
|
||||
|
||||
// Create creates a new JIRA component based on the given options.
|
||||
func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComponent, *Response, error) {
|
||||
// CreateWithContext creates a new Jira component based on the given options.
|
||||
func (s *ComponentService) CreateWithContext(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/component"
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, options)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -36,3 +37,8 @@ func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComp
|
|||
|
||||
return component, resp, nil
|
||||
}
|
||||
|
||||
// Create wraps CreateWithContext using the background context.
|
||||
func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComponent, *Response, error) {
|
||||
return s.CreateWithContext(context.Background(), options)
|
||||
}
|
||||
|
|
72
vendor/github.com/andygrunwald/go-jira/customer.go
generated
vendored
Normal file
72
vendor/github.com/andygrunwald/go-jira/customer.go
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// CustomerService handles ServiceDesk customers for the Jira instance / API.
|
||||
type CustomerService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Customer represents a ServiceDesk customer.
|
||||
type Customer struct {
|
||||
AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"`
|
||||
Name string `json:"name,omitempty" structs:"name,omitempty"`
|
||||
Key string `json:"key,omitempty" structs:"key,omitempty"`
|
||||
EmailAddress string `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"`
|
||||
DisplayName string `json:"displayName,omitempty" structs:"displayName,omitempty"`
|
||||
Active *bool `json:"active,omitempty" structs:"active,omitempty"`
|
||||
TimeZone string `json:"timeZone,omitempty" structs:"timeZone,omitempty"`
|
||||
Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"`
|
||||
}
|
||||
|
||||
// CustomerListOptions is the query options for listing customers.
|
||||
type CustomerListOptions struct {
|
||||
Query string `url:"query,omitempty"`
|
||||
Start int `url:"start,omitempty"`
|
||||
Limit int `url:"limit,omitempty"`
|
||||
}
|
||||
|
||||
// CustomerList is a page of customers.
|
||||
type CustomerList struct {
|
||||
Values []Customer `json:"values,omitempty" structs:"values,omitempty"`
|
||||
Start int `json:"start,omitempty" structs:"start,omitempty"`
|
||||
Limit int `json:"limit,omitempty" structs:"limit,omitempty"`
|
||||
IsLast bool `json:"isLastPage,omitempty" structs:"isLastPage,omitempty"`
|
||||
Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"`
|
||||
}
|
||||
|
||||
// CreateWithContext creates a ServiceDesk customer.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-customer/#api-rest-servicedeskapi-customer-post
|
||||
func (c *CustomerService) CreateWithContext(ctx context.Context, email, displayName string) (*Customer, *Response, error) {
|
||||
const apiEndpoint = "rest/servicedeskapi/customer"
|
||||
|
||||
payload := struct {
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"displayName"`
|
||||
}{
|
||||
Email: email,
|
||||
DisplayName: displayName,
|
||||
}
|
||||
|
||||
req, err := c.client.NewRequestWithContext(ctx, http.MethodPost, apiEndpoint, payload)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
responseCustomer := new(Customer)
|
||||
resp, err := c.client.Do(req, responseCustomer)
|
||||
if err != nil {
|
||||
return nil, resp, NewJiraError(resp, err)
|
||||
}
|
||||
|
||||
return responseCustomer, resp, nil
|
||||
}
|
||||
|
||||
// Create wraps CreateWithContext using the background context.
|
||||
func (c *CustomerService) Create(email, displayName string) (*Customer, *Response, error) {
|
||||
return c.CreateWithContext(context.Background(), email, displayName)
|
||||
}
|
10
vendor/github.com/andygrunwald/go-jira/error.go
generated
vendored
10
vendor/github.com/andygrunwald/go-jira/error.go
generated
vendored
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Error message from JIRA
|
||||
// Error message from Jira
|
||||
// See https://docs.atlassian.com/jira/REST/cloud/#error-responses
|
||||
type Error struct {
|
||||
HTTPError error
|
||||
|
@ -34,13 +34,13 @@ func NewJiraError(resp *Response, httpError error) error {
|
|||
if strings.HasPrefix(contentType, "application/json") {
|
||||
err = json.Unmarshal(body, &jerr)
|
||||
if err != nil {
|
||||
httpError = errors.Wrap(errors.New("Could not parse JSON"), httpError.Error())
|
||||
httpError = errors.Wrap(errors.New("could not parse JSON"), httpError.Error())
|
||||
return errors.Wrap(err, httpError.Error())
|
||||
}
|
||||
} else {
|
||||
if httpError == nil {
|
||||
return fmt.Errorf("Got Response Status %s:%s", resp.Status, string(body))
|
||||
}
|
||||
if httpError == nil {
|
||||
return fmt.Errorf("got response status %s:%s", resp.Status, string(body))
|
||||
}
|
||||
return errors.Wrap(httpError, fmt.Sprintf("%s: %s", resp.Status, string(body)))
|
||||
}
|
||||
|
||||
|
|
30
vendor/github.com/andygrunwald/go-jira/field.go
generated
vendored
30
vendor/github.com/andygrunwald/go-jira/field.go
generated
vendored
|
@ -1,13 +1,15 @@
|
|||
package jira
|
||||
|
||||
// FieldService handles fields for the JIRA instance / API.
|
||||
import "context"
|
||||
|
||||
// FieldService handles fields for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Field
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Field
|
||||
type FieldService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Field represents a field of a JIRA issue.
|
||||
// Field represents a field of a Jira issue.
|
||||
type Field struct {
|
||||
ID string `json:"id,omitempty" structs:"id,omitempty"`
|
||||
Key string `json:"key,omitempty" structs:"key,omitempty"`
|
||||
|
@ -19,17 +21,22 @@ type Field struct {
|
|||
Schema FieldSchema `json:"schema,omitempty" structs:"schema,omitempty"`
|
||||
}
|
||||
|
||||
// FieldSchema represents a schema of a Jira field.
|
||||
// Documentation: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-fields/#api-rest-api-2-field-get
|
||||
type FieldSchema struct {
|
||||
Type string `json:"type,omitempty" structs:"type,omitempty"`
|
||||
System string `json:"system,omitempty" structs:"system,omitempty"`
|
||||
Type string `json:"type,omitempty" structs:"type,omitempty"`
|
||||
Items string `json:"items,omitempty" structs:"items,omitempty"`
|
||||
Custom string `json:"custom,omitempty" structs:"custom,omitempty"`
|
||||
System string `json:"system,omitempty" structs:"system,omitempty"`
|
||||
CustomID int64 `json:"customId,omitempty" structs:"customId,omitempty"`
|
||||
}
|
||||
|
||||
// GetList gets all fields from JIRA
|
||||
// GetListWithContext gets all fields from Jira
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get
|
||||
func (s *FieldService) GetList() ([]Field, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get
|
||||
func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/field"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -41,3 +48,8 @@ func (s *FieldService) GetList() ([]Field, *Response, error) {
|
|||
}
|
||||
return fieldList, resp, nil
|
||||
}
|
||||
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (s *FieldService) GetList() ([]Field, *Response, error) {
|
||||
return s.GetListWithContext(context.Background())
|
||||
}
|
||||
|
|
79
vendor/github.com/andygrunwald/go-jira/filter.go
generated
vendored
79
vendor/github.com/andygrunwald/go-jira/filter.go
generated
vendored
|
@ -1,11 +1,15 @@
|
|||
package jira
|
||||
|
||||
import "github.com/google/go-querystring/query"
|
||||
import "fmt"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
// FilterService handles fields for the JIRA instance / API.
|
||||
"github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
// FilterService handles fields for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Filter
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Filter
|
||||
type FilterService struct {
|
||||
client *Client
|
||||
}
|
||||
|
@ -116,23 +120,21 @@ type FilterSearchOptions struct {
|
|||
Expand string `url:"expand,omitempty"`
|
||||
}
|
||||
|
||||
// GetList retrieves all filters from Jira
|
||||
func (fs *FilterService) GetList() ([]*Filter, *Response, error) {
|
||||
// GetListWithContext retrieves all filters from Jira
|
||||
func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Response, error) {
|
||||
|
||||
options := &GetQueryOptions{}
|
||||
apiEndpoint := "rest/api/2/filter"
|
||||
req, err := fs.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if options != nil {
|
||||
q, err := query.Values(options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
q, err := query.Values(options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
filters := []*Filter{}
|
||||
resp, err := fs.client.Do(req, &filters)
|
||||
|
@ -143,10 +145,15 @@ func (fs *FilterService) GetList() ([]*Filter, *Response, error) {
|
|||
return filters, resp, err
|
||||
}
|
||||
|
||||
// GetFavouriteList retrieves the user's favourited filters from Jira
|
||||
func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) {
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (fs *FilterService) GetList() ([]*Filter, *Response, error) {
|
||||
return fs.GetListWithContext(context.Background())
|
||||
}
|
||||
|
||||
// GetFavouriteListWithContext retrieves the user's favourited filters from Jira
|
||||
func (fs *FilterService) GetFavouriteListWithContext(ctx context.Context) ([]*Filter, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/filter/favourite"
|
||||
req, err := fs.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -159,10 +166,15 @@ func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) {
|
|||
return filters, resp, err
|
||||
}
|
||||
|
||||
// Get retrieves a single Filter from Jira
|
||||
func (fs *FilterService) Get(filterID int) (*Filter, *Response, error) {
|
||||
// GetFavouriteList wraps GetFavouriteListWithContext using the background context.
|
||||
func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) {
|
||||
return fs.GetFavouriteListWithContext(context.Background())
|
||||
}
|
||||
|
||||
// GetWithContext retrieves a single Filter from Jira
|
||||
func (fs *FilterService) GetWithContext(ctx context.Context, filterID int) (*Filter, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID)
|
||||
req, err := fs.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -176,16 +188,21 @@ func (fs *FilterService) Get(filterID int) (*Filter, *Response, error) {
|
|||
return filter, resp, err
|
||||
}
|
||||
|
||||
// GetMyFilters retrieves the my Filters.
|
||||
// Get wraps GetWithContext using the background context.
|
||||
func (fs *FilterService) Get(filterID int) (*Filter, *Response, error) {
|
||||
return fs.GetWithContext(context.Background(), filterID)
|
||||
}
|
||||
|
||||
// GetMyFiltersWithContext retrieves the my Filters.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-my-get
|
||||
func (fs *FilterService) GetMyFilters(opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) {
|
||||
func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) {
|
||||
apiEndpoint := "rest/api/3/filter/my"
|
||||
url, err := addOptions(apiEndpoint, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := fs.client.NewRequest("GET", url, nil)
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -199,16 +216,21 @@ func (fs *FilterService) GetMyFilters(opts *GetMyFiltersQueryOptions) ([]*Filter
|
|||
return filters, resp, nil
|
||||
}
|
||||
|
||||
// Search will search for filter according to the search options
|
||||
// GetMyFilters wraps GetMyFiltersWithContext using the background context.
|
||||
func (fs *FilterService) GetMyFilters(opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) {
|
||||
return fs.GetMyFiltersWithContext(context.Background(), opts)
|
||||
}
|
||||
|
||||
// SearchWithContext will search for filter according to the search options
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get
|
||||
func (fs *FilterService) Search(opt *FilterSearchOptions) (*FiltersList, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get
|
||||
func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) {
|
||||
apiEndpoint := "rest/api/3/filter/search"
|
||||
url, err := addOptions(apiEndpoint, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := fs.client.NewRequest("GET", url, nil)
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -222,3 +244,8 @@ func (fs *FilterService) Search(opt *FilterSearchOptions) (*FiltersList, *Respon
|
|||
|
||||
return filters, resp, err
|
||||
}
|
||||
|
||||
// Search wraps SearchWithContext using the background context.
|
||||
func (fs *FilterService) Search(opt *FilterSearchOptions) (*FiltersList, *Response, error) {
|
||||
return fs.SearchWithContext(context.Background(), opt)
|
||||
}
|
||||
|
|
61
vendor/github.com/andygrunwald/go-jira/group.go
generated
vendored
61
vendor/github.com/andygrunwald/go-jira/group.go
generated
vendored
|
@ -1,13 +1,14 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// GroupService handles Groups for the JIRA instance / API.
|
||||
// GroupService handles Groups for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group
|
||||
type GroupService struct {
|
||||
client *Client
|
||||
}
|
||||
|
@ -21,7 +22,7 @@ type groupMembersResult struct {
|
|||
Members []GroupMember `json:"values"`
|
||||
}
|
||||
|
||||
// Group represents a JIRA group
|
||||
// Group represents a Jira group
|
||||
type Group struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
|
@ -58,16 +59,16 @@ type GroupSearchOptions struct {
|
|||
IncludeInactiveUsers bool
|
||||
}
|
||||
|
||||
// Get returns a paginated list of users who are members of the specified group and its subgroups.
|
||||
// GetWithContext returns a paginated list of users who are members of the specified group and its subgroups.
|
||||
// Users in the page are ordered by user names.
|
||||
// User of this resource is required to have sysadmin or admin permissions.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
|
||||
//
|
||||
// WARNING: This API only returns the first page of group members
|
||||
func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) {
|
||||
func (s *GroupService) GetWithContext(ctx context.Context, name string) ([]GroupMember, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name))
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -81,12 +82,17 @@ func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) {
|
|||
return group.Members, resp, nil
|
||||
}
|
||||
|
||||
// GetWithOptions returns a paginated list of members of the specified group and its subgroups.
|
||||
// Get wraps GetWithContext using the background context.
|
||||
func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) {
|
||||
return s.GetWithContext(context.Background(), name)
|
||||
}
|
||||
|
||||
// GetWithOptionsWithContext returns a paginated list of members of the specified group and its subgroups.
|
||||
// Users in the page are ordered by user names.
|
||||
// User of this resource is required to have sysadmin or admin permissions.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
|
||||
func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
|
||||
func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) {
|
||||
var apiEndpoint string
|
||||
if options == nil {
|
||||
apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name))
|
||||
|
@ -99,7 +105,7 @@ func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions)
|
|||
options.IncludeInactiveUsers,
|
||||
)
|
||||
}
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -112,16 +118,21 @@ func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions)
|
|||
return group.Members, resp, nil
|
||||
}
|
||||
|
||||
// Add adds user to group
|
||||
// GetWithOptions wraps GetWithOptionsWithContext using the background context.
|
||||
func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) {
|
||||
return s.GetWithOptionsWithContext(context.Background(), name, options)
|
||||
}
|
||||
|
||||
// AddWithContext adds user to group
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup
|
||||
func (s *GroupService) Add(groupname string, username string) (*Group, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup
|
||||
func (s *GroupService) AddWithContext(ctx context.Context, groupname string, username string) (*Group, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname)
|
||||
var user struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
user.Name = username
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, &user)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, &user)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -136,12 +147,18 @@ func (s *GroupService) Add(groupname string, username string) (*Group, *Response
|
|||
return responseGroup, resp, nil
|
||||
}
|
||||
|
||||
// Remove removes user from group
|
||||
// Add wraps AddWithContext using the background context.
|
||||
func (s *GroupService) Add(groupname string, username string) (*Group, *Response, error) {
|
||||
return s.AddWithContext(context.Background(), groupname, username)
|
||||
}
|
||||
|
||||
// RemoveWithContext removes user from group
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup
|
||||
func (s *GroupService) Remove(groupname string, username string) (*Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup
|
||||
// Caller must close resp.Body
|
||||
func (s *GroupService) RemoveWithContext(ctx context.Context, groupname string, username string) (*Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username)
|
||||
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -154,3 +171,9 @@ func (s *GroupService) Remove(groupname string, username string) (*Response, err
|
|||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Remove wraps RemoveWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *GroupService) Remove(groupname string, username string) (*Response, error) {
|
||||
return s.RemoveWithContext(context.Background(), groupname, username)
|
||||
}
|
||||
|
|
629
vendor/github.com/andygrunwald/go-jira/issue.go
generated
vendored
629
vendor/github.com/andygrunwald/go-jira/issue.go
generated
vendored
File diff suppressed because it is too large
Load diff
78
vendor/github.com/andygrunwald/go-jira/issuelinktype.go
generated
vendored
78
vendor/github.com/andygrunwald/go-jira/issuelinktype.go
generated
vendored
|
@ -1,24 +1,25 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// IssueLinkTypeService handles issue link types for the JIRA instance / API.
|
||||
// IssueLinkTypeService handles issue link types for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Issue-link-types
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Issue-link-types
|
||||
type IssueLinkTypeService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// GetList gets all of the issue link types from JIRA.
|
||||
// GetListWithContext gets all of the issue link types from Jira.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get
|
||||
func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get
|
||||
func (s *IssueLinkTypeService) GetListWithContext(ctx context.Context) ([]IssueLinkType, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/issueLinkType"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -31,12 +32,17 @@ func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) {
|
|||
return linkTypeList, resp, nil
|
||||
}
|
||||
|
||||
// Get gets info of a specific issue link type from JIRA.
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) {
|
||||
return s.GetListWithContext(context.Background())
|
||||
}
|
||||
|
||||
// GetWithContext gets info of a specific issue link type from Jira.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get
|
||||
func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get
|
||||
func (s *IssueLinkTypeService) GetWithContext(ctx context.Context, ID string) (*IssueLinkType, *Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID)
|
||||
req, err := s.client.NewRequest("GET", apiEndPoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -49,12 +55,17 @@ func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error)
|
|||
return linkType, resp, nil
|
||||
}
|
||||
|
||||
// Create creates an issue link type in JIRA.
|
||||
// Get wraps GetWithContext using the background context.
|
||||
func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error) {
|
||||
return s.GetWithContext(context.Background(), ID)
|
||||
}
|
||||
|
||||
// CreateWithContext creates an issue link type in Jira.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post
|
||||
func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post
|
||||
func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
|
||||
apiEndpoint := "/rest/api/2/issueLinkType"
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, linkType)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, linkType)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -68,23 +79,29 @@ func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType,
|
|||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("Could not read the returned data")
|
||||
e := fmt.Errorf("could not read the returned data")
|
||||
return nil, resp, NewJiraError(resp, e)
|
||||
}
|
||||
err = json.Unmarshal(data, responseLinkType)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("Could no unmarshal the data into struct")
|
||||
e := fmt.Errorf("could no unmarshal the data into struct")
|
||||
return nil, resp, NewJiraError(resp, e)
|
||||
}
|
||||
return linkType, resp, nil
|
||||
}
|
||||
|
||||
// Update updates an issue link type. The issue is found by key.
|
||||
// Create wraps CreateWithContext using the background context.
|
||||
func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
|
||||
return s.CreateWithContext(context.Background(), linkType)
|
||||
}
|
||||
|
||||
// UpdateWithContext updates an issue link type. The issue is found by key.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-put
|
||||
func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-put
|
||||
// Caller must close resp.Body
|
||||
func (s *IssueLinkTypeService) UpdateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID)
|
||||
req, err := s.client.NewRequest("PUT", apiEndpoint, linkType)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, linkType)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -96,12 +113,19 @@ func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType,
|
|||
return &ret, resp, nil
|
||||
}
|
||||
|
||||
// Delete deletes an issue link type based on provided ID.
|
||||
// Update wraps UpdateWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
|
||||
return s.UpdateWithContext(context.Background(), linkType)
|
||||
}
|
||||
|
||||
// DeleteWithContext deletes an issue link type based on provided ID.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-delete
|
||||
func (s *IssueLinkTypeService) Delete(ID string) (*Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-delete
|
||||
// Caller must close resp.Body
|
||||
func (s *IssueLinkTypeService) DeleteWithContext(ctx context.Context, ID string) (*Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID)
|
||||
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -109,3 +133,9 @@ func (s *IssueLinkTypeService) Delete(ID string) (*Response, error) {
|
|||
resp, err := s.client.Do(req, nil)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Delete wraps DeleteWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *IssueLinkTypeService) Delete(ID string) (*Response, error) {
|
||||
return s.DeleteWithContext(context.Background(), ID)
|
||||
}
|
||||
|
|
137
vendor/github.com/andygrunwald/go-jira/jira.go
generated
vendored
137
vendor/github.com/andygrunwald/go-jira/jira.go
generated
vendored
|
@ -2,6 +2,7 @@ package jira
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
@ -14,7 +15,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
jwt "github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -25,7 +26,7 @@ type httpClient interface {
|
|||
Do(request *http.Request) (response *http.Response, err error)
|
||||
}
|
||||
|
||||
// A Client manages communication with the JIRA API.
|
||||
// A Client manages communication with the Jira API.
|
||||
type Client struct {
|
||||
// HTTP client used to communicate with the API.
|
||||
client httpClient
|
||||
|
@ -36,7 +37,7 @@ type Client struct {
|
|||
// Session storage if the user authenticates with a Session cookie
|
||||
session *Session
|
||||
|
||||
// Services used for talking to different parts of the JIRA API.
|
||||
// Services used for talking to different parts of the Jira API.
|
||||
Authentication *AuthenticationService
|
||||
Issue *IssueService
|
||||
Project *ProjectService
|
||||
|
@ -55,15 +56,19 @@ type Client struct {
|
|||
PermissionScheme *PermissionSchemeService
|
||||
Status *StatusService
|
||||
IssueLinkType *IssueLinkTypeService
|
||||
Organization *OrganizationService
|
||||
ServiceDesk *ServiceDeskService
|
||||
Customer *CustomerService
|
||||
Request *RequestService
|
||||
}
|
||||
|
||||
// NewClient returns a new JIRA API client.
|
||||
// NewClient returns a new Jira API client.
|
||||
// If a nil httpClient is provided, http.DefaultClient will be used.
|
||||
// To use API methods which require authentication you can follow the preferred solution and
|
||||
// provide an http.Client that will perform the authentication for you with OAuth and HTTP Basic (such as that provided by the golang.org/x/oauth2 library).
|
||||
// As an alternative you can use Session Cookie based authentication provided by this package as well.
|
||||
// See https://docs.atlassian.com/jira/REST/latest/#authentication
|
||||
// baseURL is the HTTP endpoint of your JIRA instance and should always be specified with a trailing slash.
|
||||
// baseURL is the HTTP endpoint of your Jira instance and should always be specified with a trailing slash.
|
||||
func NewClient(httpClient httpClient, baseURL string) (*Client, error) {
|
||||
if httpClient == nil {
|
||||
httpClient = http.DefaultClient
|
||||
|
@ -101,14 +106,18 @@ func NewClient(httpClient httpClient, baseURL string) (*Client, error) {
|
|||
c.PermissionScheme = &PermissionSchemeService{client: c}
|
||||
c.Status = &StatusService{client: c}
|
||||
c.IssueLinkType = &IssueLinkTypeService{client: c}
|
||||
c.Organization = &OrganizationService{client: c}
|
||||
c.ServiceDesk = &ServiceDeskService{client: c}
|
||||
c.Customer = &CustomerService{client: c}
|
||||
c.Request = &RequestService{client: c}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewRawRequest creates an API request.
|
||||
// NewRawRequestWithContext 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.
|
||||
// Allows using an optional native io.Reader for sourcing the request body.
|
||||
func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
|
||||
func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr string, body io.Reader) (*http.Request, error) {
|
||||
rel, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -118,7 +127,7 @@ func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Req
|
|||
|
||||
u := c.baseURL.ResolveReference(rel)
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), body)
|
||||
req, err := newRequestWithContext(ctx, method, u.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -143,10 +152,15 @@ func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Req
|
|||
return req, nil
|
||||
}
|
||||
|
||||
// NewRequest creates an API request.
|
||||
// NewRawRequest wraps NewRawRequestWithContext using the background context.
|
||||
func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
|
||||
return c.NewRawRequestWithContext(context.Background(), method, urlStr, body)
|
||||
}
|
||||
|
||||
// NewRequestWithContext 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.
|
||||
// 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) {
|
||||
func (c *Client) NewRequestWithContext(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) {
|
||||
rel, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -165,7 +179,7 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
|
|||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), buf)
|
||||
req, err := newRequestWithContext(ctx, method, u.String(), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -190,6 +204,11 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
|
|||
return req, nil
|
||||
}
|
||||
|
||||
// NewRequest wraps NewRequestWithContext using the background context.
|
||||
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
|
||||
return c.NewRequestWithContext(context.Background(), method, urlStr, body)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
@ -212,10 +231,10 @@ func addOptions(s string, opt interface{}) (string, error) {
|
|||
return u.String(), nil
|
||||
}
|
||||
|
||||
// NewMultiPartRequest creates an API request including a multi-part file.
|
||||
// NewMultiPartRequestWithContext creates an API request including a multi-part file.
|
||||
// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client.
|
||||
// If specified, the value pointed to by buf is a multipart form.
|
||||
func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (*http.Request, error) {
|
||||
func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, urlStr string, buf *bytes.Buffer) (*http.Request, error) {
|
||||
rel, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -225,7 +244,7 @@ func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (
|
|||
|
||||
u := c.baseURL.ResolveReference(rel)
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), buf)
|
||||
req, err := newRequestWithContext(ctx, method, u.String(), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -251,6 +270,11 @@ func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (
|
|||
return req, nil
|
||||
}
|
||||
|
||||
// NewMultiPartRequest wraps NewMultiPartRequestWithContext using the background context.
|
||||
func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (*http.Request, error) {
|
||||
return c.NewMultiPartRequestWithContext(context.Background(), method, urlStr, buf)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
|
||||
|
@ -279,13 +303,13 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
|
|||
// 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.
|
||||
// The caller is responsible to analyze the response body.
|
||||
// The body can contain JSON (if the error is intended) or xml (sometimes JIRA just failes).
|
||||
// The body can contain JSON (if the error is intended) or xml (sometimes Jira just failes).
|
||||
func CheckResponse(r *http.Response) error {
|
||||
if c := r.StatusCode; 200 <= c && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := fmt.Errorf("Request failed. Please analyze the request body for more details. Status code: %d", r.StatusCode)
|
||||
err := fmt.Errorf("request failed. Please analyze the request body for more details. Status code: %d", r.StatusCode)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -295,7 +319,7 @@ func (c *Client) GetBaseURL() url.URL {
|
|||
return *c.baseURL
|
||||
}
|
||||
|
||||
// Response represents JIRA API response. It wraps http.Response returned from
|
||||
// Response represents Jira API response. It wraps http.Response returned from
|
||||
// API and provides information about paging.
|
||||
type Response struct {
|
||||
*http.Response
|
||||
|
@ -324,7 +348,6 @@ func (r *Response) populatePageValues(v interface{}) {
|
|||
r.MaxResults = value.MaxResults
|
||||
r.Total = value.Total
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BasicAuthTransport is an http.RoundTripper that authenticates all requests
|
||||
|
@ -363,13 +386,84 @@ func (t *BasicAuthTransport) transport() http.RoundTripper {
|
|||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// BearerAuthTransport is a http.RoundTripper that authenticates all requests
|
||||
// using Jira's bearer (oauth 2.0 (3lo)) based authentication.
|
||||
type BearerAuthTransport struct {
|
||||
Token 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. We just add the
|
||||
// bearer token and return the RoundTripper for this transport type.
|
||||
func (t *BearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req2 := cloneRequest(req) // per RoundTripper contract
|
||||
|
||||
req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token))
|
||||
return t.transport().RoundTrip(req2)
|
||||
}
|
||||
|
||||
// Client returns an *http.Client that makes requests that are authenticated
|
||||
// using HTTP Basic Authentication. This is a nice little bit of sugar
|
||||
// so we can just get the client instead of creating the client in the calling code.
|
||||
// If it's necessary to send more information on client init, the calling code can
|
||||
// always skip this and set the transport itself.
|
||||
func (t *BearerAuthTransport) Client() *http.Client {
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
func (t *BearerAuthTransport) transport() http.RoundTripper {
|
||||
if t.Transport != nil {
|
||||
return t.Transport
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// PATAuthTransport is an http.RoundTripper that authenticates all requests
|
||||
// using the Personal Access Token specified.
|
||||
// See here for more info: https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html
|
||||
type PATAuthTransport struct {
|
||||
// Token is the key that was provided by Jira when creating the Personal Access Token.
|
||||
Token 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. We just add the
|
||||
// basic auth and return the RoundTripper for this transport type.
|
||||
func (t *PATAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req2 := cloneRequest(req) // per RoundTripper contract
|
||||
req2.Header.Set("Authorization", "Bearer "+t.Token)
|
||||
return t.transport().RoundTrip(req2)
|
||||
}
|
||||
|
||||
// Client returns an *http.Client that makes requests that are authenticated
|
||||
// using HTTP Basic Authentication. This is a nice little bit of sugar
|
||||
// so we can just get the client instead of creating the client in the calling code.
|
||||
// If it's necessary to send more information on client init, the calling code can
|
||||
// always skip this and set the transport itself.
|
||||
func (t *PATAuthTransport) Client() *http.Client {
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
func (t *PATAuthTransport) transport() http.RoundTripper {
|
||||
if t.Transport != nil {
|
||||
return t.Transport
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// CookieAuthTransport is an http.RoundTripper that authenticates all requests
|
||||
// using Jira's cookie-based authentication.
|
||||
//
|
||||
// Note that it is generally preferable to use HTTP BASIC authentication with the REST API.
|
||||
// However, this resource may be used to mimic the behaviour of JIRA's log-in page (e.g. to display log-in errors to a user).
|
||||
// However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user).
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
|
||||
type CookieAuthTransport struct {
|
||||
Username string
|
||||
Password string
|
||||
|
@ -425,6 +519,7 @@ func (t *CookieAuthTransport) setSessionObject() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
t.SessionObject = resp.Cookies()
|
||||
return nil
|
||||
|
@ -464,7 +559,7 @@ func (t *CookieAuthTransport) transport() http.RoundTripper {
|
|||
//
|
||||
// NOTE: this form of auth should be used by add-ons installed from the Atlassian marketplace.
|
||||
//
|
||||
// JIRA docs: https://developer.atlassian.com/cloud/jira/platform/understanding-jwt
|
||||
// Jira docs: https://developer.atlassian.com/cloud/jira/platform/understanding-jwt
|
||||
// Examples in other languages:
|
||||
// https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb
|
||||
// https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py
|
||||
|
|
62
vendor/github.com/andygrunwald/go-jira/metaissue.go
generated
vendored
62
vendor/github.com/andygrunwald/go-jira/metaissue.go
generated
vendored
|
@ -1,6 +1,7 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
@ -14,6 +15,11 @@ type CreateMetaInfo struct {
|
|||
Projects []*MetaProject `json:"projects,omitempty"`
|
||||
}
|
||||
|
||||
// EditMetaInfo contains information about fields and their attributed to edit a ticket.
|
||||
type EditMetaInfo struct {
|
||||
Fields tcontainer.MarshalMap `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// MetaProject is the meta information about a project returned from createmeta api
|
||||
type MetaProject struct {
|
||||
Expand string `json:"expand,omitempty"`
|
||||
|
@ -42,16 +48,21 @@ type MetaIssueType struct {
|
|||
Fields tcontainer.MarshalMap `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// GetCreateMeta makes the api call to get the meta information required to create a ticket
|
||||
func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Response, error) {
|
||||
return s.GetCreateMetaWithOptions(&GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"})
|
||||
// GetCreateMetaWithContext makes the api call to get the meta information required to create a ticket
|
||||
func (s *IssueService) GetCreateMetaWithContext(ctx context.Context, projectkeys string) (*CreateMetaInfo, *Response, error) {
|
||||
return s.GetCreateMetaWithOptionsWithContext(ctx, &GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"})
|
||||
}
|
||||
|
||||
// GetCreateMetaWithOptions makes the api call to get the meta information without requiring to have a projectKey
|
||||
func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*CreateMetaInfo, *Response, error) {
|
||||
// GetCreateMeta wraps GetCreateMetaWithContext using the background context.
|
||||
func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Response, error) {
|
||||
return s.GetCreateMetaWithContext(context.Background(), projectkeys)
|
||||
}
|
||||
|
||||
// GetCreateMetaWithOptionsWithContext makes the api call to get the meta information without requiring to have a projectKey
|
||||
func (s *IssueService) GetCreateMetaWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/issue/createmeta"
|
||||
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -73,11 +84,40 @@ func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*Crea
|
|||
return meta, resp, nil
|
||||
}
|
||||
|
||||
// GetCreateMetaWithOptions wraps GetCreateMetaWithOptionsWithContext using the background context.
|
||||
func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*CreateMetaInfo, *Response, error) {
|
||||
return s.GetCreateMetaWithOptionsWithContext(context.Background(), options)
|
||||
}
|
||||
|
||||
// GetEditMetaWithContext makes the api call to get the edit meta information for an issue
|
||||
func (s *IssueService) GetEditMetaWithContext(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
meta := new(EditMetaInfo)
|
||||
resp, err := s.client.Do(req, meta)
|
||||
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return meta, resp, nil
|
||||
}
|
||||
|
||||
// GetEditMeta wraps GetEditMetaWithContext using the background context.
|
||||
func (s *IssueService) GetEditMeta(issue *Issue) (*EditMetaInfo, *Response, error) {
|
||||
return s.GetEditMetaWithContext(context.Background(), issue)
|
||||
}
|
||||
|
||||
// GetProjectWithName returns a project with "name" from the meta information received. If not found, this returns nil.
|
||||
// The comparison of the name is case insensitive.
|
||||
func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject {
|
||||
for _, m := range m.Projects {
|
||||
if strings.ToLower(m.Name) == strings.ToLower(name) {
|
||||
if strings.EqualFold(m.Name, name) {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +128,7 @@ func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject {
|
|||
// The comparison of the name is case insensitive.
|
||||
func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject {
|
||||
for _, m := range m.Projects {
|
||||
if strings.ToLower(m.Key) == strings.ToLower(key) {
|
||||
if strings.EqualFold(m.Key, key) {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +139,7 @@ func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject {
|
|||
// The comparison of the name is case insensitive
|
||||
func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType {
|
||||
for _, m := range p.IssueTypes {
|
||||
if strings.ToLower(m.Name) == strings.ToLower(name) {
|
||||
if strings.EqualFold(m.Name, name) {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +215,7 @@ func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (boo
|
|||
for name := range mandatory {
|
||||
requiredFields = append(requiredFields, name)
|
||||
}
|
||||
return false, fmt.Errorf("Required field not found in provided jira.fields. Required are: %#v", requiredFields)
|
||||
return false, fmt.Errorf("required field not found in provided jira.fields. Required are: %#v", requiredFields)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +226,7 @@ func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (boo
|
|||
for name := range all {
|
||||
availableFields = append(availableFields, name)
|
||||
}
|
||||
return false, fmt.Errorf("Fields in jira.fields are not available in jira. Available are: %#v", availableFields)
|
||||
return false, fmt.Errorf("fields in jira.fields are not available in jira. Available are: %#v", availableFields)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
397
vendor/github.com/andygrunwald/go-jira/organization.go
generated
vendored
Normal file
397
vendor/github.com/andygrunwald/go-jira/organization.go
generated
vendored
Normal file
|
@ -0,0 +1,397 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// OrganizationService handles Organizations for the Jira instance / API.
|
||||
//
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/
|
||||
type OrganizationService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// OrganizationCreationDTO is DTO for creat organization API
|
||||
type OrganizationCreationDTO struct {
|
||||
Name string `json:"name,omitempty" structs:"name,omitempty"`
|
||||
}
|
||||
|
||||
// SelfLink Stores REST API URL to the organization.
|
||||
type SelfLink struct {
|
||||
Self string `json:"self,omitempty" structs:"self,omitempty"`
|
||||
}
|
||||
|
||||
// Organization contains Organization data
|
||||
type Organization struct {
|
||||
ID string `json:"id,omitempty" structs:"id,omitempty"`
|
||||
Name string `json:"name,omitempty" structs:"name,omitempty"`
|
||||
Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"`
|
||||
}
|
||||
|
||||
// OrganizationUsersDTO contains organization user ids
|
||||
type OrganizationUsersDTO struct {
|
||||
AccountIds []string `json:"accountIds,omitempty" structs:"accountIds,omitempty"`
|
||||
}
|
||||
|
||||
// PagedDTO is response of a paged list
|
||||
type PagedDTO struct {
|
||||
Size int `json:"size,omitempty" structs:"size,omitempty"`
|
||||
Start int `json:"start,omitempty" structs:"start,omitempty"`
|
||||
Limit int `limit:"size,omitempty" structs:"limit,omitempty"`
|
||||
IsLastPage bool `json:"isLastPage,omitempty" structs:"isLastPage,omitempty"`
|
||||
Values []interface{} `values:"isLastPage,omitempty" structs:"values,omitempty"`
|
||||
Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"`
|
||||
}
|
||||
|
||||
// PropertyKey contains Property key details.
|
||||
type PropertyKey struct {
|
||||
Self string `json:"self,omitempty" structs:"self,omitempty"`
|
||||
Key string `json:"key,omitempty" structs:"key,omitempty"`
|
||||
}
|
||||
|
||||
// PropertyKeys contains an array of PropertyKey
|
||||
type PropertyKeys struct {
|
||||
Keys []PropertyKey `json:"keys,omitempty" structs:"keys,omitempty"`
|
||||
}
|
||||
|
||||
// GetAllOrganizationsWithContext returns a list of organizations in
|
||||
// the Jira Service Management instance.
|
||||
// Use this method when you want to present a list
|
||||
// of organizations or want to locate an organization
|
||||
// by name.
|
||||
//
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-group-organization
|
||||
func (s *OrganizationService) GetAllOrganizationsWithContext(ctx context.Context, start int, limit int, accountID string) (*PagedDTO, *Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization?start=%d&limit=%d", start, limit)
|
||||
if accountID != "" {
|
||||
apiEndPoint += fmt.Sprintf("&accountId=%s", accountID)
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
v := new(PagedDTO)
|
||||
resp, err := s.client.Do(req, v)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
|
||||
return v, resp, nil
|
||||
}
|
||||
|
||||
// GetAllOrganizations wraps GetAllOrganizationsWithContext using the background context.
|
||||
func (s *OrganizationService) GetAllOrganizations(start int, limit int, accountID string) (*PagedDTO, *Response, error) {
|
||||
return s.GetAllOrganizationsWithContext(context.Background(), start, limit, accountID)
|
||||
}
|
||||
|
||||
// CreateOrganizationWithContext creates an organization by
|
||||
// passing the name of the organization.
|
||||
//
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-post
|
||||
func (s *OrganizationService) CreateOrganizationWithContext(ctx context.Context, name string) (*Organization, *Response, error) {
|
||||
apiEndPoint := "rest/servicedeskapi/organization"
|
||||
|
||||
organization := OrganizationCreationDTO{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
o := new(Organization)
|
||||
resp, err := s.client.Do(req, &o)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
|
||||
return o, resp, nil
|
||||
}
|
||||
|
||||
// CreateOrganization wraps CreateOrganizationWithContext using the background context.
|
||||
func (s *OrganizationService) CreateOrganization(name string) (*Organization, *Response, error) {
|
||||
return s.CreateOrganizationWithContext(context.Background(), name)
|
||||
}
|
||||
|
||||
// GetOrganizationWithContext returns details of an
|
||||
// organization. Use this method to get organization
|
||||
// details whenever your application component is
|
||||
// passed an organization ID but needs to display
|
||||
// other organization details.
|
||||
//
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-get
|
||||
func (s *OrganizationService) GetOrganizationWithContext(ctx context.Context, organizationID int) (*Organization, *Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
o := new(Organization)
|
||||
resp, err := s.client.Do(req, &o)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
|
||||
return o, resp, nil
|
||||
}
|
||||
|
||||
// GetOrganization wraps GetOrganizationWithContext using the background context.
|
||||
func (s *OrganizationService) GetOrganization(organizationID int) (*Organization, *Response, error) {
|
||||
return s.GetOrganizationWithContext(context.Background(), organizationID)
|
||||
}
|
||||
|
||||
// DeleteOrganizationWithContext deletes an organization. Note that
|
||||
// the organization is deleted regardless
|
||||
// of other associations it may have.
|
||||
// For example, associations with service desks.
|
||||
//
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-delete
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) DeleteOrganizationWithContext(ctx context.Context, organizationID int) (*Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return resp, jerr
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteOrganization wraps DeleteOrganizationWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) DeleteOrganization(organizationID int) (*Response, error) {
|
||||
return s.DeleteOrganizationWithContext(context.Background(), organizationID)
|
||||
}
|
||||
|
||||
// GetPropertiesKeysWithContext returns the keys of
|
||||
// all properties for an organization. Use this resource
|
||||
// when you need to find out what additional properties
|
||||
// items have been added to an organization.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-get
|
||||
func (s *OrganizationService) GetPropertiesKeysWithContext(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pk := new(PropertyKeys)
|
||||
resp, err := s.client.Do(req, &pk)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
|
||||
return pk, resp, nil
|
||||
}
|
||||
|
||||
// GetPropertiesKeys wraps GetPropertiesKeysWithContext using the background context.
|
||||
func (s *OrganizationService) GetPropertiesKeys(organizationID int) (*PropertyKeys, *Response, error) {
|
||||
return s.GetPropertiesKeysWithContext(context.Background(), organizationID)
|
||||
}
|
||||
|
||||
// GetPropertyWithContext returns the value of a property
|
||||
// from an organization. Use this method to obtain the JSON
|
||||
// content for an organization's property.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-get
|
||||
func (s *OrganizationService) GetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ep := new(EntityProperty)
|
||||
resp, err := s.client.Do(req, &ep)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
|
||||
return ep, resp, nil
|
||||
}
|
||||
|
||||
// GetProperty wraps GetPropertyWithContext using the background context.
|
||||
func (s *OrganizationService) GetProperty(organizationID int, propertyKey string) (*EntityProperty, *Response, error) {
|
||||
return s.GetPropertyWithContext(context.Background(), organizationID, propertyKey)
|
||||
}
|
||||
|
||||
// SetPropertyWithContext sets the value of a
|
||||
// property for an organization. Use this
|
||||
// resource to store custom data against an organization.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-put
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) SetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndPoint, nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return resp, jerr
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SetProperty wraps SetPropertyWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) SetProperty(organizationID int, propertyKey string) (*Response, error) {
|
||||
return s.SetPropertyWithContext(context.Background(), organizationID, propertyKey)
|
||||
}
|
||||
|
||||
// DeletePropertyWithContext removes a property from an organization.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-delete
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) DeletePropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return resp, jerr
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteProperty wraps DeletePropertyWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) DeleteProperty(organizationID int, propertyKey string) (*Response, error) {
|
||||
return s.DeletePropertyWithContext(context.Background(), organizationID, propertyKey)
|
||||
}
|
||||
|
||||
// GetUsersWithContext returns all the users
|
||||
// associated with an organization. Use this
|
||||
// method where you want to provide a list of
|
||||
// users for an organization or determine if
|
||||
// a user is associated with an organization.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-get
|
||||
func (s *OrganizationService) GetUsersWithContext(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
users := new(PagedDTO)
|
||||
resp, err := s.client.Do(req, &users)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
|
||||
return users, resp, nil
|
||||
}
|
||||
|
||||
// GetUsers wraps GetUsersWithContext using the background context.
|
||||
func (s *OrganizationService) GetUsers(organizationID int, start int, limit int) (*PagedDTO, *Response, error) {
|
||||
return s.GetUsersWithContext(context.Background(), organizationID, start, limit)
|
||||
}
|
||||
|
||||
// AddUsersWithContext adds users to an organization.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-post
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) AddUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, users)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return resp, jerr
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// AddUsers wraps AddUsersWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) AddUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) {
|
||||
return s.AddUsersWithContext(context.Background(), organizationID, users)
|
||||
}
|
||||
|
||||
// RemoveUsersWithContext removes users from an organization.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-delete
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) RemoveUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return resp, jerr
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// RemoveUsers wraps RemoveUsersWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *OrganizationService) RemoveUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) {
|
||||
return s.RemoveUsersWithContext(context.Background(), organizationID, users)
|
||||
}
|
37
vendor/github.com/andygrunwald/go-jira/permissionscheme.go
generated
vendored
37
vendor/github.com/andygrunwald/go-jira/permissionscheme.go
generated
vendored
|
@ -1,10 +1,13 @@
|
|||
package jira
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// PermissionSchemeService handles permissionschemes for the JIRA instance / API.
|
||||
// PermissionSchemeService handles permissionschemes for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Permissionscheme
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Permissionscheme
|
||||
type PermissionSchemeService struct {
|
||||
client *Client
|
||||
}
|
||||
|
@ -25,12 +28,12 @@ type Holder struct {
|
|||
Expand string `json:"expand" structs:"expand"`
|
||||
}
|
||||
|
||||
// GetList returns a list of all permission schemes
|
||||
// GetListWithContext returns a list of all permission schemes
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get
|
||||
func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get
|
||||
func (s *PermissionSchemeService) GetListWithContext(ctx context.Context) (*PermissionSchemes, *Response, error) {
|
||||
apiEndpoint := "/rest/api/3/permissionscheme"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -45,12 +48,17 @@ func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, erro
|
|||
return pss, resp, nil
|
||||
}
|
||||
|
||||
// Get returns a full representation of the permission scheme for the schemeID
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, error) {
|
||||
return s.GetListWithContext(context.Background())
|
||||
}
|
||||
|
||||
// GetWithContext returns a full representation of the permission scheme for the schemeID
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get
|
||||
func (s *PermissionSchemeService) Get(schemeID int) (*PermissionScheme, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get
|
||||
func (s *PermissionSchemeService) GetWithContext(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -62,8 +70,13 @@ func (s *PermissionSchemeService) Get(schemeID int) (*PermissionScheme, *Respons
|
|||
return nil, resp, jerr
|
||||
}
|
||||
if ps.Self == "" {
|
||||
return nil, resp, fmt.Errorf("No permissionscheme with ID %d found", schemeID)
|
||||
return nil, resp, fmt.Errorf("no permissionscheme with ID %d found", schemeID)
|
||||
}
|
||||
|
||||
return ps, resp, nil
|
||||
}
|
||||
|
||||
// Get wraps GetWithContext using the background context.
|
||||
func (s *PermissionSchemeService) Get(schemeID int) (*PermissionScheme, *Response, error) {
|
||||
return s.GetWithContext(context.Background(), schemeID)
|
||||
}
|
||||
|
|
21
vendor/github.com/andygrunwald/go-jira/priority.go
generated
vendored
21
vendor/github.com/andygrunwald/go-jira/priority.go
generated
vendored
|
@ -1,13 +1,15 @@
|
|||
package jira
|
||||
|
||||
// PriorityService handles priorities for the JIRA instance / API.
|
||||
import "context"
|
||||
|
||||
// PriorityService handles priorities for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Priority
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Priority
|
||||
type PriorityService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Priority represents a priority of a JIRA issue.
|
||||
// Priority represents a priority of a Jira issue.
|
||||
// Typical types are "Normal", "Moderate", "Urgent", ...
|
||||
type Priority struct {
|
||||
Self string `json:"self,omitempty" structs:"self,omitempty"`
|
||||
|
@ -18,12 +20,12 @@ type Priority struct {
|
|||
Description string `json:"description,omitempty" structs:"description,omitempty"`
|
||||
}
|
||||
|
||||
// GetList gets all priorities from JIRA
|
||||
// GetListWithContext gets all priorities from Jira
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get
|
||||
func (s *PriorityService) GetList() ([]Priority, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get
|
||||
func (s *PriorityService) GetListWithContext(ctx context.Context) ([]Priority, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/priority"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -35,3 +37,8 @@ func (s *PriorityService) GetList() ([]Priority, *Response, error) {
|
|||
}
|
||||
return priorityList, resp, nil
|
||||
}
|
||||
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (s *PriorityService) GetList() ([]Priority, *Response, error) {
|
||||
return s.GetListWithContext(context.Background())
|
||||
}
|
||||
|
|
63
vendor/github.com/andygrunwald/go-jira/project.go
generated
vendored
63
vendor/github.com/andygrunwald/go-jira/project.go
generated
vendored
|
@ -1,14 +1,15 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
// ProjectService handles projects for the JIRA instance / API.
|
||||
// ProjectService handles projects for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project
|
||||
type ProjectService struct {
|
||||
client *Client
|
||||
}
|
||||
|
@ -34,7 +35,7 @@ type ProjectCategory struct {
|
|||
Description string `json:"description" structs:"description,omitempty"`
|
||||
}
|
||||
|
||||
// Project represents a JIRA Project.
|
||||
// Project represents a Jira Project.
|
||||
type Project struct {
|
||||
Expand string `json:"expand,omitempty" structs:"expand,omitempty"`
|
||||
Self string `json:"self,omitempty" structs:"self,omitempty"`
|
||||
|
@ -80,20 +81,25 @@ type PermissionScheme struct {
|
|||
Permissions []Permission `json:"permissions" structs:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
// GetList gets all projects form JIRA
|
||||
// GetListWithContext gets all projects form Jira
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
|
||||
func (s *ProjectService) GetList() (*ProjectList, *Response, error) {
|
||||
return s.ListWithOptions(&GetQueryOptions{})
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
|
||||
func (s *ProjectService) GetListWithContext(ctx context.Context) (*ProjectList, *Response, error) {
|
||||
return s.ListWithOptionsWithContext(ctx, &GetQueryOptions{})
|
||||
}
|
||||
|
||||
// ListWithOptions gets all projects form JIRA with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (s *ProjectService) GetList() (*ProjectList, *Response, error) {
|
||||
return s.GetListWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ListWithOptionsWithContext gets all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get
|
||||
// a list of all projects and their supported issuetypes
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
|
||||
func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
|
||||
func (s *ProjectService) ListWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/project"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -116,14 +122,19 @@ func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList
|
|||
return projectList, resp, nil
|
||||
}
|
||||
|
||||
// Get returns a full representation of the project for the given issue key.
|
||||
// JIRA will attempt to identify the project by the projectIdOrKey path parameter.
|
||||
// ListWithOptions wraps ListWithOptionsWithContext using the background context.
|
||||
func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList, *Response, error) {
|
||||
return s.ListWithOptionsWithContext(context.Background(), options)
|
||||
}
|
||||
|
||||
// GetWithContext returns a full representation of the project for the given issue key.
|
||||
// Jira will attempt to identify the project by the projectIdOrKey path parameter.
|
||||
// This can be an project id, or an project key.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
|
||||
func (s *ProjectService) Get(projectID string) (*Project, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
|
||||
func (s *ProjectService) GetWithContext(ctx context.Context, projectID string) (*Project, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -138,14 +149,19 @@ func (s *ProjectService) Get(projectID string) (*Project, *Response, error) {
|
|||
return project, resp, nil
|
||||
}
|
||||
|
||||
// GetPermissionScheme returns a full representation of the permission scheme for the project
|
||||
// JIRA will attempt to identify the project by the projectIdOrKey path parameter.
|
||||
// Get wraps GetWithContext using the background context.
|
||||
func (s *ProjectService) Get(projectID string) (*Project, *Response, error) {
|
||||
return s.GetWithContext(context.Background(), projectID)
|
||||
}
|
||||
|
||||
// GetPermissionSchemeWithContext returns a full representation of the permission scheme for the project
|
||||
// Jira will attempt to identify the project by the projectIdOrKey path parameter.
|
||||
// This can be an project id, or an project key.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
|
||||
func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionScheme, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
|
||||
func (s *ProjectService) GetPermissionSchemeWithContext(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -159,3 +175,8 @@ func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionSchem
|
|||
|
||||
return ps, resp, nil
|
||||
}
|
||||
|
||||
// GetPermissionScheme wraps GetPermissionSchemeWithContext using the background context.
|
||||
func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionScheme, *Response, error) {
|
||||
return s.GetPermissionSchemeWithContext(context.Background(), projectID)
|
||||
}
|
||||
|
|
123
vendor/github.com/andygrunwald/go-jira/request.go
generated
vendored
Normal file
123
vendor/github.com/andygrunwald/go-jira/request.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// RequestService handles ServiceDesk customer requests for the Jira instance / API.
|
||||
type RequestService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Request represents a ServiceDesk customer request.
|
||||
type Request struct {
|
||||
IssueID string `json:"issueId,omitempty" structs:"issueId,omitempty"`
|
||||
IssueKey string `json:"issueKey,omitempty" structs:"issueKey,omitempty"`
|
||||
TypeID string `json:"requestTypeId,omitempty" structs:"requestTypeId,omitempty"`
|
||||
ServiceDeskID string `json:"serviceDeskId,omitempty" structs:"serviceDeskId,omitempty"`
|
||||
Reporter *Customer `json:"reporter,omitempty" structs:"reporter,omitempty"`
|
||||
FieldValues []RequestFieldValue `json:"requestFieldValues,omitempty" structs:"requestFieldValues,omitempty"`
|
||||
Status *RequestStatus `json:"currentStatus,omitempty" structs:"currentStatus,omitempty"`
|
||||
Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"`
|
||||
Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"`
|
||||
}
|
||||
|
||||
// RequestFieldValue is a request field.
|
||||
type RequestFieldValue struct {
|
||||
FieldID string `json:"fieldId,omitempty" structs:"fieldId,omitempty"`
|
||||
Label string `json:"label,omitempty" structs:"label,omitempty"`
|
||||
Value string `json:"value,omitempty" structs:"value,omitempty"`
|
||||
}
|
||||
|
||||
// RequestDate is the date format used in requests.
|
||||
type RequestDate struct {
|
||||
ISO8601 string `json:"iso8601,omitempty" structs:"iso8601,omitempty"`
|
||||
Jira string `json:"jira,omitempty" structs:"jira,omitempty"`
|
||||
Friendly string `json:"friendly,omitempty" structs:"friendly,omitempty"`
|
||||
Epoch int64 `json:"epoch,omitempty" structs:"epoch,omitempty"`
|
||||
}
|
||||
|
||||
// RequestStatus is the status for a request.
|
||||
type RequestStatus struct {
|
||||
Status string
|
||||
Category string
|
||||
Date RequestDate
|
||||
}
|
||||
|
||||
// RequestComment is a comment for a request.
|
||||
type RequestComment struct {
|
||||
ID string `json:"id,omitempty" structs:"id,omitempty"`
|
||||
Body string `json:"body,omitempty" structs:"body,omitempty"`
|
||||
Public bool `json:"public" structs:"public"`
|
||||
Author *Customer `json:"author,omitempty" structs:"author,omitempty"`
|
||||
Created *RequestDate `json:"created,omitempty" structs:"created,omitempty"`
|
||||
Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"`
|
||||
Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"`
|
||||
}
|
||||
|
||||
// CreateWithContext creates a new request.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-post
|
||||
func (r *RequestService) CreateWithContext(ctx context.Context, requester string, participants []string, request *Request) (*Request, *Response, error) {
|
||||
apiEndpoint := "rest/servicedeskapi/request"
|
||||
|
||||
payload := struct {
|
||||
*Request
|
||||
FieldValues map[string]string `json:"requestFieldValues,omitempty"`
|
||||
Requester string `json:"raiseOnBehalfOf,omitempty"`
|
||||
Participants []string `json:"requestParticipants,omitempty"`
|
||||
}{
|
||||
Request: request,
|
||||
FieldValues: make(map[string]string),
|
||||
Requester: requester,
|
||||
Participants: participants,
|
||||
}
|
||||
|
||||
for _, field := range request.FieldValues {
|
||||
payload.FieldValues[field.FieldID] = field.Value
|
||||
}
|
||||
|
||||
req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
responseRequest := new(Request)
|
||||
resp, err := r.client.Do(req, responseRequest)
|
||||
if err != nil {
|
||||
return nil, resp, NewJiraError(resp, err)
|
||||
}
|
||||
|
||||
return responseRequest, resp, nil
|
||||
}
|
||||
|
||||
// Create wraps CreateWithContext using the background context.
|
||||
func (r *RequestService) Create(requester string, participants []string, request *Request) (*Request, *Response, error) {
|
||||
return r.CreateWithContext(context.Background(), requester, participants, request)
|
||||
}
|
||||
|
||||
// CreateCommentWithContext creates a comment on a request.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-comment-post
|
||||
func (r *RequestService) CreateCommentWithContext(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey)
|
||||
|
||||
req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, comment)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
responseComment := new(RequestComment)
|
||||
resp, err := r.client.Do(req, responseComment)
|
||||
if err != nil {
|
||||
return nil, resp, NewJiraError(resp, err)
|
||||
}
|
||||
|
||||
return responseComment, resp, nil
|
||||
}
|
||||
|
||||
// CreateComment wraps CreateCommentWithContext using the background context.
|
||||
func (r *RequestService) CreateComment(issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) {
|
||||
return r.CreateCommentWithContext(context.Background(), issueIDOrKey, comment)
|
||||
}
|
24
vendor/github.com/andygrunwald/go-jira/request_context.go
generated
vendored
Normal file
24
vendor/github.com/andygrunwald/go-jira/request_context.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
// This file provides glue to use Context in `http.Request` with
|
||||
// Go version 1.13 and higher.
|
||||
|
||||
// The function `http.NewRequestWithContext` has been added in Go 1.13.
|
||||
// Before the release 1.13, to use Context we need creat `http.Request`
|
||||
// then use the method `WithContext` to create a new `http.Request`
|
||||
// with Context from the existing `http.Request`.
|
||||
//
|
||||
// Doc: https://golang.org/doc/go1.13#net/http
|
||||
|
||||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func newRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) {
|
||||
return http.NewRequestWithContext(ctx, method, url, body)
|
||||
}
|
29
vendor/github.com/andygrunwald/go-jira/request_legacy.go
generated
vendored
Normal file
29
vendor/github.com/andygrunwald/go-jira/request_legacy.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
//go:build !go1.13
|
||||
// +build !go1.13
|
||||
|
||||
// This file provides glue to use Context in `http.Request` with
|
||||
// Go version before 1.13.
|
||||
|
||||
// The function `http.NewRequestWithContext` has been added in Go 1.13.
|
||||
// Before the release 1.13, to use Context we need creat `http.Request`
|
||||
// then use the method `WithContext` to create a new `http.Request`
|
||||
// with Context from the existing `http.Request`.
|
||||
//
|
||||
// Doc: https://golang.org/doc/go1.13#net/http
|
||||
|
||||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func newRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) {
|
||||
r, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.WithContext(ctx), nil
|
||||
}
|
21
vendor/github.com/andygrunwald/go-jira/resolution.go
generated
vendored
21
vendor/github.com/andygrunwald/go-jira/resolution.go
generated
vendored
|
@ -1,13 +1,15 @@
|
|||
package jira
|
||||
|
||||
// ResolutionService handles resolutions for the JIRA instance / API.
|
||||
import "context"
|
||||
|
||||
// ResolutionService handles resolutions for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Resolution
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Resolution
|
||||
type ResolutionService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Resolution represents a resolution of a JIRA issue.
|
||||
// Resolution represents a resolution of a Jira issue.
|
||||
// Typical types are "Fixed", "Suspended", "Won't Fix", ...
|
||||
type Resolution struct {
|
||||
Self string `json:"self" structs:"self"`
|
||||
|
@ -16,12 +18,12 @@ type Resolution struct {
|
|||
Name string `json:"name" structs:"name"`
|
||||
}
|
||||
|
||||
// GetList gets all resolutions from JIRA
|
||||
// GetListWithContext gets all resolutions from Jira
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get
|
||||
func (s *ResolutionService) GetList() ([]Resolution, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get
|
||||
func (s *ResolutionService) GetListWithContext(ctx context.Context) ([]Resolution, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/resolution"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -33,3 +35,8 @@ func (s *ResolutionService) GetList() ([]Resolution, *Response, error) {
|
|||
}
|
||||
return resolutionList, resp, nil
|
||||
}
|
||||
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (s *ResolutionService) GetList() ([]Resolution, *Response, error) {
|
||||
return s.GetListWithContext(context.Background())
|
||||
}
|
||||
|
|
37
vendor/github.com/andygrunwald/go-jira/role.go
generated
vendored
37
vendor/github.com/andygrunwald/go-jira/role.go
generated
vendored
|
@ -1,17 +1,18 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// RoleService handles roles for the JIRA instance / API.
|
||||
// RoleService handles roles for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Role
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Role
|
||||
type RoleService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Role represents a JIRA product role
|
||||
// Role represents a Jira product role
|
||||
type Role struct {
|
||||
Self string `json:"self" structs:"self"`
|
||||
Name string `json:"name" structs:"name"`
|
||||
|
@ -20,7 +21,7 @@ type Role struct {
|
|||
Actors []*Actor `json:"actors" structs:"actors"`
|
||||
}
|
||||
|
||||
// Actor represents a JIRA actor
|
||||
// Actor represents a Jira actor
|
||||
type Actor struct {
|
||||
ID int `json:"id" structs:"id"`
|
||||
DisplayName string `json:"displayName" structs:"displayName"`
|
||||
|
@ -35,12 +36,12 @@ type ActorUser struct {
|
|||
AccountID string `json:"accountId" structs:"accountId"`
|
||||
}
|
||||
|
||||
// GetList returns a list of all available project roles
|
||||
// GetListWithContext returns a list of all available project roles
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get
|
||||
func (s *RoleService) GetList() (*[]Role, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get
|
||||
func (s *RoleService) GetListWithContext(ctx context.Context) (*[]Role, *Response, error) {
|
||||
apiEndpoint := "rest/api/3/role"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -53,12 +54,17 @@ func (s *RoleService) GetList() (*[]Role, *Response, error) {
|
|||
return roles, resp, err
|
||||
}
|
||||
|
||||
// Get retreives a single Role from Jira
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (s *RoleService) GetList() (*[]Role, *Response, error) {
|
||||
return s.GetListWithContext(context.Background())
|
||||
}
|
||||
|
||||
// GetWithContext retreives a single Role from Jira
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get
|
||||
func (s *RoleService) Get(roleID int) (*Role, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get
|
||||
func (s *RoleService) GetWithContext(ctx context.Context, roleID int) (*Role, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -69,8 +75,13 @@ func (s *RoleService) Get(roleID int) (*Role, *Response, error) {
|
|||
return nil, resp, jerr
|
||||
}
|
||||
if role.Self == "" {
|
||||
return nil, resp, fmt.Errorf("No role with ID %d found", roleID)
|
||||
return nil, resp, fmt.Errorf("no role with ID %d found", roleID)
|
||||
}
|
||||
|
||||
return role, resp, err
|
||||
}
|
||||
|
||||
// Get wraps GetWithContext using the background context.
|
||||
func (s *RoleService) Get(roleID int) (*Role, *Response, error) {
|
||||
return s.GetWithContext(context.Background(), roleID)
|
||||
}
|
||||
|
|
227
vendor/github.com/andygrunwald/go-jira/servicedesk.go
generated
vendored
Normal file
227
vendor/github.com/andygrunwald/go-jira/servicedesk.go
generated
vendored
Normal file
|
@ -0,0 +1,227 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
// ServiceDeskService handles ServiceDesk for the Jira instance / API.
|
||||
type ServiceDeskService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// ServiceDeskOrganizationDTO is a DTO for ServiceDesk organizations
|
||||
type ServiceDeskOrganizationDTO struct {
|
||||
OrganizationID int `json:"organizationId,omitempty" structs:"organizationId,omitempty"`
|
||||
}
|
||||
|
||||
// GetOrganizationsWithContext returns a list of
|
||||
// all organizations associated with a service desk.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-get
|
||||
func (s *ServiceDeskService) GetOrganizationsWithContext(ctx context.Context, serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization?start=%d&limit=%d", serviceDeskID, start, limit)
|
||||
if accountID != "" {
|
||||
apiEndPoint += fmt.Sprintf("&accountId=%s", accountID)
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
orgs := new(PagedDTO)
|
||||
resp, err := s.client.Do(req, &orgs)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
|
||||
return orgs, resp, nil
|
||||
}
|
||||
|
||||
// GetOrganizations wraps GetOrganizationsWithContext using the background context.
|
||||
func (s *ServiceDeskService) GetOrganizations(serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) {
|
||||
return s.GetOrganizationsWithContext(context.Background(), serviceDeskID, start, limit, accountID)
|
||||
}
|
||||
|
||||
// AddOrganizationWithContext adds an organization to
|
||||
// a service desk. If the organization ID is already
|
||||
// associated with the service desk, no change is made
|
||||
// and the resource returns a 204 success code.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-post
|
||||
// Caller must close resp.Body
|
||||
func (s *ServiceDeskService) AddOrganizationWithContext(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID)
|
||||
|
||||
organization := ServiceDeskOrganizationDTO{
|
||||
OrganizationID: organizationID,
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return resp, jerr
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// AddOrganization wraps AddOrganizationWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *ServiceDeskService) AddOrganization(serviceDeskID interface{}, organizationID int) (*Response, error) {
|
||||
return s.AddOrganizationWithContext(context.Background(), serviceDeskID, organizationID)
|
||||
}
|
||||
|
||||
// RemoveOrganizationWithContext removes an organization
|
||||
// from a service desk. If the organization ID does not
|
||||
// match an organization associated with the service desk,
|
||||
// no change is made and the resource returns a 204 success code.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-delete
|
||||
// Caller must close resp.Body
|
||||
func (s *ServiceDeskService) RemoveOrganizationWithContext(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) {
|
||||
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID)
|
||||
|
||||
organization := ServiceDeskOrganizationDTO{
|
||||
OrganizationID: organizationID,
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, organization)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return resp, jerr
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// RemoveOrganization wraps RemoveOrganizationWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *ServiceDeskService) RemoveOrganization(serviceDeskID interface{}, organizationID int) (*Response, error) {
|
||||
return s.RemoveOrganizationWithContext(context.Background(), serviceDeskID, organizationID)
|
||||
}
|
||||
|
||||
// AddCustomersWithContext adds customers to the given service desk.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-post
|
||||
func (s *ServiceDeskService) AddCustomersWithContext(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID)
|
||||
|
||||
payload := struct {
|
||||
AccountIDs []string `json:"accountIds"`
|
||||
}{
|
||||
AccountIDs: acountIDs,
|
||||
}
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return resp, NewJiraError(resp, err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
_, _ = io.Copy(ioutil.Discard, resp.Body)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// AddCustomers wraps AddCustomersWithContext using the background context.
|
||||
func (s *ServiceDeskService) AddCustomers(serviceDeskID interface{}, acountIDs ...string) (*Response, error) {
|
||||
return s.AddCustomersWithContext(context.Background(), serviceDeskID, acountIDs...)
|
||||
}
|
||||
|
||||
// RemoveCustomersWithContext removes customers to the given service desk.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-delete
|
||||
func (s *ServiceDeskService) RemoveCustomersWithContext(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID)
|
||||
|
||||
payload := struct {
|
||||
AccountIDs []string `json:"accountIDs"`
|
||||
}{
|
||||
AccountIDs: acountIDs,
|
||||
}
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return resp, NewJiraError(resp, err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
_, _ = io.Copy(ioutil.Discard, resp.Body)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// RemoveCustomers wraps RemoveCustomersWithContext using the background context.
|
||||
func (s *ServiceDeskService) RemoveCustomers(serviceDeskID interface{}, acountIDs ...string) (*Response, error) {
|
||||
return s.RemoveCustomersWithContext(context.Background(), serviceDeskID, acountIDs...)
|
||||
}
|
||||
|
||||
// ListCustomersWithContext lists customers for a ServiceDesk.
|
||||
//
|
||||
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get
|
||||
func (s *ServiceDeskService) ListCustomersWithContext(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// this is an experiemntal endpoint
|
||||
req.Header.Set("X-ExperimentalApi", "opt-in")
|
||||
|
||||
if options != nil {
|
||||
q, err := query.Values(options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return nil, resp, NewJiraError(resp, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
customerList := new(CustomerList)
|
||||
if err := json.NewDecoder(resp.Body).Decode(customerList); err != nil {
|
||||
return nil, resp, fmt.Errorf("could not unmarshall the data into struct")
|
||||
}
|
||||
|
||||
return customerList, resp, nil
|
||||
}
|
||||
|
||||
// ListCustomers wraps ListCustomersWithContext using the background context.
|
||||
func (s *ServiceDeskService) ListCustomers(serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) {
|
||||
return s.ListCustomersWithContext(context.Background(), serviceDeskID, options)
|
||||
}
|
48
vendor/github.com/andygrunwald/go-jira/sprint.go
generated
vendored
48
vendor/github.com/andygrunwald/go-jira/sprint.go
generated
vendored
|
@ -1,12 +1,13 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
// SprintService handles sprints in JIRA Agile API.
|
||||
// SprintService handles sprints in Jira Agile API.
|
||||
// See https://docs.atlassian.com/jira-software/REST/cloud/
|
||||
type SprintService struct {
|
||||
client *Client
|
||||
|
@ -22,17 +23,18 @@ type IssuesInSprintResult struct {
|
|||
Issues []Issue `json:"issues"`
|
||||
}
|
||||
|
||||
// MoveIssuesToSprint moves issues to a sprint, for a given sprint Id.
|
||||
// MoveIssuesToSprintWithContext moves issues to a sprint, for a given sprint Id.
|
||||
// Issues can only be moved to open or active sprints.
|
||||
// The maximum number of issues that can be moved in one operation is 50.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint
|
||||
func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint
|
||||
// Caller must close resp.Body
|
||||
func (s *SprintService) MoveIssuesToSprintWithContext(ctx context.Context, sprintID int, issueIDs []string) (*Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID)
|
||||
|
||||
payload := IssuesWrapper{Issues: issueIDs}
|
||||
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, payload)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -45,15 +47,21 @@ func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Re
|
|||
return resp, err
|
||||
}
|
||||
|
||||
// GetIssuesForSprint returns all issues in a sprint, for a given sprint Id.
|
||||
// MoveIssuesToSprint wraps MoveIssuesToSprintWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Response, error) {
|
||||
return s.MoveIssuesToSprintWithContext(context.Background(), sprintID, issueIDs)
|
||||
}
|
||||
|
||||
// GetIssuesForSprintWithContext returns all issues in a sprint, for a given sprint Id.
|
||||
// This only includes issues that the user has permission to view.
|
||||
// By default, the returned issues are ordered by rank.
|
||||
//
|
||||
// JIRA API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint
|
||||
func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, error) {
|
||||
// Jira API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint
|
||||
func (s *SprintService) GetIssuesForSprintWithContext(ctx context.Context, sprintID int) ([]Issue, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID)
|
||||
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -68,20 +76,25 @@ func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, er
|
|||
return result.Issues, resp, err
|
||||
}
|
||||
|
||||
// GetIssue returns a full representation of the issue for the given issue key.
|
||||
// JIRA will attempt to identify the issue by the issueIdOrKey path parameter.
|
||||
// GetIssuesForSprint wraps GetIssuesForSprintWithContext using the background context.
|
||||
func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, error) {
|
||||
return s.GetIssuesForSprintWithContext(context.Background(), sprintID)
|
||||
}
|
||||
|
||||
// GetIssueWithContext returns a full representation of the issue for the given issue key.
|
||||
// Jira will attempt to identify the issue by the issueIdOrKey path parameter.
|
||||
// This can be an issue id, or an issue key.
|
||||
// If the issue cannot be found via an exact match, JIRA will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved.
|
||||
// If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved.
|
||||
//
|
||||
// The given options will be appended to the query string
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue
|
||||
//
|
||||
// TODO: create agile service for holding all agile apis' implementation
|
||||
func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Issue, *Response, error) {
|
||||
func (s *SprintService) GetIssueWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID)
|
||||
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -105,3 +118,8 @@ func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Iss
|
|||
|
||||
return issue, resp, nil
|
||||
}
|
||||
|
||||
// GetIssue wraps GetIssueWithContext using the background context.
|
||||
func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Issue, *Response, error) {
|
||||
return s.GetIssueWithContext(context.Background(), issueID, options)
|
||||
}
|
||||
|
|
23
vendor/github.com/andygrunwald/go-jira/status.go
generated
vendored
23
vendor/github.com/andygrunwald/go-jira/status.go
generated
vendored
|
@ -1,15 +1,17 @@
|
|||
package jira
|
||||
|
||||
// StatusService handles staties for the JIRA instance / API.
|
||||
import "context"
|
||||
|
||||
// StatusService handles staties for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Workflow-statuses
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Workflow-statuses
|
||||
type StatusService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Status represents the current status of a JIRA issue.
|
||||
// Status represents the current status of a Jira issue.
|
||||
// Typical status are "Open", "In Progress", "Closed", ...
|
||||
// Status can be user defined in every JIRA instance.
|
||||
// Status can be user defined in every Jira instance.
|
||||
type Status struct {
|
||||
Self string `json:"self" structs:"self"`
|
||||
Description string `json:"description" structs:"description"`
|
||||
|
@ -19,12 +21,12 @@ type Status struct {
|
|||
StatusCategory StatusCategory `json:"statusCategory" structs:"statusCategory"`
|
||||
}
|
||||
|
||||
// GetAllStatuses returns a list of all statuses associated with workflows.
|
||||
// GetAllStatusesWithContext returns a list of all statuses associated with workflows.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get
|
||||
func (s *StatusService) GetAllStatuses() ([]Status, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get
|
||||
func (s *StatusService) GetAllStatusesWithContext(ctx context.Context) ([]Status, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/status"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -38,3 +40,8 @@ func (s *StatusService) GetAllStatuses() ([]Status, *Response, error) {
|
|||
|
||||
return statusList, resp, nil
|
||||
}
|
||||
|
||||
// GetAllStatuses wraps GetAllStatusesWithContext using the background context.
|
||||
func (s *StatusService) GetAllStatuses() ([]Status, *Response, error) {
|
||||
return s.GetAllStatusesWithContext(context.Background())
|
||||
}
|
||||
|
|
23
vendor/github.com/andygrunwald/go-jira/statuscategory.go
generated
vendored
23
vendor/github.com/andygrunwald/go-jira/statuscategory.go
generated
vendored
|
@ -1,14 +1,16 @@
|
|||
package jira
|
||||
|
||||
// StatusCategoryService handles status categories for the JIRA instance / API.
|
||||
import "context"
|
||||
|
||||
// StatusCategoryService handles status categories for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory
|
||||
type StatusCategoryService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// StatusCategory represents the category a status belongs to.
|
||||
// Those categories can be user defined in every JIRA instance.
|
||||
// Those categories can be user defined in every Jira instance.
|
||||
type StatusCategory struct {
|
||||
Self string `json:"self" structs:"self"`
|
||||
ID int `json:"id" structs:"id"`
|
||||
|
@ -17,7 +19,7 @@ type StatusCategory struct {
|
|||
ColorName string `json:"colorName" structs:"colorName"`
|
||||
}
|
||||
|
||||
// These constants are the keys of the default JIRA status categories
|
||||
// These constants are the keys of the default Jira status categories
|
||||
const (
|
||||
StatusCategoryComplete = "done"
|
||||
StatusCategoryInProgress = "indeterminate"
|
||||
|
@ -25,12 +27,12 @@ const (
|
|||
StatusCategoryUndefined = "undefined"
|
||||
)
|
||||
|
||||
// GetList gets all status categories from JIRA
|
||||
// GetListWithContext gets all status categories from Jira
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get
|
||||
func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get
|
||||
func (s *StatusCategoryService) GetListWithContext(ctx context.Context) ([]StatusCategory, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/statuscategory"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -42,3 +44,8 @@ func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) {
|
|||
}
|
||||
return statusCategoryList, resp, nil
|
||||
}
|
||||
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) {
|
||||
return s.GetListWithContext(context.Background())
|
||||
}
|
||||
|
|
9
vendor/github.com/andygrunwald/go-jira/types.go
generated
vendored
Normal file
9
vendor/github.com/andygrunwald/go-jira/types.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
package jira
|
||||
|
||||
// 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
|
||||
}
|
157
vendor/github.com/andygrunwald/go-jira/user.go
generated
vendored
157
vendor/github.com/andygrunwald/go-jira/user.go
generated
vendored
|
@ -1,25 +1,24 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// UserService handles users for the JIRA instance / API.
|
||||
// UserService handles users for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Users
|
||||
type UserService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// User represents a JIRA user.
|
||||
// User represents a Jira user.
|
||||
type User struct {
|
||||
Self string `json:"self,omitempty" structs:"self,omitempty"`
|
||||
AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"`
|
||||
AccountType string `json:"accountType,omitempty" structs:"accountType,omitempty"`
|
||||
// TODO: name & key are deprecated, see:
|
||||
// https://developer.atlassian.com/cloud/jira/platform/api-changes-for-user-privacy-announcement/
|
||||
Self string `json:"self,omitempty" structs:"self,omitempty"`
|
||||
AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"`
|
||||
AccountType string `json:"accountType,omitempty" structs:"accountType,omitempty"`
|
||||
Name string `json:"name,omitempty" structs:"name,omitempty"`
|
||||
Key string `json:"key,omitempty" structs:"key,omitempty"`
|
||||
Password string `json:"-"`
|
||||
|
@ -47,12 +46,12 @@ type userSearch []userSearchParam
|
|||
|
||||
type userSearchF func(userSearch) userSearch
|
||||
|
||||
// Get gets user info from JIRA
|
||||
// GetWithContext gets user info from Jira using its Account Id
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser
|
||||
func (s *UserService) Get(username string) (*User, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/user?username=%s", username)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get
|
||||
func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*User, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -65,12 +64,41 @@ func (s *UserService) Get(username string) (*User, *Response, error) {
|
|||
return user, resp, nil
|
||||
}
|
||||
|
||||
// Create creates an user in JIRA.
|
||||
// Get wraps GetWithContext using the background context.
|
||||
func (s *UserService) Get(accountId string) (*User, *Response, error) {
|
||||
return s.GetWithContext(context.Background(), accountId)
|
||||
}
|
||||
|
||||
// GetByAccountIDWithContext gets user info from Jira
|
||||
// Searching by another parameter that is not accountId is deprecated,
|
||||
// but this method is kept for backwards compatibility
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser
|
||||
func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID string) (*User, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
user := new(User)
|
||||
resp, err := s.client.Do(req, user)
|
||||
if err != nil {
|
||||
return nil, resp, NewJiraError(resp, err)
|
||||
}
|
||||
return user, resp, nil
|
||||
}
|
||||
|
||||
// GetByAccountID wraps GetByAccountIDWithContext using the background context.
|
||||
func (s *UserService) GetByAccountID(accountID string) (*User, *Response, error) {
|
||||
return s.GetByAccountIDWithContext(context.Background(), accountID)
|
||||
}
|
||||
|
||||
// CreateWithContext creates an user in Jira.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser
|
||||
func (s *UserService) Create(user *User) (*User, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser
|
||||
func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, *Response, error) {
|
||||
apiEndpoint := "/rest/api/2/user"
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, user)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, user)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -84,24 +112,30 @@ func (s *UserService) Create(user *User) (*User, *Response, error) {
|
|||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("Could not read the returned data")
|
||||
e := fmt.Errorf("could not read the returned data")
|
||||
return nil, resp, NewJiraError(resp, e)
|
||||
}
|
||||
err = json.Unmarshal(data, responseUser)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("Could not unmarshall the data into struct")
|
||||
e := fmt.Errorf("could not unmarshall the data into struct")
|
||||
return nil, resp, NewJiraError(resp, e)
|
||||
}
|
||||
return responseUser, resp, nil
|
||||
}
|
||||
|
||||
// Delete deletes an user from JIRA.
|
||||
// Create wraps CreateWithContext using the background context.
|
||||
func (s *UserService) Create(user *User) (*User, *Response, error) {
|
||||
return s.CreateWithContext(context.Background(), user)
|
||||
}
|
||||
|
||||
// DeleteWithContext deletes an user from Jira.
|
||||
// Returns http.StatusNoContent on success.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-user-delete
|
||||
func (s *UserService) Delete(username string) (*Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/user?username=%s", username)
|
||||
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-delete
|
||||
// Caller must close resp.Body
|
||||
func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) (*Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -113,12 +147,18 @@ func (s *UserService) Delete(username string) (*Response, error) {
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
// GetGroups returns the groups which the user belongs to
|
||||
// Delete wraps DeleteWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *UserService) Delete(accountId string) (*Response, error) {
|
||||
return s.DeleteWithContext(context.Background(), accountId)
|
||||
}
|
||||
|
||||
// GetGroupsWithContext returns the groups which the user belongs to
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUserGroups
|
||||
func (s *UserService) GetGroups(username string) (*[]UserGroup, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?username=%s", username)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get
|
||||
func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -131,12 +171,17 @@ func (s *UserService) GetGroups(username string) (*[]UserGroup, *Response, error
|
|||
return userGroups, resp, nil
|
||||
}
|
||||
|
||||
// Get information about the current logged-in user
|
||||
// GetGroups wraps GetGroupsWithContext using the background context.
|
||||
func (s *UserService) GetGroups(accountId string) (*[]UserGroup, *Response, error) {
|
||||
return s.GetGroupsWithContext(context.Background(), accountId)
|
||||
}
|
||||
|
||||
// GetSelfWithContext information about the current logged-in user
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-myself-get
|
||||
func (s *UserService) GetSelf() (*User, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get
|
||||
func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, error) {
|
||||
const apiEndpoint = "rest/api/2/myself"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -148,6 +193,11 @@ func (s *UserService) GetSelf() (*User, *Response, error) {
|
|||
return &user, resp, nil
|
||||
}
|
||||
|
||||
// GetSelf wraps GetSelfWithContext using the background context.
|
||||
func (s *UserService) GetSelf() (*User, *Response, error) {
|
||||
return s.GetSelfWithContext(context.Background())
|
||||
}
|
||||
|
||||
// WithMaxResults sets the max results to return
|
||||
func WithMaxResults(maxResults int) userSearchF {
|
||||
return func(s userSearch) userSearch {
|
||||
|
@ -180,14 +230,38 @@ func WithInactive(inactive bool) userSearchF {
|
|||
}
|
||||
}
|
||||
|
||||
// Find searches for user info from JIRA:
|
||||
// It can find users by email, username or name
|
||||
// WithUsername sets the username to search
|
||||
func WithUsername(username string) userSearchF {
|
||||
return func(s userSearch) userSearch {
|
||||
s = append(s, userSearchParam{name: "username", value: username})
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// WithAccountId sets the account id to search
|
||||
func WithAccountId(accountId string) userSearchF {
|
||||
return func(s userSearch) userSearch {
|
||||
s = append(s, userSearchParam{name: "accountId", value: accountId})
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// WithProperty sets the property (Property keys are specified by path) to search
|
||||
func WithProperty(property string) userSearchF {
|
||||
return func(s userSearch) userSearch {
|
||||
s = append(s, userSearchParam{name: "property", value: property})
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// FindWithContext searches for user info from Jira:
|
||||
// It can find users by email or display name using the query parameter
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-findUsers
|
||||
func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get
|
||||
func (s *UserService) FindWithContext(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) {
|
||||
search := []userSearchParam{
|
||||
{
|
||||
name: "username",
|
||||
name: "query",
|
||||
value: property,
|
||||
},
|
||||
}
|
||||
|
@ -201,7 +275,7 @@ func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Res
|
|||
}
|
||||
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/user/search?%s", queryString[:len(queryString)-1])
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -213,3 +287,8 @@ func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Res
|
|||
}
|
||||
return users, resp, nil
|
||||
}
|
||||
|
||||
// Find wraps FindWithContext using the background context.
|
||||
func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Response, error) {
|
||||
return s.FindWithContext(context.Background(), property, tweaks...)
|
||||
}
|
||||
|
|
54
vendor/github.com/andygrunwald/go-jira/version.go
generated
vendored
54
vendor/github.com/andygrunwald/go-jira/version.go
generated
vendored
|
@ -1,14 +1,15 @@
|
|||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// VersionService handles Versions for the JIRA instance / API.
|
||||
// VersionService handles Versions for the Jira instance / API.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/version
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/version
|
||||
type VersionService struct {
|
||||
client *Client
|
||||
}
|
||||
|
@ -19,20 +20,20 @@ type Version struct {
|
|||
ID string `json:"id,omitempty" structs:"id,omitempty"`
|
||||
Name string `json:"name,omitempty" structs:"name,omitempty"`
|
||||
Description string `json:"description,omitempty" structs:"description,omitempty"`
|
||||
Archived bool `json:"archived,omitempty" structs:"archived,omitempty"`
|
||||
Released bool `json:"released,omitempty" structs:"released,omitempty"`
|
||||
Archived *bool `json:"archived,omitempty" structs:"archived,omitempty"`
|
||||
Released *bool `json:"released,omitempty" structs:"released,omitempty"`
|
||||
ReleaseDate string `json:"releaseDate,omitempty" structs:"releaseDate,omitempty"`
|
||||
UserReleaseDate string `json:"userReleaseDate,omitempty" structs:"userReleaseDate,omitempty"`
|
||||
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` // Unlike other IDs, this is returned as a number
|
||||
StartDate string `json:"startDate,omitempty" structs:"startDate,omitempty"`
|
||||
}
|
||||
|
||||
// Get gets version info from JIRA
|
||||
// GetWithContext gets version info from Jira
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get
|
||||
func (s *VersionService) Get(versionID int) (*Version, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get
|
||||
func (s *VersionService) GetWithContext(ctx context.Context, versionID int) (*Version, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -45,12 +46,17 @@ func (s *VersionService) Get(versionID int) (*Version, *Response, error) {
|
|||
return version, resp, nil
|
||||
}
|
||||
|
||||
// Create creates a version in JIRA.
|
||||
// Get wraps GetWithContext using the background context.
|
||||
func (s *VersionService) Get(versionID int) (*Version, *Response, error) {
|
||||
return s.GetWithContext(context.Background(), versionID)
|
||||
}
|
||||
|
||||
// CreateWithContext creates a version in Jira.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post
|
||||
func (s *VersionService) Create(version *Version) (*Version, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post
|
||||
func (s *VersionService) CreateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) {
|
||||
apiEndpoint := "/rest/api/2/version"
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, version)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, version)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -64,23 +70,29 @@ func (s *VersionService) Create(version *Version) (*Version, *Response, error) {
|
|||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("Could not read the returned data")
|
||||
e := fmt.Errorf("could not read the returned data")
|
||||
return nil, resp, NewJiraError(resp, e)
|
||||
}
|
||||
err = json.Unmarshal(data, responseVersion)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("Could not unmarshall the data into struct")
|
||||
e := fmt.Errorf("could not unmarshall the data into struct")
|
||||
return nil, resp, NewJiraError(resp, e)
|
||||
}
|
||||
return responseVersion, resp, nil
|
||||
}
|
||||
|
||||
// Update updates a version from a JSON representation.
|
||||
// Create wraps CreateWithContext using the background context.
|
||||
func (s *VersionService) Create(version *Version) (*Version, *Response, error) {
|
||||
return s.CreateWithContext(context.Background(), version)
|
||||
}
|
||||
|
||||
// UpdateWithContext updates a version from a JSON representation.
|
||||
//
|
||||
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put
|
||||
func (s *VersionService) Update(version *Version) (*Version, *Response, error) {
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put
|
||||
// Caller must close resp.Body
|
||||
func (s *VersionService) UpdateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID)
|
||||
req, err := s.client.NewRequest("PUT", apiEndpoint, version)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, version)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -95,3 +107,9 @@ func (s *VersionService) Update(version *Version) (*Version, *Response, error) {
|
|||
ret := *version
|
||||
return &ret, resp, nil
|
||||
}
|
||||
|
||||
// Update wraps UpdateWithContext using the background context.
|
||||
// Caller must close resp.Body
|
||||
func (s *VersionService) Update(version *Version) (*Version, *Response, error) {
|
||||
return s.UpdateWithContext(context.Background(), version)
|
||||
}
|
||||
|
|
30
vendor/github.com/denisenkom/go-mssqldb/log.go
generated
vendored
30
vendor/github.com/denisenkom/go-mssqldb/log.go
generated
vendored
|
@ -1,30 +0,0 @@
|
|||
package mssql
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Printf(format string, v ...interface{})
|
||||
Println(v ...interface{})
|
||||
}
|
||||
|
||||
type optionalLogger struct {
|
||||
logger Logger
|
||||
}
|
||||
|
||||
func (o optionalLogger) Printf(format string, v ...interface{}) {
|
||||
if o.logger != nil {
|
||||
o.logger.Printf(format, v...)
|
||||
} else {
|
||||
log.Printf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func (o optionalLogger) Println(v ...interface{}) {
|
||||
if o.logger != nil {
|
||||
o.logger.Println(v...)
|
||||
} else {
|
||||
log.Println(v...)
|
||||
}
|
||||
}
|
2
vendor/github.com/fatih/structs/.travis.yml
generated
vendored
2
vendor/github.com/fatih/structs/.travis.yml
generated
vendored
|
@ -1,6 +1,8 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- tip
|
||||
sudo: false
|
||||
before_install:
|
||||
|
|
4
vendor/github.com/fatih/structs/README.md
generated
vendored
4
vendor/github.com/fatih/structs/README.md
generated
vendored
|
@ -81,8 +81,8 @@ n := s.Names() // Get a []string
|
|||
f := s.Field(name) // Get a *Field based on the given field name
|
||||
f, ok := s.FieldOk(name) // Get a *Field based on the given field name
|
||||
n := s.Name() // Get the struct name
|
||||
h := s.HasZero() // Check if any field is initialized
|
||||
z := s.IsZero() // Check if all fields are initialized
|
||||
h := s.HasZero() // Check if any field is uninitialized
|
||||
z := s.IsZero() // Check if all fields are uninitialized
|
||||
```
|
||||
|
||||
### Field methods
|
||||
|
|
6
vendor/github.com/fatih/structs/structs.go
generated
vendored
6
vendor/github.com/fatih/structs/structs.go
generated
vendored
|
@ -203,9 +203,7 @@ func (s *Struct) Values() []interface{} {
|
|||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
// look out for embedded structs, and convert them to a
|
||||
// []interface{} to be added to the final values slice
|
||||
for _, embeddedVal := range Values(val.Interface()) {
|
||||
t = append(t, embeddedVal)
|
||||
}
|
||||
t = append(t, Values(val.Interface())...)
|
||||
} else {
|
||||
t = append(t, val.Interface())
|
||||
}
|
||||
|
@ -573,7 +571,7 @@ func (s *Struct) nested(val reflect.Value) interface{} {
|
|||
break
|
||||
}
|
||||
|
||||
slices := make([]interface{}, val.Len(), val.Len())
|
||||
slices := make([]interface{}, val.Len())
|
||||
for x := 0; x < val.Len(); x++ {
|
||||
slices[x] = s.nested(val.Index(x))
|
||||
}
|
||||
|
|
2
vendor/github.com/fatih/structs/tags.go
generated
vendored
2
vendor/github.com/fatih/structs/tags.go
generated
vendored
|
@ -5,7 +5,7 @@ import "strings"
|
|||
// tagOptions contains a slice of tag options
|
||||
type tagOptions []string
|
||||
|
||||
// Has returns true if the given optiton is available in tagOptions
|
||||
// Has returns true if the given option is available in tagOptions
|
||||
func (t tagOptions) Has(opt string) bool {
|
||||
for _, tagOpt := range t {
|
||||
if tagOpt == opt {
|
||||
|
|
0
vendor/github.com/felixge/httpsnoop/.gitignore
generated
vendored
Normal file
0
vendor/github.com/felixge/httpsnoop/.gitignore
generated
vendored
Normal file
6
vendor/github.com/felixge/httpsnoop/.travis.yml
generated
vendored
Normal file
6
vendor/github.com/felixge/httpsnoop/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
19
vendor/github.com/felixge/httpsnoop/LICENSE.txt
generated
vendored
Normal file
19
vendor/github.com/felixge/httpsnoop/LICENSE.txt
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com)
|
||||
|
||||
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.
|
10
vendor/github.com/felixge/httpsnoop/Makefile
generated
vendored
Normal file
10
vendor/github.com/felixge/httpsnoop/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
.PHONY: ci generate clean
|
||||
|
||||
ci: clean generate
|
||||
go test -v ./...
|
||||
|
||||
generate:
|
||||
go generate .
|
||||
|
||||
clean:
|
||||
rm -rf *_generated*.go
|
95
vendor/github.com/felixge/httpsnoop/README.md
generated
vendored
Normal file
95
vendor/github.com/felixge/httpsnoop/README.md
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
# httpsnoop
|
||||
|
||||
Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
||||
response time, bytes written, and http status code) from your application's
|
||||
http.Handlers.
|
||||
|
||||
Doing this requires non-trivial wrapping of the http.ResponseWriter interface,
|
||||
which is also exposed for users interested in a more low-level API.
|
||||
|
||||
[](https://godoc.org/github.com/felixge/httpsnoop)
|
||||
[](https://travis-ci.org/felixge/httpsnoop)
|
||||
|
||||
## Usage Example
|
||||
|
||||
```go
|
||||
// myH is your app's http handler, perhaps a http.ServeMux or similar.
|
||||
var myH http.Handler
|
||||
// wrappedH wraps myH in order to log every request.
|
||||
wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
m := httpsnoop.CaptureMetrics(myH, w, r)
|
||||
log.Printf(
|
||||
"%s %s (code=%d dt=%s written=%d)",
|
||||
r.Method,
|
||||
r.URL,
|
||||
m.Code,
|
||||
m.Duration,
|
||||
m.Written,
|
||||
)
|
||||
})
|
||||
http.ListenAndServe(":8080", wrappedH)
|
||||
```
|
||||
|
||||
## Why this package exists
|
||||
|
||||
Instrumenting an application's http.Handler is surprisingly difficult.
|
||||
|
||||
However if you google for e.g. "capture ResponseWriter status code" you'll find
|
||||
lots of advise and code examples that suggest it to be a fairly trivial
|
||||
undertaking. Unfortunately everything I've seen so far has a high chance of
|
||||
breaking your application.
|
||||
|
||||
The main problem is that a `http.ResponseWriter` often implements additional
|
||||
interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and
|
||||
`io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter`
|
||||
in your own struct that also implements the `http.ResponseWriter` interface
|
||||
will hide the additional interfaces mentioned above. This has a high change of
|
||||
introducing subtle bugs into any non-trivial application.
|
||||
|
||||
Another approach I've seen people take is to return a struct that implements
|
||||
all of the interfaces above. However, that's also problematic, because it's
|
||||
difficult to fake some of these interfaces behaviors when the underlying
|
||||
`http.ResponseWriter` doesn't have an implementation. It's also dangerous,
|
||||
because an application may choose to operate differently, merely because it
|
||||
detects the presence of these additional interfaces.
|
||||
|
||||
This package solves this problem by checking which additional interfaces a
|
||||
`http.ResponseWriter` implements, returning a wrapped version implementing the
|
||||
exact same set of interfaces.
|
||||
|
||||
Additionally this package properly handles edge cases such as `WriteHeader` not
|
||||
being called, or called more than once, as well as concurrent calls to
|
||||
`http.ResponseWriter` methods, and even calls happening after the wrapped
|
||||
`ServeHTTP` has already returned.
|
||||
|
||||
Unfortunately this package is not perfect either. It's possible that it is
|
||||
still missing some interfaces provided by the go core (let me know if you find
|
||||
one), and it won't work for applications adding their own interfaces into the
|
||||
mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying
|
||||
`http.ResponseWriter` and type-assert the result to its other interfaces.
|
||||
|
||||
However, hopefully the explanation above has sufficiently scared you of rolling
|
||||
your own solution to this problem. httpsnoop may still break your application,
|
||||
but at least it tries to avoid it as much as possible.
|
||||
|
||||
Anyway, the real problem here is that smuggling additional interfaces inside
|
||||
`http.ResponseWriter` is a problematic design choice, but it probably goes as
|
||||
deep as the Go language specification itself. But that's okay, I still prefer
|
||||
Go over the alternatives ;).
|
||||
|
||||
## Performance
|
||||
|
||||
```
|
||||
BenchmarkBaseline-8 20000 94912 ns/op
|
||||
BenchmarkCaptureMetrics-8 20000 95461 ns/op
|
||||
```
|
||||
|
||||
As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an
|
||||
overhead of ~500 ns per http request on my machine. However, the margin of
|
||||
error appears to be larger than that, therefor it should be reasonable to
|
||||
assume that the overhead introduced by `CaptureMetrics` is absolutely
|
||||
negligible.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
86
vendor/github.com/felixge/httpsnoop/capture_metrics.go
generated
vendored
Normal file
86
vendor/github.com/felixge/httpsnoop/capture_metrics.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
package httpsnoop
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Metrics holds metrics captured from CaptureMetrics.
|
||||
type Metrics struct {
|
||||
// Code is the first http response code passed to the WriteHeader func of
|
||||
// the ResponseWriter. If no such call is made, a default code of 200 is
|
||||
// assumed instead.
|
||||
Code int
|
||||
// Duration is the time it took to execute the handler.
|
||||
Duration time.Duration
|
||||
// Written is the number of bytes successfully written by the Write or
|
||||
// ReadFrom function of the ResponseWriter. ResponseWriters may also write
|
||||
// data to their underlaying connection directly (e.g. headers), but those
|
||||
// are not tracked. Therefor the number of Written bytes will usually match
|
||||
// the size of the response body.
|
||||
Written int64
|
||||
}
|
||||
|
||||
// CaptureMetrics wraps the given hnd, executes it with the given w and r, and
|
||||
// returns the metrics it captured from it.
|
||||
func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics {
|
||||
return CaptureMetricsFn(w, func(ww http.ResponseWriter) {
|
||||
hnd.ServeHTTP(ww, r)
|
||||
})
|
||||
}
|
||||
|
||||
// CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the
|
||||
// resulting metrics. This is very similar to CaptureMetrics (which is just
|
||||
// sugar on top of this func), but is a more usable interface if your
|
||||
// application doesn't use the Go http.Handler interface.
|
||||
func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics {
|
||||
m := Metrics{Code: http.StatusOK}
|
||||
m.CaptureMetrics(w, fn)
|
||||
return m
|
||||
}
|
||||
|
||||
// CaptureMetrics wraps w and calls fn with the wrapped w and updates
|
||||
// Metrics m with the resulting metrics. This is similar to CaptureMetricsFn,
|
||||
// but allows one to customize starting Metrics object.
|
||||
func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWriter)) {
|
||||
var (
|
||||
start = time.Now()
|
||||
headerWritten bool
|
||||
hooks = Hooks{
|
||||
WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc {
|
||||
return func(code int) {
|
||||
next(code)
|
||||
|
||||
if !headerWritten {
|
||||
m.Code = code
|
||||
headerWritten = true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Write: func(next WriteFunc) WriteFunc {
|
||||
return func(p []byte) (int, error) {
|
||||
n, err := next(p)
|
||||
|
||||
m.Written += int64(n)
|
||||
headerWritten = true
|
||||
return n, err
|
||||
}
|
||||
},
|
||||
|
||||
ReadFrom: func(next ReadFromFunc) ReadFromFunc {
|
||||
return func(src io.Reader) (int64, error) {
|
||||
n, err := next(src)
|
||||
|
||||
headerWritten = true
|
||||
m.Written += n
|
||||
return n, err
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
fn(Wrap(w, hooks))
|
||||
m.Duration += time.Since(start)
|
||||
}
|
10
vendor/github.com/felixge/httpsnoop/docs.go
generated
vendored
Normal file
10
vendor/github.com/felixge/httpsnoop/docs.go
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
// Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
||||
// response time, bytes written, and http status code) from your application's
|
||||
// http.Handlers.
|
||||
//
|
||||
// Doing this requires non-trivial wrapping of the http.ResponseWriter
|
||||
// interface, which is also exposed for users interested in a more low-level
|
||||
// API.
|
||||
package httpsnoop
|
||||
|
||||
//go:generate go run codegen/main.go
|
436
vendor/github.com/felixge/httpsnoop/wrap_generated_gteq_1.8.go
generated
vendored
Normal file
436
vendor/github.com/felixge/httpsnoop/wrap_generated_gteq_1.8.go
generated
vendored
Normal file
|
@ -0,0 +1,436 @@
|
|||
// +build go1.8
|
||||
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
|
||||
|
||||
package httpsnoop
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HeaderFunc is part of the http.ResponseWriter interface.
|
||||
type HeaderFunc func() http.Header
|
||||
|
||||
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
||||
type WriteHeaderFunc func(code int)
|
||||
|
||||
// WriteFunc is part of the http.ResponseWriter interface.
|
||||
type WriteFunc func(b []byte) (int, error)
|
||||
|
||||
// FlushFunc is part of the http.Flusher interface.
|
||||
type FlushFunc func()
|
||||
|
||||
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
||||
type CloseNotifyFunc func() <-chan bool
|
||||
|
||||
// HijackFunc is part of the http.Hijacker interface.
|
||||
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
||||
|
||||
// ReadFromFunc is part of the io.ReaderFrom interface.
|
||||
type ReadFromFunc func(src io.Reader) (int64, error)
|
||||
|
||||
// PushFunc is part of the http.Pusher interface.
|
||||
type PushFunc func(target string, opts *http.PushOptions) error
|
||||
|
||||
// Hooks defines a set of method interceptors for methods included in
|
||||
// http.ResponseWriter as well as some others. You can think of them as
|
||||
// middleware for the function calls they target. See Wrap for more details.
|
||||
type Hooks struct {
|
||||
Header func(HeaderFunc) HeaderFunc
|
||||
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
||||
Write func(WriteFunc) WriteFunc
|
||||
Flush func(FlushFunc) FlushFunc
|
||||
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
||||
Hijack func(HijackFunc) HijackFunc
|
||||
ReadFrom func(ReadFromFunc) ReadFromFunc
|
||||
Push func(PushFunc) PushFunc
|
||||
}
|
||||
|
||||
// Wrap returns a wrapped version of w that provides the exact same interface
|
||||
// as w. Specifically if w implements any combination of:
|
||||
//
|
||||
// - http.Flusher
|
||||
// - http.CloseNotifier
|
||||
// - http.Hijacker
|
||||
// - io.ReaderFrom
|
||||
// - http.Pusher
|
||||
//
|
||||
// The wrapped version will implement the exact same combination. If no hooks
|
||||
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
||||
// methods not supported by w are ignored. Any other hooks will intercept the
|
||||
// method they target and may modify the call's arguments and/or return values.
|
||||
// The CaptureMetrics implementation serves as a working example for how the
|
||||
// hooks can be used.
|
||||
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
||||
rw := &rw{w: w, h: hooks}
|
||||
_, i0 := w.(http.Flusher)
|
||||
_, i1 := w.(http.CloseNotifier)
|
||||
_, i2 := w.(http.Hijacker)
|
||||
_, i3 := w.(io.ReaderFrom)
|
||||
_, i4 := w.(http.Pusher)
|
||||
switch {
|
||||
// combination 1/32
|
||||
case !i0 && !i1 && !i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
}{rw, rw}
|
||||
// combination 2/32
|
||||
case !i0 && !i1 && !i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Pusher
|
||||
}{rw, rw, rw}
|
||||
// combination 3/32
|
||||
case !i0 && !i1 && !i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw}
|
||||
// combination 4/32
|
||||
case !i0 && !i1 && !i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 5/32
|
||||
case !i0 && !i1 && i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
}{rw, rw, rw}
|
||||
// combination 6/32
|
||||
case !i0 && !i1 && i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 7/32
|
||||
case !i0 && !i1 && i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 8/32
|
||||
case !i0 && !i1 && i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 9/32
|
||||
case !i0 && i1 && !i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
}{rw, rw, rw}
|
||||
// combination 10/32
|
||||
case !i0 && i1 && !i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 11/32
|
||||
case !i0 && i1 && !i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 12/32
|
||||
case !i0 && i1 && !i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 13/32
|
||||
case !i0 && i1 && i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 14/32
|
||||
case !i0 && i1 && i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 15/32
|
||||
case !i0 && i1 && i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 16/32
|
||||
case !i0 && i1 && i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 17/32
|
||||
case i0 && !i1 && !i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
}{rw, rw, rw}
|
||||
// combination 18/32
|
||||
case i0 && !i1 && !i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 19/32
|
||||
case i0 && !i1 && !i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 20/32
|
||||
case i0 && !i1 && !i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 21/32
|
||||
case i0 && !i1 && i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 22/32
|
||||
case i0 && !i1 && i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 23/32
|
||||
case i0 && !i1 && i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 24/32
|
||||
case i0 && !i1 && i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 25/32
|
||||
case i0 && i1 && !i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 26/32
|
||||
case i0 && i1 && !i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 27/32
|
||||
case i0 && i1 && !i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 28/32
|
||||
case i0 && i1 && !i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 29/32
|
||||
case i0 && i1 && i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 30/32
|
||||
case i0 && i1 && i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 31/32
|
||||
case i0 && i1 && i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 32/32
|
||||
case i0 && i1 && i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw, rw}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
type rw struct {
|
||||
w http.ResponseWriter
|
||||
h Hooks
|
||||
}
|
||||
|
||||
func (w *rw) Unwrap() http.ResponseWriter {
|
||||
return w.w
|
||||
}
|
||||
|
||||
func (w *rw) Header() http.Header {
|
||||
f := w.w.(http.ResponseWriter).Header
|
||||
if w.h.Header != nil {
|
||||
f = w.h.Header(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) WriteHeader(code int) {
|
||||
f := w.w.(http.ResponseWriter).WriteHeader
|
||||
if w.h.WriteHeader != nil {
|
||||
f = w.h.WriteHeader(f)
|
||||
}
|
||||
f(code)
|
||||
}
|
||||
|
||||
func (w *rw) Write(b []byte) (int, error) {
|
||||
f := w.w.(http.ResponseWriter).Write
|
||||
if w.h.Write != nil {
|
||||
f = w.h.Write(f)
|
||||
}
|
||||
return f(b)
|
||||
}
|
||||
|
||||
func (w *rw) Flush() {
|
||||
f := w.w.(http.Flusher).Flush
|
||||
if w.h.Flush != nil {
|
||||
f = w.h.Flush(f)
|
||||
}
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *rw) CloseNotify() <-chan bool {
|
||||
f := w.w.(http.CloseNotifier).CloseNotify
|
||||
if w.h.CloseNotify != nil {
|
||||
f = w.h.CloseNotify(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
f := w.w.(http.Hijacker).Hijack
|
||||
if w.h.Hijack != nil {
|
||||
f = w.h.Hijack(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
||||
f := w.w.(io.ReaderFrom).ReadFrom
|
||||
if w.h.ReadFrom != nil {
|
||||
f = w.h.ReadFrom(f)
|
||||
}
|
||||
return f(src)
|
||||
}
|
||||
|
||||
func (w *rw) Push(target string, opts *http.PushOptions) error {
|
||||
f := w.w.(http.Pusher).Push
|
||||
if w.h.Push != nil {
|
||||
f = w.h.Push(f)
|
||||
}
|
||||
return f(target, opts)
|
||||
}
|
||||
|
||||
type Unwrapper interface {
|
||||
Unwrap() http.ResponseWriter
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying http.ResponseWriter from within zero or more
|
||||
// layers of httpsnoop wrappers.
|
||||
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
|
||||
if rw, ok := w.(Unwrapper); ok {
|
||||
// recurse until rw.Unwrap() returns a non-Unwrapper
|
||||
return Unwrap(rw.Unwrap())
|
||||
} else {
|
||||
return w
|
||||
}
|
||||
}
|
278
vendor/github.com/felixge/httpsnoop/wrap_generated_lt_1.8.go
generated
vendored
Normal file
278
vendor/github.com/felixge/httpsnoop/wrap_generated_lt_1.8.go
generated
vendored
Normal file
|
@ -0,0 +1,278 @@
|
|||
// +build !go1.8
|
||||
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
|
||||
|
||||
package httpsnoop
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HeaderFunc is part of the http.ResponseWriter interface.
|
||||
type HeaderFunc func() http.Header
|
||||
|
||||
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
||||
type WriteHeaderFunc func(code int)
|
||||
|
||||
// WriteFunc is part of the http.ResponseWriter interface.
|
||||
type WriteFunc func(b []byte) (int, error)
|
||||
|
||||
// FlushFunc is part of the http.Flusher interface.
|
||||
type FlushFunc func()
|
||||
|
||||
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
||||
type CloseNotifyFunc func() <-chan bool
|
||||
|
||||
// HijackFunc is part of the http.Hijacker interface.
|
||||
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
||||
|
||||
// ReadFromFunc is part of the io.ReaderFrom interface.
|
||||
type ReadFromFunc func(src io.Reader) (int64, error)
|
||||
|
||||
// Hooks defines a set of method interceptors for methods included in
|
||||
// http.ResponseWriter as well as some others. You can think of them as
|
||||
// middleware for the function calls they target. See Wrap for more details.
|
||||
type Hooks struct {
|
||||
Header func(HeaderFunc) HeaderFunc
|
||||
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
||||
Write func(WriteFunc) WriteFunc
|
||||
Flush func(FlushFunc) FlushFunc
|
||||
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
||||
Hijack func(HijackFunc) HijackFunc
|
||||
ReadFrom func(ReadFromFunc) ReadFromFunc
|
||||
}
|
||||
|
||||
// Wrap returns a wrapped version of w that provides the exact same interface
|
||||
// as w. Specifically if w implements any combination of:
|
||||
//
|
||||
// - http.Flusher
|
||||
// - http.CloseNotifier
|
||||
// - http.Hijacker
|
||||
// - io.ReaderFrom
|
||||
//
|
||||
// The wrapped version will implement the exact same combination. If no hooks
|
||||
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
||||
// methods not supported by w are ignored. Any other hooks will intercept the
|
||||
// method they target and may modify the call's arguments and/or return values.
|
||||
// The CaptureMetrics implementation serves as a working example for how the
|
||||
// hooks can be used.
|
||||
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
||||
rw := &rw{w: w, h: hooks}
|
||||
_, i0 := w.(http.Flusher)
|
||||
_, i1 := w.(http.CloseNotifier)
|
||||
_, i2 := w.(http.Hijacker)
|
||||
_, i3 := w.(io.ReaderFrom)
|
||||
switch {
|
||||
// combination 1/16
|
||||
case !i0 && !i1 && !i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
}{rw, rw}
|
||||
// combination 2/16
|
||||
case !i0 && !i1 && !i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw}
|
||||
// combination 3/16
|
||||
case !i0 && !i1 && i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
}{rw, rw, rw}
|
||||
// combination 4/16
|
||||
case !i0 && !i1 && i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 5/16
|
||||
case !i0 && i1 && !i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
}{rw, rw, rw}
|
||||
// combination 6/16
|
||||
case !i0 && i1 && !i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 7/16
|
||||
case !i0 && i1 && i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 8/16
|
||||
case !i0 && i1 && i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 9/16
|
||||
case i0 && !i1 && !i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
}{rw, rw, rw}
|
||||
// combination 10/16
|
||||
case i0 && !i1 && !i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 11/16
|
||||
case i0 && !i1 && i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 12/16
|
||||
case i0 && !i1 && i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 13/16
|
||||
case i0 && i1 && !i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 14/16
|
||||
case i0 && i1 && !i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 15/16
|
||||
case i0 && i1 && i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 16/16
|
||||
case i0 && i1 && i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
type rw struct {
|
||||
w http.ResponseWriter
|
||||
h Hooks
|
||||
}
|
||||
|
||||
func (w *rw) Unwrap() http.ResponseWriter {
|
||||
return w.w
|
||||
}
|
||||
|
||||
func (w *rw) Header() http.Header {
|
||||
f := w.w.(http.ResponseWriter).Header
|
||||
if w.h.Header != nil {
|
||||
f = w.h.Header(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) WriteHeader(code int) {
|
||||
f := w.w.(http.ResponseWriter).WriteHeader
|
||||
if w.h.WriteHeader != nil {
|
||||
f = w.h.WriteHeader(f)
|
||||
}
|
||||
f(code)
|
||||
}
|
||||
|
||||
func (w *rw) Write(b []byte) (int, error) {
|
||||
f := w.w.(http.ResponseWriter).Write
|
||||
if w.h.Write != nil {
|
||||
f = w.h.Write(f)
|
||||
}
|
||||
return f(b)
|
||||
}
|
||||
|
||||
func (w *rw) Flush() {
|
||||
f := w.w.(http.Flusher).Flush
|
||||
if w.h.Flush != nil {
|
||||
f = w.h.Flush(f)
|
||||
}
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *rw) CloseNotify() <-chan bool {
|
||||
f := w.w.(http.CloseNotifier).CloseNotify
|
||||
if w.h.CloseNotify != nil {
|
||||
f = w.h.CloseNotify(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
f := w.w.(http.Hijacker).Hijack
|
||||
if w.h.Hijack != nil {
|
||||
f = w.h.Hijack(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
||||
f := w.w.(io.ReaderFrom).ReadFrom
|
||||
if w.h.ReadFrom != nil {
|
||||
f = w.h.ReadFrom(f)
|
||||
}
|
||||
return f(src)
|
||||
}
|
||||
|
||||
type Unwrapper interface {
|
||||
Unwrap() http.ResponseWriter
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying http.ResponseWriter from within zero or more
|
||||
// layers of httpsnoop wrappers.
|
||||
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
|
||||
if rw, ok := w.(Unwrapper); ok {
|
||||
// recurse until rw.Unwrap() returns a non-Unwrapper
|
||||
return Unwrap(rw.Unwrap())
|
||||
} else {
|
||||
return w
|
||||
}
|
||||
}
|
43
vendor/github.com/go-asn1-ber/asn1-ber/.travis.yml
generated
vendored
43
vendor/github.com/go-asn1-ber/asn1-ber/.travis.yml
generated
vendored
|
@ -1,43 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.2.x
|
||||
- 1.6.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.14.x
|
||||
- tip
|
||||
|
||||
os:
|
||||
- linux
|
||||
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
||||
|
||||
dist: xenial
|
||||
|
||||
env:
|
||||
- GOARCH=amd64
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: windows
|
||||
go: 1.14.x
|
||||
- os: osx
|
||||
go: 1.14.x
|
||||
- os: linux
|
||||
go: 1.14.x
|
||||
arch: arm64
|
||||
- os: linux
|
||||
go: 1.14.x
|
||||
env:
|
||||
- GOARCH=386
|
||||
|
||||
script:
|
||||
- go test -v -cover ./... || go test -v ./...
|
||||
matrix:
|
||||
allowfailures:
|
||||
go: 1.2.x
|
29
vendor/github.com/go-asn1-ber/asn1-ber/ber.go
generated
vendored
29
vendor/github.com/go-asn1-ber/asn1-ber/ber.go
generated
vendored
|
@ -170,12 +170,10 @@ func PrintPacket(p *Packet) {
|
|||
printPacket(os.Stdout, p, 0, false)
|
||||
}
|
||||
|
||||
func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) {
|
||||
indentStr := ""
|
||||
|
||||
for len(indentStr) != indent {
|
||||
indentStr += " "
|
||||
}
|
||||
// Return a string describing packet content. This is not recursive,
|
||||
// If the packet is a sequence, use `printPacket()`, or browse
|
||||
// sequence yourself.
|
||||
func DescribePacket(p *Packet) string {
|
||||
|
||||
classStr := ClassMap[p.ClassType]
|
||||
|
||||
|
@ -194,7 +192,17 @@ func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) {
|
|||
description = p.Description + ": "
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(out, "%s%s(%s, %s, %s) Len=%d %q\n", indentStr, description, classStr, tagTypeStr, tagStr, p.Data.Len(), value)
|
||||
return fmt.Sprintf("%s(%s, %s, %s) Len=%d %q", description, classStr, tagTypeStr, tagStr, p.Data.Len(), value)
|
||||
}
|
||||
|
||||
func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) {
|
||||
indentStr := ""
|
||||
|
||||
for len(indentStr) != indent {
|
||||
indentStr += " "
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(out, "%s%s\n", indentStr, DescribePacket(p))
|
||||
|
||||
if printBytes {
|
||||
PrintBytes(out, p.Bytes(), indentStr)
|
||||
|
@ -317,7 +325,7 @@ func readPacket(reader io.Reader) (*Packet, int, error) {
|
|||
// Read the next packet
|
||||
child, r, err := readPacket(reader)
|
||||
if err != nil {
|
||||
return nil, read, err
|
||||
return nil, read, unexpectedEOF(err)
|
||||
}
|
||||
contentRead += r
|
||||
read += r
|
||||
|
@ -348,10 +356,7 @@ func readPacket(reader io.Reader) (*Packet, int, error) {
|
|||
if length > 0 {
|
||||
_, err := io.ReadFull(reader, content)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil, read, io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, read, err
|
||||
return nil, read, unexpectedEOF(err)
|
||||
}
|
||||
read += length
|
||||
}
|
||||
|
|
2
vendor/github.com/go-asn1-ber/asn1-ber/identifier.go
generated
vendored
2
vendor/github.com/go-asn1-ber/asn1-ber/identifier.go
generated
vendored
|
@ -37,7 +37,7 @@ func readIdentifier(reader io.Reader) (Identifier, int, error) {
|
|||
if Debug {
|
||||
fmt.Printf("error reading high-tag-number tag byte %d: %v\n", tagBytes, err)
|
||||
}
|
||||
return Identifier{}, read, err
|
||||
return Identifier{}, read, unexpectedEOF(err)
|
||||
}
|
||||
tagBytes++
|
||||
read++
|
||||
|
|
4
vendor/github.com/go-asn1-ber/asn1-ber/length.go
generated
vendored
4
vendor/github.com/go-asn1-ber/asn1-ber/length.go
generated
vendored
|
@ -13,7 +13,7 @@ func readLength(reader io.Reader) (length int, read int, err error) {
|
|||
if Debug {
|
||||
fmt.Printf("error reading length byte: %v\n", err)
|
||||
}
|
||||
return 0, 0, err
|
||||
return 0, 0, unexpectedEOF(err)
|
||||
}
|
||||
read++
|
||||
|
||||
|
@ -47,7 +47,7 @@ func readLength(reader io.Reader) (length int, read int, err error) {
|
|||
if Debug {
|
||||
fmt.Printf("error reading long-form length byte %d: %v\n", i, err)
|
||||
}
|
||||
return 0, read, err
|
||||
return 0, read, unexpectedEOF(err)
|
||||
}
|
||||
read++
|
||||
|
||||
|
|
6
vendor/github.com/go-asn1-ber/asn1-ber/real.go
generated
vendored
6
vendor/github.com/go-asn1-ber/asn1-ber/real.go
generated
vendored
|
@ -89,12 +89,18 @@ func parseBinaryFloat(v []byte) (float64, error) {
|
|||
case 0x02:
|
||||
expLen = 3
|
||||
case 0x03:
|
||||
if len(v) < 2 {
|
||||
return 0.0, errors.New("invalid data")
|
||||
}
|
||||
expLen = int(v[0])
|
||||
if expLen > 8 {
|
||||
return 0.0, errors.New("too big value of exponent")
|
||||
}
|
||||
v = v[1:]
|
||||
}
|
||||
if expLen > len(v) {
|
||||
return 0.0, errors.New("too big value of exponent")
|
||||
}
|
||||
buf, v = v[:expLen], v[expLen:]
|
||||
exponent, err := ParseInt64(buf)
|
||||
if err != nil {
|
||||
|
|
10
vendor/github.com/go-asn1-ber/asn1-ber/util.go
generated
vendored
10
vendor/github.com/go-asn1-ber/asn1-ber/util.go
generated
vendored
|
@ -6,14 +6,18 @@ func readByte(reader io.Reader) (byte, error) {
|
|||
bytes := make([]byte, 1)
|
||||
_, err := io.ReadFull(reader, bytes)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
return bytes[0], nil
|
||||
}
|
||||
|
||||
func unexpectedEOF(err error) error {
|
||||
if err == io.EOF {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func isEOCPacket(p *Packet) bool {
|
||||
return p != nil &&
|
||||
p.Tag == TagEOC &&
|
||||
|
|
6
vendor/github.com/go-ldap/ldap/v3/add.go
generated
vendored
6
vendor/github.com/go-ldap/ldap/v3/add.go
generated
vendored
|
@ -1,8 +1,7 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"fmt"
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
)
|
||||
|
||||
|
@ -63,7 +62,6 @@ func NewAddRequest(dn string, controls []Control) *AddRequest {
|
|||
DN: dn,
|
||||
Controls: controls,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add performs the given AddRequest
|
||||
|
@ -85,7 +83,7 @@ func (l *Conn) Add(addRequest *AddRequest) error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
||||
return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
209
vendor/github.com/go-ldap/ldap/v3/bind.go
generated
vendored
209
vendor/github.com/go-ldap/ldap/v3/bind.go
generated
vendored
|
@ -261,7 +261,7 @@ func parseParams(str string) (map[string]string, error) {
|
|||
var state int
|
||||
for i := 0; i <= len(str); i++ {
|
||||
switch state {
|
||||
case 0: //reading key
|
||||
case 0: // reading key
|
||||
if i == len(str) {
|
||||
return nil, fmt.Errorf("syntax error on %d", i)
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ func parseParams(str string) (map[string]string, error) {
|
|||
continue
|
||||
}
|
||||
state = 1
|
||||
case 1: //reading value
|
||||
case 1: // reading value
|
||||
if i == len(str) {
|
||||
m[key] = value
|
||||
break
|
||||
|
@ -289,7 +289,7 @@ func parseParams(str string) (map[string]string, error) {
|
|||
default:
|
||||
value += string(str[i])
|
||||
}
|
||||
case 2: //inside quotes
|
||||
case 2: // inside quotes
|
||||
if i == len(str) {
|
||||
return nil, fmt.Errorf("syntax error on %d", i)
|
||||
}
|
||||
|
@ -399,6 +399,9 @@ type NTLMBindRequest struct {
|
|||
Username string
|
||||
// Password is the credentials to bind with
|
||||
Password string
|
||||
// AllowEmptyPassword sets whether the client allows binding with an empty password
|
||||
// (normally used for unauthenticated bind).
|
||||
AllowEmptyPassword bool
|
||||
// Hash is the hex NTLM hash to bind with. Password or hash must be provided
|
||||
Hash string
|
||||
// Controls are optional controls to send with the bind request
|
||||
|
@ -442,6 +445,22 @@ func (l *Conn) NTLMBind(domain, username, password string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// NTLMUnauthenticatedBind performs an bind with an empty password.
|
||||
//
|
||||
// A username is required. The anonymous bind is not (yet) supported by the go-ntlmssp library (https://github.com/Azure/go-ntlmssp/blob/819c794454d067543bc61d29f61fef4b3c3df62c/authenticate_message.go#L87)
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4 part 3.2.5.1.2
|
||||
func (l *Conn) NTLMUnauthenticatedBind(domain, username string) error {
|
||||
req := &NTLMBindRequest{
|
||||
Domain: domain,
|
||||
Username: username,
|
||||
Password: "",
|
||||
AllowEmptyPassword: true,
|
||||
}
|
||||
_, err := l.NTLMChallengeBind(req)
|
||||
return err
|
||||
}
|
||||
|
||||
// NTLMBindWithHash performs an NTLM Bind with an NTLM hash instead of plaintext password (pass-the-hash)
|
||||
func (l *Conn) NTLMBindWithHash(domain, username, hash string) error {
|
||||
req := &NTLMBindRequest{
|
||||
|
@ -455,7 +474,7 @@ func (l *Conn) NTLMBindWithHash(domain, username, hash string) error {
|
|||
|
||||
// NTLMChallengeBind performs the NTLMSSP bind operation defined in the given request
|
||||
func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindResult, error) {
|
||||
if ntlmBindRequest.Password == "" && ntlmBindRequest.Hash == "" {
|
||||
if !ntlmBindRequest.AllowEmptyPassword && ntlmBindRequest.Password == "" && ntlmBindRequest.Hash == "" {
|
||||
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
|
||||
}
|
||||
|
||||
|
@ -496,10 +515,11 @@ func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindRes
|
|||
var err error
|
||||
var responseMessage []byte
|
||||
// generate a response message to the challenge with the given Username/Password if password is provided
|
||||
if ntlmBindRequest.Password != "" {
|
||||
responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password)
|
||||
} else if ntlmBindRequest.Hash != "" {
|
||||
if ntlmBindRequest.Hash != "" {
|
||||
responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash)
|
||||
} else if ntlmBindRequest.Password != "" || ntlmBindRequest.AllowEmptyPassword {
|
||||
_, _, domainNeeded := ntlmssp.GetDomain(ntlmBindRequest.Username)
|
||||
responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password, domainNeeded)
|
||||
} else {
|
||||
err = fmt.Errorf("need a password or hash to generate reply")
|
||||
}
|
||||
|
@ -538,3 +558,178 @@ func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindRes
|
|||
err = GetLDAPError(packet)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GSSAPIClient interface is used as the client-side implementation for the
|
||||
// GSSAPI SASL mechanism.
|
||||
// Interface inspired by GSSAPIClient from golang.org/x/crypto/ssh
|
||||
type GSSAPIClient interface {
|
||||
// InitSecContext initiates the establishment of a security context for
|
||||
// GSS-API between the client and server.
|
||||
// Initially the token parameter should be specified as nil.
|
||||
// The routine may return a outputToken which should be transferred to
|
||||
// the server, where the server will present it to AcceptSecContext.
|
||||
// If no token need be sent, InitSecContext will indicate this by setting
|
||||
// needContinue to false. To complete the context
|
||||
// establishment, one or more reply tokens may be required from the server;
|
||||
// if so, InitSecContext will return a needContinue which is true.
|
||||
// In this case, InitSecContext should be called again when the
|
||||
// reply token is received from the server, passing the reply token
|
||||
// to InitSecContext via the token parameters.
|
||||
// See RFC 4752 section 3.1.
|
||||
InitSecContext(target string, token []byte) (outputToken []byte, needContinue bool, err error)
|
||||
// NegotiateSaslAuth performs the last step of the Sasl handshake.
|
||||
// It takes a token, which, when unwrapped, describes the servers supported
|
||||
// security layers (first octet) and maximum receive buffer (remaining
|
||||
// three octets).
|
||||
// If the received token is unacceptable an error must be returned to abort
|
||||
// the handshake.
|
||||
// Outputs a signed token describing the client's selected security layer
|
||||
// and receive buffer size and optionally an authorization identity.
|
||||
// The returned token will be sent to the server and the handshake considered
|
||||
// completed successfully and the server authenticated.
|
||||
// See RFC 4752 section 3.1.
|
||||
NegotiateSaslAuth(token []byte, authzid string) ([]byte, error)
|
||||
// DeleteSecContext destroys any established secure context.
|
||||
DeleteSecContext() error
|
||||
}
|
||||
|
||||
// GSSAPIBindRequest represents a GSSAPI SASL mechanism bind request.
|
||||
// See rfc4752 and rfc4513 section 5.2.1.2.
|
||||
type GSSAPIBindRequest struct {
|
||||
// Service Principal Name user for the service ticket. Eg. "ldap/<host>"
|
||||
ServicePrincipalName string
|
||||
// (Optional) Authorization entity
|
||||
AuthZID string
|
||||
// (Optional) Controls to send with the bind request
|
||||
Controls []Control
|
||||
}
|
||||
|
||||
// GSSAPIBind performs the GSSAPI SASL bind using the provided GSSAPI client.
|
||||
func (l *Conn) GSSAPIBind(client GSSAPIClient, servicePrincipal, authzid string) error {
|
||||
return l.GSSAPIBindRequest(client, &GSSAPIBindRequest{
|
||||
ServicePrincipalName: servicePrincipal,
|
||||
AuthZID: authzid,
|
||||
})
|
||||
}
|
||||
|
||||
// GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client.
|
||||
func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) error {
|
||||
//nolint:errcheck
|
||||
defer client.DeleteSecContext()
|
||||
|
||||
var err error
|
||||
var reqToken []byte
|
||||
var recvToken []byte
|
||||
needInit := true
|
||||
for {
|
||||
if needInit {
|
||||
// Establish secure context between client and server.
|
||||
reqToken, needInit, err = client.InitSecContext(req.ServicePrincipalName, recvToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Secure context is set up, perform the last step of SASL handshake.
|
||||
reqToken, err = client.NegotiateSaslAuth(recvToken, req.AuthZID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Send Bind request containing the current token and extract the
|
||||
// token sent by server.
|
||||
recvToken, err = l.saslBindTokenExchange(req.Controls, reqToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !needInit && len(recvToken) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Conn) saslBindTokenExchange(reqControls []Control, reqToken []byte) ([]byte, error) {
|
||||
// Construct LDAP Bind request with GSSAPI SASL mechanism.
|
||||
envelope := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
||||
envelope.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
||||
|
||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
|
||||
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
|
||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
|
||||
|
||||
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
|
||||
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "GSSAPI", "SASL Mech"))
|
||||
if len(reqToken) > 0 {
|
||||
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(reqToken), "Credentials"))
|
||||
}
|
||||
request.AppendChild(auth)
|
||||
envelope.AppendChild(request)
|
||||
if len(reqControls) > 0 {
|
||||
envelope.AppendChild(encodeControls(reqControls))
|
||||
}
|
||||
|
||||
msgCtx, err := l.sendMessage(envelope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer l.finishMessage(msgCtx)
|
||||
|
||||
packet, err := l.readPacket(msgCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
||||
if l.Debug {
|
||||
if err = addLDAPDescriptions(packet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ber.PrintPacket(packet)
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc4511#section-4.1.1
|
||||
// packet is an envelope
|
||||
// child 0 is message id
|
||||
// child 1 is protocolOp
|
||||
if len(packet.Children) != 2 {
|
||||
return nil, fmt.Errorf("bad bind response")
|
||||
}
|
||||
|
||||
protocolOp := packet.Children[1]
|
||||
RESP:
|
||||
switch protocolOp.Description {
|
||||
case "Bind Response": // Bind Response
|
||||
// Bind Reponse is an LDAP Response (https://www.rfc-editor.org/rfc/rfc4511#section-4.1.9)
|
||||
// with an additional optional serverSaslCreds string (https://www.rfc-editor.org/rfc/rfc4511#section-4.2.2)
|
||||
// child 0 is resultCode
|
||||
resultCode := protocolOp.Children[0]
|
||||
if resultCode.Tag != ber.TagEnumerated {
|
||||
break RESP
|
||||
}
|
||||
switch resultCode.Value.(int64) {
|
||||
case 14: // Sasl bind in progress
|
||||
if len(protocolOp.Children) < 3 {
|
||||
break RESP
|
||||
}
|
||||
referral := protocolOp.Children[3]
|
||||
switch referral.Description {
|
||||
case "Referral":
|
||||
if referral.ClassType != ber.ClassContext || referral.Tag != ber.TagObjectDescriptor {
|
||||
break RESP
|
||||
}
|
||||
return ioutil.ReadAll(referral.Data)
|
||||
}
|
||||
// Optional:
|
||||
//if len(protocolOp.Children) == 4 {
|
||||
// serverSaslCreds := protocolOp.Children[4]
|
||||
//}
|
||||
case 0: // Success - Bind OK.
|
||||
// SASL layer in effect (if any) (See https://www.rfc-editor.org/rfc/rfc4513#section-5.2.1.4)
|
||||
// NOTE: SASL security layers are not supported currently.
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, GetLDAPError(packet)
|
||||
}
|
||||
|
|
11
vendor/github.com/go-ldap/ldap/v3/client.go
generated
vendored
11
vendor/github.com/go-ldap/ldap/v3/client.go
generated
vendored
|
@ -1,6 +1,7 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
)
|
||||
|
@ -9,14 +10,18 @@ import (
|
|||
type Client interface {
|
||||
Start()
|
||||
StartTLS(*tls.Config) error
|
||||
Close()
|
||||
Close() error
|
||||
GetLastError() error
|
||||
IsClosing() bool
|
||||
SetTimeout(time.Duration)
|
||||
TLSConnectionState() (tls.ConnectionState, bool)
|
||||
|
||||
Bind(username, password string) error
|
||||
UnauthenticatedBind(username string) error
|
||||
SimpleBind(*SimpleBindRequest) (*SimpleBindResult, error)
|
||||
ExternalBind() error
|
||||
NTLMUnauthenticatedBind(domain, username string) error
|
||||
Unbind() error
|
||||
|
||||
Add(*AddRequest) error
|
||||
Del(*DelRequest) error
|
||||
|
@ -28,5 +33,9 @@ type Client interface {
|
|||
PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)
|
||||
|
||||
Search(*SearchRequest) (*SearchResult, error)
|
||||
SearchAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response
|
||||
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
|
||||
DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error)
|
||||
DirSyncAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte) Response
|
||||
Syncrepl(ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool) Response
|
||||
}
|
||||
|
|
3
vendor/github.com/go-ldap/ldap/v3/compare.go
generated
vendored
3
vendor/github.com/go-ldap/ldap/v3/compare.go
generated
vendored
|
@ -34,7 +34,8 @@ func (l *Conn) Compare(dn, attribute, value string) (bool, error) {
|
|||
msgCtx, err := l.doRequest(&CompareRequest{
|
||||
DN: dn,
|
||||
Attribute: attribute,
|
||||
Value: value})
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
126
vendor/github.com/go-ldap/ldap/v3/conn.go
generated
vendored
126
vendor/github.com/go-ldap/ldap/v3/conn.go
generated
vendored
|
@ -2,10 +2,10 @@ package ldap
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
@ -61,13 +61,21 @@ type messageContext struct {
|
|||
|
||||
// sendResponse should only be called within the processMessages() loop which
|
||||
// is also responsible for closing the responses channel.
|
||||
func (msgCtx *messageContext) sendResponse(packet *PacketResponse) {
|
||||
func (msgCtx *messageContext) sendResponse(packet *PacketResponse, timeout time.Duration) {
|
||||
timeoutCtx := context.Background()
|
||||
if timeout > 0 {
|
||||
var cancelFunc context.CancelFunc
|
||||
timeoutCtx, cancelFunc = context.WithTimeout(context.Background(), timeout)
|
||||
defer cancelFunc()
|
||||
}
|
||||
select {
|
||||
case msgCtx.responses <- packet:
|
||||
// Successfully sent packet to message handler.
|
||||
case <-msgCtx.done:
|
||||
// The request handler is done and will not receive more
|
||||
// packets.
|
||||
case <-timeoutCtx.Done():
|
||||
// The timeout was reached before the packet was sent.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,6 +96,7 @@ const (
|
|||
type Conn struct {
|
||||
// requestTimeout is loaded atomically
|
||||
// so we need to ensure 64-bit alignment on 32-bit platforms.
|
||||
// https://github.com/go-ldap/ldap/pull/199
|
||||
requestTimeout int64
|
||||
conn net.Conn
|
||||
isTLS bool
|
||||
|
@ -102,6 +111,8 @@ type Conn struct {
|
|||
wgClose sync.WaitGroup
|
||||
outstandingRequests uint
|
||||
messageMutex sync.Mutex
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
var _ Client = &Conn{}
|
||||
|
@ -119,30 +130,31 @@ type DialOpt func(*DialContext)
|
|||
// DialWithDialer updates net.Dialer in DialContext.
|
||||
func DialWithDialer(d *net.Dialer) DialOpt {
|
||||
return func(dc *DialContext) {
|
||||
dc.d = d
|
||||
dc.dialer = d
|
||||
}
|
||||
}
|
||||
|
||||
// DialWithTLSConfig updates tls.Config in DialContext.
|
||||
func DialWithTLSConfig(tc *tls.Config) DialOpt {
|
||||
return func(dc *DialContext) {
|
||||
dc.tc = tc
|
||||
dc.tlsConfig = tc
|
||||
}
|
||||
}
|
||||
|
||||
// DialWithTLSDialer is a wrapper for DialWithTLSConfig with the option to
|
||||
// specify a net.Dialer to for example define a timeout or a custom resolver.
|
||||
// @deprecated Use DialWithDialer and DialWithTLSConfig instead
|
||||
func DialWithTLSDialer(tlsConfig *tls.Config, dialer *net.Dialer) DialOpt {
|
||||
return func(dc *DialContext) {
|
||||
dc.tc = tlsConfig
|
||||
dc.d = dialer
|
||||
dc.tlsConfig = tlsConfig
|
||||
dc.dialer = dialer
|
||||
}
|
||||
}
|
||||
|
||||
// DialContext contains necessary parameters to dial the given ldap URL.
|
||||
type DialContext struct {
|
||||
d *net.Dialer
|
||||
tc *tls.Config
|
||||
dialer *net.Dialer
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
func (dc *DialContext) dial(u *url.URL) (net.Conn, error) {
|
||||
|
@ -150,7 +162,7 @@ func (dc *DialContext) dial(u *url.URL) (net.Conn, error) {
|
|||
if u.Path == "" || u.Path == "/" {
|
||||
u.Path = "/var/run/slapd/ldapi"
|
||||
}
|
||||
return dc.d.Dial("unix", u.Path)
|
||||
return dc.dialer.Dial("unix", u.Path)
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
|
@ -161,16 +173,21 @@ func (dc *DialContext) dial(u *url.URL) (net.Conn, error) {
|
|||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "cldap":
|
||||
if port == "" {
|
||||
port = DefaultLdapPort
|
||||
}
|
||||
return dc.dialer.Dial("udp", net.JoinHostPort(host, port))
|
||||
case "ldap":
|
||||
if port == "" {
|
||||
port = DefaultLdapPort
|
||||
}
|
||||
return dc.d.Dial("tcp", net.JoinHostPort(host, port))
|
||||
return dc.dialer.Dial("tcp", net.JoinHostPort(host, port))
|
||||
case "ldaps":
|
||||
if port == "" {
|
||||
port = DefaultLdapsPort
|
||||
}
|
||||
return tls.DialWithDialer(dc.d, "tcp", net.JoinHostPort(host, port), dc.tc)
|
||||
return tls.DialWithDialer(dc.dialer, "tcp", net.JoinHostPort(host, port), dc.tlsConfig)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unknown scheme '%s'", u.Scheme)
|
||||
|
@ -203,7 +220,8 @@ func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
|
|||
}
|
||||
|
||||
// DialURL connects to the given ldap URL.
|
||||
// The following schemas are supported: ldap://, ldaps://, ldapi://.
|
||||
// The following schemas are supported: ldap://, ldaps://, ldapi://,
|
||||
// and cldap:// (RFC1798, deprecated but used by Active Directory).
|
||||
// On success a new Conn for the connection is returned.
|
||||
func DialURL(addr string, opts ...DialOpt) (*Conn, error) {
|
||||
u, err := url.Parse(addr)
|
||||
|
@ -215,8 +233,8 @@ func DialURL(addr string, opts ...DialOpt) (*Conn, error) {
|
|||
for _, opt := range opts {
|
||||
opt(&dc)
|
||||
}
|
||||
if dc.d == nil {
|
||||
dc.d = &net.Dialer{Timeout: DefaultTimeout}
|
||||
if dc.dialer == nil {
|
||||
dc.dialer = &net.Dialer{Timeout: DefaultTimeout}
|
||||
}
|
||||
|
||||
c, err := dc.dial(u)
|
||||
|
@ -231,7 +249,7 @@ func DialURL(addr string, opts ...DialOpt) (*Conn, error) {
|
|||
|
||||
// NewConn returns a new Conn using conn for network I/O.
|
||||
func NewConn(conn net.Conn, isTLS bool) *Conn {
|
||||
return &Conn{
|
||||
l := &Conn{
|
||||
conn: conn,
|
||||
chanConfirm: make(chan struct{}),
|
||||
chanMessageID: make(chan int64),
|
||||
|
@ -240,11 +258,12 @@ func NewConn(conn net.Conn, isTLS bool) *Conn {
|
|||
requestTimeout: 0,
|
||||
isTLS: isTLS,
|
||||
}
|
||||
l.wgClose.Add(1)
|
||||
return l
|
||||
}
|
||||
|
||||
// Start initializes goroutines to read responses and process messages
|
||||
func (l *Conn) Start() {
|
||||
l.wgClose.Add(1)
|
||||
go l.reader()
|
||||
go l.processMessages()
|
||||
}
|
||||
|
@ -260,31 +279,45 @@ func (l *Conn) setClosing() bool {
|
|||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (l *Conn) Close() {
|
||||
func (l *Conn) Close() (err error) {
|
||||
l.messageMutex.Lock()
|
||||
defer l.messageMutex.Unlock()
|
||||
|
||||
if l.setClosing() {
|
||||
l.Debug.Printf("Sending quit message and waiting for confirmation")
|
||||
l.chanMessage <- &messagePacket{Op: MessageQuit}
|
||||
<-l.chanConfirm
|
||||
|
||||
timeoutCtx := context.Background()
|
||||
if l.getTimeout() > 0 {
|
||||
var cancelFunc context.CancelFunc
|
||||
timeoutCtx, cancelFunc = context.WithTimeout(timeoutCtx, time.Duration(l.getTimeout()))
|
||||
defer cancelFunc()
|
||||
}
|
||||
select {
|
||||
case <-l.chanConfirm:
|
||||
// Confirmation was received.
|
||||
case <-timeoutCtx.Done():
|
||||
// The timeout was reached before confirmation was received.
|
||||
}
|
||||
|
||||
close(l.chanMessage)
|
||||
|
||||
l.Debug.Printf("Closing network connection")
|
||||
if err := l.conn.Close(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
err = l.conn.Close()
|
||||
l.wgClose.Done()
|
||||
}
|
||||
l.wgClose.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SetTimeout sets the time after a request is sent that a MessageTimeout triggers
|
||||
func (l *Conn) SetTimeout(timeout time.Duration) {
|
||||
if timeout > 0 {
|
||||
atomic.StoreInt64(&l.requestTimeout, int64(timeout))
|
||||
}
|
||||
atomic.StoreInt64(&l.requestTimeout, int64(timeout))
|
||||
}
|
||||
|
||||
func (l *Conn) getTimeout() int64 {
|
||||
return atomic.LoadInt64(&l.requestTimeout)
|
||||
}
|
||||
|
||||
// Returns the next available messageID
|
||||
|
@ -295,6 +328,14 @@ func (l *Conn) nextMessageID() int64 {
|
|||
return 0
|
||||
}
|
||||
|
||||
// GetLastError returns the last recorded error from goroutines like processMessages and reader.
|
||||
// Only the last recorded error will be returned.
|
||||
func (l *Conn) GetLastError() error {
|
||||
l.messageMutex.Lock()
|
||||
defer l.messageMutex.Unlock()
|
||||
return l.err
|
||||
}
|
||||
|
||||
// StartTLS sends the command to start a TLS session and then creates a new TLS Client
|
||||
func (l *Conn) StartTLS(config *tls.Config) error {
|
||||
if l.isTLS {
|
||||
|
@ -443,13 +484,13 @@ func (l *Conn) sendProcessMessage(message *messagePacket) bool {
|
|||
func (l *Conn) processMessages() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("ldap: recovered panic in processMessages: %v", err)
|
||||
l.err = fmt.Errorf("ldap: recovered panic in processMessages: %v", err)
|
||||
}
|
||||
for messageID, msgCtx := range l.messageContexts {
|
||||
// If we are closing due to an error, inform anyone who
|
||||
// is waiting about the error.
|
||||
if l.IsClosing() && l.closeErr.Load() != nil {
|
||||
msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)})
|
||||
msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)}, time.Duration(l.getTimeout()))
|
||||
}
|
||||
l.Debug.Printf("Closing channel for MessageID %d", messageID)
|
||||
close(msgCtx.responses)
|
||||
|
@ -477,7 +518,7 @@ func (l *Conn) processMessages() {
|
|||
_, err := l.conn.Write(buf)
|
||||
if err != nil {
|
||||
l.Debug.Printf("Error Sending Message: %s", err.Error())
|
||||
message.Context.sendResponse(&PacketResponse{Error: fmt.Errorf("unable to send request: %s", err)})
|
||||
message.Context.sendResponse(&PacketResponse{Error: fmt.Errorf("unable to send request: %s", err)}, time.Duration(l.getTimeout()))
|
||||
close(message.Context.responses)
|
||||
break
|
||||
}
|
||||
|
@ -487,28 +528,35 @@ func (l *Conn) processMessages() {
|
|||
l.messageContexts[message.MessageID] = message.Context
|
||||
|
||||
// Add timeout if defined
|
||||
requestTimeout := time.Duration(atomic.LoadInt64(&l.requestTimeout))
|
||||
requestTimeout := l.getTimeout()
|
||||
if requestTimeout > 0 {
|
||||
go func() {
|
||||
timer := time.NewTimer(time.Duration(requestTimeout))
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("ldap: recovered panic in RequestTimeout: %v", err)
|
||||
l.err = fmt.Errorf("ldap: recovered panic in RequestTimeout: %v", err)
|
||||
}
|
||||
|
||||
timer.Stop()
|
||||
}()
|
||||
time.Sleep(requestTimeout)
|
||||
timeoutMessage := &messagePacket{
|
||||
Op: MessageTimeout,
|
||||
MessageID: message.MessageID,
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
timeoutMessage := &messagePacket{
|
||||
Op: MessageTimeout,
|
||||
MessageID: message.MessageID,
|
||||
}
|
||||
l.sendProcessMessage(timeoutMessage)
|
||||
case <-message.Context.done:
|
||||
}
|
||||
l.sendProcessMessage(timeoutMessage)
|
||||
}()
|
||||
}
|
||||
case MessageResponse:
|
||||
l.Debug.Printf("Receiving message %d", message.MessageID)
|
||||
if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
|
||||
msgCtx.sendResponse(&PacketResponse{message.Packet, nil})
|
||||
msgCtx.sendResponse(&PacketResponse{message.Packet, nil}, time.Duration(l.getTimeout()))
|
||||
} else {
|
||||
log.Printf("Received unexpected message %d, %v", message.MessageID, l.IsClosing())
|
||||
l.err = fmt.Errorf("ldap: received unexpected message %d, %v", message.MessageID, l.IsClosing())
|
||||
l.Debug.PrintPacket(message.Packet)
|
||||
}
|
||||
case MessageTimeout:
|
||||
|
@ -516,7 +564,7 @@ func (l *Conn) processMessages() {
|
|||
// All reads will return immediately
|
||||
if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
|
||||
l.Debug.Printf("Receiving message timeout for %d", message.MessageID)
|
||||
msgCtx.sendResponse(&PacketResponse{message.Packet, NewError(ErrorNetwork, errors.New("ldap: connection timed out"))})
|
||||
msgCtx.sendResponse(&PacketResponse{message.Packet, NewError(ErrorNetwork, errors.New("ldap: connection timed out"))}, time.Duration(l.getTimeout()))
|
||||
delete(l.messageContexts, message.MessageID)
|
||||
close(msgCtx.responses)
|
||||
}
|
||||
|
@ -535,7 +583,7 @@ func (l *Conn) reader() {
|
|||
cleanstop := false
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("ldap: recovered panic in reader: %v", err)
|
||||
l.err = fmt.Errorf("ldap: recovered panic in reader: %v", err)
|
||||
}
|
||||
if !cleanstop {
|
||||
l.Close()
|
||||
|
|
788
vendor/github.com/go-ldap/ldap/v3/control.go
generated
vendored
788
vendor/github.com/go-ldap/ldap/v3/control.go
generated
vendored
|
@ -5,6 +5,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -20,6 +21,13 @@ const (
|
|||
ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
|
||||
// ControlTypeWhoAmI - https://tools.ietf.org/html/rfc4532
|
||||
ControlTypeWhoAmI = "1.3.6.1.4.1.4203.1.11.3"
|
||||
// ControlTypeSubtreeDelete - https://datatracker.ietf.org/doc/html/draft-armijo-ldap-treedelete-02
|
||||
ControlTypeSubtreeDelete = "1.2.840.113556.1.4.805"
|
||||
|
||||
// ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt
|
||||
ControlTypeServerSideSorting = "1.2.840.113556.1.4.473"
|
||||
// ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt
|
||||
ControlTypeServerSideSortingResult = "1.2.840.113556.1.4.474"
|
||||
|
||||
// ControlTypeMicrosoftNotification - https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx
|
||||
ControlTypeMicrosoftNotification = "1.2.840.113556.1.4.528"
|
||||
|
@ -27,16 +35,43 @@ const (
|
|||
ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417"
|
||||
// ControlTypeMicrosoftServerLinkTTL - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN
|
||||
ControlTypeMicrosoftServerLinkTTL = "1.2.840.113556.1.4.2309"
|
||||
// ControlTypeDirSync - Active Directory DirSync - https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx
|
||||
ControlTypeDirSync = "1.2.840.113556.1.4.841"
|
||||
|
||||
// ControlTypeSyncRequest - https://www.ietf.org/rfc/rfc4533.txt
|
||||
ControlTypeSyncRequest = "1.3.6.1.4.1.4203.1.9.1.1"
|
||||
// ControlTypeSyncState - https://www.ietf.org/rfc/rfc4533.txt
|
||||
ControlTypeSyncState = "1.3.6.1.4.1.4203.1.9.1.2"
|
||||
// ControlTypeSyncDone - https://www.ietf.org/rfc/rfc4533.txt
|
||||
ControlTypeSyncDone = "1.3.6.1.4.1.4203.1.9.1.3"
|
||||
// ControlTypeSyncInfo - https://www.ietf.org/rfc/rfc4533.txt
|
||||
ControlTypeSyncInfo = "1.3.6.1.4.1.4203.1.9.1.4"
|
||||
)
|
||||
|
||||
// Flags for DirSync control
|
||||
const (
|
||||
DirSyncIncrementalValues int64 = 2147483648
|
||||
DirSyncPublicDataOnly int64 = 8192
|
||||
DirSyncAncestorsFirstOrder int64 = 2048
|
||||
DirSyncObjectSecurity int64 = 1
|
||||
)
|
||||
|
||||
// ControlTypeMap maps controls to text descriptions
|
||||
var ControlTypeMap = map[string]string{
|
||||
ControlTypePaging: "Paging",
|
||||
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
|
||||
ControlTypeManageDsaIT: "Manage DSA IT",
|
||||
ControlTypeMicrosoftNotification: "Change Notification - Microsoft",
|
||||
ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft",
|
||||
ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft",
|
||||
ControlTypePaging: "Paging",
|
||||
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
|
||||
ControlTypeManageDsaIT: "Manage DSA IT",
|
||||
ControlTypeSubtreeDelete: "Subtree Delete Control",
|
||||
ControlTypeMicrosoftNotification: "Change Notification - Microsoft",
|
||||
ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft",
|
||||
ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft",
|
||||
ControlTypeServerSideSorting: "Server Side Sorting Request - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
|
||||
ControlTypeServerSideSortingResult: "Server Side Sorting Results - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
|
||||
ControlTypeDirSync: "DirSync",
|
||||
ControlTypeSyncRequest: "Sync Request",
|
||||
ControlTypeSyncState: "Sync State",
|
||||
ControlTypeSyncDone: "Sync Done",
|
||||
ControlTypeSyncInfo: "Sync Info",
|
||||
}
|
||||
|
||||
// Control defines an interface controls provide to encode and describe themselves
|
||||
|
@ -229,7 +264,7 @@ func (c *ControlManageDsaIT) GetControlType() string {
|
|||
|
||||
// Encode returns the ber packet representation
|
||||
func (c *ControlManageDsaIT) Encode() *ber.Packet {
|
||||
//FIXME
|
||||
// FIXME
|
||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeManageDsaIT, "Control Type ("+ControlTypeMap[ControlTypeManageDsaIT]+")"))
|
||||
if c.Criticality {
|
||||
|
@ -369,7 +404,13 @@ func DecodeControl(packet *ber.Packet) (Control, error) {
|
|||
|
||||
case 2:
|
||||
packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
|
||||
ControlType = packet.Children[0].Value.(string)
|
||||
if packet.Children[0].Value != nil {
|
||||
ControlType = packet.Children[0].Value.(string)
|
||||
} else if packet.Children[0].Data != nil {
|
||||
ControlType = packet.Children[0].Data.String()
|
||||
} else {
|
||||
return nil, fmt.Errorf("not found where to get the control type")
|
||||
}
|
||||
|
||||
// Children[1] could be criticality or value (both are optional)
|
||||
// duck-type on whether this is a boolean
|
||||
|
@ -436,18 +477,18 @@ func DecodeControl(packet *ber.Packet) (Control, error) {
|
|||
|
||||
for _, child := range sequence.Children {
|
||||
if child.Tag == 0 {
|
||||
//Warning
|
||||
// Warning
|
||||
warningPacket := child.Children[0]
|
||||
val, err := ber.ParseInt64(warningPacket.Data.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
|
||||
}
|
||||
if warningPacket.Tag == 0 {
|
||||
//timeBeforeExpiration
|
||||
// timeBeforeExpiration
|
||||
c.Expire = val
|
||||
warningPacket.Value = c.Expire
|
||||
} else if warningPacket.Tag == 1 {
|
||||
//graceAuthNsRemaining
|
||||
// graceAuthNsRemaining
|
||||
c.Grace = val
|
||||
warningPacket.Value = c.Grace
|
||||
}
|
||||
|
@ -485,6 +526,36 @@ func DecodeControl(packet *ber.Packet) (Control, error) {
|
|||
return NewControlMicrosoftShowDeleted(), nil
|
||||
case ControlTypeMicrosoftServerLinkTTL:
|
||||
return NewControlMicrosoftServerLinkTTL(), nil
|
||||
case ControlTypeSubtreeDelete:
|
||||
return NewControlSubtreeDelete(), nil
|
||||
case ControlTypeServerSideSorting:
|
||||
return NewControlServerSideSorting(value)
|
||||
case ControlTypeServerSideSortingResult:
|
||||
return NewControlServerSideSortingResult(value)
|
||||
case ControlTypeDirSync:
|
||||
value.Description += " (DirSync)"
|
||||
return NewResponseControlDirSync(value)
|
||||
case ControlTypeSyncState:
|
||||
value.Description += " (Sync State)"
|
||||
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
|
||||
}
|
||||
return NewControlSyncState(valueChildren)
|
||||
case ControlTypeSyncDone:
|
||||
value.Description += " (Sync Done)"
|
||||
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
|
||||
}
|
||||
return NewControlSyncDone(valueChildren)
|
||||
case ControlTypeSyncInfo:
|
||||
value.Description += " (Sync Info)"
|
||||
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
|
||||
}
|
||||
return NewControlSyncInfo(valueChildren)
|
||||
default:
|
||||
c := new(ControlString)
|
||||
c.ControlType = ControlType
|
||||
|
@ -519,6 +590,35 @@ func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy {
|
|||
}
|
||||
}
|
||||
|
||||
// ControlSubtreeDelete implements the subtree delete control described in
|
||||
// https://datatracker.ietf.org/doc/html/draft-armijo-ldap-treedelete-02
|
||||
type ControlSubtreeDelete struct{}
|
||||
|
||||
// GetControlType returns the OID
|
||||
func (c *ControlSubtreeDelete) GetControlType() string {
|
||||
return ControlTypeSubtreeDelete
|
||||
}
|
||||
|
||||
// NewControlSubtreeDelete returns a ControlSubtreeDelete control.
|
||||
func NewControlSubtreeDelete() *ControlSubtreeDelete {
|
||||
return &ControlSubtreeDelete{}
|
||||
}
|
||||
|
||||
// Encode returns the ber packet representation
|
||||
func (c *ControlSubtreeDelete) Encode() *ber.Packet {
|
||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeSubtreeDelete, "Control Type ("+ControlTypeMap[ControlTypeSubtreeDelete]+")"))
|
||||
|
||||
return packet
|
||||
}
|
||||
|
||||
func (c *ControlSubtreeDelete) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Control Type: %s (%q)",
|
||||
ControlTypeMap[ControlTypeSubtreeDelete],
|
||||
ControlTypeSubtreeDelete)
|
||||
}
|
||||
|
||||
func encodeControls(controls []Control) *ber.Packet {
|
||||
packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls")
|
||||
for _, control := range controls {
|
||||
|
@ -526,3 +626,669 @@ func encodeControls(controls []Control) *ber.Packet {
|
|||
}
|
||||
return packet
|
||||
}
|
||||
|
||||
// ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx
|
||||
type ControlDirSync struct {
|
||||
Criticality bool
|
||||
Flags int64
|
||||
MaxAttrCount int64
|
||||
Cookie []byte
|
||||
}
|
||||
|
||||
// @deprecated Use NewRequestControlDirSync instead
|
||||
func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync {
|
||||
return NewRequestControlDirSync(flags, maxAttrCount, cookie)
|
||||
}
|
||||
|
||||
// NewRequestControlDirSync returns a dir sync control
|
||||
func NewRequestControlDirSync(
|
||||
flags int64, maxAttrCount int64, cookie []byte,
|
||||
) *ControlDirSync {
|
||||
return &ControlDirSync{
|
||||
Criticality: true,
|
||||
Flags: flags,
|
||||
MaxAttrCount: maxAttrCount,
|
||||
Cookie: cookie,
|
||||
}
|
||||
}
|
||||
|
||||
// NewResponseControlDirSync returns a dir sync control
|
||||
func NewResponseControlDirSync(value *ber.Packet) (*ControlDirSync, error) {
|
||||
if value.Value != nil {
|
||||
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
|
||||
}
|
||||
value.Data.Truncate(0)
|
||||
value.Value = nil
|
||||
value.AppendChild(valueChildren)
|
||||
}
|
||||
child := value.Children[0]
|
||||
if len(child.Children) != 3 { // also on initial creation, Cookie is an empty string
|
||||
return nil, fmt.Errorf("invalid number of children in dirSync control")
|
||||
}
|
||||
child.Description = "DirSync Control Value"
|
||||
child.Children[0].Description = "Flags"
|
||||
child.Children[1].Description = "MaxAttrCount"
|
||||
child.Children[2].Description = "Cookie"
|
||||
|
||||
cookie := child.Children[2].Data.Bytes()
|
||||
child.Children[2].Value = cookie
|
||||
return &ControlDirSync{
|
||||
Criticality: true,
|
||||
Flags: child.Children[0].Value.(int64),
|
||||
MaxAttrCount: child.Children[1].Value.(int64),
|
||||
Cookie: cookie,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetControlType returns the OID
|
||||
func (c *ControlDirSync) GetControlType() string {
|
||||
return ControlTypeDirSync
|
||||
}
|
||||
|
||||
// String returns a human-readable description
|
||||
func (c *ControlDirSync) String() string {
|
||||
return fmt.Sprintf(
|
||||
"ControlType: %s (%q) Criticality: %t ControlValue: Flags: %d MaxAttrCount: %d",
|
||||
ControlTypeMap[ControlTypeDirSync],
|
||||
ControlTypeDirSync,
|
||||
c.Criticality,
|
||||
c.Flags,
|
||||
c.MaxAttrCount,
|
||||
)
|
||||
}
|
||||
|
||||
// Encode returns the ber packet representation
|
||||
func (c *ControlDirSync) Encode() *ber.Packet {
|
||||
cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie")
|
||||
if len(c.Cookie) != 0 {
|
||||
cookie.Value = c.Cookie
|
||||
cookie.Data.Write(c.Cookie)
|
||||
}
|
||||
|
||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")"))
|
||||
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) // must be true always
|
||||
|
||||
val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)")
|
||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value")
|
||||
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags"))
|
||||
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCount), "MaxAttrCount"))
|
||||
seq.AppendChild(cookie)
|
||||
val.AppendChild(seq)
|
||||
|
||||
packet.AppendChild(val)
|
||||
return packet
|
||||
}
|
||||
|
||||
// SetCookie stores the given cookie in the dirSync control
|
||||
func (c *ControlDirSync) SetCookie(cookie []byte) {
|
||||
c.Cookie = cookie
|
||||
}
|
||||
|
||||
// ControlServerSideSorting
|
||||
|
||||
type SortKey struct {
|
||||
Reverse bool
|
||||
AttributeType string
|
||||
MatchingRule string
|
||||
}
|
||||
|
||||
type ControlServerSideSorting struct {
|
||||
SortKeys []*SortKey
|
||||
}
|
||||
|
||||
func (c *ControlServerSideSorting) GetControlType() string {
|
||||
return ControlTypeServerSideSorting
|
||||
}
|
||||
|
||||
func NewControlServerSideSorting(value *ber.Packet) (*ControlServerSideSorting, error) {
|
||||
sortKeys := []*SortKey{}
|
||||
|
||||
val := value.Children[1].Children
|
||||
|
||||
if len(val) != 1 {
|
||||
return nil, fmt.Errorf("no sequence value in packet")
|
||||
}
|
||||
|
||||
sequences := val[0].Children
|
||||
|
||||
for i, sequence := range sequences {
|
||||
sortKey := &SortKey{}
|
||||
|
||||
if len(sequence.Children) < 2 {
|
||||
return nil, fmt.Errorf("attributeType or matchingRule is missing from sequence %d", i)
|
||||
}
|
||||
|
||||
sortKey.AttributeType = sequence.Children[0].Value.(string)
|
||||
sortKey.MatchingRule = sequence.Children[1].Value.(string)
|
||||
|
||||
if len(sequence.Children) == 3 {
|
||||
sortKey.Reverse = sequence.Children[2].Value.(bool)
|
||||
}
|
||||
|
||||
sortKeys = append(sortKeys, sortKey)
|
||||
}
|
||||
|
||||
return &ControlServerSideSorting{SortKeys: sortKeys}, nil
|
||||
}
|
||||
|
||||
func NewControlServerSideSortingWithSortKeys(sortKeys []*SortKey) *ControlServerSideSorting {
|
||||
return &ControlServerSideSorting{SortKeys: sortKeys}
|
||||
}
|
||||
|
||||
func (c *ControlServerSideSorting) Encode() *ber.Packet {
|
||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
||||
control := ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.GetControlType(), "Control Type")
|
||||
|
||||
value := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value")
|
||||
seqs := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortKeyList")
|
||||
|
||||
for _, f := range c.SortKeys {
|
||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "")
|
||||
|
||||
seq.AppendChild(
|
||||
ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, f.AttributeType, "attributeType"),
|
||||
)
|
||||
seq.AppendChild(
|
||||
ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, f.MatchingRule, "orderingRule"),
|
||||
)
|
||||
if f.Reverse {
|
||||
seq.AppendChild(
|
||||
ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, 1, f.Reverse, "reverseOrder"),
|
||||
)
|
||||
}
|
||||
|
||||
seqs.AppendChild(seq)
|
||||
}
|
||||
|
||||
value.AppendChild(seqs)
|
||||
|
||||
packet.AppendChild(control)
|
||||
packet.AppendChild(value)
|
||||
|
||||
return packet
|
||||
}
|
||||
|
||||
func (c *ControlServerSideSorting) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Control Type: %s (%q) Criticality:%t %+v",
|
||||
"Server Side Sorting",
|
||||
c.GetControlType(),
|
||||
false,
|
||||
c.SortKeys,
|
||||
)
|
||||
}
|
||||
|
||||
// ControlServerSideSortingResponse
|
||||
|
||||
const (
|
||||
ControlServerSideSortingCodeSuccess ControlServerSideSortingCode = 0
|
||||
ControlServerSideSortingCodeOperationsError ControlServerSideSortingCode = 1
|
||||
ControlServerSideSortingCodeTimeLimitExceeded ControlServerSideSortingCode = 2
|
||||
ControlServerSideSortingCodeStrongAuthRequired ControlServerSideSortingCode = 8
|
||||
ControlServerSideSortingCodeAdminLimitExceeded ControlServerSideSortingCode = 11
|
||||
ControlServerSideSortingCodeNoSuchAttribute ControlServerSideSortingCode = 16
|
||||
ControlServerSideSortingCodeInappropriateMatching ControlServerSideSortingCode = 18
|
||||
ControlServerSideSortingCodeInsufficientAccessRights ControlServerSideSortingCode = 50
|
||||
ControlServerSideSortingCodeBusy ControlServerSideSortingCode = 51
|
||||
ControlServerSideSortingCodeUnwillingToPerform ControlServerSideSortingCode = 53
|
||||
ControlServerSideSortingCodeOther ControlServerSideSortingCode = 80
|
||||
)
|
||||
|
||||
var ControlServerSideSortingCodes = []ControlServerSideSortingCode{
|
||||
ControlServerSideSortingCodeSuccess,
|
||||
ControlServerSideSortingCodeOperationsError,
|
||||
ControlServerSideSortingCodeTimeLimitExceeded,
|
||||
ControlServerSideSortingCodeStrongAuthRequired,
|
||||
ControlServerSideSortingCodeAdminLimitExceeded,
|
||||
ControlServerSideSortingCodeNoSuchAttribute,
|
||||
ControlServerSideSortingCodeInappropriateMatching,
|
||||
ControlServerSideSortingCodeInsufficientAccessRights,
|
||||
ControlServerSideSortingCodeBusy,
|
||||
ControlServerSideSortingCodeUnwillingToPerform,
|
||||
ControlServerSideSortingCodeOther,
|
||||
}
|
||||
|
||||
type ControlServerSideSortingCode int64
|
||||
|
||||
// Valid test the code contained in the control against the ControlServerSideSortingCodes slice and return an error if the code is unknown.
|
||||
func (c ControlServerSideSortingCode) Valid() error {
|
||||
for _, validRet := range ControlServerSideSortingCodes {
|
||||
if c == validRet {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unknown return code : %d", c)
|
||||
}
|
||||
|
||||
func NewControlServerSideSortingResult(pkt *ber.Packet) (*ControlServerSideSortingResult, error) {
|
||||
control := &ControlServerSideSortingResult{}
|
||||
|
||||
if pkt == nil || len(pkt.Children) == 0 {
|
||||
return nil, fmt.Errorf("bad packet")
|
||||
}
|
||||
|
||||
codeInt, err := ber.ParseInt64(pkt.Children[0].Data.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
code := ControlServerSideSortingCode(codeInt)
|
||||
if err := code.Valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return control, nil
|
||||
}
|
||||
|
||||
type ControlServerSideSortingResult struct {
|
||||
Criticality bool
|
||||
|
||||
Result ControlServerSideSortingCode
|
||||
|
||||
// Not populated for now. I can't get openldap to send me this value, so I think this is specific to other directory server
|
||||
// AttributeType string
|
||||
}
|
||||
|
||||
func (control *ControlServerSideSortingResult) GetControlType() string {
|
||||
return ControlTypeServerSideSortingResult
|
||||
}
|
||||
|
||||
func (c *ControlServerSideSortingResult) Encode() *ber.Packet {
|
||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortResult sequence")
|
||||
sortResult := ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, int64(c.Result), "SortResult")
|
||||
packet.AppendChild(sortResult)
|
||||
|
||||
return packet
|
||||
}
|
||||
|
||||
func (c *ControlServerSideSortingResult) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Control Type: %s (%q) Criticality:%t ResultCode:%+v",
|
||||
"Server Side Sorting Result",
|
||||
c.GetControlType(),
|
||||
c.Criticality,
|
||||
c.Result,
|
||||
)
|
||||
}
|
||||
|
||||
// Mode for ControlTypeSyncRequest
|
||||
type ControlSyncRequestMode int64
|
||||
|
||||
const (
|
||||
SyncRequestModeRefreshOnly ControlSyncRequestMode = 1
|
||||
SyncRequestModeRefreshAndPersist ControlSyncRequestMode = 3
|
||||
)
|
||||
|
||||
// ControlSyncRequest implements the Sync Request Control described in https://www.ietf.org/rfc/rfc4533.txt
|
||||
type ControlSyncRequest struct {
|
||||
Criticality bool
|
||||
Mode ControlSyncRequestMode
|
||||
Cookie []byte
|
||||
ReloadHint bool
|
||||
}
|
||||
|
||||
func NewControlSyncRequest(
|
||||
mode ControlSyncRequestMode, cookie []byte, reloadHint bool,
|
||||
) *ControlSyncRequest {
|
||||
return &ControlSyncRequest{
|
||||
Criticality: true,
|
||||
Mode: mode,
|
||||
Cookie: cookie,
|
||||
ReloadHint: reloadHint,
|
||||
}
|
||||
}
|
||||
|
||||
// GetControlType returns the OID
|
||||
func (c *ControlSyncRequest) GetControlType() string {
|
||||
return ControlTypeSyncRequest
|
||||
}
|
||||
|
||||
// Encode encodes the control
|
||||
func (c *ControlSyncRequest) Encode() *ber.Packet {
|
||||
_mode := int64(c.Mode)
|
||||
mode := ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, _mode, "Mode")
|
||||
var cookie *ber.Packet
|
||||
if len(c.Cookie) > 0 {
|
||||
cookie = ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
|
||||
cookie.Value = c.Cookie
|
||||
cookie.Data.Write(c.Cookie)
|
||||
}
|
||||
reloadHint := ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.ReloadHint, "Reload Hint")
|
||||
|
||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeSyncRequest, "Control Type ("+ControlTypeMap[ControlTypeSyncRequest]+")"))
|
||||
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
|
||||
|
||||
val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Sync Request)")
|
||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Sync Request Value")
|
||||
seq.AppendChild(mode)
|
||||
if cookie != nil {
|
||||
seq.AppendChild(cookie)
|
||||
}
|
||||
seq.AppendChild(reloadHint)
|
||||
val.AppendChild(seq)
|
||||
|
||||
packet.AppendChild(val)
|
||||
return packet
|
||||
}
|
||||
|
||||
// String returns a human-readable description
|
||||
func (c *ControlSyncRequest) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Control Type: %s (%q) Criticality: %t Mode: %d Cookie: %s ReloadHint: %t",
|
||||
ControlTypeMap[ControlTypeSyncRequest],
|
||||
ControlTypeSyncRequest,
|
||||
c.Criticality,
|
||||
c.Mode,
|
||||
string(c.Cookie),
|
||||
c.ReloadHint,
|
||||
)
|
||||
}
|
||||
|
||||
// State for ControlSyncState
|
||||
type ControlSyncStateState int64
|
||||
|
||||
const (
|
||||
SyncStatePresent ControlSyncStateState = 0
|
||||
SyncStateAdd ControlSyncStateState = 1
|
||||
SyncStateModify ControlSyncStateState = 2
|
||||
SyncStateDelete ControlSyncStateState = 3
|
||||
)
|
||||
|
||||
// ControlSyncState implements the Sync State Control described in https://www.ietf.org/rfc/rfc4533.txt
|
||||
type ControlSyncState struct {
|
||||
Criticality bool
|
||||
State ControlSyncStateState
|
||||
EntryUUID uuid.UUID
|
||||
Cookie []byte
|
||||
}
|
||||
|
||||
func NewControlSyncState(pkt *ber.Packet) (*ControlSyncState, error) {
|
||||
var (
|
||||
state ControlSyncStateState
|
||||
entryUUID uuid.UUID
|
||||
cookie []byte
|
||||
err error
|
||||
)
|
||||
switch len(pkt.Children) {
|
||||
case 0, 1:
|
||||
return nil, fmt.Errorf("at least two children are required: %d", len(pkt.Children))
|
||||
case 2:
|
||||
state = ControlSyncStateState(pkt.Children[0].Value.(int64))
|
||||
entryUUID, err = uuid.FromBytes(pkt.Children[1].ByteValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode uuid: %w", err)
|
||||
}
|
||||
case 3:
|
||||
state = ControlSyncStateState(pkt.Children[0].Value.(int64))
|
||||
entryUUID, err = uuid.FromBytes(pkt.Children[1].ByteValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode uuid: %w", err)
|
||||
}
|
||||
cookie = pkt.Children[2].ByteValue
|
||||
}
|
||||
return &ControlSyncState{
|
||||
Criticality: false,
|
||||
State: state,
|
||||
EntryUUID: entryUUID,
|
||||
Cookie: cookie,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetControlType returns the OID
|
||||
func (c *ControlSyncState) GetControlType() string {
|
||||
return ControlTypeSyncState
|
||||
}
|
||||
|
||||
// Encode encodes the control
|
||||
func (c *ControlSyncState) Encode() *ber.Packet {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a human-readable description
|
||||
func (c *ControlSyncState) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Control Type: %s (%q) Criticality: %t State: %d EntryUUID: %s Cookie: %s",
|
||||
ControlTypeMap[ControlTypeSyncState],
|
||||
ControlTypeSyncState,
|
||||
c.Criticality,
|
||||
c.State,
|
||||
c.EntryUUID.String(),
|
||||
string(c.Cookie),
|
||||
)
|
||||
}
|
||||
|
||||
// ControlSyncDone implements the Sync Done Control described in https://www.ietf.org/rfc/rfc4533.txt
|
||||
type ControlSyncDone struct {
|
||||
Criticality bool
|
||||
Cookie []byte
|
||||
RefreshDeletes bool
|
||||
}
|
||||
|
||||
func NewControlSyncDone(pkt *ber.Packet) (*ControlSyncDone, error) {
|
||||
var (
|
||||
cookie []byte
|
||||
refreshDeletes bool
|
||||
)
|
||||
switch len(pkt.Children) {
|
||||
case 0:
|
||||
// have nothing to do
|
||||
case 1:
|
||||
cookie = pkt.Children[0].ByteValue
|
||||
case 2:
|
||||
cookie = pkt.Children[0].ByteValue
|
||||
refreshDeletes = pkt.Children[1].Value.(bool)
|
||||
}
|
||||
return &ControlSyncDone{
|
||||
Criticality: false,
|
||||
Cookie: cookie,
|
||||
RefreshDeletes: refreshDeletes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetControlType returns the OID
|
||||
func (c *ControlSyncDone) GetControlType() string {
|
||||
return ControlTypeSyncDone
|
||||
}
|
||||
|
||||
// Encode encodes the control
|
||||
func (c *ControlSyncDone) Encode() *ber.Packet {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a human-readable description
|
||||
func (c *ControlSyncDone) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Control Type: %s (%q) Criticality: %t Cookie: %s RefreshDeletes: %t",
|
||||
ControlTypeMap[ControlTypeSyncDone],
|
||||
ControlTypeSyncDone,
|
||||
c.Criticality,
|
||||
string(c.Cookie),
|
||||
c.RefreshDeletes,
|
||||
)
|
||||
}
|
||||
|
||||
// Tag For ControlSyncInfo
|
||||
type ControlSyncInfoValue uint64
|
||||
|
||||
const (
|
||||
SyncInfoNewcookie ControlSyncInfoValue = 0
|
||||
SyncInfoRefreshDelete ControlSyncInfoValue = 1
|
||||
SyncInfoRefreshPresent ControlSyncInfoValue = 2
|
||||
SyncInfoSyncIdSet ControlSyncInfoValue = 3
|
||||
)
|
||||
|
||||
// ControlSyncInfoNewCookie implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt
|
||||
type ControlSyncInfoNewCookie struct {
|
||||
Cookie []byte
|
||||
}
|
||||
|
||||
// String returns a human-readable description
|
||||
func (c *ControlSyncInfoNewCookie) String() string {
|
||||
return fmt.Sprintf(
|
||||
"NewCookie[Cookie: %s]",
|
||||
string(c.Cookie),
|
||||
)
|
||||
}
|
||||
|
||||
// ControlSyncInfoRefreshDelete implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt
|
||||
type ControlSyncInfoRefreshDelete struct {
|
||||
Cookie []byte
|
||||
RefreshDone bool
|
||||
}
|
||||
|
||||
// String returns a human-readable description
|
||||
func (c *ControlSyncInfoRefreshDelete) String() string {
|
||||
return fmt.Sprintf(
|
||||
"RefreshDelete[Cookie: %s RefreshDone: %t]",
|
||||
string(c.Cookie),
|
||||
c.RefreshDone,
|
||||
)
|
||||
}
|
||||
|
||||
// ControlSyncInfoRefreshPresent implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt
|
||||
type ControlSyncInfoRefreshPresent struct {
|
||||
Cookie []byte
|
||||
RefreshDone bool
|
||||
}
|
||||
|
||||
// String returns a human-readable description
|
||||
func (c *ControlSyncInfoRefreshPresent) String() string {
|
||||
return fmt.Sprintf(
|
||||
"RefreshPresent[Cookie: %s RefreshDone: %t]",
|
||||
string(c.Cookie),
|
||||
c.RefreshDone,
|
||||
)
|
||||
}
|
||||
|
||||
// ControlSyncInfoSyncIdSet implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt
|
||||
type ControlSyncInfoSyncIdSet struct {
|
||||
Cookie []byte
|
||||
RefreshDeletes bool
|
||||
SyncUUIDs []uuid.UUID
|
||||
}
|
||||
|
||||
// String returns a human-readable description
|
||||
func (c *ControlSyncInfoSyncIdSet) String() string {
|
||||
return fmt.Sprintf(
|
||||
"SyncIdSet[Cookie: %s RefreshDeletes: %t SyncUUIDs: %v]",
|
||||
string(c.Cookie),
|
||||
c.RefreshDeletes,
|
||||
c.SyncUUIDs,
|
||||
)
|
||||
}
|
||||
|
||||
// ControlSyncInfo implements the Sync Info Control described in https://www.ietf.org/rfc/rfc4533.txt
|
||||
type ControlSyncInfo struct {
|
||||
Criticality bool
|
||||
Value ControlSyncInfoValue
|
||||
NewCookie *ControlSyncInfoNewCookie
|
||||
RefreshDelete *ControlSyncInfoRefreshDelete
|
||||
RefreshPresent *ControlSyncInfoRefreshPresent
|
||||
SyncIdSet *ControlSyncInfoSyncIdSet
|
||||
}
|
||||
|
||||
func NewControlSyncInfo(pkt *ber.Packet) (*ControlSyncInfo, error) {
|
||||
var (
|
||||
cookie []byte
|
||||
refreshDone = true
|
||||
refreshDeletes bool
|
||||
syncUUIDs []uuid.UUID
|
||||
)
|
||||
c := &ControlSyncInfo{Criticality: false}
|
||||
switch ControlSyncInfoValue(pkt.Identifier.Tag) {
|
||||
case SyncInfoNewcookie:
|
||||
c.Value = SyncInfoNewcookie
|
||||
c.NewCookie = &ControlSyncInfoNewCookie{
|
||||
Cookie: pkt.ByteValue,
|
||||
}
|
||||
case SyncInfoRefreshDelete:
|
||||
c.Value = SyncInfoRefreshDelete
|
||||
switch len(pkt.Children) {
|
||||
case 0:
|
||||
// have nothing to do
|
||||
case 1:
|
||||
cookie = pkt.Children[0].ByteValue
|
||||
case 2:
|
||||
cookie = pkt.Children[0].ByteValue
|
||||
refreshDone = pkt.Children[1].Value.(bool)
|
||||
}
|
||||
c.RefreshDelete = &ControlSyncInfoRefreshDelete{
|
||||
Cookie: cookie,
|
||||
RefreshDone: refreshDone,
|
||||
}
|
||||
case SyncInfoRefreshPresent:
|
||||
c.Value = SyncInfoRefreshPresent
|
||||
switch len(pkt.Children) {
|
||||
case 0:
|
||||
// have nothing to do
|
||||
case 1:
|
||||
cookie = pkt.Children[0].ByteValue
|
||||
case 2:
|
||||
cookie = pkt.Children[0].ByteValue
|
||||
refreshDone = pkt.Children[1].Value.(bool)
|
||||
}
|
||||
c.RefreshPresent = &ControlSyncInfoRefreshPresent{
|
||||
Cookie: cookie,
|
||||
RefreshDone: refreshDone,
|
||||
}
|
||||
case SyncInfoSyncIdSet:
|
||||
c.Value = SyncInfoSyncIdSet
|
||||
switch len(pkt.Children) {
|
||||
case 0:
|
||||
// have nothing to do
|
||||
case 1:
|
||||
cookie = pkt.Children[0].ByteValue
|
||||
case 2:
|
||||
cookie = pkt.Children[0].ByteValue
|
||||
refreshDeletes = pkt.Children[1].Value.(bool)
|
||||
case 3:
|
||||
cookie = pkt.Children[0].ByteValue
|
||||
refreshDeletes = pkt.Children[1].Value.(bool)
|
||||
syncUUIDs = make([]uuid.UUID, 0, len(pkt.Children[2].Children))
|
||||
for _, child := range pkt.Children[2].Children {
|
||||
u, err := uuid.FromBytes(child.ByteValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode uuid: %w", err)
|
||||
}
|
||||
syncUUIDs = append(syncUUIDs, u)
|
||||
}
|
||||
}
|
||||
c.SyncIdSet = &ControlSyncInfoSyncIdSet{
|
||||
Cookie: cookie,
|
||||
RefreshDeletes: refreshDeletes,
|
||||
SyncUUIDs: syncUUIDs,
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown sync info value: %d", pkt.Identifier.Tag)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// GetControlType returns the OID
|
||||
func (c *ControlSyncInfo) GetControlType() string {
|
||||
return ControlTypeSyncInfo
|
||||
}
|
||||
|
||||
// Encode encodes the control
|
||||
func (c *ControlSyncInfo) Encode() *ber.Packet {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a human-readable description
|
||||
func (c *ControlSyncInfo) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Control Type: %s (%q) Criticality: %t Value: %d %s %s %s %s",
|
||||
ControlTypeMap[ControlTypeSyncInfo],
|
||||
ControlTypeSyncInfo,
|
||||
c.Criticality,
|
||||
c.Value,
|
||||
c.NewCookie,
|
||||
c.RefreshDelete,
|
||||
c.RefreshPresent,
|
||||
c.SyncIdSet,
|
||||
)
|
||||
}
|
||||
|
|
8
vendor/github.com/go-ldap/ldap/v3/debug.go
generated
vendored
8
vendor/github.com/go-ldap/ldap/v3/debug.go
generated
vendored
|
@ -1,13 +1,11 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
)
|
||||
|
||||
// debugging type
|
||||
// - has a Printf method to write the debug output
|
||||
// - has a Printf method to write the debug output
|
||||
type debugging bool
|
||||
|
||||
// Enable controls debugging mode.
|
||||
|
@ -18,13 +16,13 @@ func (debug *debugging) Enable(b bool) {
|
|||
// Printf writes debug output.
|
||||
func (debug debugging) Printf(format string, args ...interface{}) {
|
||||
if debug {
|
||||
log.Printf(format, args...)
|
||||
logger.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPacket dumps a packet.
|
||||
func (debug debugging) PrintPacket(packet *ber.Packet) {
|
||||
if debug {
|
||||
ber.WritePacket(log.Writer(), packet)
|
||||
ber.WritePacket(logger.Writer(), packet)
|
||||
}
|
||||
}
|
||||
|
|
6
vendor/github.com/go-ldap/ldap/v3/del.go
generated
vendored
6
vendor/github.com/go-ldap/ldap/v3/del.go
generated
vendored
|
@ -1,8 +1,7 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"fmt"
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
)
|
||||
|
||||
|
@ -53,7 +52,8 @@ func (l *Conn) Del(delRequest *DelRequest) error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
||||
return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
92
vendor/github.com/go-ldap/ldap/v3/dn.go
generated
vendored
92
vendor/github.com/go-ldap/ldap/v3/dn.go
generated
vendored
|
@ -5,6 +5,7 @@ import (
|
|||
enchex "encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
|
@ -18,16 +19,95 @@ type AttributeTypeAndValue struct {
|
|||
Value string
|
||||
}
|
||||
|
||||
// String returns a normalized string representation of this attribute type and
|
||||
// value pair which is the a lowercased join of the Type and Value with a "=".
|
||||
func (a *AttributeTypeAndValue) String() string {
|
||||
return strings.ToLower(a.Type) + "=" + a.encodeValue()
|
||||
}
|
||||
|
||||
func (a *AttributeTypeAndValue) encodeValue() string {
|
||||
// Normalize the value first.
|
||||
// value := strings.ToLower(a.Value)
|
||||
value := a.Value
|
||||
|
||||
encodedBuf := bytes.Buffer{}
|
||||
|
||||
escapeChar := func(c byte) {
|
||||
encodedBuf.WriteByte('\\')
|
||||
encodedBuf.WriteByte(c)
|
||||
}
|
||||
|
||||
escapeHex := func(c byte) {
|
||||
encodedBuf.WriteByte('\\')
|
||||
encodedBuf.WriteString(enchex.EncodeToString([]byte{c}))
|
||||
}
|
||||
|
||||
for i := 0; i < len(value); i++ {
|
||||
char := value[i]
|
||||
if i == 0 && char == ' ' || char == '#' {
|
||||
// Special case leading space or number sign.
|
||||
escapeChar(char)
|
||||
continue
|
||||
}
|
||||
if i == len(value)-1 && char == ' ' {
|
||||
// Special case trailing space.
|
||||
escapeChar(char)
|
||||
continue
|
||||
}
|
||||
|
||||
switch char {
|
||||
case '"', '+', ',', ';', '<', '>', '\\':
|
||||
// Each of these special characters must be escaped.
|
||||
escapeChar(char)
|
||||
continue
|
||||
}
|
||||
|
||||
if char < ' ' || char > '~' {
|
||||
// All special character escapes are handled first
|
||||
// above. All bytes less than ASCII SPACE and all bytes
|
||||
// greater than ASCII TILDE must be hex-escaped.
|
||||
escapeHex(char)
|
||||
continue
|
||||
}
|
||||
|
||||
// Any other character does not require escaping.
|
||||
encodedBuf.WriteByte(char)
|
||||
}
|
||||
|
||||
return encodedBuf.String()
|
||||
}
|
||||
|
||||
// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
|
||||
type RelativeDN struct {
|
||||
Attributes []*AttributeTypeAndValue
|
||||
}
|
||||
|
||||
// String returns a normalized string representation of this relative DN which
|
||||
// is the a join of all attributes (sorted in increasing order) with a "+".
|
||||
func (r *RelativeDN) String() string {
|
||||
attrs := make([]string, len(r.Attributes))
|
||||
for i := range r.Attributes {
|
||||
attrs[i] = r.Attributes[i].String()
|
||||
}
|
||||
sort.Strings(attrs)
|
||||
return strings.Join(attrs, "+")
|
||||
}
|
||||
|
||||
// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
|
||||
type DN struct {
|
||||
RDNs []*RelativeDN
|
||||
}
|
||||
|
||||
// String returns a normalized string representation of this DN which is the
|
||||
// join of all relative DNs with a ",".
|
||||
func (d *DN) String() string {
|
||||
rdns := make([]string, len(d.RDNs))
|
||||
for i := range d.RDNs {
|
||||
rdns[i] = d.RDNs[i].String()
|
||||
}
|
||||
return strings.Join(rdns, ",")
|
||||
}
|
||||
|
||||
// ParseDN returns a distinguishedName or an error.
|
||||
// The function respects https://tools.ietf.org/html/rfc4514
|
||||
func ParseDN(str string) (*DN, error) {
|
||||
|
@ -76,7 +156,7 @@ func ParseDN(str string) (*DN, error) {
|
|||
case char == '\\':
|
||||
unescapedTrailingSpaces = 0
|
||||
escaping = true
|
||||
case char == '=':
|
||||
case char == '=' && attribute.Type == "":
|
||||
attribute.Type = stringFromBuffer()
|
||||
// Special case: If the first character in the value is # the
|
||||
// following data is BER encoded so we can just fast forward
|
||||
|
@ -84,7 +164,7 @@ func ParseDN(str string) (*DN, error) {
|
|||
if len(str) > i+1 && str[i+1] == '#' {
|
||||
i += 2
|
||||
index := strings.IndexAny(str[i:], ",+")
|
||||
data := str
|
||||
var data string
|
||||
if index > 0 {
|
||||
data = str[i : i+index]
|
||||
} else {
|
||||
|
@ -101,7 +181,7 @@ func ParseDN(str string) (*DN, error) {
|
|||
buffer.WriteString(packet.Data.String())
|
||||
i += len(data) - 1
|
||||
}
|
||||
case char == ',' || char == '+':
|
||||
case char == ',' || char == '+' || char == ';':
|
||||
// We're done with this RDN or value, push it
|
||||
if len(attribute.Type) == 0 {
|
||||
return nil, errors.New("incomplete type, value pair")
|
||||
|
@ -109,7 +189,7 @@ func ParseDN(str string) (*DN, error) {
|
|||
attribute.Value = stringFromBuffer()
|
||||
rdn.Attributes = append(rdn.Attributes, attribute)
|
||||
attribute = new(AttributeTypeAndValue)
|
||||
if char == ',' {
|
||||
if char == ',' || char == ';' {
|
||||
dn.RDNs = append(dn.RDNs, rdn)
|
||||
rdn = new(RelativeDN)
|
||||
rdn.Attributes = make([]*AttributeTypeAndValue, 0)
|
||||
|
@ -206,7 +286,7 @@ func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool {
|
|||
return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value
|
||||
}
|
||||
|
||||
// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
|
||||
// EqualFold returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
|
||||
// Returns true if they have the same number of relative distinguished names
|
||||
// and corresponding relative distinguished names (by position) are the same.
|
||||
// Case of the attribute type and value is not significant
|
||||
|
@ -238,7 +318,7 @@ func (d *DN) AncestorOfFold(other *DN) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
|
||||
// EqualFold returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
|
||||
// Case of the attribute type is not significant
|
||||
func (r *RelativeDN) EqualFold(other *RelativeDN) bool {
|
||||
if len(r.Attributes) != len(other.Attributes) {
|
||||
|
|
26
vendor/github.com/go-ldap/ldap/v3/error.go
generated
vendored
26
vendor/github.com/go-ldap/ldap/v3/error.go
generated
vendored
|
@ -192,6 +192,8 @@ func (e *Error) Error() string {
|
|||
return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error())
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error { return e.Err }
|
||||
|
||||
// GetLDAPError creates an Error out of a BER packet representing a LDAPResult
|
||||
// The return is an error object. It can be casted to a Error structure.
|
||||
// This function returns nil if resultCode in the LDAPResult sequence is success(0).
|
||||
|
@ -206,15 +208,21 @@ func GetLDAPError(packet *ber.Packet) error {
|
|||
return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty response in packet"), Packet: packet}
|
||||
}
|
||||
if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 {
|
||||
resultCode := uint16(response.Children[0].Value.(int64))
|
||||
if resultCode == 0 { // No error
|
||||
return nil
|
||||
}
|
||||
return &Error{
|
||||
ResultCode: resultCode,
|
||||
MatchedDN: response.Children[1].Value.(string),
|
||||
Err: fmt.Errorf("%s", response.Children[2].Value.(string)),
|
||||
Packet: packet,
|
||||
if ber.Type(response.Children[0].Tag) == ber.Type(ber.TagInteger) || ber.Type(response.Children[0].Tag) == ber.Type(ber.TagEnumerated) {
|
||||
resultCode := uint16(response.Children[0].Value.(int64))
|
||||
if resultCode == 0 { // No error
|
||||
return nil
|
||||
}
|
||||
|
||||
if ber.Type(response.Children[1].Tag) == ber.Type(ber.TagOctetString) &&
|
||||
ber.Type(response.Children[2].Tag) == ber.Type(ber.TagOctetString) {
|
||||
return &Error{
|
||||
ResultCode: resultCode,
|
||||
MatchedDN: response.Children[1].Value.(string),
|
||||
Err: fmt.Errorf("%s", response.Children[2].Value.(string)),
|
||||
Packet: packet,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
3
vendor/github.com/go-ldap/ldap/v3/filter.go
generated
vendored
3
vendor/github.com/go-ldap/ldap/v3/filter.go
generated
vendored
|
@ -396,7 +396,7 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
|||
|
||||
case packet.Tag == FilterEqualityMatch && bytes.Equal(condition.Bytes(), _SymbolAny):
|
||||
packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute.String(), FilterMap[FilterPresent])
|
||||
case packet.Tag == FilterEqualityMatch && bytes.Index(condition.Bytes(), _SymbolAny) > -1:
|
||||
case packet.Tag == FilterEqualityMatch && bytes.Contains(condition.Bytes(), _SymbolAny):
|
||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute.String(), "Attribute"))
|
||||
packet.Tag = FilterSubstrings
|
||||
packet.Description = FilterMap[uint64(packet.Tag)]
|
||||
|
@ -438,7 +438,6 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
|||
|
||||
// Convert from "ABC\xx\xx\xx" form to literal bytes for transport
|
||||
func decodeEscapedSymbols(src []byte) (string, error) {
|
||||
|
||||
var (
|
||||
buffer bytes.Buffer
|
||||
offset int
|
||||
|
|
57
vendor/github.com/go-ldap/ldap/v3/ldap.go
generated
vendored
57
vendor/github.com/go-ldap/ldap/v3/ldap.go
generated
vendored
|
@ -3,7 +3,9 @@ package ldap
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
)
|
||||
|
@ -30,6 +32,7 @@ const (
|
|||
ApplicationSearchResultReference = 19
|
||||
ApplicationExtendedRequest = 23
|
||||
ApplicationExtendedResponse = 24
|
||||
ApplicationIntermediateResponse = 25
|
||||
)
|
||||
|
||||
// ApplicationMap contains human readable descriptions of LDAP Application Codes
|
||||
|
@ -54,6 +57,7 @@ var ApplicationMap = map[uint8]string{
|
|||
ApplicationSearchResultReference: "Search Result Reference",
|
||||
ApplicationExtendedRequest: "Extended Request",
|
||||
ApplicationExtendedResponse: "Extended Response",
|
||||
ApplicationIntermediateResponse: "Intermediate Response",
|
||||
}
|
||||
|
||||
// Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10)
|
||||
|
@ -82,6 +86,13 @@ var BeheraPasswordPolicyErrorMap = map[int8]string{
|
|||
BeheraPasswordInHistory: "New password is in list of old passwords",
|
||||
}
|
||||
|
||||
var logger = log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
||||
// Logger allows clients to override the default logger
|
||||
func Logger(l *log.Logger) {
|
||||
logger = l
|
||||
}
|
||||
|
||||
// Adds descriptions to an LDAP Response packet for debugging
|
||||
func addLDAPDescriptions(packet *ber.Packet) (err error) {
|
||||
defer func() {
|
||||
|
@ -221,18 +232,18 @@ func addControlDescriptions(packet *ber.Packet) error {
|
|||
sequence := value.Children[0]
|
||||
for _, child := range sequence.Children {
|
||||
if child.Tag == 0 {
|
||||
//Warning
|
||||
// Warning
|
||||
warningPacket := child.Children[0]
|
||||
val, err := ber.ParseInt64(warningPacket.Data.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode data bytes: %s", err)
|
||||
}
|
||||
if warningPacket.Tag == 0 {
|
||||
//timeBeforeExpiration
|
||||
// timeBeforeExpiration
|
||||
value.Description += " (TimeBeforeExpiration)"
|
||||
warningPacket.Value = val
|
||||
} else if warningPacket.Tag == 1 {
|
||||
//graceAuthNsRemaining
|
||||
// graceAuthNsRemaining
|
||||
value.Description += " (GraceAuthNsRemaining)"
|
||||
warningPacket.Value = val
|
||||
}
|
||||
|
@ -337,3 +348,43 @@ func EscapeFilter(filter string) string {
|
|||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// EscapeDN escapes distinguished names as described in RFC4514. Characters in the
|
||||
// set `"+,;<>\` are escaped by prepending a backslash, which is also done for trailing
|
||||
// spaces or a leading `#`. Null bytes are replaced with `\00`.
|
||||
func EscapeDN(dn string) string {
|
||||
if dn == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
builder := strings.Builder{}
|
||||
|
||||
for i, r := range dn {
|
||||
// Escape leading and trailing spaces
|
||||
if (i == 0 || i == len(dn)-1) && r == ' ' {
|
||||
builder.WriteRune('\\')
|
||||
builder.WriteRune(r)
|
||||
continue
|
||||
}
|
||||
|
||||
// Escape leading '#'
|
||||
if i == 0 && r == '#' {
|
||||
builder.WriteRune('\\')
|
||||
builder.WriteRune(r)
|
||||
continue
|
||||
}
|
||||
|
||||
// Escape characters as defined in RFC4514
|
||||
switch r {
|
||||
case '"', '+', ',', ';', '<', '>', '\\':
|
||||
builder.WriteRune('\\')
|
||||
builder.WriteRune(r)
|
||||
case '\x00': // Null byte may not be escaped by a leading backslash
|
||||
builder.WriteString("\\00")
|
||||
default:
|
||||
builder.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
|
10
vendor/github.com/go-ldap/ldap/v3/moddn.go
generated
vendored
10
vendor/github.com/go-ldap/ldap/v3/moddn.go
generated
vendored
|
@ -1,8 +1,7 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"fmt"
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
)
|
||||
|
||||
|
@ -25,7 +24,9 @@ type ModifyDNRequest struct {
|
|||
// RDN of the given DN.
|
||||
//
|
||||
// A call like
|
||||
// mdnReq := NewModifyDNRequest("uid=someone,dc=example,dc=org", "uid=newname", true, "")
|
||||
//
|
||||
// mdnReq := NewModifyDNRequest("uid=someone,dc=example,dc=org", "uid=newname", true, "")
|
||||
//
|
||||
// will setup the request to just rename uid=someone,dc=example,dc=org to
|
||||
// uid=newname,dc=example,dc=org.
|
||||
func NewModifyDNRequest(dn string, rdn string, delOld bool, newSup string) *ModifyDNRequest {
|
||||
|
@ -94,7 +95,8 @@ func (l *Conn) ModifyDN(m *ModifyDNRequest) error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
||||
return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
14
vendor/github.com/go-ldap/ldap/v3/modify.go
generated
vendored
14
vendor/github.com/go-ldap/ldap/v3/modify.go
generated
vendored
|
@ -2,7 +2,7 @@ package ldap
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"fmt"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
)
|
||||
|
@ -127,8 +127,9 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
||||
return fmt.Errorf("ldap: unexpected response: %d", packet.Children[1].Tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -136,6 +137,8 @@ func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
|
|||
type ModifyResult struct {
|
||||
// Controls are the returned controls
|
||||
Controls []Control
|
||||
// Referral is the returned referral
|
||||
Referral string
|
||||
}
|
||||
|
||||
// ModifyWithResult performs the ModifyRequest and returns the result
|
||||
|
@ -158,9 +161,10 @@ func (l *Conn) ModifyWithResult(modifyRequest *ModifyRequest) (*ModifyResult, er
|
|||
|
||||
switch packet.Children[1].Tag {
|
||||
case ApplicationModifyResponse:
|
||||
err := GetLDAPError(packet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err = GetLDAPError(packet); err != nil {
|
||||
result.Referral = getReferral(err, packet)
|
||||
|
||||
return result, err
|
||||
}
|
||||
if len(packet.Children) == 3 {
|
||||
for _, child := range packet.Children[2].Children {
|
||||
|
|
17
vendor/github.com/go-ldap/ldap/v3/passwdmodify.go
generated
vendored
17
vendor/github.com/go-ldap/ldap/v3/passwdmodify.go
generated
vendored
|
@ -70,7 +70,6 @@ func (req *PasswordModifyRequest) appendTo(envelope *ber.Packet) error {
|
|||
// newPassword is the desired user's password. If empty the server can return
|
||||
// an error or generate a new password that will be available in the
|
||||
// PasswordModifyResult.GeneratedPassword
|
||||
//
|
||||
func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPassword string) *PasswordModifyRequest {
|
||||
return &PasswordModifyRequest{
|
||||
UserIdentity: userIdentity,
|
||||
|
@ -95,15 +94,9 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa
|
|||
result := &PasswordModifyResult{}
|
||||
|
||||
if packet.Children[1].Tag == ApplicationExtendedResponse {
|
||||
err := GetLDAPError(packet)
|
||||
if err != nil {
|
||||
if IsErrorWithCode(err, LDAPResultReferral) {
|
||||
for _, child := range packet.Children[1].Children {
|
||||
if child.Tag == 3 {
|
||||
result.Referral = child.Children[0].Value.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = GetLDAPError(packet); err != nil {
|
||||
result.Referral = getReferral(err, packet)
|
||||
|
||||
return result, err
|
||||
}
|
||||
} else {
|
||||
|
@ -112,10 +105,10 @@ func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*Pa
|
|||
|
||||
extendedResponse := packet.Children[1]
|
||||
for _, child := range extendedResponse.Children {
|
||||
if child.Tag == 11 {
|
||||
if child.Tag == ber.TagEmbeddedPDV {
|
||||
passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes())
|
||||
if len(passwordModifyResponseValue.Children) == 1 {
|
||||
if passwordModifyResponseValue.Children[0].Tag == 0 {
|
||||
if passwordModifyResponseValue.Children[0].Tag == ber.TagEOC {
|
||||
result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes())
|
||||
}
|
||||
}
|
||||
|
|
41
vendor/github.com/go-ldap/ldap/v3/request.go
generated
vendored
41
vendor/github.com/go-ldap/ldap/v3/request.go
generated
vendored
|
@ -9,7 +9,8 @@ import (
|
|||
var (
|
||||
errRespChanClosed = errors.New("ldap: response channel closed")
|
||||
errCouldNotRetMsg = errors.New("ldap: could not retrieve message")
|
||||
ErrNilConnection = errors.New("ldap: conn is nil, expected net.Conn")
|
||||
// ErrNilConnection is returned if doRequest is called with a nil connection.
|
||||
ErrNilConnection = errors.New("ldap: conn is nil, expected net.Conn")
|
||||
)
|
||||
|
||||
type request interface {
|
||||
|
@ -69,3 +70,41 @@ func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) {
|
|||
}
|
||||
return packet, nil
|
||||
}
|
||||
|
||||
func getReferral(err error, packet *ber.Packet) (referral string) {
|
||||
if !IsErrorWithCode(err, LDAPResultReferral) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(packet.Children) < 2 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// The packet Tag itself (of child 2) is generally a ber.TagObjectDescriptor with referrals however OpenLDAP
|
||||
// seemingly returns a ber.Tag.GeneralizedTime. Every currently tested LDAP server which returns referrals returns
|
||||
// an ASN.1 BER packet with the Type of ber.TypeConstructed and Class of ber.ClassApplication however. Thus this
|
||||
// check expressly checks these fields instead.
|
||||
//
|
||||
// Related Issues:
|
||||
// - https://github.com/authelia/authelia/issues/4199 (downstream)
|
||||
if len(packet.Children[1].Children) == 0 || (packet.Children[1].TagType != ber.TypeConstructed || packet.Children[1].ClassType != ber.ClassApplication) {
|
||||
return ""
|
||||
}
|
||||
|
||||
var ok bool
|
||||
|
||||
for _, child := range packet.Children[1].Children {
|
||||
// The referral URI itself should be contained within a child which has a Tag of ber.BitString or
|
||||
// ber.TagPrintableString, and the Type of ber.TypeConstructed and the Class of ClassContext. As soon as any of
|
||||
// these conditions is not true we can skip this child.
|
||||
if (child.Tag != ber.TagBitString && child.Tag != ber.TagPrintableString) || child.TagType != ber.TypeConstructed || child.ClassType != ber.ClassContext {
|
||||
continue
|
||||
}
|
||||
|
||||
if referral, ok = child.Children[0].Value.(string); ok {
|
||||
return referral
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
207
vendor/github.com/go-ldap/ldap/v3/response.go
generated
vendored
Normal file
207
vendor/github.com/go-ldap/ldap/v3/response.go
generated
vendored
Normal file
|
@ -0,0 +1,207 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
)
|
||||
|
||||
// Response defines an interface to get data from an LDAP server
|
||||
type Response interface {
|
||||
Entry() *Entry
|
||||
Referral() string
|
||||
Controls() []Control
|
||||
Err() error
|
||||
Next() bool
|
||||
}
|
||||
|
||||
type searchResponse struct {
|
||||
conn *Conn
|
||||
ch chan *SearchSingleResult
|
||||
|
||||
entry *Entry
|
||||
referral string
|
||||
controls []Control
|
||||
err error
|
||||
}
|
||||
|
||||
// Entry returns an entry from the given search request
|
||||
func (r *searchResponse) Entry() *Entry {
|
||||
return r.entry
|
||||
}
|
||||
|
||||
// Referral returns a referral from the given search request
|
||||
func (r *searchResponse) Referral() string {
|
||||
return r.referral
|
||||
}
|
||||
|
||||
// Controls returns controls from the given search request
|
||||
func (r *searchResponse) Controls() []Control {
|
||||
return r.controls
|
||||
}
|
||||
|
||||
// Err returns an error when the given search request was failed
|
||||
func (r *searchResponse) Err() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
// Next returns whether next data exist or not
|
||||
func (r *searchResponse) Next() bool {
|
||||
res, ok := <-r.ch
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if res == nil {
|
||||
return false
|
||||
}
|
||||
r.err = res.Error
|
||||
if r.err != nil {
|
||||
return false
|
||||
}
|
||||
r.entry = res.Entry
|
||||
r.referral = res.Referral
|
||||
r.controls = res.Controls
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *searchResponse) start(ctx context.Context, searchRequest *SearchRequest) {
|
||||
go func() {
|
||||
defer func() {
|
||||
close(r.ch)
|
||||
if err := recover(); err != nil {
|
||||
r.conn.err = fmt.Errorf("ldap: recovered panic in searchResponse: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if r.conn.IsClosing() {
|
||||
return
|
||||
}
|
||||
|
||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, r.conn.nextMessageID(), "MessageID"))
|
||||
// encode search request
|
||||
err := searchRequest.appendTo(packet)
|
||||
if err != nil {
|
||||
r.ch <- &SearchSingleResult{Error: err}
|
||||
return
|
||||
}
|
||||
r.conn.Debug.PrintPacket(packet)
|
||||
|
||||
msgCtx, err := r.conn.sendMessage(packet)
|
||||
if err != nil {
|
||||
r.ch <- &SearchSingleResult{Error: err}
|
||||
return
|
||||
}
|
||||
defer r.conn.finishMessage(msgCtx)
|
||||
|
||||
foundSearchSingleResultDone := false
|
||||
for !foundSearchSingleResultDone {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
r.conn.Debug.Printf("%d: %s", msgCtx.id, ctx.Err().Error())
|
||||
return
|
||||
default:
|
||||
r.conn.Debug.Printf("%d: waiting for response", msgCtx.id)
|
||||
packetResponse, ok := <-msgCtx.responses
|
||||
if !ok {
|
||||
err := NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
||||
r.ch <- &SearchSingleResult{Error: err}
|
||||
return
|
||||
}
|
||||
packet, err = packetResponse.ReadPacket()
|
||||
r.conn.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
||||
if err != nil {
|
||||
r.ch <- &SearchSingleResult{Error: err}
|
||||
return
|
||||
}
|
||||
|
||||
if r.conn.Debug {
|
||||
if err := addLDAPDescriptions(packet); err != nil {
|
||||
r.ch <- &SearchSingleResult{Error: err}
|
||||
return
|
||||
}
|
||||
ber.PrintPacket(packet)
|
||||
}
|
||||
|
||||
switch packet.Children[1].Tag {
|
||||
case ApplicationSearchResultEntry:
|
||||
result := &SearchSingleResult{
|
||||
Entry: &Entry{
|
||||
DN: packet.Children[1].Children[0].Value.(string),
|
||||
Attributes: unpackAttributes(packet.Children[1].Children[1].Children),
|
||||
},
|
||||
}
|
||||
if len(packet.Children) != 3 {
|
||||
r.ch <- result
|
||||
continue
|
||||
}
|
||||
decoded, err := DecodeControl(packet.Children[2].Children[0])
|
||||
if err != nil {
|
||||
werr := fmt.Errorf("failed to decode search result entry: %w", err)
|
||||
result.Error = werr
|
||||
r.ch <- result
|
||||
return
|
||||
}
|
||||
result.Controls = append(result.Controls, decoded)
|
||||
r.ch <- result
|
||||
|
||||
case ApplicationSearchResultDone:
|
||||
if err := GetLDAPError(packet); err != nil {
|
||||
r.ch <- &SearchSingleResult{Error: err}
|
||||
return
|
||||
}
|
||||
if len(packet.Children) == 3 {
|
||||
result := &SearchSingleResult{}
|
||||
for _, child := range packet.Children[2].Children {
|
||||
decodedChild, err := DecodeControl(child)
|
||||
if err != nil {
|
||||
werr := fmt.Errorf("failed to decode child control: %w", err)
|
||||
r.ch <- &SearchSingleResult{Error: werr}
|
||||
return
|
||||
}
|
||||
result.Controls = append(result.Controls, decodedChild)
|
||||
}
|
||||
r.ch <- result
|
||||
}
|
||||
foundSearchSingleResultDone = true
|
||||
|
||||
case ApplicationSearchResultReference:
|
||||
ref := packet.Children[1].Children[0].Value.(string)
|
||||
r.ch <- &SearchSingleResult{Referral: ref}
|
||||
|
||||
case ApplicationIntermediateResponse:
|
||||
decoded, err := DecodeControl(packet.Children[1])
|
||||
if err != nil {
|
||||
werr := fmt.Errorf("failed to decode intermediate response: %w", err)
|
||||
r.ch <- &SearchSingleResult{Error: werr}
|
||||
return
|
||||
}
|
||||
result := &SearchSingleResult{}
|
||||
result.Controls = append(result.Controls, decoded)
|
||||
r.ch <- result
|
||||
|
||||
default:
|
||||
err := fmt.Errorf("unknown tag: %d", packet.Children[1].Tag)
|
||||
r.ch <- &SearchSingleResult{Error: err}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
r.conn.Debug.Printf("%d: returning", msgCtx.id)
|
||||
}()
|
||||
}
|
||||
|
||||
func newSearchResponse(conn *Conn, bufferSize int) *searchResponse {
|
||||
var ch chan *SearchSingleResult
|
||||
if bufferSize > 0 {
|
||||
ch = make(chan *SearchSingleResult, bufferSize)
|
||||
} else {
|
||||
ch = make(chan *SearchSingleResult)
|
||||
}
|
||||
return &searchResponse{
|
||||
conn: conn,
|
||||
ch: ch,
|
||||
}
|
||||
}
|
303
vendor/github.com/go-ldap/ldap/v3/search.go
generated
vendored
303
vendor/github.com/go-ldap/ldap/v3/search.go
generated
vendored
|
@ -1,10 +1,14 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
)
|
||||
|
@ -161,6 +165,155 @@ func (e *Entry) PrettyPrint(indent int) {
|
|||
}
|
||||
}
|
||||
|
||||
// Describe the tag to use for struct field tags
|
||||
const decoderTagName = "ldap"
|
||||
|
||||
// readTag will read the reflect.StructField value for
|
||||
// the key defined in decoderTagName. If omitempty is
|
||||
// specified, the field may not be filled.
|
||||
func readTag(f reflect.StructField) (string, bool) {
|
||||
val, ok := f.Tag.Lookup(decoderTagName)
|
||||
if !ok {
|
||||
return f.Name, false
|
||||
}
|
||||
opts := strings.Split(val, ",")
|
||||
omit := false
|
||||
if len(opts) == 2 {
|
||||
omit = opts[1] == "omitempty"
|
||||
}
|
||||
return opts[0], omit
|
||||
}
|
||||
|
||||
// Unmarshal parses the Entry in the value pointed to by i
|
||||
//
|
||||
// Currently, this methods only supports struct fields of type
|
||||
// string, []string, int, int64, []byte, *DN, []*DN or time.Time. Other field types
|
||||
// will not be regarded. If the field type is a string or int but multiple
|
||||
// attribute values are returned, the first value will be used to fill the field.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type UserEntry struct {
|
||||
// // Fields with the tag key `dn` are automatically filled with the
|
||||
// // objects distinguishedName. This can be used multiple times.
|
||||
// DN string `ldap:"dn"`
|
||||
//
|
||||
// // This field will be filled with the attribute value for
|
||||
// // userPrincipalName. An attribute can be read into a struct field
|
||||
// // multiple times. Missing attributes will not result in an error.
|
||||
// UserPrincipalName string `ldap:"userPrincipalName"`
|
||||
//
|
||||
// // memberOf may have multiple values. If you don't
|
||||
// // know the amount of attribute values at runtime, use a string array.
|
||||
// MemberOf []string `ldap:"memberOf"`
|
||||
//
|
||||
// // ID is an integer value, it will fail unmarshaling when the given
|
||||
// // attribute value cannot be parsed into an integer.
|
||||
// ID int `ldap:"id"`
|
||||
//
|
||||
// // LongID is similar to ID but uses an int64 instead.
|
||||
// LongID int64 `ldap:"longId"`
|
||||
//
|
||||
// // Data is similar to MemberOf a slice containing all attribute
|
||||
// // values.
|
||||
// Data []byte `ldap:"data"`
|
||||
//
|
||||
// // Time is parsed with the generalizedTime spec into a time.Time
|
||||
// Created time.Time `ldap:"createdTimestamp"`
|
||||
//
|
||||
// // *DN is parsed with the ParseDN
|
||||
// Owner *ldap.DN `ldap:"owner"`
|
||||
//
|
||||
// // []*DN is parsed with the ParseDN
|
||||
// Children []*ldap.DN `ldap:"children"`
|
||||
//
|
||||
// // This won't work, as the field is not of type string. For this
|
||||
// // to work, you'll have to temporarily store the result in string
|
||||
// // (or string array) and convert it to the desired type afterwards.
|
||||
// UserAccountControl uint32 `ldap:"userPrincipalName"`
|
||||
// }
|
||||
// user := UserEntry{}
|
||||
//
|
||||
// if err := result.Unmarshal(&user); err != nil {
|
||||
// // ...
|
||||
// }
|
||||
func (e *Entry) Unmarshal(i interface{}) (err error) {
|
||||
// Make sure it's a ptr
|
||||
if vo := reflect.ValueOf(i).Kind(); vo != reflect.Ptr {
|
||||
return fmt.Errorf("ldap: cannot use %s, expected pointer to a struct", vo)
|
||||
}
|
||||
|
||||
sv, st := reflect.ValueOf(i).Elem(), reflect.TypeOf(i).Elem()
|
||||
// Make sure it's pointing to a struct
|
||||
if sv.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("ldap: expected pointer to a struct, got %s", sv.Kind())
|
||||
}
|
||||
|
||||
for n := 0; n < st.NumField(); n++ {
|
||||
// Holds struct field value and type
|
||||
fv, ft := sv.Field(n), st.Field(n)
|
||||
|
||||
// skip unexported fields
|
||||
if ft.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// omitempty can be safely discarded, as it's not needed when unmarshalling
|
||||
fieldTag, _ := readTag(ft)
|
||||
|
||||
// Fill the field with the distinguishedName if the tag key is `dn`
|
||||
if fieldTag == "dn" {
|
||||
fv.SetString(e.DN)
|
||||
continue
|
||||
}
|
||||
|
||||
values := e.GetAttributeValues(fieldTag)
|
||||
if len(values) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch fv.Interface().(type) {
|
||||
case []string:
|
||||
for _, item := range values {
|
||||
fv.Set(reflect.Append(fv, reflect.ValueOf(item)))
|
||||
}
|
||||
case string:
|
||||
fv.SetString(values[0])
|
||||
case []byte:
|
||||
fv.SetBytes([]byte(values[0]))
|
||||
case int, int64:
|
||||
intVal, err := strconv.ParseInt(values[0], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ldap: could not parse value '%s' into int field", values[0])
|
||||
}
|
||||
fv.SetInt(intVal)
|
||||
case time.Time:
|
||||
t, err := ber.ParseGeneralizedTime([]byte(values[0]))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ldap: could not parse value '%s' into time.Time field", values[0])
|
||||
}
|
||||
fv.Set(reflect.ValueOf(t))
|
||||
case *DN:
|
||||
dn, err := ParseDN(values[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", values[0])
|
||||
}
|
||||
fv.Set(reflect.ValueOf(dn))
|
||||
case []*DN:
|
||||
for _, item := range values {
|
||||
dn, err := ParseDN(item)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", item)
|
||||
}
|
||||
fv.Set(reflect.Append(fv, reflect.ValueOf(dn)))
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte, *DN, []*DN or time.Time, got %v", ft.Type)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewEntryAttribute returns a new EntryAttribute with the desired key-value pair
|
||||
func NewEntryAttribute(name string, values []string) *EntryAttribute {
|
||||
var bytes [][]byte
|
||||
|
@ -218,6 +371,35 @@ func (s *SearchResult) PrettyPrint(indent int) {
|
|||
}
|
||||
}
|
||||
|
||||
// appendTo appends all entries of `s` to `r`
|
||||
func (s *SearchResult) appendTo(r *SearchResult) {
|
||||
r.Entries = append(r.Entries, s.Entries...)
|
||||
r.Referrals = append(r.Referrals, s.Referrals...)
|
||||
r.Controls = append(r.Controls, s.Controls...)
|
||||
}
|
||||
|
||||
// SearchSingleResult holds the server's single entry response to a search request
|
||||
type SearchSingleResult struct {
|
||||
// Entry is the returned entry
|
||||
Entry *Entry
|
||||
// Referral is the returned referral
|
||||
Referral string
|
||||
// Controls are the returned controls
|
||||
Controls []Control
|
||||
// Error is set when the search request was failed
|
||||
Error error
|
||||
}
|
||||
|
||||
// Print outputs a human-readable description
|
||||
func (s *SearchSingleResult) Print() {
|
||||
s.Entry.Print()
|
||||
}
|
||||
|
||||
// PrettyPrint outputs a human-readable description with indenting
|
||||
func (s *SearchSingleResult) PrettyPrint(indent int) {
|
||||
s.Entry.PrettyPrint(indent)
|
||||
}
|
||||
|
||||
// SearchRequest represents a search request to send to the server
|
||||
type SearchRequest struct {
|
||||
BaseDN string
|
||||
|
@ -285,10 +467,11 @@ func NewSearchRequest(
|
|||
// SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the
|
||||
// search request. All paged LDAP query responses will be buffered and the final result will be returned atomically.
|
||||
// The following four cases are possible given the arguments:
|
||||
// - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size
|
||||
// - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries
|
||||
// - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request
|
||||
// - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries
|
||||
// - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size
|
||||
// - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries
|
||||
// - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request
|
||||
// - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries
|
||||
//
|
||||
// A requested pagingSize of 0 is interpreted as no limit by LDAP servers.
|
||||
func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
|
||||
var pagingControl *ControlPaging
|
||||
|
@ -311,23 +494,19 @@ func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32)
|
|||
searchResult := new(SearchResult)
|
||||
for {
|
||||
result, err := l.Search(searchRequest)
|
||||
l.Debug.Printf("Looking for Paging Control...")
|
||||
if result != nil {
|
||||
result.appendTo(searchResult)
|
||||
} else {
|
||||
if err == nil {
|
||||
// We have to do this beautifulness in case something absolutely strange happens, which
|
||||
// should only occur in case there is no packet, but also no error.
|
||||
return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
// If an error occurred, all results that have been received so far will be returned
|
||||
return searchResult, err
|
||||
}
|
||||
if result == nil {
|
||||
return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
|
||||
}
|
||||
|
||||
for _, entry := range result.Entries {
|
||||
searchResult.Entries = append(searchResult.Entries, entry)
|
||||
}
|
||||
for _, referral := range result.Referrals {
|
||||
searchResult.Referrals = append(searchResult.Referrals, referral)
|
||||
}
|
||||
for _, control := range result.Controls {
|
||||
searchResult.Controls = append(searchResult.Controls, control)
|
||||
}
|
||||
|
||||
l.Debug.Printf("Looking for Paging Control...")
|
||||
pagingResult := FindControl(result.Controls, ControlTypePaging)
|
||||
|
@ -349,7 +528,9 @@ func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32)
|
|||
if pagingControl != nil {
|
||||
l.Debug.Printf("Abandoning Paging...")
|
||||
pagingControl.PagingSize = 0
|
||||
l.Search(searchRequest)
|
||||
if _, err := l.Search(searchRequest); err != nil {
|
||||
return searchResult, err
|
||||
}
|
||||
}
|
||||
|
||||
return searchResult, nil
|
||||
|
@ -366,7 +547,8 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
|
|||
result := &SearchResult{
|
||||
Entries: make([]*Entry, 0),
|
||||
Referrals: make([]string, 0),
|
||||
Controls: make([]Control, 0)}
|
||||
Controls: make([]Control, 0),
|
||||
}
|
||||
|
||||
for {
|
||||
packet, err := l.readPacket(msgCtx)
|
||||
|
@ -402,6 +584,32 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// SearchAsync performs a search request and returns all search results asynchronously.
|
||||
// This means you get all results until an error happens (or the search successfully finished),
|
||||
// e.g. for size / time limited requests all are recieved until the limit is reached.
|
||||
// To stop the search, call cancel function of the context.
|
||||
func (l *Conn) SearchAsync(
|
||||
ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response {
|
||||
r := newSearchResponse(l, bufferSize)
|
||||
r.start(ctx, searchRequest)
|
||||
return r
|
||||
}
|
||||
|
||||
// Syncrepl is a short name for LDAP Sync Replication engine that works on the
|
||||
// consumer-side. This can perform a persistent search and returns an entry
|
||||
// when the entry is updated on the server side.
|
||||
// To stop the search, call cancel function of the context.
|
||||
func (l *Conn) Syncrepl(
|
||||
ctx context.Context, searchRequest *SearchRequest, bufferSize int,
|
||||
mode ControlSyncRequestMode, cookie []byte, reloadHint bool,
|
||||
) Response {
|
||||
control := NewControlSyncRequest(mode, cookie, reloadHint)
|
||||
searchRequest.Controls = append(searchRequest.Controls, control)
|
||||
r := newSearchResponse(l, bufferSize)
|
||||
r.start(ctx, searchRequest)
|
||||
return r
|
||||
}
|
||||
|
||||
// unpackAttributes will extract all given LDAP attributes and it's values
|
||||
// from the ber.Packet
|
||||
func unpackAttributes(children []*ber.Packet) []*EntryAttribute {
|
||||
|
@ -425,3 +633,58 @@ func unpackAttributes(children []*ber.Packet) []*EntryAttribute {
|
|||
|
||||
return entries
|
||||
}
|
||||
|
||||
// DirSync does a Search with dirSync Control.
|
||||
func (l *Conn) DirSync(
|
||||
searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte,
|
||||
) (*SearchResult, error) {
|
||||
control := FindControl(searchRequest.Controls, ControlTypeDirSync)
|
||||
if control == nil {
|
||||
c := NewRequestControlDirSync(flags, maxAttrCount, cookie)
|
||||
searchRequest.Controls = append(searchRequest.Controls, c)
|
||||
} else {
|
||||
c := control.(*ControlDirSync)
|
||||
if c.Flags != flags {
|
||||
return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", c.Flags, flags)
|
||||
}
|
||||
if c.MaxAttrCount != maxAttrCount {
|
||||
return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", c.MaxAttrCount, maxAttrCount)
|
||||
}
|
||||
}
|
||||
searchResult, err := l.Search(searchRequest)
|
||||
l.Debug.Printf("Looking for result...")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if searchResult == nil {
|
||||
return nil, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
|
||||
}
|
||||
|
||||
l.Debug.Printf("Looking for DirSync Control...")
|
||||
resultControl := FindControl(searchResult.Controls, ControlTypeDirSync)
|
||||
if resultControl == nil {
|
||||
l.Debug.Printf("Could not find dirSyncControl control. Breaking...")
|
||||
return searchResult, nil
|
||||
}
|
||||
|
||||
cookie = resultControl.(*ControlDirSync).Cookie
|
||||
if len(cookie) == 0 {
|
||||
l.Debug.Printf("Could not find cookie. Breaking...")
|
||||
return searchResult, nil
|
||||
}
|
||||
|
||||
return searchResult, nil
|
||||
}
|
||||
|
||||
// DirSyncDirSyncAsync performs a search request and returns all search results
|
||||
// asynchronously. This is efficient when the server returns lots of entries.
|
||||
func (l *Conn) DirSyncAsync(
|
||||
ctx context.Context, searchRequest *SearchRequest, bufferSize int,
|
||||
flags, maxAttrCount int64, cookie []byte,
|
||||
) Response {
|
||||
control := NewRequestControlDirSync(flags, maxAttrCount, cookie)
|
||||
searchRequest.Controls = append(searchRequest.Controls, control)
|
||||
r := newSearchResponse(l, bufferSize)
|
||||
r.start(ctx, searchRequest)
|
||||
return r
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue