58 Commits

Author SHA1 Message Date
moul-bot
d6e112de34 feat: semantic release (#151)
feat: semantic release
2020-01-15 17:40:37 +01:00
Manfred Touron
f3d1bfdee9 feat: semantic release 2020-01-15 17:36:50 +01:00
Manfred Touron
066f210f39 Merge pull request #148 from batazor/fix
Fix index helpers
2019-12-04 11:52:42 +01:00
Victor Login
b244c9cc31 Fix index helpers 2019-11-27 19:30:42 +03:00
Manfred Touron
3a6b47dc92 add parameter file-mode for template at file level (#143)
add parameter file-mode for template at file level
2019-05-30 20:12:52 +02:00
Manfred Touron
5b5d8bf81e Add stringFileOptionsExtension (#144)
Add stringFileOptionsExtension
2019-05-30 20:12:25 +02:00
Manfred Touron
42f1289ddc Update README.md 2019-05-03 13:58:23 +02:00
Gero
2d28c0f341 Add stringFileOptionsExtension 2019-04-26 15:34:25 -03:00
Mike Lee
e4d1fef8fe fmt 2019-03-29 19:24:47 -04:00
Mike Lee
1d71729a7c add parameter file-mode for templates that wish to parse at a file level instead of each service 2019-03-29 13:35:55 -04:00
Manfred Touron
c6eca2356c Merge pull request #142 from moul/dev/moul/rebase-85
Introducing two new helpers, one for returning in templates go_packag…
2019-02-10 22:00:41 +01:00
Manfred Touron
3576fd314a Merge pull request #141 from moul/dev/moul/fix-int64-extension
fix: fix 'int64*Extension' helpers
2019-02-10 21:41:51 +01:00
webii
f43d2a2ac8 Introducing two new helpers, one for returning in templates go_package path, and second one giving only last element of go_package namespace 2019-02-10 21:41:03 +01:00
Manfred Touron
baffaa4b6a Merge pull request #127 from piotrekmonko/fix_intermittent_errors
fixes intermittent error reporting
2019-02-10 21:37:31 +01:00
Manfred Touron
db5e9b7f99 fix: fix 'int64*Extension' helpers 2019-02-10 19:55:05 +01:00
Manfred Touron
be3a88a34a Merge pull request #140 from moul/dev/moul/message-options
feat: add message option helpers
2019-02-08 19:12:27 +01:00
Manfred Touron
bd48cf6168 feat: add message option helpers 2019-02-08 19:07:56 +01:00
Manfred Touron
7110803a05 Merge pull request #139 from moul/dev/moul/int64FieldExtension
feat: add int64FieldExtension helper
2019-01-20 07:21:16 +01:00
Manfred Touron
c41132a4f6 chore: switch to golangci-lint 2019-01-20 07:18:16 +01:00
Manfred Touron
36f4177b68 chore: lint 2019-01-19 20:01:03 +01:00
Manfred Touron
1ff20846eb feat: add int64FieldExtension helper 2019-01-18 19:41:46 +01:00
Manfred Touron
5202ea7a53 chore: group renovate bump PRs into one 2019-01-09 16:37:05 +01:00
Peter Monko
6949b76e12 fixes intermittent error reporting 2018-10-03 13:31:08 +02:00
Manfred Touron
0a47157c26 Merge pull request #109 from moul/dev/moul/escaped-paths
feat: support escaped paths (#107)
2018-09-13 17:57:54 +02:00
Manfred Touron
48265e1947 feat: support escaped paths (#107) 2018-09-13 17:52:52 +02:00
Manfred Touron
5ab09755c8 Merge pull request #108 from moul/dev/moul/canonical-url
configure modules + switch to canonical URL
2018-09-13 16:52:01 +02:00
Manfred Touron
8b191e85f2 chore: switch to go modules 2018-09-13 16:47:21 +02:00
Manfred Touron
c015ff9539 chore: switch to moul.io/protoc-gen-gotemplate 2018-09-13 16:38:39 +02:00
Manfred Touron
ac2e25e74f Merge pull request #105 from moul/fix/gfanton/store-concurrency
fix(store): Fix store concurrency
2018-09-12 20:13:57 +02:00
Manfred Touron
7d8c6c4b19 Merge pull request #103 from moul/renovate/configure
Configure Renovate
2018-09-12 20:13:39 +02:00
Renovate Bot
8ec535ac58 Add renovate.json 2018-08-28 16:31:46 +00:00
Manfred Touron
ca3f353d44 Merge pull request #106 from sfroment/dev/sfroment/update-protoc
Update the znly/protoc image
2018-08-28 18:26:05 +02:00
Sacha Froment
9b14e8fb8a fix: update to the latest image
Signed-off-by: Sacha Froment <sfroment42@gmail.com>
2018-08-28 17:11:38 +02:00
Guilhem Fanton
5b8733f8ec fix(store): Fix store concurrency 2018-08-23 18:49:20 +02:00
Manfred Touron
90a63053d4 Merge pull request #104 from moul/dev/gfanton/add-bool-method-option
feat(options): Add bool method option
2018-08-20 17:55:15 +02:00
Guilhem Fanton
8a8c54b5b4 chore(store): Dont panic on get store 2018-08-20 17:41:58 +02:00
Guilhem Fanton
9938cbdf5a feat(option): Add bool method option 2018-08-20 17:41:36 +02:00
Manfred Touron
0fe0e49401 chore: set git remote to GitHub https URL 2018-08-14 00:56:16 +02:00
Julien Hayotte
5b056b2579 Merge pull request #102 from jhayotte/MessageEmbedded
feat: add goTypeWithGoPackage
2018-08-09 10:40:48 +02:00
jhayotte
4b9116a5dd feat: add goTypeWithGoPackage
This function allows you to prefix your field of type message with what
you defined in the option go_package.

Moreover it handles message embedded.

In this commit, I rename gometalinter gas option in gosec.
2018-08-09 10:28:26 +02:00
Manfred Touron
6abbdf4cfb Merge pull request #96 from moul/helpers/gfanton/add-store-helper
Add Concat/Join/Store helpers
2018-04-07 19:42:21 +02:00
Guilhem Fanton
adb58bd940 Add concat & Join helper methods 2018-04-06 17:18:38 +02:00
Julien Hayotte
283713bd93 Merge pull request #95 from Tommy-42/master
Add arithmetics helpers - fix #94
2018-03-21 17:29:47 +01:00
Tommy PAGEARD
19fb3c8da9 Add arithmetics helpers - fix #94 2018-03-21 15:03:26 +01:00
Manfred Touron
77a820ea6f Merge pull request #93 from sumup/feature/stringMethodOptionsExtension
Add stringMethodOptionsExtension
2018-02-27 11:02:53 +01:00
Jan Weitz
6765ff04eb Add stringMethodOptionsExtension
This helper function can be used on service methods
to read options, which extend method options by a string.

e.g.

It is possible to read "Some string" for a custom

`my_method_option.bar` message option extension.

See:

https://developers.google.com/protocol-buffers/docs/proto#customoptions

on how to define a custom extension and referencing is by a fieldID.

```
service MyService {

  rpc MyMethod(RequestType) returns(ResponseType) {
    option (my_method_option.bar) = "Some string";
  }
}
```

If `my_method_option.bar` was defined for `fieldID: 50000`
one can reference it using protoc-gen-template like:

```
{{- stringMethodOptionsExtension 50000 .method }}
```
2018-02-23 16:55:45 +01:00
Alexandre Beslic
bb3105a862 Merge pull request #91 from tkerambloch/dev/tkerambloch/getHttpPathInAdditionalBindings
Helper: add helper like httpPath for get path in additional bindings options
2018-02-13 17:34:22 +01:00
Thomas KERAMBLOCH
0c29a9922f add helper like httpPath for get path in additional bindings options 2018-02-13 16:00:04 +01:00
Alexandre Beslic
aff21c1e7c Merge pull request #90 from tkerambloch/dev/tkerambloch/helperGetIndexArray
fix type int32 in index helper
2018-02-12 17:03:31 +01:00
Thomas KERAMBLOCH
15ac366c8d fix type int32 in index helper 2018-02-12 16:48:36 +01:00
Alexandre Beslic
5a9555bcf5 Merge pull request #89 from tkerambloch/dev/tkerambloch/helperGetIndexArray
Add Helper 'index': get elem of array with the index
2018-02-10 18:43:45 +01:00
Thomas KERAMBLOCH
3f1d5001d9 Add Helper 'index': get elem of array with the index 2018-02-10 18:00:39 +01:00
Manfred Touron
be5f14041f Merge pull request #83 from pmoroney/new-helpers
add new helpers
2018-01-12 23:25:50 +01:00
Pat Moroney
100279cd16 add comment about tree traversal 2018-01-12 11:36:29 -07:00
Pat Moroney
4f138c4f92 remove duplicated line 2018-01-12 11:23:17 -07:00
Pat Moroney
78cb5588b1 make nilString constant 2018-01-12 11:12:15 -07:00
Pat Moroney
0b600dd93f rename fieldId to fieldID 2018-01-12 10:56:25 -07:00
Pat Moroney
decb64ccd8 add new helpers 2018-01-12 10:38:10 -07:00
265 changed files with 1435 additions and 50652 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @moul

6
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
#github: ["moul"]
patreon: moul
open_collective: moul
custom:
- "https://www.buymeacoffee.com/moul"
- "https://manfred.life/donate"

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] "
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Type '....'
3. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots / Logs**
If applicable, add screenshots or logs to help explain your problem.
**Versions (please complete the following information, if relevant):**
- Software version: [e.g. v1.2.3, latest, building from sources]
- OS: [e.g. Ubuntu, Mac, iOS, ...]
- Golang version [e.g. 1.13]
**Additional context**
Add any other context about the problem here.

8
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View File

@@ -0,0 +1,8 @@
---
name: Custom
about: 'Anything else: questions, discussions, thanks, ascii-arts, ...'
title: ''
labels: discussion
assignees: moul
---

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[IDEA] "
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

9
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,9 @@
<!--
Thank you for your contribution to this repo!
Before submitting a pull request, please check the following:
- reference any related issue, PR, link
- use the "WIP" title prefix if you need help or more time to finish your PR
you can remove this markdown comment
-->

6
.github/renovate.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": [
"config:base"
],
"groupName": "all"
}

13
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Semantic Release
on: push
jobs:
semantic-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: codfish/semantic-release-action@v1
if: github.ref == 'refs/heads/master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
vendor/**/.travis.yml
/protoc-gen-gotemplate
# Compiled Object files, Static and Dynamic libs (Shared Objects)

34
.golangci.yml Normal file
View File

@@ -0,0 +1,34 @@
run:
deadline: 1m
tests: false
#skip-files:
# - ".*\\.gen\\.go"
linters-settings:
golint:
min-confidence: 0
maligned:
suggest-new: true
goconst:
min-len: 5
min-occurrences: 4
misspell:
locale: US
linters:
disable-all: true
enable:
- goconst
- misspell
- deadcode
- misspell
- structcheck
- errcheck
- unused
- varcheck
- staticcheck
- unconvert
- gofmt
- goimports
- golint
- ineffassign

8
.releaserc.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
branch: 'master',
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/github',
],
};

View File

@@ -1,15 +1,16 @@
language: go
go: 1.8.x
go: 1.11.x
go_import_path: moul.io/protoc-gen-gotemplate
install:
- go get github.com/Masterminds/glide
- wget https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/.travis/install-protoc.sh && chmod +x install-protoc.sh && ./install-protoc.sh 3.2.0
- wget https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v1.5.0/.travis/install-protoc.sh && chmod +x install-protoc.sh && ./install-protoc.sh 3.4.0
- go get -u github.com/golang/protobuf/protoc-gen-go
- go get -u github.com/alecthomas/gometalinter
- gometalinter --install
- go get github.com/securego/gosec/cmd/gosec/...
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.12.2
script:
- make install
- make test
- make lint
- PATH=$PATH:$(pwd)/bin make lint
cache:
directories:
- $HOME/local

View File

@@ -1,4 +1,4 @@
FROM znly/protoc
FROM znly/protoc:0.3.0
ENV GOPATH=/go \
PATH=/go/bin:${PATH}
@@ -8,6 +8,7 @@ RUN apk --update add make git go rsync libc-dev \
&& go get -u golang.org/x/tools/cmd/goimports
# Install protoc-gen-gotemplate
COPY . /go/src/github.com/moul/protoc-gen-gotemplate
WORKDIR /go/src/github.com/moul/protoc-gen-gotemplate
COPY . /go/src/moul.io/protoc-gen-gotemplate
WORKDIR /go/src/moul.io/protoc-gen-gotemplate
RUN git remote set-url origin https://github.com/moul/protoc-gen-gotemplate
RUN go install . ./cmd/web-editor

View File

@@ -18,8 +18,9 @@ test: install
cd examples/flow && make
cd examples/sitemap && make
cd examples/go-generate && make
cd examples/single-package-mode && make
# cd examples/single-package-mode && make
cd examples/helpers && make
cd examples/arithmetics && make
# cd examples/go-kit && make
.PHONY: docker.build
@@ -32,4 +33,4 @@ docker.push: docker.build
.PHONY: lint
lint:
gometalinter --disable-all --enable=errcheck --enable=vet --enable=vetshadow --enable=golint --enable=gas --enable=ineffassign --enable=goconst --enable=goimports --enable=gofmt --exclude="Binds to all network interfaces" --exclude="should have comment" --enable=staticcheck --enable=gosimple --enable=misspell --deadline=120s . ./cmd/... ./helpers/...
golangci-lint run --verbose . ./helpers ./cmd/...

View File

@@ -73,33 +73,59 @@ See [examples](./examples).
This project uses [Masterminds/sprig](https://github.com/Masterminds/sprig) library and additional functions to extend the builtin [text/template](https://golang.org/pkg/text/template) helpers.
Non-exhaustive list of new helpers:s
Non-exhaustive list of new helpers:
* **all the functions from [sprig](https://github.com/Masterminds/sprig)**
* `json`
* `prettyjson`
* `first`
* `last`
* `splitArray`
* `upperFirst`
* `lowerFirst`
* `add`
* `boolFieldExtension`
* `camelCase`
* `lowerCamelCase`
* `kebabCase`
* `snakeCase`
* `getProtoFile`
* `getMessageType`
* `contains`
* `divide`
* `fieldMapKeyType`
* `fieldMapValueType`
* `first`
* `getEnumValue`
* `getMessageType`
* `getProtoFile`
* `goNormalize`
* `goTypeWithPackage`
* `goType`
* `goZeroValue`
* `haskellType`
* `httpBody`
* `httpPath`
* `httpPathsAdditionalBindings`
* `httpVerb`
* `index`
* `int64FieldExtension`
* `isFieldMap`
* `isFieldMessageTimeStamp`
* `isFieldMessage`
* `isFieldRepeated`
* `goType`
* `goTypeWithPackage`
* `jsType`
* `jsSuffixReserved`
* `jsType`
* `json`
* `kebabCase`
* `last`
* `leadingComment`
* `leadingDetachedComments`
* `lowerCamelCase`
* `lowerFirst`
* `lowerGoNormalize`
* `multiply`
* `namespacedFlowType`
* `httpVerb`
* `httpPath`
* `prettyjson`
* `replaceDict`
* `shortType`
* `snakeCase`
* `splitArray`
* `stringFieldExtension`
* `stringMethodOptionsExtension`
* `string`
* `subtract`
* `trailingComment`
* `trimstr`
* `upperFirst`
* `urlHasVarsFromMessage`
See the project helpers for the complete list.
@@ -108,7 +134,7 @@ See the project helpers for the complete list.
* Install the **Go** compiler and tools from https://golang.org/doc/install
* Install **protobuf**: `go get -u github.com/golang/protobuf/{proto,protoc-gen-go}`
* Install **protoc-gen-gotemplate**: `go get -u github.com/moul/protoc-gen-gotemplate`
* Install **protoc-gen-gotemplate**: `go get -u moul.io/protoc-gen-gotemplate`
## Docker
@@ -127,6 +153,10 @@ $> docker run --rm -v "$(pwd):$(pwd)" -w "$(pwd)" moul/protoc-gen-gotemplate -I.
* [translator](https://github.com/moul/translator): Translator Micro-service using Gettext and Go-Kit
* [acl](https://github.com/moul/acl): ACL micro-service (gRPC/protobuf + http/json)
## See also
* [pbhbs](https://github.com/gponsinet/pbhbs): protobuf gen based on handlebarjs template
## License
MIT

6
assets/assets Normal file
View File

@@ -0,0 +1,6 @@
/Users/moul/go/src/moul.io/protoc-gen-gotemplate/assets:
total used in directory 552 available 9223372036849978910
drwxr-xr-x 4 moul staff 128 Dec 9 11:31 .
lrwxr-xr-x 1 moul staff 33 Dec 9 11:31 .#web-editor.jpg -> moul@manfred-spacegray.local.9471
drwxr-xr-x 21 moul staff 672 Sep 13 18:05 ..
-rw-r--r--@ 1 moul staff 280357 Oct 26 2017 web-editor.jpg

View File

@@ -57,7 +57,7 @@ func generate(w http.ResponseWriter, r *http.Request) {
}
// read output
content, err := ioutil.ReadFile(filepath.Join(dir, "example.output"))
content, err := ioutil.ReadFile(filepath.Join(dir, "example.output")) // #nosec
if err != nil {
returnError(w, err)
return

View File

@@ -129,10 +129,10 @@
<div class="row">
<div class="col-md-12">
<div>Command: <code>protoc --gotemplate_out=template_dir=.:. example.proto</code></div>
<div>Powered by <a href="https://github.com/moul/protoc-gen-gotemplate">protoc-gen-gotemplate</a></div>
<div>Powered by <a href="https://moul.io/protoc-gen-gotemplate">protoc-gen-gotemplate</a></div>
</div>
</div>
</div>
<a href="https://github.com/moul/protoc-gen-gotemplate"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
<a href="https://moul.io/protoc-gen-gotemplate"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
</body>
</html>

View File

@@ -3,6 +3,7 @@ package main
import (
"bytes"
"log"
"net/url"
"os"
"path/filepath"
"strings"
@@ -10,9 +11,9 @@ import (
"time"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/golang/protobuf/protoc-gen-go/plugin"
plugin_go "github.com/golang/protobuf/protoc-gen-go/plugin"
pgghelpers "github.com/moul/protoc-gen-gotemplate/helpers"
pgghelpers "moul.io/protoc-gen-gotemplate/helpers"
)
type GenericTemplateBasedEncoder struct {
@@ -52,6 +53,7 @@ func NewGenericServiceTemplateBasedEncoder(templateDir string, service *descript
if debug {
log.Printf("new encoder: file=%q service=%q template-dir=%q", file.GetName(), service.GetName(), templateDir)
}
pgghelpers.InitPathMap(file)
return
}
@@ -68,6 +70,7 @@ func NewGenericTemplateBasedEncoder(templateDir string, file *descriptor.FileDes
if debug {
log.Printf("new encoder: file=%q template-dir=%q", file.GetName(), templateDir)
}
pgghelpers.InitPathMap(file)
return
}
@@ -92,6 +95,7 @@ func (e *GenericTemplateBasedEncoder) templates() ([]string, error) {
if e.debug {
log.Printf("new template: %q", rel)
}
filenames = append(filenames, rel)
return nil
})
@@ -133,6 +137,14 @@ func (e *GenericTemplateBasedEncoder) genAst(templateFilename string) (*Ast, err
Enum: e.enum,
}
buffer := new(bytes.Buffer)
unescaped, err := url.QueryUnescape(templateFilename)
if err != nil {
log.Printf("failed to unescape filepath %q: %v", templateFilename, err)
} else {
templateFilename = unescaped
}
tmpl, err := template.New("").Funcs(pgghelpers.ProtoHelpersFuncMap).Parse(templateFilename)
if err != nil {
return nil, err
@@ -198,10 +210,8 @@ func (e *GenericTemplateBasedEncoder) Files() []*plugin_go.CodeGeneratorResponse
case f := <-resultChan:
files = append(files, f)
case err = <-errChan:
panic(err)
}
}
if err != nil {
panic(err)
}
return files
}

View File

@@ -0,0 +1,13 @@
.PHONY: build
build:
mkdir -p output
protoc -I. --gotemplate_out=template_dir=templates,debug=true,all=true:output proto/*.proto
.PHONY: re
re: clean build
.PHONY: clean
clean:
rm -rf output

View File

@@ -0,0 +1,5 @@
add(1,2) = 3
subtract(1,2) = -1
multiply(1,2) = 2
divide(2,1) = 2

View File

@@ -0,0 +1,2 @@
syntax = "proto3";
package Arithmetics;

View File

@@ -0,0 +1,6 @@
{{with $a := 1}}{{with $b := 2}}
add(1,2) = {{add $a $b}}
subtract(1,2) = {{subtract $a $b}}
multiply(1,2) = {{multiply $a $b}}
divide(2,1) = {{divide $b $a}}
{{end}}{{end}}

View File

@@ -1,4 +1,4 @@
package: github.com/moul/protoc-gen-gotemplate/examples/go-kit
package: moul.io/protoc-gen-gotemplate/examples/go-kit
import:
- package: github.com/go-kit/kit
subpackages:

View File

@@ -12,23 +12,23 @@ import (
"github.com/gorilla/handlers"
"google.golang.org/grpc"
session_svc "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session"
session_endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/endpoints"
session_pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
session_grpctransport "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/transports/grpc"
session_httptransport "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/transports/http"
session_svc "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session"
session_endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/endpoints"
session_pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
session_grpctransport "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/transports/grpc"
session_httptransport "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/transports/http"
sprint_svc "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint"
sprint_endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/endpoints"
sprint_pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
sprint_grpctransport "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/transports/grpc"
sprint_httptransport "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/transports/http"
sprint_svc "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint"
sprint_endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/endpoints"
sprint_pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
sprint_grpctransport "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/transports/grpc"
sprint_httptransport "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/transports/http"
user_svc "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user"
user_endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
user_pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
user_grpctransport "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/transports/grpc"
user_httptransport "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/transports/http"
user_svc "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user"
user_endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
user_pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
user_grpctransport "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/transports/grpc"
user_httptransport "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/transports/http"
)
func main() {

View File

@@ -9,8 +9,8 @@ import (
grpctransport "github.com/go-kit/kit/transport/grpc"
"google.golang.org/grpc"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
)
func New(conn *grpc.ClientConn, logger log.Logger) pb.SessionServiceServer {

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"github.com/go-kit/kit/endpoint"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
oldcontext "golang.org/x/net/context"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
)
var _ = endpoint.Chain

View File

@@ -7,8 +7,8 @@ import (
grpctransport "github.com/go-kit/kit/transport/grpc"
oldcontext "golang.org/x/net/context"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
)
// avoid import errors

View File

@@ -8,8 +8,8 @@ import (
gokit_endpoint "github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
)
var _ = log.Printf

View File

@@ -5,7 +5,7 @@ import (
"golang.org/x/net/context"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
)
type Service struct{}

View File

@@ -9,8 +9,8 @@ import (
grpctransport "github.com/go-kit/kit/transport/grpc"
"google.golang.org/grpc"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
)
func New(conn *grpc.ClientConn, logger log.Logger) pb.SprintServiceServer {

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"github.com/go-kit/kit/endpoint"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
oldcontext "golang.org/x/net/context"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
)
var _ = endpoint.Chain

View File

@@ -7,8 +7,8 @@ import (
grpctransport "github.com/go-kit/kit/transport/grpc"
oldcontext "golang.org/x/net/context"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
)
// avoid import errors

View File

@@ -8,8 +8,8 @@ import (
gokit_endpoint "github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
)
var _ = log.Printf

View File

@@ -5,7 +5,7 @@ import (
"golang.org/x/net/context"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/sprint/gen/pb"
)
type Service struct{}

View File

@@ -9,8 +9,8 @@ import (
grpctransport "github.com/go-kit/kit/transport/grpc"
"google.golang.org/grpc"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
)
func New(conn *grpc.ClientConn, logger log.Logger) pb.UserServiceServer {

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"github.com/go-kit/kit/endpoint"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
oldcontext "golang.org/x/net/context"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
)
var _ = endpoint.Chain

View File

@@ -7,8 +7,8 @@ import (
grpctransport "github.com/go-kit/kit/transport/grpc"
oldcontext "golang.org/x/net/context"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
)
// avoid import errors

View File

@@ -8,8 +8,8 @@ import (
gokit_endpoint "github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
)
var _ = log.Printf

View File

@@ -5,7 +5,7 @@ import (
"golang.org/x/net/context"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
)
type Service struct{}

0
examples/go.mod Normal file
View File

View File

@@ -25,7 +25,7 @@
{{abbrev 5 "hello world"}}: he...
{{abbrevboth 5 10 "1234 5678 9123"}}: ...5678...
{{initials "First Try"}}: FT
{{randNumeric 3}}: 146
{{randNumeric 3}}: 528
{{- /*{{wrap 80 $someText}}*/}}:
{{wrapWith 5 "\t" "Hello World"}}: Hello World
{{contains "cat" "catch"}}: true
@@ -45,6 +45,9 @@
{{regexReplaceAllLiteral "a(x*)b" "-ab-axxb-" "${1}"}}: -${1}-${1}-
{{regexSplit "z+" "pizza" -1}}: [pi a]
# Get one specific method on array method using index
{{ index .Service.Method 1 }}: name:"Iii" input_type:".dummy.Dummy2" output_type:".dummy.Dummy1" options:<>
# Sprig: advanced
{{if contains "cat" "catch"}}yes{{else}}no{{end}}: yes
{{1 | plural "one anchovy" "many anchovies"}}: one anchovy

View File

@@ -46,6 +46,9 @@
{{`{{regexReplaceAllLiteral "a(x*)b" "-ab-axxb-" "${1}"}}`}}: {{regexReplaceAllLiteral "a(x*)b" "-ab-axxb-" "${1}"}}
{{`{{regexSplit "z+" "pizza" -1}}`}}: {{regexSplit "z+" "pizza" -1}}
# Get one specific method on array method using index
{{`{{ index .Service.Method 1 }}`}}: {{ index .Service.Method 1 }}
# Sprig: advanced
{{`{{if contains "cat" "catch"}}yes{{else}}no{{end}}`}}: {{if contains "cat" "catch"}}yes{{else}}no{{end}}
{{`{{1 | plural "one anchovy" "many anchovies"}}`}}: {{1 | plural "one anchovy" "many anchovies"}}

View File

@@ -2,7 +2,7 @@ syntax = "proto3";
package dummy;
option go_package = "github.com/moul/protoc-gen-gotemplate/examples/helpers";
option go_package = "moul.io/protoc-gen-gotemplate/examples/helpers";
message Dummy1 {
float aaa = 1;

View File

@@ -3,7 +3,7 @@ build:
mkdir -p output
# generate pb.go inluding imported proto
protoc --go_out=Mproto/common.proto=github.com/moul/protoc-gen-gotemplate/examples/import/output/models/common:./output proto/article.proto
protoc --go_out=Mproto/common.proto=moul.io/protoc-gen-gotemplate/examples/import/output/models/common:./output proto/article.proto
protoc --go_out=,plugins=grpc:./output proto/common.proto
# build our go file based on our template

View File

@@ -1,6 +1,5 @@
// Code generated by protoc-gen-go.
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: proto/article.proto
// DO NOT EDIT!
/*
Package article is a generated protocol buffer package.
@@ -18,7 +17,7 @@ package article
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import common "github.com/moul/protoc-gen-gotemplate/examples/import/output/models/common"
import common "moul.io/protoc-gen-gotemplate/examples/import/output/models/common"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@@ -49,6 +48,8 @@ func (m *GetArticleRequest) GetGetarticle() *common.GetArticle {
type GetArticleResponse struct {
Article *Article `protobuf:"bytes,1,opt,name=article" json:"article,omitempty"`
// The generated output should write []*GetArticleResponse_Storage.Storage for this field.
Storages []*GetArticleResponse_Storage `protobuf:"bytes,2,rep,name=storages" json:"storages,omitempty"`
}
func (m *GetArticleResponse) Reset() { *m = GetArticleResponse{} }
@@ -63,6 +64,29 @@ func (m *GetArticleResponse) GetArticle() *Article {
return nil
}
func (m *GetArticleResponse) GetStorages() []*GetArticleResponse_Storage {
if m != nil {
return m.Storages
}
return nil
}
type GetArticleResponse_Storage struct {
Code string `protobuf:"bytes,1,opt,name=code" json:"code,omitempty"`
}
func (m *GetArticleResponse_Storage) Reset() { *m = GetArticleResponse_Storage{} }
func (m *GetArticleResponse_Storage) String() string { return proto.CompactTextString(m) }
func (*GetArticleResponse_Storage) ProtoMessage() {}
func (*GetArticleResponse_Storage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
func (m *GetArticleResponse_Storage) GetCode() string {
if m != nil {
return m.Code
}
return ""
}
type Article struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
@@ -88,26 +112,30 @@ func (m *Article) GetName() string {
}
func init() {
proto.RegisterType((*GetArticleRequest)(nil), "article.GetArticleRequest")
proto.RegisterType((*GetArticleResponse)(nil), "article.GetArticleResponse")
proto.RegisterType((*Article)(nil), "article.Article")
proto.RegisterType((*GetArticleRequest)(nil), "company.GetArticleRequest")
proto.RegisterType((*GetArticleResponse)(nil), "company.GetArticleResponse")
proto.RegisterType((*GetArticleResponse_Storage)(nil), "company.GetArticleResponse.Storage")
proto.RegisterType((*Article)(nil), "company.Article")
}
func init() { proto.RegisterFile("proto/article.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 208 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2e, 0x28, 0xca, 0x2f,
0xc9, 0xd7, 0x4f, 0x2c, 0x2a, 0xc9, 0x4c, 0xce, 0x49, 0xd5, 0x03, 0xf3, 0x84, 0xd8, 0xa1, 0x5c,
0x29, 0x21, 0x88, 0x6c, 0x72, 0x7e, 0x6e, 0x6e, 0x7e, 0x1e, 0x44, 0x52, 0xc9, 0x9d, 0x4b, 0xd0,
0x3d, 0xb5, 0xc4, 0x11, 0xa2, 0x22, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44, 0xc8, 0x88, 0x8b,
0x2b, 0x3d, 0xb5, 0x04, 0xaa, 0x4d, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x48, 0x0f, 0xaa,
0x0f, 0x49, 0x39, 0x92, 0x2a, 0x25, 0x07, 0x2e, 0x21, 0x64, 0x83, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a,
0x53, 0x85, 0xb4, 0xb8, 0xd8, 0x51, 0x8d, 0x11, 0xd0, 0x83, 0x39, 0x0e, 0xa6, 0x14, 0xa6, 0x40,
0x49, 0x97, 0x8b, 0x1d, 0x2a, 0x26, 0xc4, 0xc7, 0xc5, 0x94, 0x99, 0x02, 0xd6, 0xc1, 0x19, 0xc4,
0x94, 0x99, 0x22, 0x24, 0xc4, 0xc5, 0x92, 0x97, 0x98, 0x9b, 0x2a, 0xc1, 0x04, 0x16, 0x01, 0xb3,
0x8d, 0x42, 0xb9, 0xb8, 0xa0, 0x3a, 0x8b, 0xcb, 0x92, 0x85, 0xdc, 0xb9, 0xb8, 0x10, 0xd6, 0x0b,
0x49, 0xc1, 0x6d, 0xc1, 0xf0, 0x9c, 0x94, 0x34, 0x56, 0x39, 0x88, 0x7b, 0x95, 0x18, 0x9c, 0x24,
0xa2, 0xc4, 0x72, 0xf3, 0x53, 0x52, 0x73, 0x8a, 0x61, 0xa1, 0x68, 0x0d, 0xa5, 0x93, 0xd8, 0xc0,
0x21, 0x66, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x16, 0x18, 0x87, 0xc4, 0x65, 0x01, 0x00, 0x00,
// 256 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0x41, 0x4b, 0xc4, 0x30,
0x10, 0x85, 0x6d, 0x15, 0xab, 0xb3, 0x20, 0x3a, 0x82, 0x94, 0x8a, 0xb0, 0xd4, 0xcb, 0x22, 0x18,
0xa1, 0x1e, 0x3d, 0x88, 0x5e, 0x7a, 0xaf, 0x78, 0xf1, 0x56, 0xd3, 0x61, 0x29, 0x6c, 0x3a, 0xb5,
0x89, 0x82, 0xff, 0xc6, 0x9f, 0x2a, 0x9b, 0x4c, 0xd7, 0x8a, 0xb2, 0xa7, 0x4e, 0xf3, 0xbe, 0xf7,
0xf2, 0xc8, 0xc0, 0x69, 0x3f, 0xb0, 0xe3, 0x9b, 0x7a, 0x70, 0xad, 0x5e, 0x91, 0xf2, 0x7f, 0x98,
0x68, 0x36, 0x7d, 0xdd, 0x7d, 0x66, 0x18, 0x54, 0xcd, 0xc6, 0x70, 0x17, 0xc4, 0xbc, 0x84, 0x93,
0x92, 0xdc, 0x43, 0x30, 0x54, 0xf4, 0xf6, 0x4e, 0xd6, 0x61, 0x01, 0xb0, 0x24, 0x27, 0x29, 0x69,
0x34, 0x8f, 0x16, 0xb3, 0x02, 0x95, 0xf8, 0x26, 0xf8, 0x84, 0xca, 0xbf, 0x22, 0xc0, 0x69, 0x92,
0xed, 0xb9, 0xb3, 0x84, 0x57, 0x90, 0xfc, 0xce, 0x39, 0x56, 0x52, 0x47, 0x8d, 0xe8, 0x08, 0xe0,
0x3d, 0x1c, 0x58, 0xc7, 0x43, 0xbd, 0x24, 0x9b, 0xc6, 0xf3, 0xdd, 0xc5, 0xac, 0xb8, 0xdc, 0xc0,
0x7f, 0xa3, 0xd5, 0x53, 0x60, 0xab, 0x8d, 0x29, 0xbb, 0x80, 0x44, 0x0e, 0x11, 0x61, 0x4f, 0x73,
0x13, 0x2e, 0x3d, 0xac, 0xfc, 0x9c, 0x5f, 0x43, 0x22, 0x19, 0x78, 0x04, 0x71, 0xdb, 0x88, 0x18,
0xb7, 0xcd, 0x1a, 0xef, 0x6a, 0x43, 0x69, 0x1c, 0xf0, 0xf5, 0x5c, 0x3c, 0x03, 0x48, 0x33, 0xfb,
0xa1, 0xb1, 0x04, 0xf8, 0xe9, 0x80, 0xd9, 0xbf, 0xc5, 0xfc, 0xeb, 0x65, 0xe7, 0x5b, 0x4a, 0xe7,
0x3b, 0x8f, 0xe9, 0xcb, 0x99, 0xe1, 0x86, 0x56, 0x76, 0x5c, 0xd3, 0x9d, 0x7c, 0x5f, 0xf7, 0xfd,
0x4a, 0x6e, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x93, 0xb5, 0x4a, 0x95, 0xc6, 0x01, 0x00, 0x00,
}

View File

@@ -1,6 +1,5 @@
// Code generated by protoc-gen-go.
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: proto/common.proto
// DO NOT EDIT!
/*
Package common is a generated protocol buffer package.

View File

@@ -1,11 +1,26 @@
// Code generated by protoc-gen-gotemplate
package article
package company
import (
"github.com/moul/protoc-gen-gotemplate/examples/import/output/models/article"
"github.com/moul/protoc-gen-gotemplate/examples/import/output/models/common"
"context"
"moul.io/protoc-gen-gotemplate/examples/import/output/models/article"
"moul.io/protoc-gen-gotemplate/examples/import/output/models/common"
)
type Repository interface {
GetArticle(getarticle *common.GetArticle ) (*article.Article, error)
GetArticle(getarticle *common.GetArticle) (*company.Article, []*company.Storage, error)
}
// ------------------------- Public SDK -----------------------------
// GetArticle : proto: missing extension proto: missing extension
func (sdk *Sdk) GetArticle(ctx context.Context,
getarticle *article.GetArticle, token, requestID string) (article *article.Article, storages []*article.GetArticleResponse_Storage, err error) {
out := &pb.GetArticleResponse{}
_ = out
return out.Article, out.Storages, nil
}

View File

@@ -1,13 +1,25 @@
syntax = "proto3";
package article;
package company;
option go_package = "models/article;article";
import "proto/common.proto";
message GetArticleRequest { common.GetArticle getarticle = 1;}
message GetArticleResponse { Article article = 1;}
message GetArticleRequest {
common.GetArticle getarticle = 1;
}
message GetArticleResponse {
Article article = 1;
message Storage {
string code = 1;
}
// The generated output should write []*GetArticleResponse_Storage.Storage for this field.
repeated Storage storages = 2;
}
message Article{
string id = 1;

View File

@@ -1,11 +1,41 @@
// Code generated by protoc-gen-gotemplate
{{- $file := .File}}
package {{.File.Package}}
import (
"github.com/moul/protoc-gen-gotemplate/examples/import/output/models/article"
"github.com/moul/protoc-gen-gotemplate/examples/import/output/models/common"
"moul.io/protoc-gen-gotemplate/examples/import/output/models/article"
"moul.io/protoc-gen-gotemplate/examples/import/output/models/common"
)
type Repository interface {
{{range $m := .Service.Method}}{{with $t := $m.InputType | getMessageType $.File}} {{$m.Name}}({{range $f := $t.Field}}{{$f.Name|lowerCamelCase}} {{$f| goTypeWithPackage }} {{end}}) ({{with $out := $m.OutputType | getMessageType $.File}}{{range $f := $out.Field}}{{$f | goTypeWithPackage}}, {{end}}{{end}} error){{end}}{{end}}
}
// ------------------------- Public SDK -----------------------------
{{$pkg := "pb"}}
{{range $m := .Service.Method}}
{{with $t := $m.InputType | getMessageType $.File}}
{{if and (not $m.ServerStreaming) (not $m.ClientStreaming)}}
{{/* ----------------------------- nominal case ---------------------------- */}}
// {{$m.Name}} : {{$m | httpVerb}} {{$m | httpPath}}
func (sdk *Sdk) {{$m.Name}}(ctx context.Context, {{if $t.OneofDecl}} req *{{$pkg}}.{{$m.Name}}Request,{{else}}{{range $f := $t.Field}}
{{$f.Name|lowerCamelCase}} {{$f| goTypeWithGoPackage $.File}},{{end}}{{end}} token, requestID string)({{with $out := $m.OutputType | getMessageType $.File}}{{range $f := $out.Field}}{{$f.Name|lowerCamelCase}} {{$f | goTypeWithGoPackage $.File}}, {{end}}{{end}}err error) {
out := &{{$pkg}}.{{$m.Name}}Response{}
_ = out
{{with $out := $m.OutputType | getMessageType $.File}}
return {{range $f := $out.Field}}out.{{$f.Name|camelCase}}, {{end}}nil
{{end}} {{/* with */}}
}
{{end}} {{/* streaming ifs */}}
{{end}}{{end}} {{/* range with */}}

View File

@@ -9,7 +9,7 @@ build:
protoc -I./proto --go_out=plugins=grpc:output proto/aaa/aaa.proto
protoc -I./proto --go_out=plugins=grpc:output proto/bbb/bbb.proto
@rm -rf output/aaa output/bbb
@mv output/github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/* output/
@mv output/moul.io/protoc-gen-gotemplate/examples/single-package-mode/output/* output/
@rm -rf output/github.com
@# protoc-gen-gotemplate

View File

@@ -17,7 +17,7 @@ package bbb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import the_aaa_package "github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/aaa"
import the_aaa_package "moul.io/protoc-gen-gotemplate/examples/single-package-mode/output/aaa"
import (
context "golang.org/x/net/context"

View File

@@ -4,8 +4,8 @@ package bbb
import (
"fmt"
"github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/aaa"
"golang.org/x/net/context"
"moul.io/protoc-gen-gotemplate/examples/single-package-mode/output/aaa"
)
type Service struct{}

View File

@@ -1,6 +1,6 @@
syntax = "proto3";
option go_package = "github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/aaa";
option go_package = "moul.io/protoc-gen-gotemplate/examples/single-package-mode/output/aaa";
package the.aaa.package;

View File

@@ -2,7 +2,7 @@ syntax = "proto3";
package the.bbb.package;
option go_package = "github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/bbb";
option go_package = "moul.io/protoc-gen-gotemplate/examples/single-package-mode/output/bbb";
import "aaa/aaa.proto";

View File

@@ -2,9 +2,9 @@
package foo
import (
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/golang/protobuf/ptypes/timestamp"
)
type Repository interface {
GetFoo(timestamp *timestamp.Timestamp ) (*timestamp.Timestamp, error)
GetFoo(timestamp *timestamp.Timestamp) (*timestamp.Timestamp, error)
}

21
go.mod Normal file
View File

@@ -0,0 +1,21 @@
module moul.io/protoc-gen-gotemplate
require (
github.com/Masterminds/semver v1.2.2 // indirect
github.com/Masterminds/sprig v2.14.1+incompatible
github.com/aokoli/goutils v0.0.0-20170502144750-e57d01ace047 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f // indirect
github.com/gorilla/handlers v0.0.0-20161028133215-e1b2144f2167
github.com/gorilla/mux v1.5.0
github.com/grpc-ecosystem/grpc-gateway v1.2.2
github.com/huandu/xstrings v0.0.0-20151130125119-3959339b3335
github.com/imdario/mergo v0.0.0-20171009183408-7fe0c75c13ab // indirect
github.com/satori/go.uuid v1.1.0 // indirect
github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/crypto v0.0.0-20170516161655-0fe963104e9d // indirect
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
google.golang.org/genproto v0.0.0-20170517234824-bb3573be0c48
gopkg.in/yaml.v2 v2.2.2 // indirect
)

41
go.sum Normal file
View File

@@ -0,0 +1,41 @@
github.com/Masterminds/semver v1.2.2 h1:ptelpryog9A0pR4TGFvIAvw2c8SaNrYkFtfrxhSviss=
github.com/Masterminds/semver v1.2.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.14.1+incompatible h1:rTHERm50Xp1Cbb8x7xBCeDp//jMMqqR44EWw7KwSXUQ=
github.com/Masterminds/sprig v2.14.1+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/aokoli/goutils v0.0.0-20170502144750-e57d01ace047 h1:Bn0iqJ/349f606hR0juIGyheAI6+hyg9XUOLhN9udLo=
github.com/aokoli/goutils v0.0.0-20170502144750-e57d01ace047/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b h1:fE/yi9pibxGEc0gSJuEShcsBXE2d5FW3OudsjE9tKzQ=
github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f h1:9oNbS1z4rVpbnkHBdPZU4jo9bSmrLpII768arSyMFgk=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v0.0.0-20161028133215-e1b2144f2167 h1:uANqGlSIT3JwuQVokEBcvc9WAu5+v4cVD5szF5k+iIU=
github.com/gorilla/handlers v0.0.0-20161028133215-e1b2144f2167/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.5.0 h1:mq8bRov+5x+pZNR/uAHyUEgovR9gLgYFwDQIeuYi9TM=
github.com/gorilla/mux v1.5.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/grpc-ecosystem/grpc-gateway v1.2.2 h1:oR2ZMoJtQccW6NIJ9yFxRqAr2rkmcNsCaZKT66A9zt4=
github.com/grpc-ecosystem/grpc-gateway v1.2.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/huandu/xstrings v0.0.0-20151130125119-3959339b3335 h1:KZOP9q7J/P4eMBibPuVwuloXgd2dTbLAHRPqxw7NXOw=
github.com/huandu/xstrings v0.0.0-20151130125119-3959339b3335/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
github.com/imdario/mergo v0.0.0-20171009183408-7fe0c75c13ab h1:k/Biv+LJL35wkk0Hveko1nj7as4tSHkHdZaNlzn/gcQ=
github.com/imdario/mergo v0.0.0-20171009183408-7fe0c75c13ab/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/satori/go.uuid v1.1.0 h1:B9KXyj+GzIpJbV7gmr873NsY6zpbxNy24CBtGrk7jHo=
github.com/satori/go.uuid v1.1.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20170516161655-0fe963104e9d h1:qjfFh1YMn6m60QTGoG+IlwUMhNlXJbDyCa6EkHM/N2w=
golang.org/x/crypto v0.0.0-20170516161655-0fe963104e9d/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
google.golang.org/genproto v0.0.0-20170517234824-bb3573be0c48 h1:xfoW+Di7qQQUnjptcGb4/rpc701e3RvfNmaywfMxQVI=
google.golang.org/genproto v0.0.0-20170517234824-bb3573be0c48/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -3,8 +3,10 @@ package pgghelpers
import (
"encoding/json"
"fmt"
"reflect"
"regexp"
"strings"
"sync"
"text/template"
"github.com/Masterminds/sprig"
@@ -21,10 +23,6 @@ var (
registry *ggdescriptor.Registry // some helpers need access to registry
)
func SetRegistry(reg *ggdescriptor.Registry) {
registry = reg
}
var ProtoHelpersFuncMap = template.FuncMap{
"string": func(i interface {
String() string
@@ -61,6 +59,12 @@ var ProtoHelpersFuncMap = template.FuncMap{
"last": func(a []string) string {
return a[len(a)-1]
},
"concat": func(a string, b ...string) string {
return strings.Join(append([]string{a}, b...), "")
},
"join": func(sep string, a ...string) string {
return strings.Join(a, sep)
},
"upperFirst": func(s string) string {
return strings.ToUpper(s[:1]) + s[1:]
},
@@ -90,26 +94,544 @@ var ProtoHelpersFuncMap = template.FuncMap{
"trimstr": func(cutset, s string) string {
return strings.Trim(s, cutset)
},
"snakeCase": xstrings.ToSnakeCase,
"getProtoFile": getProtoFile,
"getMessageType": getMessageType,
"getEnumValue": getEnumValue,
"isFieldMessage": isFieldMessage,
"isFieldMessageTimeStamp": isFieldMessageTimeStamp,
"isFieldRepeated": isFieldRepeated,
"haskellType": haskellType,
"goType": goType,
"goTypeWithPackage": goTypeWithPackage,
"jsType": jsType,
"jsSuffixReserved": jsSuffixReservedKeyword,
"namespacedFlowType": namespacedFlowType,
"httpVerb": httpVerb,
"httpPath": httpPath,
"httpBody": httpBody,
"shortType": shortType,
"urlHasVarsFromMessage": urlHasVarsFromMessage,
"lowerGoNormalize": lowerGoNormalize,
"goNormalize": goNormalize,
"index": func(array interface{}, i int) interface{} {
slice := reflect.ValueOf(array)
if slice.Kind() != reflect.Slice {
panic("Error in index(): given a non-slice type")
}
if i < 0 || i >= slice.Len() {
panic("Error in index(): index out of bounds")
}
return slice.Index(i).Interface()
},
"add": func(a int, b int) int {
return a + b
},
"subtract": func(a int, b int) int {
return a - b
},
"multiply": func(a int, b int) int {
return a * b
},
"divide": func(a int, b int) int {
if b == 0 {
panic("psssst ... little help here ... you cannot divide by 0")
}
return a / b
},
"snakeCase": xstrings.ToSnakeCase,
"getProtoFile": getProtoFile,
"getMessageType": getMessageType,
"getEnumValue": getEnumValue,
"isFieldMessage": isFieldMessage,
"isFieldMessageTimeStamp": isFieldMessageTimeStamp,
"isFieldRepeated": isFieldRepeated,
"haskellType": haskellType,
"goType": goType,
"goZeroValue": goZeroValue,
"goTypeWithPackage": goTypeWithPackage,
"goTypeWithGoPackage": goTypeWithGoPackage,
"jsType": jsType,
"jsSuffixReserved": jsSuffixReservedKeyword,
"namespacedFlowType": namespacedFlowType,
"httpVerb": httpVerb,
"httpPath": httpPath,
"httpPathsAdditionalBindings": httpPathsAdditionalBindings,
"httpBody": httpBody,
"shortType": shortType,
"urlHasVarsFromMessage": urlHasVarsFromMessage,
"lowerGoNormalize": lowerGoNormalize,
"goNormalize": goNormalize,
"leadingComment": leadingComment,
"trailingComment": trailingComment,
"leadingDetachedComments": leadingDetachedComments,
"stringFileOptionsExtension": stringFileOptionsExtension,
"stringMessageExtension": stringMessageExtension,
"stringFieldExtension": stringFieldExtension,
"int64FieldExtension": int64FieldExtension,
"int64MessageExtension": int64MessageExtension,
"stringMethodOptionsExtension": stringMethodOptionsExtension,
"boolMethodOptionsExtension": boolMethodOptionsExtension,
"boolMessageExtension": boolMessageExtension,
"boolFieldExtension": boolFieldExtension,
"isFieldMap": isFieldMap,
"fieldMapKeyType": fieldMapKeyType,
"fieldMapValueType": fieldMapValueType,
"replaceDict": replaceDict,
"setStore": setStore,
"getStore": getStore,
"goPkg": goPkg,
"goPkgLastElement": goPkgLastElement,
}
var pathMap map[interface{}]*descriptor.SourceCodeInfo_Location
var store = newStore()
// Utility to store some vars across multiple scope
type globalStore struct {
store map[string]interface{}
mu sync.Mutex
}
func newStore() *globalStore {
return &globalStore{
store: make(map[string]interface{}),
}
}
func (s *globalStore) getData(key string) interface{} {
s.mu.Lock()
defer s.mu.Unlock()
if v, ok := s.store[key]; ok {
return v
}
return false
}
func (s *globalStore) setData(key string, o interface{}) {
s.mu.Lock()
s.store[key] = o
s.mu.Unlock()
}
func setStore(key string, o interface{}) string {
store.setData(key, o)
return ""
}
func getStore(key string) interface{} {
return store.getData(key)
}
func SetRegistry(reg *ggdescriptor.Registry) {
registry = reg
}
func InitPathMap(file *descriptor.FileDescriptorProto) {
pathMap = make(map[interface{}]*descriptor.SourceCodeInfo_Location)
addToPathMap(file.GetSourceCodeInfo(), file, []int32{})
}
func InitPathMaps(files []*descriptor.FileDescriptorProto) {
pathMap = make(map[interface{}]*descriptor.SourceCodeInfo_Location)
for _, file := range files {
addToPathMap(file.GetSourceCodeInfo(), file, []int32{})
}
}
// addToPathMap traverses through the AST adding SourceCodeInfo_Location entries to the pathMap.
// Since the AST is a tree, the recursion finishes once it has gone through all the nodes.
func addToPathMap(info *descriptor.SourceCodeInfo, i interface{}, path []int32) {
loc := findLoc(info, path)
if loc != nil {
pathMap[i] = loc
}
switch d := i.(type) {
case *descriptor.FileDescriptorProto:
for index, descriptor := range d.MessageType {
addToPathMap(info, descriptor, newPath(path, 4, index))
}
for index, descriptor := range d.EnumType {
addToPathMap(info, descriptor, newPath(path, 5, index))
}
for index, descriptor := range d.Service {
addToPathMap(info, descriptor, newPath(path, 6, index))
}
case *descriptor.DescriptorProto:
for index, descriptor := range d.Field {
addToPathMap(info, descriptor, newPath(path, 2, index))
}
for index, descriptor := range d.NestedType {
addToPathMap(info, descriptor, newPath(path, 3, index))
}
for index, descriptor := range d.EnumType {
addToPathMap(info, descriptor, newPath(path, 4, index))
}
case *descriptor.EnumDescriptorProto:
for index, descriptor := range d.Value {
addToPathMap(info, descriptor, newPath(path, 2, index))
}
case *descriptor.ServiceDescriptorProto:
for index, descriptor := range d.Method {
addToPathMap(info, descriptor, newPath(path, 2, index))
}
}
}
func newPath(base []int32, field int32, index int) []int32 {
p := append([]int32{}, base...)
p = append(p, field, int32(index))
return p
}
func findLoc(info *descriptor.SourceCodeInfo, path []int32) *descriptor.SourceCodeInfo_Location {
for _, loc := range info.GetLocation() {
if samePath(loc.Path, path) {
return loc
}
}
return nil
}
func samePath(a, b []int32) bool {
if len(a) != len(b) {
return false
}
for i, p := range a {
if p != b[i] {
return false
}
}
return true
}
/*func findSourceInfoLocation(i interface{}) *descriptor.SourceCodeInfo_Location {
if pathMap == nil {
return nil
}
return pathMap[i]
}*/
func leadingComment(i interface{}) string {
loc := pathMap[i]
return loc.GetLeadingComments()
}
func trailingComment(i interface{}) string {
loc := pathMap[i]
return loc.GetTrailingComments()
}
func leadingDetachedComments(i interface{}) []string {
loc := pathMap[i]
return loc.GetLeadingDetachedComments()
}
// stringMethodOptionsExtension extracts method options of a string type.
// To define your own extensions see:
// https://developers.google.com/protocol-buffers/docs/proto#customoptions
// Typically the fieldID of private extensions should be in the range:
// 50000-99999
func stringMethodOptionsExtension(fieldID int32, f *descriptor.MethodDescriptorProto) string {
if f == nil {
return ""
}
if f.Options == nil {
return ""
}
var extendedType *descriptor.MethodOptions
var extensionType *string
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("bytes,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return ""
}
str, ok := ext.(*string)
if !ok {
return ""
}
return *str
}
// stringFileOptionsExtension extracts file options of a string type.
// To define your own extensions see:
// https://developers.google.com/protocol-buffers/docs/proto#customoptions
// Typically the fieldID of private extensions should be in the range:
// 50000-99999
func stringFileOptionsExtension(fieldID int32, f *descriptor.FileDescriptorProto) string {
if f == nil {
return ""
}
if f.Options == nil {
return ""
}
var extendedType *descriptor.FileOptions
var extensionType *string
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("bytes,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return ""
}
str, ok := ext.(*string)
if !ok {
return ""
}
return *str
}
func stringFieldExtension(fieldID int32, f *descriptor.FieldDescriptorProto) string {
if f == nil {
return ""
}
if f.Options == nil {
return ""
}
var extendedType *descriptor.FieldOptions
var extensionType *string
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("bytes,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return ""
}
str, ok := ext.(*string)
if !ok {
return ""
}
return *str
}
func int64FieldExtension(fieldID int32, f *descriptor.FieldDescriptorProto) int64 {
if f == nil {
return 0
}
if f.Options == nil {
return 0
}
var extendedType *descriptor.FieldOptions
var extensionType *int64
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("varint,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return 0
}
i, ok := ext.(*int64)
if !ok {
return 0
}
return *i
}
func int64MessageExtension(fieldID int32, f *descriptor.DescriptorProto) int64 {
if f == nil {
return 0
}
if f.Options == nil {
return 0
}
var extendedType *descriptor.MessageOptions
var extensionType *int64
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("varint,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return 0
}
i, ok := ext.(*int64)
if !ok {
return 0
}
return *i
}
func stringMessageExtension(fieldID int32, f *descriptor.DescriptorProto) string {
if f == nil {
return ""
}
if f.Options == nil {
return ""
}
var extendedType *descriptor.MessageOptions
var extensionType *string
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("bytes,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return ""
}
str, ok := ext.(*string)
if !ok {
return ""
}
return *str
}
func boolMethodOptionsExtension(fieldID int32, f *descriptor.MethodDescriptorProto) bool {
if f == nil {
return false
}
if f.Options == nil {
return false
}
var extendedType *descriptor.MethodOptions
var extensionType *bool
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("varint,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return false
}
b, ok := ext.(*bool)
if !ok {
return false
}
return *b
}
func boolFieldExtension(fieldID int32, f *descriptor.FieldDescriptorProto) bool {
if f == nil {
return false
}
if f.Options == nil {
return false
}
var extendedType *descriptor.FieldOptions
var extensionType *bool
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("varint,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return false
}
b, ok := ext.(*bool)
if !ok {
return false
}
return *b
}
func boolMessageExtension(fieldID int32, f *descriptor.DescriptorProto) bool {
if f == nil {
return false
}
if f.Options == nil {
return false
}
var extendedType *descriptor.MessageOptions
var extensionType *bool
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("varint,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return false
}
b, ok := ext.(*bool)
if !ok {
return false
}
return *b
}
func init() {
@@ -180,6 +702,9 @@ func isFieldMessage(f *descriptor.FieldDescriptorProto) bool {
}
func isFieldRepeated(f *descriptor.FieldDescriptorProto) bool {
if f == nil {
return false
}
if f.Type != nil && f.Label != nil && *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return true
}
@@ -187,10 +712,137 @@ func isFieldRepeated(f *descriptor.FieldDescriptorProto) bool {
return false
}
func isFieldMap(f *descriptor.FieldDescriptorProto, m *descriptor.DescriptorProto) bool {
if f.TypeName == nil {
return false
}
shortName := shortType(*f.TypeName)
var nt *descriptor.DescriptorProto
for _, t := range m.NestedType {
if *t.Name == shortName {
nt = t
break
}
}
if nt == nil {
return false
}
for _, f := range nt.Field {
switch *f.Name {
case "key":
if *f.Number != 1 {
return false
}
case "value":
if *f.Number != 2 {
return false
}
default:
return false
}
}
return true
}
func fieldMapKeyType(f *descriptor.FieldDescriptorProto, m *descriptor.DescriptorProto) *descriptor.FieldDescriptorProto {
if f.TypeName == nil {
return nil
}
shortName := shortType(*f.TypeName)
var nt *descriptor.DescriptorProto
for _, t := range m.NestedType {
if *t.Name == shortName {
nt = t
break
}
}
if nt == nil {
return nil
}
for _, f := range nt.Field {
if *f.Name == "key" {
return f
}
}
return nil
}
func fieldMapValueType(f *descriptor.FieldDescriptorProto, m *descriptor.DescriptorProto) *descriptor.FieldDescriptorProto {
if f.TypeName == nil {
return nil
}
shortName := shortType(*f.TypeName)
var nt *descriptor.DescriptorProto
for _, t := range m.NestedType {
if *t.Name == shortName {
nt = t
break
}
}
if nt == nil {
return nil
}
for _, f := range nt.Field {
if *f.Name == "value" {
return f
}
}
return nil
}
// goTypeWithGoPackage types the field MESSAGE and ENUM with the go_package name.
// This method is an evolution of goTypeWithPackage. It handles message embedded.
//
// example:
// ```proto
// message GetArticleResponse {
// Article article = 1;
// message Storage {
// string code = 1;
// }
// repeated Storage storages = 2;
// }
// ```
// Then the type of `storages` is `GetArticleResponse_Storage` for the go language.
//
func goTypeWithGoPackage(p *descriptor.FileDescriptorProto, f *descriptor.FieldDescriptorProto) string {
pkg := ""
if *f.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE || *f.Type == descriptor.FieldDescriptorProto_TYPE_ENUM {
if isTimestampPackage(*f.TypeName) {
pkg = "timestamp"
} else {
pkg = *p.GetOptions().GoPackage
if strings.Contains(*p.GetOptions().GoPackage, ";") {
pkg = strings.Split(*p.GetOptions().GoPackage, ";")[1]
}
}
}
return goTypeWithEmbedded(pkg, f, p)
}
// Warning does not handle message embedded like goTypeWithGoPackage does.
func goTypeWithPackage(f *descriptor.FieldDescriptorProto) string {
pkg := ""
if *f.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE || *f.Type == descriptor.FieldDescriptorProto_TYPE_ENUM {
pkg = getPackageTypeName(*f.TypeName)
if isTimestampPackage(*f.TypeName) {
pkg = "timestamp"
} else {
pkg = getPackageTypeName(*f.TypeName)
}
}
return goType(pkg, f)
}
@@ -257,6 +909,84 @@ func haskellType(pkg string, f *descriptor.FieldDescriptorProto) string {
}
}
func goTypeWithEmbedded(pkg string, f *descriptor.FieldDescriptorProto, p *descriptor.FileDescriptorProto) string {
if pkg != "" {
pkg = pkg + "."
}
switch *f.Type {
case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]float64"
}
return "float64"
case descriptor.FieldDescriptorProto_TYPE_FLOAT:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]float32"
}
return "float32"
case descriptor.FieldDescriptorProto_TYPE_INT64:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]int64"
}
return "int64"
case descriptor.FieldDescriptorProto_TYPE_UINT64:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]uint64"
}
return "uint64"
case descriptor.FieldDescriptorProto_TYPE_INT32:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]int32"
}
return "int32"
case descriptor.FieldDescriptorProto_TYPE_UINT32:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]uint32"
}
return "uint32"
case descriptor.FieldDescriptorProto_TYPE_BOOL:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]bool"
}
return "bool"
case descriptor.FieldDescriptorProto_TYPE_STRING:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]string"
}
return "string"
case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
name := *f.TypeName
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
fieldPackage := strings.Split(*f.TypeName, ".")
filePackage := strings.Split(*p.Package, ".")
// check if we are working with a message embedded.
if len(fieldPackage) > 1 && len(fieldPackage)+1 > len(filePackage)+1 {
name = strings.Join(fieldPackage[len(filePackage)+1:], "_")
}
return fmt.Sprintf("[]*%s%s", pkg, shortType(name))
}
return fmt.Sprintf("*%s%s", pkg, shortType(name))
case descriptor.FieldDescriptorProto_TYPE_BYTES:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]byte"
}
return "byte"
case descriptor.FieldDescriptorProto_TYPE_ENUM:
name := *f.TypeName
fieldPackage := strings.Split(*f.TypeName, ".")
filePackage := strings.Split(*p.Package, ".")
// check if we are working with a message embedded.
if len(fieldPackage) > 1 && len(fieldPackage)+1 > len(filePackage)+1 {
name = strings.Join(fieldPackage[len(filePackage)+1:], "_")
}
return fmt.Sprintf("*%s%s", pkg, shortType(name))
default:
return "interface{}"
}
}
//Deprecated. Instead use goTypeWithEmbedded
func goType(pkg string, f *descriptor.FieldDescriptorProto) string {
if pkg != "" {
pkg = pkg + "."
@@ -319,6 +1049,39 @@ func goType(pkg string, f *descriptor.FieldDescriptorProto) string {
}
}
func goZeroValue(f *descriptor.FieldDescriptorProto) string {
const nilString = "nil"
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return nilString
}
switch *f.Type {
case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
return "0.0"
case descriptor.FieldDescriptorProto_TYPE_FLOAT:
return "0.0"
case descriptor.FieldDescriptorProto_TYPE_INT64:
return "0"
case descriptor.FieldDescriptorProto_TYPE_UINT64:
return "0"
case descriptor.FieldDescriptorProto_TYPE_INT32:
return "0"
case descriptor.FieldDescriptorProto_TYPE_UINT32:
return "0"
case descriptor.FieldDescriptorProto_TYPE_BOOL:
return "false"
case descriptor.FieldDescriptorProto_TYPE_STRING:
return "\"\""
case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
return nilString
case descriptor.FieldDescriptorProto_TYPE_BYTES:
return "0"
case descriptor.FieldDescriptorProto_TYPE_ENUM:
return nilString
default:
return nilString
}
}
func jsType(f *descriptor.FieldDescriptorProto) string {
template := "%s"
if isFieldRepeated(f) {
@@ -357,10 +1120,15 @@ func jsSuffixReservedKeyword(s string) string {
return jsReservedRe.ReplaceAllString(s, "${1}${2}_${3}")
}
func getPackageTypeName(s string) string {
func isTimestampPackage(s string) bool {
var isTimestampPackage bool
if strings.Compare(s, ".google.protobuf.Timestamp") == 0 {
return "timestamp"
isTimestampPackage = true
}
return isTimestampPackage
}
func getPackageTypeName(s string) string {
if strings.Contains(s, ".") {
return strings.Split(s, ".")[1]
}
@@ -407,6 +1175,40 @@ func httpPath(m *descriptor.MethodDescriptorProto) string {
}
}
func httpPathsAdditionalBindings(m *descriptor.MethodDescriptorProto) []string {
ext, err := proto.GetExtension(m.Options, options.E_Http)
if err != nil {
panic(err.Error())
}
opts, ok := ext.(*options.HttpRule)
if !ok {
panic(fmt.Sprintf("extension is %T; want an HttpRule", ext))
}
var httpPaths []string
var optsAdditionalBindings = opts.GetAdditionalBindings()
for _, optAdditionalBindings := range optsAdditionalBindings {
switch t := optAdditionalBindings.Pattern.(type) {
case *options.HttpRule_Get:
httpPaths = append(httpPaths, t.Get)
case *options.HttpRule_Post:
httpPaths = append(httpPaths, t.Post)
case *options.HttpRule_Put:
httpPaths = append(httpPaths, t.Put)
case *options.HttpRule_Delete:
httpPaths = append(httpPaths, t.Delete)
case *options.HttpRule_Patch:
httpPaths = append(httpPaths, t.Patch)
case *options.HttpRule_Custom:
httpPaths = append(httpPaths, t.Custom.Path)
default:
// nothing
}
}
return httpPaths
}
func httpVerb(m *descriptor.MethodDescriptorProto) string {
ext, err := proto.GetExtension(m.Options, options.E_Http)
@@ -502,3 +1304,24 @@ func formatID(base string, formatted string) string {
}
return formatted
}
func replaceDict(src string, dict map[string]interface{}) string {
for old, v := range dict {
new, ok := v.(string)
if !ok {
continue
}
src = strings.Replace(src, old, new, -1)
}
return src
}
func goPkg(f *descriptor.FileDescriptorProto) string {
return f.Options.GetGoPackage()
}
func goPkgLastElement(f *descriptor.FileDescriptorProto) string {
pkg := goPkg(f)
pkgSplitted := strings.Split(pkg, "/")
return pkgSplitted[len(pkgSplitted)-1]
}

26
main.go
View File

@@ -1,4 +1,4 @@
package main
package main // import "moul.io/protoc-gen-gotemplate"
import (
"io/ioutil"
@@ -8,10 +8,10 @@ import (
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/generator"
"github.com/golang/protobuf/protoc-gen-go/plugin"
plugin_go "github.com/golang/protobuf/protoc-gen-go/plugin"
ggdescriptor "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
pgghelpers "github.com/moul/protoc-gen-gotemplate/helpers"
pgghelpers "moul.io/protoc-gen-gotemplate/helpers"
)
var (
@@ -48,6 +48,7 @@ func main() {
debug = false
all = false
singlePackageMode = false
fileMode = false
)
if parameter := g.Request.GetParameter(); parameter != "" {
for _, param := range strings.Split(parameter, ",") {
@@ -85,6 +86,14 @@ func main() {
default:
log.Printf("Err: invalid value for debug: %q", parts[1])
}
case "file-mode":
switch strings.ToLower(parts[1]) {
case boolTrue, "t":
fileMode = true
case boolFalse, "f":
default:
log.Printf("Err: invalid value for file-mode: %q", parts[1])
}
default:
log.Printf("Err: unknown parameter: %q", param)
}
@@ -125,6 +134,17 @@ func main() {
continue
}
if fileMode {
if s := file.GetService(); s != nil && len(s) > 0 {
encoder := NewGenericTemplateBasedEncoder(templateDir, file, debug, destinationDir)
for _, tmpl := range encoder.Files() {
concatOrAppend(tmpl)
}
}
continue
}
for _, service := range file.GetService() {
encoder := NewGenericServiceTemplateBasedEncoder(templateDir, service, file, debug, destinationDir)
for _, tmpl := range encoder.Files() {

6
renovate.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": [
"config:base"
],
"groupName": "all"
}

View File

@@ -26,7 +26,7 @@
* the good old ./generate.sh bash script
* go:generate
* make
* protobuf + [protoc-gen-gotemplate](https://github.com/moul/protoc-gen-gotemplate)
* protobuf + [protoc-gen-gotemplate](https://moul.io/protoc-gen-gotemplate)
---
@@ -69,7 +69,7 @@ package sessionsvc
import (
"fmt"
"golang.org/x/net/context"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
)
type Service struct{}
@@ -94,8 +94,8 @@ package {{.File.Package}}_httptransport
import (
gokit_endpoint "github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/{{.File.Package}}/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/{{.File.Package}}/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/{{.File.Package}}/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/{{.File.Package}}/gen/pb"
)
```
@@ -105,8 +105,8 @@ package user_httptransport
import (
gokit_endpoint "github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
endpoints "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
pb "github.com/moul/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
)
```
@@ -197,7 +197,7 @@ func RegisterHandlers(ctx context.Context, svc pb.UserServiceServer, mux *http.S
# generation usages
* go-kit boilerplate (see [examples/go-kit](https://github.com/moul/protoc-gen-gotemplate/tree/master/examples/go-kit))
* go-kit boilerplate (see [examples/go-kit](https://moul.io/protoc-gen-gotemplate/tree/master/examples/go-kit))
* k8s configuration
* Dockerfile
* documentation
@@ -243,5 +243,5 @@ func RegisterHandlers(ctx context.Context, svc pb.UserServiceServer, mux *http.S
# questions?
### github.com/moul/protoc-gen-gotemplate
### moul.io/protoc-gen-gotemplate
### @moul

2
vendor/github.com/Masterminds/sprig/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
vendor/
/.glide

View File

@@ -1,8 +0,0 @@
Copyright (c) 2012 Dave Grijalva
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.

View File

@@ -1,96 +0,0 @@
## Migration Guide from v2 -> v3
Version 3 adds several new, frequently requested features. To do so, it introduces a few breaking changes. We've worked to keep these as minimal as possible. This guide explains the breaking changes and how you can quickly update your code.
### `Token.Claims` is now an interface type
The most requested feature from the 2.0 verison of this library was the ability to provide a custom type to the JSON parser for claims. This was implemented by introducing a new interface, `Claims`, to replace `map[string]interface{}`. We also included two concrete implementations of `Claims`: `MapClaims` and `StandardClaims`.
`MapClaims` is an alias for `map[string]interface{}` with built in validation behavior. It is the default claims type when using `Parse`. The usage is unchanged except you must type cast the claims property.
The old example for parsing a token looked like this..
```go
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
}
```
is now directly mapped to...
```go
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
claims := token.Claims.(jwt.MapClaims)
fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
}
```
`StandardClaims` is designed to be embedded in your custom type. You can supply a custom claims type with the new `ParseWithClaims` function. Here's an example of using a custom claims type.
```go
type MyCustomClaims struct {
User string
*StandardClaims
}
if token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, keyLookupFunc); err == nil {
claims := token.Claims.(*MyCustomClaims)
fmt.Printf("Token for user %v expires %v", claims.User, claims.StandardClaims.ExpiresAt)
}
```
### `ParseFromRequest` has been moved
To keep this library focused on the tokens without becoming overburdened with complex request processing logic, `ParseFromRequest` and its new companion `ParseFromRequestWithClaims` have been moved to a subpackage, `request`. The method signatues have also been augmented to receive a new argument: `Extractor`.
`Extractors` do the work of picking the token string out of a request. The interface is simple and composable.
This simple parsing example:
```go
if token, err := jwt.ParseFromRequest(tokenString, req, keyLookupFunc); err == nil {
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
}
```
is directly mapped to:
```go
if token, err := request.ParseFromRequest(tokenString, request.OAuth2Extractor, req, keyLookupFunc); err == nil {
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
}
```
There are several concrete `Extractor` types provided for your convenience:
* `HeaderExtractor` will search a list of headers until one contains content.
* `ArgumentExtractor` will search a list of keys in request query and form arguments until one contains content.
* `MultiExtractor` will try a list of `Extractors` in order until one returns content.
* `AuthorizationHeaderExtractor` will look in the `Authorization` header for a `Bearer` token.
* `OAuth2Extractor` searches the places an OAuth2 token would be specified (per the spec): `Authorization` header and `access_token` argument
* `PostExtractionFilter` wraps an `Extractor`, allowing you to process the content before it's parsed. A simple example is stripping the `Bearer ` text from a header
### RSA signing methods no longer accept `[]byte` keys
Due to a [critical vulnerability](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/), we've decided the convenience of accepting `[]byte` instead of `rsa.PublicKey` or `rsa.PrivateKey` isn't worth the risk of misuse.
To replace this behavior, we've added two helper methods: `ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)` and `ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)`. These are just simple helpers for unpacking PEM encoded PKCS1 and PKCS8 keys. If your keys are encoded any other way, all you need to do is convert them to the `crypto/rsa` package's types.
```go
func keyLookupFunc(*Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
// Look up key
key, err := lookupPublicKey(token.Header["kid"])
if err != nil {
return nil, err
}
// Unpack key from PEM encoded PKCS8
return jwt.ParseRSAPublicKeyFromPEM(key)
}
```

View File

@@ -1,85 +0,0 @@
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html)
[![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go)
**BREAKING CHANGES:*** Version 3.0.0 is here. It includes _a lot_ of changes including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code.
**NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected. There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API. In the short term, please make sure your implementation verifies the `alg` is what you expect.
## What the heck is a JWT?
JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens.
In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way.
The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used.
The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own.
## What's in the box?
This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own.
## Examples
See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage:
* [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac)
* [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-New--Hmac)
* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples)
## Extensions
This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`.
Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go
## Compliance
This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences:
* In order to protect against accidental use of [Unsecured JWTs](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#UnsecuredJWT), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key.
## Project Status & Versioning
This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason).
This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases).
While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v2`. It will do the right thing WRT semantic versioning.
## Usage Tips
### Signing vs Encryption
A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data:
* The author of the token was in the possession of the signing secret
* The data has not been modified since it was signed
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library.
### Choosing a Signing Method
There are several signing methods available, and you should probably take the time to learn about the various options before choosing one. The principal design decision is most likely going to be symmetric vs asymmetric.
Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation.
Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification.
### JWT and OAuth
It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication.
Without going too far down the rabbit hole, here's a description of the interaction of these technologies:
* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth.
* OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token.
* Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL.
## More
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).
The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in to documentation.

View File

@@ -1,105 +0,0 @@
## `jwt-go` Version History
#### 3.0.0
* **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code
* Dropped support for `[]byte` keys when using RSA signing methods. This convenience feature could contribute to security vulnerabilities involving mismatched key types with signing methods.
* `ParseFromRequest` has been moved to `request` subpackage and usage has changed
* The `Claims` property on `Token` is now type `Claims` instead of `map[string]interface{}`. The default value is type `MapClaims`, which is an alias to `map[string]interface{}`. This makes it possible to use a custom type when decoding claims.
* Other Additions and Changes
* Added `Claims` interface type to allow users to decode the claims into a custom type
* Added `ParseWithClaims`, which takes a third argument of type `Claims`. Use this function instead of `Parse` if you have a custom type you'd like to decode into.
* Dramatically improved the functionality and flexibility of `ParseFromRequest`, which is now in the `request` subpackage
* Added `ParseFromRequestWithClaims` which is the `FromRequest` equivalent of `ParseWithClaims`
* Added new interface type `Extractor`, which is used for extracting JWT strings from http requests. Used with `ParseFromRequest` and `ParseFromRequestWithClaims`.
* Added several new, more specific, validation errors to error type bitmask
* Moved examples from README to executable example files
* Signing method registry is now thread safe
* Added new property to `ValidationError`, which contains the raw error returned by calls made by parse/verify (such as those returned by keyfunc or json parser)
#### 2.7.0
This will likely be the last backwards compatible release before 3.0.0, excluding essential bug fixes.
* Added new option `-show` to the `jwt` command that will just output the decoded token without verifying
* Error text for expired tokens includes how long it's been expired
* Fixed incorrect error returned from `ParseRSAPublicKeyFromPEM`
* Documentation updates
#### 2.6.0
* Exposed inner error within ValidationError
* Fixed validation errors when using UseJSONNumber flag
* Added several unit tests
#### 2.5.0
* Added support for signing method none. You shouldn't use this. The API tries to make this clear.
* Updated/fixed some documentation
* Added more helpful error message when trying to parse tokens that begin with `BEARER `
#### 2.4.0
* Added new type, Parser, to allow for configuration of various parsing parameters
* You can now specify a list of valid signing methods. Anything outside this set will be rejected.
* You can now opt to use the `json.Number` type instead of `float64` when parsing token JSON
* Added support for [Travis CI](https://travis-ci.org/dgrijalva/jwt-go)
* Fixed some bugs with ECDSA parsing
#### 2.3.0
* Added support for ECDSA signing methods
* Added support for RSA PSS signing methods (requires go v1.4)
#### 2.2.0
* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic.
#### 2.1.0
Backwards compatible API change that was missed in 2.0.0.
* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte`
#### 2.0.0
There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change.
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`.
* **Compatibility Breaking Changes**
* `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct`
* `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct`
* `KeyFunc` now returns `interface{}` instead of `[]byte`
* `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key
* `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key
* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type.
* Added public package global `SigningMethodHS256`
* Added public package global `SigningMethodHS384`
* Added public package global `SigningMethodHS512`
* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type.
* Added public package global `SigningMethodRS256`
* Added public package global `SigningMethodRS384`
* Added public package global `SigningMethodRS512`
* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged.
* Refactored the RSA implementation to be easier to read
* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM`
#### 1.0.2
* Fixed bug in parsing public keys from certificates
* Added more tests around the parsing of keys for RS256
* Code refactoring in RS256 implementation. No functional changes
#### 1.0.1
* Fixed panic if RS256 signing method was passed an invalid key
#### 1.0.0
* First versioned release
* API stabilized
* Supports creating, signing, parsing, and validating JWT tokens
* Supports RS256 and HS256 signing methods

View File

@@ -1,134 +0,0 @@
package jwt
import (
"crypto/subtle"
"fmt"
"time"
)
// For a type to be a Claims object, it must just have a Valid method that determines
// if the token is invalid for any supported reason
type Claims interface {
Valid() error
}
// Structured version of Claims Section, as referenced at
// https://tools.ietf.org/html/rfc7519#section-4.1
// See examples for how to use this with your own claim types
type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}
// Validates time based claims "exp, iat, nbf".
// There is no accounting for clock skew.
// As well, if any of the above claims are not in the token, it will still
// be considered a valid claim.
func (c StandardClaims) Valid() error {
vErr := new(ValidationError)
now := TimeFunc().Unix()
// The claims below are optional, by default, so if they are set to the
// default value in Go, let's not fail the verification for them.
if c.VerifyExpiresAt(now, false) == false {
delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
vErr.Inner = fmt.Errorf("token is expired by %v", delta)
vErr.Errors |= ValidationErrorExpired
}
if c.VerifyIssuedAt(now, false) == false {
vErr.Inner = fmt.Errorf("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}
if c.VerifyNotBefore(now, false) == false {
vErr.Inner = fmt.Errorf("token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}
if vErr.valid() {
return nil
}
return vErr
}
// Compares the aud claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
return verifyAud(c.Audience, cmp, req)
}
// Compares the exp claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
return verifyExp(c.ExpiresAt, cmp, req)
}
// Compares the iat claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
return verifyIat(c.IssuedAt, cmp, req)
}
// Compares the iss claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
return verifyIss(c.Issuer, cmp, req)
}
// Compares the nbf claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
return verifyNbf(c.NotBefore, cmp, req)
}
// ----- helpers
func verifyAud(aud string, cmp string, required bool) bool {
if aud == "" {
return !required
}
if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 {
return true
} else {
return false
}
}
func verifyExp(exp int64, now int64, required bool) bool {
if exp == 0 {
return !required
}
return now <= exp
}
func verifyIat(iat int64, now int64, required bool) bool {
if iat == 0 {
return !required
}
return now >= iat
}
func verifyIss(iss string, cmp string, required bool) bool {
if iss == "" {
return !required
}
if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
return true
} else {
return false
}
}
func verifyNbf(nbf int64, now int64, required bool) bool {
if nbf == 0 {
return !required
}
return now >= nbf
}

View File

@@ -1,4 +0,0 @@
// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html
//
// See README.md for more info.
package jwt

View File

@@ -1,147 +0,0 @@
package jwt
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"errors"
"math/big"
)
var (
// Sadly this is missing from crypto/ecdsa compared to crypto/rsa
ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
)
// Implements the ECDSA family of signing methods signing methods
type SigningMethodECDSA struct {
Name string
Hash crypto.Hash
KeySize int
CurveBits int
}
// Specific instances for EC256 and company
var (
SigningMethodES256 *SigningMethodECDSA
SigningMethodES384 *SigningMethodECDSA
SigningMethodES512 *SigningMethodECDSA
)
func init() {
// ES256
SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256}
RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod {
return SigningMethodES256
})
// ES384
SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384}
RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod {
return SigningMethodES384
})
// ES512
SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521}
RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod {
return SigningMethodES512
})
}
func (m *SigningMethodECDSA) Alg() string {
return m.Name
}
// Implements the Verify method from SigningMethod
// For this verify method, key must be an ecdsa.PublicKey struct
func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
// Get the key
var ecdsaKey *ecdsa.PublicKey
switch k := key.(type) {
case *ecdsa.PublicKey:
ecdsaKey = k
default:
return ErrInvalidKeyType
}
if len(sig) != 2*m.KeySize {
return ErrECDSAVerification
}
r := big.NewInt(0).SetBytes(sig[:m.KeySize])
s := big.NewInt(0).SetBytes(sig[m.KeySize:])
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Verify the signature
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true {
return nil
} else {
return ErrECDSAVerification
}
}
// Implements the Sign method from SigningMethod
// For this signing method, key must be an ecdsa.PrivateKey struct
func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) {
// Get the key
var ecdsaKey *ecdsa.PrivateKey
switch k := key.(type) {
case *ecdsa.PrivateKey:
ecdsaKey = k
default:
return "", ErrInvalidKeyType
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return r, s
if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil {
curveBits := ecdsaKey.Curve.Params().BitSize
if m.CurveBits != curveBits {
return "", ErrInvalidKey
}
keyBytes := curveBits / 8
if curveBits%8 > 0 {
keyBytes += 1
}
// We serialize the outpus (r and s) into big-endian byte arrays and pad
// them with zeros on the left to make sure the sizes work out. Both arrays
// must be keyBytes long, and the output must be 2*keyBytes long.
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
out := append(rBytesPadded, sBytesPadded...)
return EncodeSegment(out), nil
} else {
return "", err
}
}

View File

@@ -1,67 +0,0 @@
package jwt
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
)
var (
ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key")
ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key")
)
// Parse PEM encoded Elliptic Curve Private Key Structure
func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
return nil, err
}
var pkey *ecdsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
return nil, ErrNotECPrivateKey
}
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 public key
func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *ecdsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
return nil, ErrNotECPublicKey
}
return pkey, nil
}

View File

@@ -1,63 +0,0 @@
package jwt
import (
"errors"
)
// Error constants
var (
ErrInvalidKey = errors.New("key is invalid")
ErrInvalidKeyType = errors.New("key is of invalid type")
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
)
// The errors that might occur when parsing and validating a token
const (
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
ValidationErrorUnverifiable // Token could not be verified because of signing problems
ValidationErrorSignatureInvalid // Signature validation failed
// Standard Claim validation errors
ValidationErrorAudience // AUD validation failed
ValidationErrorExpired // EXP validation failed
ValidationErrorIssuedAt // IAT validation failed
ValidationErrorIssuer // ISS validation failed
ValidationErrorNotValidYet // NBF validation failed
ValidationErrorId // JTI validation failed
ValidationErrorClaimsInvalid // Generic claims validation error
)
// Helper for constructing a ValidationError with a string error message
func NewValidationError(errorText string, errorFlags uint32) *ValidationError {
return &ValidationError{
text: errorText,
Errors: errorFlags,
}
}
// The error from Parse if token is not valid
type ValidationError struct {
Inner error // stores the error returned by external dependencies, i.e.: KeyFunc
Errors uint32 // bitfield. see ValidationError... constants
text string // errors that do not have a valid error just have text
}
// Validation error is an error type
func (e ValidationError) Error() string {
if e.Inner != nil {
return e.Inner.Error()
} else if e.text != "" {
return e.text
} else {
return "token is invalid"
}
return e.Inner.Error()
}
// No errors
func (e *ValidationError) valid() bool {
if e.Errors > 0 {
return false
}
return true
}

View File

@@ -1,94 +0,0 @@
package jwt
import (
"crypto"
"crypto/hmac"
"errors"
)
// Implements the HMAC-SHA family of signing methods signing methods
type SigningMethodHMAC struct {
Name string
Hash crypto.Hash
}
// Specific instances for HS256 and company
var (
SigningMethodHS256 *SigningMethodHMAC
SigningMethodHS384 *SigningMethodHMAC
SigningMethodHS512 *SigningMethodHMAC
ErrSignatureInvalid = errors.New("signature is invalid")
)
func init() {
// HS256
SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256}
RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod {
return SigningMethodHS256
})
// HS384
SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384}
RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod {
return SigningMethodHS384
})
// HS512
SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512}
RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod {
return SigningMethodHS512
})
}
func (m *SigningMethodHMAC) Alg() string {
return m.Name
}
// Verify the signature of HSXXX tokens. Returns nil if the signature is valid.
func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
// Verify the key is the right type
keyBytes, ok := key.([]byte)
if !ok {
return ErrInvalidKeyType
}
// Decode signature, for comparison
sig, err := DecodeSegment(signature)
if err != nil {
return err
}
// Can we use the specified hashing method?
if !m.Hash.Available() {
return ErrHashUnavailable
}
// This signing method is symmetric, so we validate the signature
// by reproducing the signature from the signing string and key, then
// comparing that against the provided signature.
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
if !hmac.Equal(sig, hasher.Sum(nil)) {
return ErrSignatureInvalid
}
// No validation errors. Signature is good.
return nil
}
// Implements the Sign method from SigningMethod for this signing method.
// Key must be []byte
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
if keyBytes, ok := key.([]byte); ok {
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
return EncodeSegment(hasher.Sum(nil)), nil
}
return "", ErrInvalidKey
}

View File

@@ -1,94 +0,0 @@
package jwt
import (
"encoding/json"
"errors"
// "fmt"
)
// Claims type that uses the map[string]interface{} for JSON decoding
// This is the default claims type if you don't supply one
type MapClaims map[string]interface{}
// Compares the aud claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyAudience(cmp string, req bool) bool {
aud, _ := m["aud"].(string)
return verifyAud(aud, cmp, req)
}
// Compares the exp claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool {
switch exp := m["exp"].(type) {
case float64:
return verifyExp(int64(exp), cmp, req)
case json.Number:
v, _ := exp.Int64()
return verifyExp(v, cmp, req)
}
return req == false
}
// Compares the iat claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool {
switch iat := m["iat"].(type) {
case float64:
return verifyIat(int64(iat), cmp, req)
case json.Number:
v, _ := iat.Int64()
return verifyIat(v, cmp, req)
}
return req == false
}
// Compares the iss claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyIssuer(cmp string, req bool) bool {
iss, _ := m["iss"].(string)
return verifyIss(iss, cmp, req)
}
// Compares the nbf claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool {
switch nbf := m["nbf"].(type) {
case float64:
return verifyNbf(int64(nbf), cmp, req)
case json.Number:
v, _ := nbf.Int64()
return verifyNbf(v, cmp, req)
}
return req == false
}
// Validates time based claims "exp, iat, nbf".
// There is no accounting for clock skew.
// As well, if any of the above claims are not in the token, it will still
// be considered a valid claim.
func (m MapClaims) Valid() error {
vErr := new(ValidationError)
now := TimeFunc().Unix()
if m.VerifyExpiresAt(now, false) == false {
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}
if m.VerifyIssuedAt(now, false) == false {
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}
if m.VerifyNotBefore(now, false) == false {
vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}
if vErr.valid() {
return nil
}
return vErr
}

View File

@@ -1,52 +0,0 @@
package jwt
// Implements the none signing method. This is required by the spec
// but you probably should never use it.
var SigningMethodNone *signingMethodNone
const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"
var NoneSignatureTypeDisallowedError error
type signingMethodNone struct{}
type unsafeNoneMagicConstant string
func init() {
SigningMethodNone = &signingMethodNone{}
NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid)
RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod {
return SigningMethodNone
})
}
func (m *signingMethodNone) Alg() string {
return "none"
}
// Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key
func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) {
// Key must be UnsafeAllowNoneSignatureType to prevent accidentally
// accepting 'none' signing method
if _, ok := key.(unsafeNoneMagicConstant); !ok {
return NoneSignatureTypeDisallowedError
}
// If signing method is none, signature must be an empty string
if signature != "" {
return NewValidationError(
"'none' signing method with non-empty signature",
ValidationErrorSignatureInvalid,
)
}
// Accept 'none' signing method.
return nil
}
// Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key
func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) {
if _, ok := key.(unsafeNoneMagicConstant); ok {
return "", nil
}
return "", NoneSignatureTypeDisallowedError
}

View File

@@ -1,131 +0,0 @@
package jwt
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
type Parser struct {
ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // Skip claims validation during token parsing
}
// Parse, validate, and return a token.
// keyFunc will receive the parsed token and should return the key for validating.
// If everything is kosher, err will be nil
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
}
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
}
var err error
token := &Token{Raw: tokenString}
// parse Header
var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
return token, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
}
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// parse Claims
var claimBytes []byte
token.Claims = claims
if claimBytes, err = DecodeSegment(parts[1]); err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
if p.UseJSONNumber {
dec.UseNumber()
}
// JSON Decode. Special case for map type to avoid weird pointer behavior
if c, ok := token.Claims.(MapClaims); ok {
err = dec.Decode(&c)
} else {
err = dec.Decode(&claims)
}
// Handle decode error
if err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// Lookup signature method
if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil {
return token, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
}
} else {
return token, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
}
// Verify signing method is in the required set
if p.ValidMethods != nil {
var signingMethodValid = false
var alg = token.Method.Alg()
for _, m := range p.ValidMethods {
if m == alg {
signingMethodValid = true
break
}
}
if !signingMethodValid {
// signing method is not in the listed set
return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
}
}
// Lookup key
var key interface{}
if keyFunc == nil {
// keyFunc was not provided. short circuiting validation
return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
}
if key, err = keyFunc(token); err != nil {
// keyFunc returned an error
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
}
vErr := &ValidationError{}
// Validate Claims
if !p.SkipClaimsValidation {
if err := token.Claims.Valid(); err != nil {
// If the Claims Valid returned an error, check if it is a validation error,
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
if e, ok := err.(*ValidationError); !ok {
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
} else {
vErr = e
}
}
}
// Perform validation
token.Signature = parts[2]
if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
vErr.Inner = err
vErr.Errors |= ValidationErrorSignatureInvalid
}
if vErr.valid() {
token.Valid = true
return token, nil
}
return token, vErr
}

View File

@@ -1,100 +0,0 @@
package jwt
import (
"crypto"
"crypto/rand"
"crypto/rsa"
)
// Implements the RSA family of signing methods signing methods
type SigningMethodRSA struct {
Name string
Hash crypto.Hash
}
// Specific instances for RS256 and company
var (
SigningMethodRS256 *SigningMethodRSA
SigningMethodRS384 *SigningMethodRSA
SigningMethodRS512 *SigningMethodRSA
)
func init() {
// RS256
SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256}
RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod {
return SigningMethodRS256
})
// RS384
SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384}
RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod {
return SigningMethodRS384
})
// RS512
SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512}
RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod {
return SigningMethodRS512
})
}
func (m *SigningMethodRSA) Alg() string {
return m.Name
}
// Implements the Verify method from SigningMethod
// For this signing method, must be an rsa.PublicKey structure.
func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
var rsaKey *rsa.PublicKey
var ok bool
if rsaKey, ok = key.(*rsa.PublicKey); !ok {
return ErrInvalidKeyType
}
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Verify the signature
return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig)
}
// Implements the Sign method from SigningMethod
// For this signing method, must be an rsa.PrivateKey structure.
func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
var rsaKey *rsa.PrivateKey
var ok bool
// Validate type of key
if rsaKey, ok = key.(*rsa.PrivateKey); !ok {
return "", ErrInvalidKey
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return the encoded bytes
if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
return EncodeSegment(sigBytes), nil
} else {
return "", err
}
}

View File

@@ -1,126 +0,0 @@
// +build go1.4
package jwt
import (
"crypto"
"crypto/rand"
"crypto/rsa"
)
// Implements the RSAPSS family of signing methods signing methods
type SigningMethodRSAPSS struct {
*SigningMethodRSA
Options *rsa.PSSOptions
}
// Specific instances for RS/PS and company
var (
SigningMethodPS256 *SigningMethodRSAPSS
SigningMethodPS384 *SigningMethodRSAPSS
SigningMethodPS512 *SigningMethodRSAPSS
)
func init() {
// PS256
SigningMethodPS256 = &SigningMethodRSAPSS{
&SigningMethodRSA{
Name: "PS256",
Hash: crypto.SHA256,
},
&rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA256,
},
}
RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod {
return SigningMethodPS256
})
// PS384
SigningMethodPS384 = &SigningMethodRSAPSS{
&SigningMethodRSA{
Name: "PS384",
Hash: crypto.SHA384,
},
&rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA384,
},
}
RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod {
return SigningMethodPS384
})
// PS512
SigningMethodPS512 = &SigningMethodRSAPSS{
&SigningMethodRSA{
Name: "PS512",
Hash: crypto.SHA512,
},
&rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA512,
},
}
RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod {
return SigningMethodPS512
})
}
// Implements the Verify method from SigningMethod
// For this verify method, key must be an rsa.PublicKey struct
func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
var rsaKey *rsa.PublicKey
switch k := key.(type) {
case *rsa.PublicKey:
rsaKey = k
default:
return ErrInvalidKey
}
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options)
}
// Implements the Sign method from SigningMethod
// For this signing method, key must be an rsa.PrivateKey struct
func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) {
var rsaKey *rsa.PrivateKey
switch k := key.(type) {
case *rsa.PrivateKey:
rsaKey = k
default:
return "", ErrInvalidKeyType
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return the encoded bytes
if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil {
return EncodeSegment(sigBytes), nil
} else {
return "", err
}
}

View File

@@ -1,69 +0,0 @@
package jwt
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
)
var (
ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key")
ErrNotRSAPublicKey = errors.New("Key is not a valid RSA public key")
)
// Parse PEM encoded PKCS1 or PKCS8 private key
func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
var parsedKey interface{}
if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
return nil, err
}
}
var pkey *rsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
return nil, ErrNotRSAPrivateKey
}
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 public key
func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *rsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
return nil, ErrNotRSAPublicKey
}
return pkey, nil
}

View File

@@ -1,35 +0,0 @@
package jwt
import (
"sync"
)
var signingMethods = map[string]func() SigningMethod{}
var signingMethodLock = new(sync.RWMutex)
// Implement SigningMethod to add new methods for signing or verifying tokens.
type SigningMethod interface {
Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid
Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error
Alg() string // returns the alg identifier for this method (example: 'HS256')
}
// Register the "alg" name and a factory function for signing method.
// This is typically done during init() in the method's implementation
func RegisterSigningMethod(alg string, f func() SigningMethod) {
signingMethodLock.Lock()
defer signingMethodLock.Unlock()
signingMethods[alg] = f
}
// Get a signing method from an "alg" string
func GetSigningMethod(alg string) (method SigningMethod) {
signingMethodLock.RLock()
defer signingMethodLock.RUnlock()
if methodF, ok := signingMethods[alg]; ok {
method = methodF()
}
return
}

View File

@@ -1,108 +0,0 @@
package jwt
import (
"encoding/base64"
"encoding/json"
"strings"
"time"
)
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
// You can override it to use another time value. This is useful for testing or if your
// server uses a different time zone than your tokens.
var TimeFunc = time.Now
// Parse methods use this callback function to supply
// the key for verification. The function receives the parsed,
// but unverified Token. This allows you to use properties in the
// Header of the token (such as `kid`) to identify which key to use.
type Keyfunc func(*Token) (interface{}, error)
// A JWT Token. Different fields will be used depending on whether you're
// creating or parsing/verifying a token.
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims Claims // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}
// Create a new Token. Takes a signing method
func New(method SigningMethod) *Token {
return NewWithClaims(method, MapClaims{})
}
func NewWithClaims(method SigningMethod, claims Claims) *Token {
return &Token{
Header: map[string]interface{}{
"typ": "JWT",
"alg": method.Alg(),
},
Claims: claims,
Method: method,
}
}
// Get the complete, signed token
func (t *Token) SignedString(key interface{}) (string, error) {
var sig, sstr string
var err error
if sstr, err = t.SigningString(); err != nil {
return "", err
}
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
return strings.Join([]string{sstr, sig}, "."), nil
}
// Generate the signing string. This is the
// most expensive part of the whole deal. Unless you
// need this for something special, just go straight for
// the SignedString.
func (t *Token) SigningString() (string, error) {
var err error
parts := make([]string, 2)
for i, _ := range parts {
var jsonValue []byte
if i == 0 {
if jsonValue, err = json.Marshal(t.Header); err != nil {
return "", err
}
} else {
if jsonValue, err = json.Marshal(t.Claims); err != nil {
return "", err
}
}
parts[i] = EncodeSegment(jsonValue)
}
return strings.Join(parts, "."), nil
}
// Parse, validate, and return a token.
// keyFunc will receive the parsed token and should return the key for validating.
// If everything is kosher, err will be nil
func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return new(Parser).Parse(tokenString, keyFunc)
}
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)
}
// Encode JWT specific base64url encoding with padding stripped
func EncodeSegment(seg []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
}
// Decode JWT specific base64url encoding with padding stripped
func DecodeSegment(seg string) ([]byte, error) {
if l := len(seg) % 4; l > 0 {
seg += strings.Repeat("=", 4-l)
}
return base64.URLEncoding.DecodeString(seg)
}

View File

@@ -1,18 +0,0 @@
# Contributing
First, thank you for contributing! We love and encourage pull requests from everyone.
Before submitting major changes, here are a few guidelines to follow:
1. Check the [open issues][issues] and [pull requests][prs] for existing discussions.
1. Open an [issue][issues] first, to discuss a new feature or enhancement.
1. Write tests, and make sure the test suite passes locally and on CI.
1. Open a pull request, and reference the relevant issue(s).
1. After receiving feedback, [squash your commits][squash] and add a [great commit message][message].
1. Have fun!
[issues]: https://github.com/go-kit/kit/issues
[prs]: https://github.com/go-kit/kit/pulls
[squash]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html
[message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html

22
vendor/github.com/go-kit/kit/LICENSE generated vendored
View File

@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Peter Bourgon
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.

View File

@@ -1,117 +0,0 @@
# Go kit [![Circle CI](https://circleci.com/gh/go-kit/kit.svg?style=svg)](https://circleci.com/gh/go-kit/kit) [![Drone.io](https://drone.io/github.com/go-kit/kit/status.png)](https://drone.io/github.com/go-kit/kit/latest) [![Travis CI](https://travis-ci.org/go-kit/kit.svg?branch=master)](https://travis-ci.org/go-kit/kit) [![GoDoc](https://godoc.org/github.com/go-kit/kit?status.svg)](https://godoc.org/github.com/go-kit/kit) [![Coverage Status](https://coveralls.io/repos/go-kit/kit/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-kit/kit?branch=master) [![Go Report Card](https://goreportcard.com/badge/go-kit/kit)](https://goreportcard.com/report/go-kit/kit)
**Go kit** is a **distributed programming toolkit** for building microservices
in large organizations. We solve common problems in distributed systems, so
you can focus on your business logic.
- Website: [gokit.io](https://gokit.io)
- Mailing list: [go-kit](https://groups.google.com/forum/#!forum/go-kit)
- Slack: [gophers.slack.com](https://gophers.slack.com) **#go-kit** ([invite](https://gophersinvite.herokuapp.com/))
## Motivation
Go has emerged as the language of the server, but it remains underrepresented
in large, consumer-focused tech companies like Facebook, Twitter, Netflix, and
SoundCloud. These organizations have largely adopted JVM-based stacks for
their business logic, owing in large part to libraries and ecosystems that
directly support their microservice architectures.
To reach its next level of success, Go needs more than simple primitives and
idioms. It needs a comprehensive toolkit, for coherent distributed programming
in the large. Go kit is a set of packages and best practices, which provide a
comprehensive, robust, and trustable way of building microservices for
organizations of any size.
For more details, see
[the website](https://gokit.io),
[the motivating blog post](http://peter.bourgon.org/go-kit/) and
[the video of the talk](https://www.youtube.com/watch?v=iFR_7AKkJFU).
See also the
[Go kit talk at GopherCon 2015](https://www.youtube.com/watch?v=1AjaZi4QuGo).
## Goals
- Operate in a heterogeneous SOA — expect to interact with mostly non-Go-kit services
- RPC as the primary messaging pattern
- Pluggable serialization and transport — not just JSON over HTTP
- Operate within existing infrastructures — no mandates for specific tools or technologies
## Non-goals
- Supporting messaging patterns other than RPC (for now) — e.g. MPI, pub/sub, CQRS, etc.
- Re-implementing functionality that can be provided by adapting existing software
- Having opinions on operational concerns: deployment, configuration, process supervision, orchestration, etc.
## Contributing
Please see [CONTRIBUTING.md](/CONTRIBUTING.md).
Thank you, [contributors](https://github.com/go-kit/kit/graphs/contributors)!
## Dependency management
Go kit is a library, designed to be imported into a binary package.
Vendoring is currently the best way for binary package authors
to ensure reliable, reproducible builds.
Therefore, we strongly recommend our users use vendoring for all of their dependencies,
including Go kit.
To avoid compatibility and availability issues,
Go kit doesn't vendor its own dependencies,
and doesn't recommend use of third-party import proxies.
There are several tools which make vendoring easier, including
[gb](http://getgb.io),
[glide](https://github.com/Masterminds/glide),
[gvt](https://github.com/FiloSottile/gvt),
[govendor](https://github.com/kardianos/govendor), and
[vendetta](https://github.com/dpw/vendetta).
In addition, Go kit uses a variety of continuous integration providers
to find and fix compatibility problems as soon as they occur.
## Related projects
Projects with a ★ have had particular influence on Go kit's design (or vice-versa).
### Service frameworks
- [gizmo](https://github.com/nytimes/gizmo), a microservice toolkit from The New York Times ★
- [go-micro](https://github.com/myodc/go-micro), a microservices client/server library ★
- [h2](https://github.com/hailocab/h2), a microservices framework ★
- [gotalk](https://github.com/rsms/gotalk), async peer communication protocol &amp; library
- [Kite](https://github.com/koding/kite), a micro-service framework
- [gocircuit](https://github.com/gocircuit/circuit), dynamic cloud orchestration
### Individual components
- [afex/hystrix-go](https://github.com/afex/hystrix-go), client-side latency and fault tolerance library
- [armon/go-metrics](https://github.com/armon/go-metrics), library for exporting performance and runtime metrics to external metrics systems
- [codahale/lunk](https://github.com/codahale/lunk), structured logging in the style of Google's Dapper or Twitter's Zipkin
- [eapache/go-resiliency](https://github.com/eapache/go-resiliency), resiliency patterns
- [sasbury/logging](https://github.com/sasbury/logging), a tagged style of logging
- [grpc/grpc-go](https://github.com/grpc/grpc-go), HTTP/2 based RPC
- [inconshreveable/log15](https://github.com/inconshreveable/log15), simple, powerful logging for Go ★
- [mailgun/vulcand](https://github.com/vulcand/vulcand), programmatic load balancer backed by etcd
- [mattheath/phosphor](https://github.com/mondough/phosphor), distributed system tracing
- [pivotal-golang/lager](https://github.com/pivotal-golang/lager), an opinionated logging library
- [rubyist/circuitbreaker](https://github.com/rubyist/circuitbreaker), circuit breaker library
- [Sirupsen/logrus](https://github.com/Sirupsen/logrus), structured, pluggable logging for Go ★
- [sourcegraph/appdash](https://github.com/sourcegraph/appdash), application tracing system based on Google's Dapper
- [spacemonkeygo/monitor](https://github.com/spacemonkeygo/monitor), data collection, monitoring, instrumentation, and Zipkin client library
- [streadway/handy](https://github.com/streadway/handy), net/http handler filters
- [vitess/rpcplus](https://godoc.org/github.com/youtube/vitess/go/rpcplus), package rpc + context.Context
- [gdamore/mangos](https://github.com/gdamore/mangos), nanomsg implementation in pure Go
### Web frameworks
- [Gorilla](http://www.gorillatoolkit.org)
- [Gin](https://gin-gonic.github.io/gin/)
- [Negroni](https://github.com/codegangsta/negroni)
- [Goji](https://github.com/zenazn/goji)
- [Martini](https://github.com/go-martini/martini)
- [Beego](http://beego.me/)
- [Revel](https://revel.github.io/) (considered harmful)
## Additional reading
- [Architecting for the Cloud](http://fr.slideshare.net/stonse/architecting-for-the-cloud-using-netflixoss-codemash-workshop-29852233) — Netflix
- [Dapper, a Large-Scale Distributed Systems Tracing Infrastructure](http://research.google.com/pubs/pub36356.html) — Google
- [Your Server as a Function](http://monkey.org/~marius/funsrv.pdf) (PDF) — Twitter

View File

@@ -1,17 +0,0 @@
# Roadmap
This is a coarse-grained roadmap of Go kit development direction in the short
to mid-term future. It will be kept reasonably up-to-date by the project
maintainers. Suggest new ideas, enhancements, and features using the standard
[issues](https://github.com/go-kit/kit/issues) model.
## Prioritized
1. kitgen code generation (#308, #70)
1. package pubsub (#298, #295)
## Unprioritized
- package log/levels refactor (#250, #269, #252)
- package auth/jwt (#255)

View File

@@ -1,122 +0,0 @@
# package auth/jwt
`package auth/jwt` provides a set of interfaces for service authorization
through [JSON Web Tokens](https://jwt.io/).
## Usage
NewParser takes a key function and an expected signing method and returns an
`endpoint.Middleware`. The middleware will parse a token passed into the
context via the `jwt.JWTTokenContextKey`. If the token is valid, any claims
will be added to the context via the `jwt.JWTClaimsContextKey`.
```go
import (
stdjwt "github.com/dgrijalva/jwt-go"
"github.com/go-kit/kit/auth/jwt"
"github.com/go-kit/kit/endpoint"
)
func main() {
var exampleEndpoint endpoint.Endpoint
{
kf := func(token *stdjwt.Token) (interface{}, error) { return []byte("SigningString"), nil }
exampleEndpoint = MakeExampleEndpoint(service)
exampleEndpoint = jwt.NewParser(kf, stdjwt.SigningMethodHS256)(exampleEndpoint)
}
}
```
NewSigner takes a JWT key ID header, the signing key, signing method, and a
claims object. It returns an `endpoint.Middleware`. The middleware will build
the token string and add it to the context via the `jwt.JWTTokenContextKey`.
```go
import (
stdjwt "github.com/dgrijalva/jwt-go"
"github.com/go-kit/kit/auth/jwt"
"github.com/go-kit/kit/endpoint"
)
func main() {
var exampleEndpoint endpoint.Endpoint
{
exampleEndpoint = grpctransport.NewClient(...).Endpoint()
exampleEndpoint = jwt.NewSigner(
"kid-header",
[]byte("SigningString"),
stdjwt.SigningMethodHS256,
jwt.Claims{},
)(exampleEndpoint)
}
}
```
In order for the parser and the signer to work, the authorization headers need
to be passed between the request and the context. `ToHTTPContext()`,
`FromHTTPContext()`, `ToGRPCContext()`, and `FromGRPCContext()` are given as
helpers to do this. These functions implement the correlating transport's
RequestFunc interface and can be passed as ClientBefore or ServerBefore
options.
Example of use in a client:
```go
import (
stdjwt "github.com/dgrijalva/jwt-go"
grpctransport "github.com/go-kit/kit/transport/grpc"
"github.com/go-kit/kit/auth/jwt"
"github.com/go-kit/kit/endpoint"
)
func main() {
options := []httptransport.ClientOption{}
var exampleEndpoint endpoint.Endpoint
{
exampleEndpoint = grpctransport.NewClient(..., grpctransport.ClientBefore(jwt.FromGRPCContext())).Endpoint()
exampleEndpoint = jwt.NewSigner(
"kid-header",
[]byte("SigningString"),
stdjwt.SigningMethodHS256,
jwt.Claims{},
)(exampleEndpoint)
}
}
```
Example of use in a server:
```go
import (
"golang.org/x/net/context"
"github.com/go-kit/kit/auth/jwt"
"github.com/go-kit/kit/log"
grpctransport "github.com/go-kit/kit/transport/grpc"
)
func MakeGRPCServer(ctx context.Context, endpoints Endpoints, logger log.Logger) pb.ExampleServer {
options := []grpctransport.ServerOption{grpctransport.ServerErrorLogger(logger)}
return &grpcServer{
createUser: grpctransport.NewServer(
ctx,
endpoints.CreateUserEndpoint,
DecodeGRPCCreateUserRequest,
EncodeGRPCCreateUserResponse,
append(options, grpctransport.ServerBefore(jwt.ToGRPCContext()))...,
),
getUser: grpctransport.NewServer(
ctx,
endpoints.GetUserEndpoint,
DecodeGRPCGetUserRequest,
EncodeGRPCGetUserResponse,
options...,
),
}
}
```

View File

@@ -1,122 +0,0 @@
package jwt
import (
"errors"
jwt "github.com/dgrijalva/jwt-go"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
type contextKey string
const (
// JWTTokenContextKey holds the key used to store a JWT Token in the
// context.
JWTTokenContextKey contextKey = "JWTToken"
// JWTClaimsContxtKey holds the key used to store the JWT Claims in the
// context.
JWTClaimsContextKey contextKey = "JWTClaims"
)
var (
// ErrTokenContextMissing denotes a token was not passed into the parsing
// middleware's context.
ErrTokenContextMissing = errors.New("token up for parsing was not passed through the context")
// ErrTokenInvalid denotes a token was not able to be validated.
ErrTokenInvalid = errors.New("JWT Token was invalid")
// ErrTokenExpired denotes a token's expire header (exp) has since passed.
ErrTokenExpired = errors.New("JWT Token is expired")
// ErrTokenMalformed denotes a token was not formatted as a JWT token.
ErrTokenMalformed = errors.New("JWT Token is malformed")
// ErrTokenNotActive denotes a token's not before header (nbf) is in the
// future.
ErrTokenNotActive = errors.New("token is not valid yet")
// ErrUncesptedSigningMethod denotes a token was signed with an unexpected
// signing method.
ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
)
type Claims map[string]interface{}
// NewSigner creates a new JWT token generating middleware, specifying key ID,
// signing string, signing method and the claims you would like it to contain.
// Tokens are signed with a Key ID header (kid) which is useful for determining
// the key to use for parsing. Particularly useful for clients.
func NewSigner(kid string, key []byte, method jwt.SigningMethod, claims Claims) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
token := jwt.NewWithClaims(method, jwt.MapClaims(claims))
token.Header["kid"] = kid
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(key)
if err != nil {
return nil, err
}
ctx = context.WithValue(ctx, JWTTokenContextKey, tokenString)
return next(ctx, request)
}
}
}
// NewParser creates a new JWT token parsing middleware, specifying a
// jwt.Keyfunc interface and the signing method. NewParser adds the resulting
// claims to endpoint context or returns error on invalid token. Particularly
// useful for servers.
func NewParser(keyFunc jwt.Keyfunc, method jwt.SigningMethod) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
// tokenString is stored in the context from the transport handlers.
tokenString, ok := ctx.Value(JWTTokenContextKey).(string)
if !ok {
return nil, ErrTokenContextMissing
}
// Parse takes the token string and a function for looking up the
// key. The latter is especially useful if you use multiple keys
// for your application. The standard is to use 'kid' in the head
// of the token to identify which key to use, but the parsed token
// (head and claims) is provided to the callback, providing
// flexibility.
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if token.Method != method {
return nil, ErrUnexpectedSigningMethod
}
return keyFunc(token)
})
if err != nil {
if e, ok := err.(*jwt.ValidationError); ok && e.Inner != nil {
if e.Errors&jwt.ValidationErrorMalformed != 0 {
// Token is malformed
return nil, ErrTokenMalformed
} else if e.Errors&jwt.ValidationErrorExpired != 0 {
// Token is expired
return nil, ErrTokenExpired
} else if e.Errors&jwt.ValidationErrorNotValidYet != 0 {
// Token is not active yet
return nil, ErrTokenNotActive
}
return nil, e.Inner
}
return nil, err
}
if !token.Valid {
return nil, ErrTokenInvalid
}
if claims, ok := token.Claims.(jwt.MapClaims); ok {
ctx = context.WithValue(ctx, JWTClaimsContextKey, Claims(claims))
}
return next(ctx, request)
}
}
}

View File

@@ -1,89 +0,0 @@
package jwt
import (
"fmt"
stdhttp "net/http"
"strings"
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
"github.com/go-kit/kit/transport/grpc"
"github.com/go-kit/kit/transport/http"
)
const (
bearer string = "bearer"
bearerFormat string = "Bearer %s"
)
// ToHTTPContext moves JWT token from request header to context. Particularly
// useful for servers.
func ToHTTPContext() http.RequestFunc {
return func(ctx context.Context, r *stdhttp.Request) context.Context {
token, ok := extractTokenFromAuthHeader(r.Header.Get("Authorization"))
if !ok {
return ctx
}
return context.WithValue(ctx, JWTTokenContextKey, token)
}
}
// FromHTTPContext moves JWT token from context to request header. Particularly
// useful for clients.
func FromHTTPContext() http.RequestFunc {
return func(ctx context.Context, r *stdhttp.Request) context.Context {
token, ok := ctx.Value(JWTTokenContextKey).(string)
if ok {
r.Header.Add("Authorization", generateAuthHeaderFromToken(token))
}
return ctx
}
}
// ToGRPCContext moves JWT token from grpc metadata to context. Particularly
// userful for servers.
func ToGRPCContext() grpc.RequestFunc {
return func(ctx context.Context, md *metadata.MD) context.Context {
// capital "Key" is illegal in HTTP/2.
authHeader, ok := (*md)["authorization"]
if !ok {
return ctx
}
token, ok := extractTokenFromAuthHeader(authHeader[0])
if ok {
ctx = context.WithValue(ctx, JWTTokenContextKey, token)
}
return ctx
}
}
// FromGRPCContext moves JWT token from context to grpc metadata. Particularly
// useful for clients.
func FromGRPCContext() grpc.RequestFunc {
return func(ctx context.Context, md *metadata.MD) context.Context {
token, ok := ctx.Value(JWTTokenContextKey).(string)
if ok {
// capital "Key" is illegal in HTTP/2.
(*md)["authorization"] = []string{generateAuthHeaderFromToken(token)}
}
return ctx
}
}
func extractTokenFromAuthHeader(val string) (token string, ok bool) {
authHeaderParts := strings.Split(val, " ")
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != bearer {
return "", false
}
return authHeaderParts[1], true
}
func generateAuthHeaderFromToken(token string) string {
return fmt.Sprintf(bearerFormat, token)
}

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env bash
# This script runs the cover tool on all packages with test files. If you set a
# WEB environment variable, it will additionally open the web-based coverage
# visualizer for each package.
set -e
function go_files { find . -name '*_test.go' ; }
function filter { grep -v '/_' ; }
function remove_relative_prefix { sed -e 's/^\.\///g' ; }
function directories {
go_files | filter | remove_relative_prefix | while read f
do
dirname $f
done
}
function unique_directories { directories | sort | uniq ; }
PATHS=${1:-$(unique_directories)}
function report {
for path in $PATHS
do
go test -coverprofile=$path/cover.coverprofile ./$path
done
}
function combine {
gover
}
function clean {
find . -name cover.coverprofile | xargs rm
}
report
combine
clean
if [ -n "${WEB+x}" ]
then
go tool cover -html=gover.coverprofile
fi

View File

@@ -1,5 +0,0 @@
// Package endpoint defines an abstraction for RPCs.
//
// Endpoints are a fundamental building block for many Go kit components.
// Endpoints are implemented by servers, and called by clients.
package endpoint

View File

@@ -1,28 +0,0 @@
package endpoint
import (
"golang.org/x/net/context"
)
// Endpoint is the fundamental building block of servers and clients.
// It represents a single RPC method.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
// Nop is an endpoint that does nothing and returns a nil error.
// Useful for tests.
func Nop(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }
// Middleware is a chainable behavior modifier for endpoints.
type Middleware func(Endpoint) Endpoint
// Chain is a helper function for composing middlewares. Requests will
// traverse them in the order they're declared. That is, the first middleware
// is treated as the outermost middleware.
func Chain(outer Middleware, others ...Middleware) Middleware {
return func(next Endpoint) Endpoint {
for i := len(others) - 1; i >= 0; i-- { // reverse
next = others[i](next)
}
return outer(next)
}
}

26
vendor/github.com/go-kit/kit/lint generated vendored
View File

@@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
if [ ! $(command -v gometalinter) ]
then
go get github.com/alecthomas/gometalinter
gometalinter --update --install
fi
time gometalinter \
--exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \
--exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \
--exclude='/thrift/' \
--exclude='/pb/' \
--exclude='no args in Log call \(vet\)' \
--disable=dupl \
--disable=aligncheck \
--disable=gotype \
--cyclo-over=20 \
--tests \
--concurrency=2 \
--deadline=300s \
./...

View File

@@ -1,148 +0,0 @@
# package log
`package log` provides a minimal interface for structured logging in services.
It may be wrapped to encode conventions, enforce type-safety, provide leveled logging, and so on.
It can be used for both typical application log events, and log-structured data streams.
## Structured logging
Structured logging is, basically, conceding to the reality that logs are _data_,
and warrant some level of schematic rigor.
Using a stricter, key/value-oriented message format for our logs,
containing contextual and semantic information,
makes it much easier to get insight into the operational activity of the systems we build.
Consequently, `package log` is of the strong belief that
"[the benefits of structured logging outweigh the minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
Migrating from unstructured to structured logging is probably a lot easier than you'd expect.
```go
// Unstructured
log.Printf("HTTP server listening on %s", addr)
// Structured
logger.Log("transport", "HTTP", "addr", addr, "msg", "listening")
```
## Usage
### Typical application logging
```go
w := log.NewSyncWriter(os.Stderr)
logger := log.NewLogfmtLogger(w)
logger.Log("question", "what is the meaning of life?", "answer", 42)
// Output:
// question="what is the meaning of life?" answer=42
```
### Log contexts
```go
func main() {
var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.NewContext(logger).With("instance_id", 123)
logger.Log("msg", "starting")
NewWorker(log.NewContext(logger).With("component", "worker")).Run()
NewSlacker(log.NewContext(logger).With("component", "slacker")).Run()
}
// Output:
// instance_id=123 msg=starting
// instance_id=123 component=worker msg=running
// instance_id=123 component=slacker msg=running
```
### Interact with stdlib logger
Redirect stdlib logger to Go kit logger.
```go
import (
"os"
stdlog "log"
kitlog "github.com/go-kit/kit/log"
)
func main() {
logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout))
stdlog.SetOutput(kitlog.NewStdlibAdapter(logger))
stdlog.Print("I sure like pie")
}
// Output:
// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"}
```
Or, if, for legacy reasons,
you need to pipe all of your logging through the stdlib log package,
you can redirect Go kit logger to the stdlib logger.
```go
logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
logger.Log("legacy", true, "msg", "at least it's something")
// Output:
// 2016/01/01 12:34:56 legacy=true msg="at least it's something"
```
### Timestamps and callers
```go
var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
logger.Log("msg", "hello")
// Output:
// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello
```
## Supported output formats
- [Logfmt](https://brandur.org/logfmt)
- JSON
## Enhancements
`package log` is centered on the one-method Logger interface.
```go
type Logger interface {
Log(keyvals ...interface{}) error
}
```
This interface, and its supporting code like [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context),
is the product of much iteration and evaluation.
For more details on the evolution of the Logger interface,
see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
a talk by [Chris Hines](https://github.com/ChrisHines).
Also, please see
[#63](https://github.com/go-kit/kit/issues/63),
[#76](https://github.com/go-kit/kit/pull/76),
[#131](https://github.com/go-kit/kit/issues/131),
[#157](https://github.com/go-kit/kit/pull/157),
[#164](https://github.com/go-kit/kit/issues/164), and
[#252](https://github.com/go-kit/kit/pull/252)
to review historical conversations about package log and the Logger interface.
Value-add packages and suggestions,
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/levels),
are of course welcome.
Good proposals should
- Be composable with [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context),
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped context, and
- Be friendly to packages that accept only an unadorned log.Logger.
## Benchmarks & comparisons
There are a few Go logging benchmarks and comparisons that include Go kit's package log.
- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log
- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log

View File

@@ -1,93 +0,0 @@
// Package log provides a structured logger.
//
// Structured logging produces logs easily consumed later by humans or
// machines. Humans might be interested in debugging errors, or tracing
// specific requests. Machines might be interested in counting interesting
// events, or aggregating information for off-line processing. In both cases,
// it is important that the log messages are structured and actionable.
// Package log is designed to encourage both of these best practices.
//
// Basic Usage
//
// The fundamental interface is Logger. Loggers create log events from
// key/value data. The Logger interface has a single method, Log, which
// accepts a sequence of alternating key/value pairs, which this package names
// keyvals.
//
// type Logger interface {
// Log(keyvals ...interface{}) error
// }
//
// Here is an example of a function using a Logger to create log events.
//
// func RunTask(task Task, logger log.Logger) string {
// logger.Log("taskID", task.ID, "event", "starting task")
// ...
// logger.Log("taskID", task.ID, "event", "task complete")
// }
//
// The keys in the above example are "taskID" and "event". The values are
// task.ID, "starting task", and "task complete". Every key is followed
// immediately by its value.
//
// Keys are usually plain strings. Values may be any type that has a sensible
// encoding in the chosen log format. With structured logging it is a good
// idea to log simple values without formatting them. This practice allows
// the chosen logger to encode values in the most appropriate way.
//
// Log Context
//
// A log context stores keyvals that it includes in all log events. Building
// appropriate log contexts reduces repetition and aids consistency in the
// resulting log output. We can use a context to improve the RunTask example.
//
// func RunTask(task Task, logger log.Logger) string {
// logger = log.NewContext(logger).With("taskID", task.ID)
// logger.Log("event", "starting task")
// ...
// taskHelper(task.Cmd, logger)
// ...
// logger.Log("event", "task complete")
// }
//
// The improved version emits the same log events as the original for the
// first and last calls to Log. The call to taskHelper highlights that a
// context may be passed as a logger to other functions. Each log event
// created by the called function will include the task.ID even though the
// function does not have access to that value. Using log contexts this way
// simplifies producing log output that enables tracing the life cycle of
// individual tasks. (See the Context example for the full code of the
// above snippet.)
//
// Dynamic Context Values
//
// A Valuer function stored in a log context generates a new value each time
// the context logs an event. The Valuer example demonstrates how this
// feature works.
//
// Valuers provide the basis for consistently logging timestamps and source
// code location. The log package defines several valuers for that purpose.
// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and
// DefaultCaller. A common logger initialization sequence that ensures all log
// entries contain a timestamp and source location looks like this:
//
// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
// logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
//
// Concurrent Safety
//
// Applications with multiple goroutines want each log event written to the
// same logger to remain separate from other log events. Package log provides
// two simple solutions for concurrent safe logging.
//
// NewSyncWriter wraps an io.Writer and serializes each call to its Write
// method. Using a SyncWriter has the benefit that the smallest practical
// portion of the logging logic is performed within a mutex, but it requires
// the formatting Logger to make only one call to Write per log event.
//
// NewSyncLogger wraps any Logger and serializes each call to its Log method.
// Using a SyncLogger has the benefit that it guarantees each log event is
// handled atomically within the wrapped logger, but it typically serializes
// both the formatting and output logic. Use a SyncLogger if the formatting
// logger may perform multiple writes per log event.
package log

View File

@@ -1,92 +0,0 @@
package log
import (
"encoding"
"encoding/json"
"fmt"
"io"
"reflect"
)
type jsonLogger struct {
io.Writer
}
// NewJSONLogger returns a Logger that encodes keyvals to the Writer as a
// single JSON object. Each log event produces no more than one call to
// w.Write. The passed Writer must be safe for concurrent use by multiple
// goroutines if the returned Logger will be used concurrently.
func NewJSONLogger(w io.Writer) Logger {
return &jsonLogger{w}
}
func (l *jsonLogger) Log(keyvals ...interface{}) error {
n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd
m := make(map[string]interface{}, n)
for i := 0; i < len(keyvals); i += 2 {
k := keyvals[i]
var v interface{} = ErrMissingValue
if i+1 < len(keyvals) {
v = keyvals[i+1]
}
merge(m, k, v)
}
return json.NewEncoder(l.Writer).Encode(m)
}
func merge(dst map[string]interface{}, k, v interface{}) {
var key string
switch x := k.(type) {
case string:
key = x
case fmt.Stringer:
key = safeString(x)
default:
key = fmt.Sprint(x)
}
if x, ok := v.(error); ok {
v = safeError(x)
}
// We want json.Marshaler and encoding.TextMarshaller to take priority over
// err.Error() and v.String(). But json.Marshall (called later) does that by
// default so we force a no-op if it's one of those 2 case.
switch x := v.(type) {
case json.Marshaler:
case encoding.TextMarshaler:
case error:
v = safeError(x)
case fmt.Stringer:
v = safeString(x)
}
dst[key] = v
}
func safeString(str fmt.Stringer) (s string) {
defer func() {
if panicVal := recover(); panicVal != nil {
if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
s = "NULL"
} else {
panic(panicVal)
}
}
}()
s = str.String()
return
}
func safeError(err error) (s interface{}) {
defer func() {
if panicVal := recover(); panicVal != nil {
if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
s = nil
} else {
panic(panicVal)
}
}
}()
s = err.Error()
return
}

View File

@@ -1,144 +0,0 @@
package log
import "errors"
// Logger is the fundamental interface for all log operations. Log creates a
// log event from keyvals, a variadic sequence of alternating keys and values.
// Implementations must be safe for concurrent use by multiple goroutines. In
// particular, any implementation of Logger that appends to keyvals or
// modifies any of its elements must make a copy first.
type Logger interface {
Log(keyvals ...interface{}) error
}
// ErrMissingValue is appended to keyvals slices with odd length to substitute
// the missing value.
var ErrMissingValue = errors.New("(MISSING)")
// NewContext returns a new Context that logs to logger.
func NewContext(logger Logger) *Context {
if c, ok := logger.(*Context); ok {
return c
}
return &Context{logger: logger}
}
// Context must always have the same number of stack frames between calls to
// its Log method and the eventual binding of Valuers to their value. This
// requirement comes from the functional requirement to allow a context to
// resolve application call site information for a log.Caller stored in the
// context. To do this we must be able to predict the number of logging
// functions on the stack when bindValues is called.
//
// Three implementation details provide the needed stack depth consistency.
// The first two of these details also result in better amortized performance,
// and thus make sense even without the requirements regarding stack depth.
// The third detail, however, is subtle and tied to the implementation of the
// Go compiler.
//
// 1. NewContext avoids introducing an additional layer when asked to
// wrap another Context.
// 2. With avoids introducing an additional layer by returning a newly
// constructed Context with a merged keyvals rather than simply
// wrapping the existing Context.
// 3. All of Context's methods take pointer receivers even though they
// do not mutate the Context.
//
// Before explaining the last detail, first some background. The Go compiler
// generates wrapper methods to implement the auto dereferencing behavior when
// calling a value method through a pointer variable. These wrapper methods
// are also used when calling a value method through an interface variable
// because interfaces store a pointer to the underlying concrete value.
// Calling a pointer receiver through an interface does not require generating
// an additional function.
//
// If Context had value methods then calling Context.Log through a variable
// with type Logger would have an extra stack frame compared to calling
// Context.Log through a variable with type Context. Using pointer receivers
// avoids this problem.
// A Context wraps a Logger and holds keyvals that it includes in all log
// events. When logging, a Context replaces all value elements (odd indexes)
// containing a Valuer with their generated value for each call to its Log
// method.
type Context struct {
logger Logger
keyvals []interface{}
hasValuer bool
}
// Log replaces all value elements (odd indexes) containing a Valuer in the
// stored context with their generated value, appends keyvals, and passes the
// result to the wrapped Logger.
func (l *Context) Log(keyvals ...interface{}) error {
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
if l.hasValuer {
// If no keyvals were appended above then we must copy l.keyvals so
// that future log events will reevaluate the stored Valuers.
if len(keyvals) == 0 {
kvs = append([]interface{}{}, l.keyvals...)
}
bindValues(kvs[:len(l.keyvals)])
}
return l.logger.Log(kvs...)
}
// With returns a new Context with keyvals appended to those of the receiver.
func (l *Context) With(keyvals ...interface{}) *Context {
if len(keyvals) == 0 {
return l
}
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
return &Context{
logger: l.logger,
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
keyvals: kvs[:len(kvs):len(kvs)],
hasValuer: l.hasValuer || containsValuer(keyvals),
}
}
// WithPrefix returns a new Context with keyvals prepended to those of the
// receiver.
func (l *Context) WithPrefix(keyvals ...interface{}) *Context {
if len(keyvals) == 0 {
return l
}
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
n := len(l.keyvals) + len(keyvals)
if len(keyvals)%2 != 0 {
n++
}
kvs := make([]interface{}, 0, n)
kvs = append(kvs, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
kvs = append(kvs, l.keyvals...)
return &Context{
logger: l.logger,
keyvals: kvs,
hasValuer: l.hasValuer || containsValuer(keyvals),
}
}
// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
// f is a function with the appropriate signature, LoggerFunc(f) is a Logger
// object that calls f.
type LoggerFunc func(...interface{}) error
// Log implements Logger by calling f(keyvals...).
func (f LoggerFunc) Log(keyvals ...interface{}) error {
return f(keyvals...)
}

View File

@@ -1,62 +0,0 @@
package log
import (
"bytes"
"io"
"sync"
"github.com/go-logfmt/logfmt"
)
type logfmtEncoder struct {
*logfmt.Encoder
buf bytes.Buffer
}
func (l *logfmtEncoder) Reset() {
l.Encoder.Reset()
l.buf.Reset()
}
var logfmtEncoderPool = sync.Pool{
New: func() interface{} {
var enc logfmtEncoder
enc.Encoder = logfmt.NewEncoder(&enc.buf)
return &enc
},
}
type logfmtLogger struct {
w io.Writer
}
// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in
// logfmt format. Each log event produces no more than one call to w.Write.
// The passed Writer must be safe for concurrent use by multiple goroutines if
// the returned Logger will be used concurrently.
func NewLogfmtLogger(w io.Writer) Logger {
return &logfmtLogger{w}
}
func (l logfmtLogger) Log(keyvals ...interface{}) error {
enc := logfmtEncoderPool.Get().(*logfmtEncoder)
enc.Reset()
defer logfmtEncoderPool.Put(enc)
if err := enc.EncodeKeyvals(keyvals...); err != nil {
return err
}
// Add newline to the end of the buffer
if err := enc.EndRecord(); err != nil {
return err
}
// The Logger interface requires implementations to be safe for concurrent
// use by multiple goroutines. For this implementation that means making
// only one call to l.w.Write() for each call to Log.
if _, err := l.w.Write(enc.buf.Bytes()); err != nil {
return err
}
return nil
}

View File

@@ -1,8 +0,0 @@
package log
type nopLogger struct{}
// NewNopLogger returns a logger that doesn't do anything.
func NewNopLogger() Logger { return nopLogger{} }
func (nopLogger) Log(...interface{}) error { return nil }

Some files were not shown because too many files have changed in this diff Show More