Compare commits
2 Commits
master
...
release/0.
Author | SHA1 | Date |
---|---|---|
Thomas Boerger | c7566b7ae6 | |
Thomas Boerger | 4d96bc5ec1 |
15
.drone.yml
15
.drone.yml
|
@ -1,24 +1,19 @@
|
|||
workspace:
|
||||
base: /go
|
||||
path: src/github.com/drone-plugins/drone-webhook
|
||||
|
||||
pipeline:
|
||||
build:
|
||||
test:
|
||||
image: golang:1.6
|
||||
environment:
|
||||
- CGO_ENABLED=0
|
||||
- GOPATH=/drone
|
||||
commands:
|
||||
- make deps
|
||||
- make vet
|
||||
- make build
|
||||
- make test
|
||||
|
||||
docker:
|
||||
repo: plugins/drone-webhook
|
||||
storage_driver: overlay
|
||||
tag: latest
|
||||
repo: plugins/drone-webhook
|
||||
tag: [ "latest" ]
|
||||
when:
|
||||
branch: master
|
||||
branch: release/0.4
|
||||
event: push
|
||||
|
||||
plugin:
|
||||
|
|
|
@ -1 +1 @@
|
|||
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwogIHBhdGg6IHNyYy9naXRodWIuY29tL2Ryb25lLXBsdWdpbnMvZHJvbmUtd2ViaG9vawoKcGlwZWxpbmU6CiAgYnVpbGQ6CiAgICBpbWFnZTogZ29sYW5nOjEuNgogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gQ0dPX0VOQUJMRUQ9MAogICAgY29tbWFuZHM6CiAgICAgIC0gbWFrZSBkZXBzCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGJ1aWxkCiAgICAgIC0gbWFrZSB0ZXN0CgogIGRvY2tlcjoKICAgIHJlcG86IHBsdWdpbnMvZHJvbmUtd2ViaG9vawogICAgc3RvcmFnZV9kcml2ZXI6IG92ZXJsYXkKICAgIHRhZzogbGF0ZXN0CiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBldmVudDogcHVzaAoKcGx1Z2luOgogIG5hbWU6IFdlYmhvb2sKICBkZXNjOiBTZW5kIGJ1aWxkIHN0YXR1cyBub3RpZmljYXRpb25zIHZpYSBXZWJob29rCiAgdHlwZTogbm90aWZ5CiAgaW1hZ2U6IHBsdWdpbnMvZHJvbmUtd2ViaG9vawogIGxhYmVsczoKICAgIC0gbm90aWZ5CiAgICAtIHdlYmhvb2sKICAgIC0gcmVzdAogICAgLSBqc29uCiAgICAtIGhvb2sK.XE_NdTdvCanuvvQR1DWo2i25DK8GUOYTS0X84UQVJds
|
||||
eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBnb2xhbmc6MS42CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBDR09fRU5BQkxFRD0wCiAgICAgIC0gR09QQVRIPS9kcm9uZQogICAgY29tbWFuZHM6CiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGJ1aWxkCiAgICAgIC0gbWFrZSB0ZXN0CiAgZG9ja2VyOgogICAgc3RvcmFnZV9kcml2ZXI6IG92ZXJsYXkKICAgIHJlcG86IHBsdWdpbnMvZHJvbmUtd2ViaG9vawogICAgdGFnOiBbICJsYXRlc3QiIF0KICAgIHdoZW46CiAgICAgIGJyYW5jaDogcmVsZWFzZS8wLjQKICAgICAgZXZlbnQ6IHB1c2gKCnBsdWdpbjoKICBuYW1lOiBXZWJob29rCiAgZGVzYzogU2VuZCBidWlsZCBzdGF0dXMgbm90aWZpY2F0aW9ucyB2aWEgV2ViaG9vawogIHR5cGU6IG5vdGlmeQogIGltYWdlOiBwbHVnaW5zL2Ryb25lLXdlYmhvb2sKICBsYWJlbHM6CiAgICAtIG5vdGlmeQogICAgLSB3ZWJob29rCiAgICAtIHJlc3QKICAgIC0ganNvbgogICAgLSBob29rCg.UtZ26l9LNl8trj1e6kx7a-h0TgtBJbTkuE__AblgAiQ
|
|
@ -0,0 +1,13 @@
|
|||
vendors:
|
||||
- path: github.com/aymerick/raymond
|
||||
rev: a2232af10b53ef1ae5a767f5178db3a6c1dab655
|
||||
- path: github.com/drone/drone-go
|
||||
rev: eaa41f7836a191224ec5702d2458db07882ec269
|
||||
- path: github.com/golang/protobuf
|
||||
rev: 0c1f6d65b5a189c2250d10e71a5506f06f9fa0a0
|
||||
- path: golang.org/x/net
|
||||
rev: 3f122ce3dbbe488b7e6a8bdb26f41edec852a40b
|
||||
- path: golang.org/x/oauth2
|
||||
rev: 65a8d08c6292395d47053be10b3c5e91960def76
|
||||
- path: google.golang.org/appengine
|
||||
rev: 7f59a8c76b8594d06044bfe0bcbe475cb2020482
|
|
@ -0,0 +1,46 @@
|
|||
# Benchmarks
|
||||
|
||||
Hardware: MacBookPro11,1 - Intel Core i5 - 2,6 GHz - 8 Go RAM
|
||||
|
||||
With:
|
||||
|
||||
- handlebars.js #8cba84df119c317fcebc49fb285518542ca9c2d0
|
||||
- raymond #7bbaaf50ed03c96b56687d7fa6c6e04e02375a98
|
||||
|
||||
|
||||
## handlebars.js (ops/ms)
|
||||
|
||||
arguments 198 ±4 (5)
|
||||
array-each 568 ±23 (5)
|
||||
array-mustache 522 ±18 (4)
|
||||
complex 71 ±7 (3)
|
||||
data 67 ±2 (3)
|
||||
depth-1 47 ±2 (3)
|
||||
depth-2 14 ±1 (2)
|
||||
object-mustache 1099 ±47 (5)
|
||||
object 907 ±58 (4)
|
||||
partial-recursion 46 ±3 (4)
|
||||
partial 68 ±3 (3)
|
||||
paths 1650 ±50 (3)
|
||||
string 2552 ±157 (3)
|
||||
subexpression 141 ±2 (4)
|
||||
variables 2671 ±83 (4)
|
||||
|
||||
|
||||
## raymond
|
||||
|
||||
BenchmarkArguments 200000 6642 ns/op 151 ops/ms
|
||||
BenchmarkArrayEach 100000 19584 ns/op 51 ops/ms
|
||||
BenchmarkArrayMustache 100000 17305 ns/op 58 ops/ms
|
||||
BenchmarkComplex 30000 50270 ns/op 20 ops/ms
|
||||
BenchmarkData 50000 25551 ns/op 39 ops/ms
|
||||
BenchmarkDepth1 100000 20162 ns/op 50 ops/ms
|
||||
BenchmarkDepth2 30000 47782 ns/op 21 ops/ms
|
||||
BenchmarkObjectMustache 200000 7668 ns/op 130 ops/ms
|
||||
BenchmarkObject 200000 8843 ns/op 113 ops/ms
|
||||
BenchmarkPartialRecursion 50000 23139 ns/op 43 ops/ms
|
||||
BenchmarkPartial 50000 31015 ns/op 32 ops/ms
|
||||
BenchmarkPath 200000 8997 ns/op 111 ops/ms
|
||||
BenchmarkString 1000000 1879 ns/op 532 ops/ms
|
||||
BenchmarkSubExpression 300000 4935 ns/op 203 ops/ms
|
||||
BenchmarkVariables 200000 6478 ns/op 154 ops/ms
|
|
@ -0,0 +1,33 @@
|
|||
# Raymond Changelog
|
||||
|
||||
### Raymond 2.0.1 _(June 01, 2016)_
|
||||
|
||||
- [BUGFIX] Removes data races [#3](https://github.com/aymerick/raymond/issues/3) - Thanks [@markbates](https://github.com/markbates)
|
||||
|
||||
### Raymond 2.0.0 _(May 01, 2016)_
|
||||
|
||||
- [BUGFIX] Fixes passing of context in helper options [#2](https://github.com/aymerick/raymond/issues/2) - Thanks [@GhostRussia](https://github.com/GhostRussia)
|
||||
- [BREAKING] Renames and unexports constants:
|
||||
|
||||
- `handlebars.DUMP_TPL`
|
||||
- `lexer.ESCAPED_ESCAPED_OPEN_MUSTACHE`
|
||||
- `lexer.ESCAPED_OPEN_MUSTACHE`
|
||||
- `lexer.OPEN_MUSTACHE`
|
||||
- `lexer.CLOSE_MUSTACHE`
|
||||
- `lexer.CLOSE_STRIP_MUSTACHE`
|
||||
- `lexer.CLOSE_UNESCAPED_STRIP_MUSTACHE`
|
||||
- `lexer.DUMP_TOKEN_POS`
|
||||
- `lexer.DUMP_ALL_TOKENS_VAL`
|
||||
|
||||
|
||||
### Raymond 1.1.0 _(June 15, 2015)_
|
||||
|
||||
- Permits templates references with lowercase versions of struct fields.
|
||||
- Adds `ParseFile()` function.
|
||||
- Adds `RegisterPartialFile()`, `RegisterPartialFiles()` and `Clone()` methods on `Template`.
|
||||
- Helpers can now be struct methods.
|
||||
- Ensures safe concurrent access to helpers and partials.
|
||||
|
||||
### Raymond 1.0.0 _(June 09, 2015)_
|
||||
|
||||
- This is the first release. Raymond supports almost all handlebars features. See https://github.com/aymerick/raymond#limitations for a list of differences with the javascript implementation.
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Aymerick JEHANNE
|
||||
|
||||
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.
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
2.0.1
|
|
@ -0,0 +1,785 @@
|
|||
// Package ast provides structures to represent a handlebars Abstract Syntax Tree, and a Visitor interface to visit that tree.
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// References:
|
||||
// - https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/ast.js
|
||||
// - https://github.com/wycats/handlebars.js/blob/master/docs/compiler-api.md
|
||||
// - https://github.com/golang/go/blob/master/src/text/template/parse/node.go
|
||||
|
||||
// Node is an element in the AST.
|
||||
type Node interface {
|
||||
// node type
|
||||
Type() NodeType
|
||||
|
||||
// location of node in original input string
|
||||
Location() Loc
|
||||
|
||||
// string representation, used for debugging
|
||||
String() string
|
||||
|
||||
// accepts visitor
|
||||
Accept(Visitor) interface{}
|
||||
}
|
||||
|
||||
// Visitor is the interface to visit an AST.
|
||||
type Visitor interface {
|
||||
VisitProgram(*Program) interface{}
|
||||
|
||||
// statements
|
||||
VisitMustache(*MustacheStatement) interface{}
|
||||
VisitBlock(*BlockStatement) interface{}
|
||||
VisitPartial(*PartialStatement) interface{}
|
||||
VisitContent(*ContentStatement) interface{}
|
||||
VisitComment(*CommentStatement) interface{}
|
||||
|
||||
// expressions
|
||||
VisitExpression(*Expression) interface{}
|
||||
VisitSubExpression(*SubExpression) interface{}
|
||||
VisitPath(*PathExpression) interface{}
|
||||
|
||||
// literals
|
||||
VisitString(*StringLiteral) interface{}
|
||||
VisitBoolean(*BooleanLiteral) interface{}
|
||||
VisitNumber(*NumberLiteral) interface{}
|
||||
|
||||
// miscellaneous
|
||||
VisitHash(*Hash) interface{}
|
||||
VisitHashPair(*HashPair) interface{}
|
||||
}
|
||||
|
||||
// NodeType represents an AST Node type.
|
||||
type NodeType int
|
||||
|
||||
// Type returns itself, and permits struct includers to satisfy that part of Node interface.
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
// NodeProgram is the program node
|
||||
NodeProgram NodeType = iota
|
||||
|
||||
// NodeMustache is the mustache statement node
|
||||
NodeMustache
|
||||
|
||||
// NodeBlock is the block statement node
|
||||
NodeBlock
|
||||
|
||||
// NodePartial is the partial statement node
|
||||
NodePartial
|
||||
|
||||
// NodeContent is the content statement node
|
||||
NodeContent
|
||||
|
||||
// NodeComment is the comment statement node
|
||||
NodeComment
|
||||
|
||||
// NodeExpression is the expression node
|
||||
NodeExpression
|
||||
|
||||
// NodeSubExpression is the subexpression node
|
||||
NodeSubExpression
|
||||
|
||||
// NodePath is the expression path node
|
||||
NodePath
|
||||
|
||||
// NodeBoolean is the literal boolean node
|
||||
NodeBoolean
|
||||
|
||||
// NodeNumber is the literal number node
|
||||
NodeNumber
|
||||
|
||||
// NodeString is the literal string node
|
||||
NodeString
|
||||
|
||||
// NodeHash is the hash node
|
||||
NodeHash
|
||||
|
||||
// NodeHashPair is the hash pair node
|
||||
NodeHashPair
|
||||
)
|
||||
|
||||
// Loc represents the position of a parsed node in source file.
|
||||
type Loc struct {
|
||||
Pos int // Byte position
|
||||
Line int // Line number
|
||||
}
|
||||
|
||||
// Location returns itself, and permits struct includers to satisfy that part of Node interface.
|
||||
func (l Loc) Location() Loc {
|
||||
return l
|
||||
}
|
||||
|
||||
// Strip describes node whitespace management.
|
||||
type Strip struct {
|
||||
Open bool
|
||||
Close bool
|
||||
|
||||
OpenStandalone bool
|
||||
CloseStandalone bool
|
||||
InlineStandalone bool
|
||||
}
|
||||
|
||||
// NewStrip instanciates a Strip for given open and close mustaches.
|
||||
func NewStrip(openStr, closeStr string) *Strip {
|
||||
return &Strip{
|
||||
Open: (len(openStr) > 2) && openStr[2] == '~',
|
||||
Close: (len(closeStr) > 2) && closeStr[len(closeStr)-3] == '~',
|
||||
}
|
||||
}
|
||||
|
||||
// NewStripForStr instanciates a Strip for given tag.
|
||||
func NewStripForStr(str string) *Strip {
|
||||
return &Strip{
|
||||
Open: (len(str) > 2) && str[2] == '~',
|
||||
Close: (len(str) > 2) && str[len(str)-3] == '~',
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (s *Strip) String() string {
|
||||
return fmt.Sprintf("Open: %t, Close: %t, OpenStandalone: %t, CloseStandalone: %t, InlineStandalone: %t", s.Open, s.Close, s.OpenStandalone, s.CloseStandalone, s.InlineStandalone)
|
||||
}
|
||||
|
||||
//
|
||||
// Program
|
||||
//
|
||||
|
||||
// Program represents a program node.
|
||||
type Program struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Body []Node // [ Statement ... ]
|
||||
BlockParams []string
|
||||
Chained bool
|
||||
|
||||
// whitespace management
|
||||
Strip *Strip
|
||||
}
|
||||
|
||||
// NewProgram instanciates a new program node.
|
||||
func NewProgram(pos int, line int) *Program {
|
||||
return &Program{
|
||||
NodeType: NodeProgram,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *Program) String() string {
|
||||
return fmt.Sprintf("Program{Pos: %d}", node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *Program) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitProgram(node)
|
||||
}
|
||||
|
||||
// AddStatement adds given statement to program.
|
||||
func (node *Program) AddStatement(statement Node) {
|
||||
node.Body = append(node.Body, statement)
|
||||
}
|
||||
|
||||
//
|
||||
// Mustache Statement
|
||||
//
|
||||
|
||||
// MustacheStatement represents a mustache node.
|
||||
type MustacheStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Unescaped bool
|
||||
Expression *Expression
|
||||
|
||||
// whitespace management
|
||||
Strip *Strip
|
||||
}
|
||||
|
||||
// NewMustacheStatement instanciates a new mustache node.
|
||||
func NewMustacheStatement(pos int, line int, unescaped bool) *MustacheStatement {
|
||||
return &MustacheStatement{
|
||||
NodeType: NodeMustache,
|
||||
Loc: Loc{pos, line},
|
||||
Unescaped: unescaped,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *MustacheStatement) String() string {
|
||||
return fmt.Sprintf("Mustache{Pos: %d}", node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *MustacheStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitMustache(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Block Statement
|
||||
//
|
||||
|
||||
// BlockStatement represents a block node.
|
||||
type BlockStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Expression *Expression
|
||||
|
||||
Program *Program
|
||||
Inverse *Program
|
||||
|
||||
// whitespace management
|
||||
OpenStrip *Strip
|
||||
InverseStrip *Strip
|
||||
CloseStrip *Strip
|
||||
}
|
||||
|
||||
// NewBlockStatement instanciates a new block node.
|
||||
func NewBlockStatement(pos int, line int) *BlockStatement {
|
||||
return &BlockStatement{
|
||||
NodeType: NodeBlock,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *BlockStatement) String() string {
|
||||
return fmt.Sprintf("Block{Pos: %d}", node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *BlockStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitBlock(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Partial Statement
|
||||
//
|
||||
|
||||
// PartialStatement represents a partial node.
|
||||
type PartialStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Name Node // PathExpression | SubExpression
|
||||
Params []Node // [ Expression ... ]
|
||||
Hash *Hash
|
||||
|
||||
// whitespace management
|
||||
Strip *Strip
|
||||
Indent string
|
||||
}
|
||||
|
||||
// NewPartialStatement instanciates a new partial node.
|
||||
func NewPartialStatement(pos int, line int) *PartialStatement {
|
||||
return &PartialStatement{
|
||||
NodeType: NodePartial,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *PartialStatement) String() string {
|
||||
return fmt.Sprintf("Partial{Name:%s, Pos:%d}", node.Name, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *PartialStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitPartial(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Content Statement
|
||||
//
|
||||
|
||||
// ContentStatement represents a content node.
|
||||
type ContentStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value string
|
||||
Original string
|
||||
|
||||
// whitespace management
|
||||
RightStripped bool
|
||||
LeftStripped bool
|
||||
}
|
||||
|
||||
// NewContentStatement instanciates a new content node.
|
||||
func NewContentStatement(pos int, line int, val string) *ContentStatement {
|
||||
return &ContentStatement{
|
||||
NodeType: NodeContent,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
Original: val,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *ContentStatement) String() string {
|
||||
return fmt.Sprintf("Content{Value:'%s', Pos:%d}", node.Value, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *ContentStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitContent(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Comment Statement
|
||||
//
|
||||
|
||||
// CommentStatement represents a comment node.
|
||||
type CommentStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value string
|
||||
|
||||
// whitespace management
|
||||
Strip *Strip
|
||||
}
|
||||
|
||||
// NewCommentStatement instanciates a new comment node.
|
||||
func NewCommentStatement(pos int, line int, val string) *CommentStatement {
|
||||
return &CommentStatement{
|
||||
NodeType: NodeComment,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *CommentStatement) String() string {
|
||||
return fmt.Sprintf("Comment{Value:'%s', Pos:%d}", node.Value, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *CommentStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitComment(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Expression
|
||||
//
|
||||
|
||||
// Expression represents an expression node.
|
||||
type Expression struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Path Node // PathExpression | StringLiteral | BooleanLiteral | NumberLiteral
|
||||
Params []Node // [ Expression ... ]
|
||||
Hash *Hash
|
||||
}
|
||||
|
||||
// NewExpression instanciates a new expression node.
|
||||
func NewExpression(pos int, line int) *Expression {
|
||||
return &Expression{
|
||||
NodeType: NodeExpression,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *Expression) String() string {
|
||||
return fmt.Sprintf("Expr{Path:%s, Pos:%d}", node.Path, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *Expression) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitExpression(node)
|
||||
}
|
||||
|
||||
// HelperName returns helper name, or an empty string if this expression can't be a helper.
|
||||
func (node *Expression) HelperName() string {
|
||||
path, ok := node.Path.(*PathExpression)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
if path.Data || (len(path.Parts) != 1) || (path.Depth > 0) || path.Scoped {
|
||||
return ""
|
||||
}
|
||||
|
||||
return path.Parts[0]
|
||||
}
|
||||
|
||||
// FieldPath returns path expression representing a field path, or nil if this is not a field path.
|
||||
func (node *Expression) FieldPath() *PathExpression {
|
||||
path, ok := node.Path.(*PathExpression)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// LiteralStr returns the string representation of literal value, with a boolean set to false if this is not a literal.
|
||||
func (node *Expression) LiteralStr() (string, bool) {
|
||||
return LiteralStr(node.Path)
|
||||
}
|
||||
|
||||
// Canonical returns the canonical form of expression node as a string.
|
||||
func (node *Expression) Canonical() string {
|
||||
if str, ok := HelperNameStr(node.Path); ok {
|
||||
return str
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// HelperNameStr returns the string representation of a helper name, with a boolean set to false if this is not a valid helper name.
|
||||
//
|
||||
// helperName : path | dataName | STRING | NUMBER | BOOLEAN | UNDEFINED | NULL
|
||||
func HelperNameStr(node Node) (string, bool) {
|
||||
// PathExpression
|
||||
if str, ok := PathExpressionStr(node); ok {
|
||||
return str, ok
|
||||
}
|
||||
|
||||
// Literal
|
||||
if str, ok := LiteralStr(node); ok {
|
||||
return str, ok
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// PathExpressionStr returns the string representation of path expression value, with a boolean set to false if this is not a path expression.
|
||||
func PathExpressionStr(node Node) (string, bool) {
|
||||
if path, ok := node.(*PathExpression); ok {
|
||||
result := path.Original
|
||||
|
||||
// "[foo bar]"" => "foo bar"
|
||||
if (len(result) >= 2) && (result[0] == '[') && (result[len(result)-1] == ']') {
|
||||
result = result[1 : len(result)-1]
|
||||
}
|
||||
|
||||
return result, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// LiteralStr returns the string representation of literal value, with a boolean set to false if this is not a literal.
|
||||
func LiteralStr(node Node) (string, bool) {
|
||||
if lit, ok := node.(*StringLiteral); ok {
|
||||
return lit.Value, true
|
||||
}
|
||||
|
||||
if lit, ok := node.(*BooleanLiteral); ok {
|
||||
return lit.Canonical(), true
|
||||
}
|
||||
|
||||
if lit, ok := node.(*NumberLiteral); ok {
|
||||
return lit.Canonical(), true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
//
|
||||
// SubExpression
|
||||
//
|
||||
|
||||
// SubExpression represents a subexpression node.
|
||||
type SubExpression struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Expression *Expression
|
||||
}
|
||||
|
||||
// NewSubExpression instanciates a new subexpression node.
|
||||
func NewSubExpression(pos int, line int) *SubExpression {
|
||||
return &SubExpression{
|
||||
NodeType: NodeSubExpression,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *SubExpression) String() string {
|
||||
return fmt.Sprintf("Sexp{Path:%s, Pos:%d}", node.Expression.Path, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *SubExpression) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitSubExpression(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Path Expression
|
||||
//
|
||||
|
||||
// PathExpression represents a path expression node.
|
||||
type PathExpression struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Original string
|
||||
Depth int
|
||||
Parts []string
|
||||
Data bool
|
||||
Scoped bool
|
||||
}
|
||||
|
||||
// NewPathExpression instanciates a new path expression node.
|
||||
func NewPathExpression(pos int, line int, data bool) *PathExpression {
|
||||
result := &PathExpression{
|
||||
NodeType: NodePath,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Data: data,
|
||||
}
|
||||
|
||||
if data {
|
||||
result.Original = "@"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *PathExpression) String() string {
|
||||
return fmt.Sprintf("Path{Original:'%s', Pos:%d}", node.Original, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *PathExpression) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitPath(node)
|
||||
}
|
||||
|
||||
// Part adds path part.
|
||||
func (node *PathExpression) Part(part string) {
|
||||
node.Original += part
|
||||
|
||||
switch part {
|
||||
case "..":
|
||||
node.Depth++
|
||||
node.Scoped = true
|
||||
case ".", "this":
|
||||
node.Scoped = true
|
||||
default:
|
||||
node.Parts = append(node.Parts, part)
|
||||
}
|
||||
}
|
||||
|
||||
// Sep adds path separator.
|
||||
func (node *PathExpression) Sep(separator string) {
|
||||
node.Original += separator
|
||||
}
|
||||
|
||||
// IsDataRoot returns true if path expression is @root.
|
||||
func (node *PathExpression) IsDataRoot() bool {
|
||||
return node.Data && (node.Parts[0] == "root")
|
||||
}
|
||||
|
||||
//
|
||||
// String Literal
|
||||
//
|
||||
|
||||
// StringLiteral represents a string node.
|
||||
type StringLiteral struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value string
|
||||
}
|
||||
|
||||
// NewStringLiteral instanciates a new string node.
|
||||
func NewStringLiteral(pos int, line int, val string) *StringLiteral {
|
||||
return &StringLiteral{
|
||||
NodeType: NodeString,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *StringLiteral) String() string {
|
||||
return fmt.Sprintf("String{Value:'%s', Pos:%d}", node.Value, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *StringLiteral) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitString(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Boolean Literal
|
||||
//
|
||||
|
||||
// BooleanLiteral represents a boolean node.
|
||||
type BooleanLiteral struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value bool
|
||||
Original string
|
||||
}
|
||||
|
||||
// NewBooleanLiteral instanciates a new boolean node.
|
||||
func NewBooleanLiteral(pos int, line int, val bool, original string) *BooleanLiteral {
|
||||
return &BooleanLiteral{
|
||||
NodeType: NodeBoolean,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
Original: original,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *BooleanLiteral) String() string {
|
||||
return fmt.Sprintf("Boolean{Value:%s, Pos:%d}", node.Canonical(), node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *BooleanLiteral) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitBoolean(node)
|
||||
}
|
||||
|
||||
// Canonical returns the canonical form of boolean node as a string (ie. "true" | "false").
|
||||
func (node *BooleanLiteral) Canonical() string {
|
||||
if node.Value {
|
||||
return "true"
|
||||
}
|
||||
|
||||
return "false"
|
||||
}
|
||||
|
||||
//
|
||||
// Number Literal
|
||||
//
|
||||
|
||||
// NumberLiteral represents a number node.
|
||||
type NumberLiteral struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value float64
|
||||
IsInt bool
|
||||
Original string
|
||||
}
|
||||
|
||||
// NewNumberLiteral instanciates a new number node.
|
||||
func NewNumberLiteral(pos int, line int, val float64, isInt bool, original string) *NumberLiteral {
|
||||
return &NumberLiteral{
|
||||
NodeType: NodeNumber,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
IsInt: isInt,
|
||||
Original: original,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *NumberLiteral) String() string {
|
||||
return fmt.Sprintf("Number{Value:%s, Pos:%d}", node.Canonical(), node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *NumberLiteral) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitNumber(node)
|
||||
}
|
||||
|
||||
// Canonical returns the canonical form of number node as a string (eg: "12", "-1.51").
|
||||
func (node *NumberLiteral) Canonical() string {
|
||||
prec := -1
|
||||
if node.IsInt {
|
||||
prec = 0
|
||||
}
|
||||
return strconv.FormatFloat(node.Value, 'f', prec, 64)
|
||||
}
|
||||
|
||||
// Number returns an integer or a float.
|
||||
func (node *NumberLiteral) Number() interface{} {
|
||||
if node.IsInt {
|
||||
return int(node.Value)
|
||||
}
|
||||
|
||||
return node.Value
|
||||
}
|
||||
|
||||
//
|
||||
// Hash
|
||||
//
|
||||
|
||||
// Hash represents a hash node.
|
||||
type Hash struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Pairs []*HashPair
|
||||
}
|
||||
|
||||
// NewHash instanciates a new hash node.
|
||||
func NewHash(pos int, line int) *Hash {
|
||||
return &Hash{
|
||||
NodeType: NodeHash,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *Hash) String() string {
|
||||
result := fmt.Sprintf("Hash{[%d", node.Loc.Pos)
|
||||
|
||||
for i, p := range node.Pairs {
|
||||
if i > 0 {
|
||||
result += ", "
|
||||
}
|
||||
result += p.String()
|
||||
}
|
||||
|
||||
return result + fmt.Sprintf("], Pos:%d}", node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *Hash) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitHash(node)
|
||||
}
|
||||
|
||||
//
|
||||
// HashPair
|
||||
//
|
||||
|
||||
// HashPair represents a hash pair node.
|
||||
type HashPair struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Key string
|
||||
Val Node // Expression
|
||||
}
|
||||
|
||||
// NewHashPair instanciates a new hash pair node.
|
||||
func NewHashPair(pos int, line int) *HashPair {
|
||||
return &HashPair{
|
||||
NodeType: NodeHashPair,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *HashPair) String() string {
|
||||
return node.Key + "=" + node.Val.String()
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *HashPair) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitHashPair(node)
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// printVisitor implements the Visitor interface to print a AST.
|
||||
type printVisitor struct {
|
||||
buf string
|
||||
depth int
|
||||
|
||||
original bool
|
||||
inBlock bool
|
||||
}
|
||||
|
||||
func newPrintVisitor() *printVisitor {
|
||||
return &printVisitor{}
|
||||
}
|
||||
|
||||
// Print returns a string representation of given AST, that can be used for debugging purpose.
|
||||
func Print(node Node) string {
|
||||
visitor := newPrintVisitor()
|
||||
node.Accept(visitor)
|
||||
return visitor.output()
|
||||
}
|
||||
|
||||
func (v *printVisitor) output() string {
|
||||
return v.buf
|
||||
}
|
||||
|
||||
func (v *printVisitor) indent() {
|
||||
for i := 0; i < v.depth; {
|
||||
v.buf += " "
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (v *printVisitor) str(val string) {
|
||||
v.buf += val
|
||||
}
|
||||
|
||||
func (v *printVisitor) nl() {
|
||||
v.str("\n")
|
||||
}
|
||||
|
||||
func (v *printVisitor) line(val string) {
|
||||
v.indent()
|
||||
v.str(val)
|
||||
v.nl()
|
||||
}
|
||||
|
||||
//
|
||||
// Visitor interface
|
||||
//
|
||||
|
||||
// Statements
|
||||
|
||||
// VisitProgram implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitProgram(node *Program) interface{} {
|
||||
if len(node.BlockParams) > 0 {
|
||||
v.line("BLOCK PARAMS: [ " + strings.Join(node.BlockParams, " ") + " ]")
|
||||
}
|
||||
|
||||
for _, n := range node.Body {
|
||||
n.Accept(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitMustache implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitMustache(node *MustacheStatement) interface{} {
|
||||
v.indent()
|
||||
v.str("{{ ")
|
||||
|
||||
node.Expression.Accept(v)
|
||||
|
||||
v.str(" }}")
|
||||
v.nl()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitBlock implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitBlock(node *BlockStatement) interface{} {
|
||||
v.inBlock = true
|
||||
|
||||
v.line("BLOCK:")
|
||||
v.depth++
|
||||
|
||||
node.Expression.Accept(v)
|
||||
|
||||
if node.Program != nil {
|
||||
v.line("PROGRAM:")
|
||||
v.depth++
|
||||
node.Program.Accept(v)
|
||||
v.depth--
|
||||
}
|
||||
|
||||
if node.Inverse != nil {
|
||||
// if node.Program != nil {
|
||||
// v.depth++
|
||||
// }
|
||||
|
||||
v.line("{{^}}")
|
||||
v.depth++
|
||||
node.Inverse.Accept(v)
|
||||
v.depth--
|
||||
|
||||
// if node.Program != nil {
|
||||
// v.depth--
|
||||
// }
|
||||
}
|
||||
|
||||
v.inBlock = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitPartial implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitPartial(node *PartialStatement) interface{} {
|
||||
v.indent()
|
||||
v.str("{{> PARTIAL:")
|
||||
|
||||
v.original = true
|
||||
node.Name.Accept(v)
|
||||
v.original = false
|
||||
|
||||
if len(node.Params) > 0 {
|
||||
v.str(" ")
|
||||
node.Params[0].Accept(v)
|
||||
}
|
||||
|
||||
// hash
|
||||
if node.Hash != nil {
|
||||
v.str(" ")
|
||||
node.Hash.Accept(v)
|
||||
}
|
||||
|
||||
v.str(" }}")
|
||||
v.nl()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitContent implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitContent(node *ContentStatement) interface{} {
|
||||
v.line("CONTENT[ '" + node.Value + "' ]")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitComment implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitComment(node *CommentStatement) interface{} {
|
||||
v.line("{{! '" + node.Value + "' }}")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Expressions
|
||||
|
||||
// VisitExpression implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitExpression(node *Expression) interface{} {
|
||||
if v.inBlock {
|
||||
v.indent()
|
||||
}
|
||||
|
||||
// path
|
||||
node.Path.Accept(v)
|
||||
|
||||
// params
|
||||
v.str(" [")
|
||||
for i, n := range node.Params {
|
||||
if i > 0 {
|
||||
v.str(", ")
|
||||
}
|
||||
n.Accept(v)
|
||||
}
|
||||
v.str("]")
|
||||
|
||||
// hash
|
||||
if node.Hash != nil {
|
||||
v.str(" ")
|
||||
node.Hash.Accept(v)
|
||||
}
|
||||
|
||||
if v.inBlock {
|
||||
v.nl()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitSubExpression implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitSubExpression(node *SubExpression) interface{} {
|
||||
node.Expression.Accept(v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitPath implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitPath(node *PathExpression) interface{} {
|
||||
if v.original {
|
||||
v.str(node.Original)
|
||||
} else {
|
||||
path := strings.Join(node.Parts, "/")
|
||||
|
||||
result := ""
|
||||
if node.Data {
|
||||
result += "@"
|
||||
}
|
||||
|
||||
v.str(result + "PATH:" + path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Literals
|
||||
|
||||
// VisitString implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitString(node *StringLiteral) interface{} {
|
||||
if v.original {
|
||||
v.str(node.Value)
|
||||
} else {
|
||||
v.str("\"" + node.Value + "\"")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitBoolean implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitBoolean(node *BooleanLiteral) interface{} {
|
||||
if v.original {
|
||||
v.str(node.Original)
|
||||
} else {
|
||||
v.str(fmt.Sprintf("BOOLEAN{%s}", node.Canonical()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitNumber implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitNumber(node *NumberLiteral) interface{} {
|
||||
if v.original {
|
||||
v.str(node.Original)
|
||||
} else {
|
||||
v.str(fmt.Sprintf("NUMBER{%s}", node.Canonical()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
|
||||
// VisitHash implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitHash(node *Hash) interface{} {
|
||||
v.str("HASH{")
|
||||
|
||||
for i, p := range node.Pairs {
|
||||
if i > 0 {
|
||||
v.str(", ")
|
||||
}
|
||||
p.Accept(v)
|
||||
}
|
||||
|
||||
v.str("}")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitHashPair implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitHashPair(node *HashPair) interface{} {
|
||||
v.str(node.Key + "=")
|
||||
node.Val.Accept(v)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package raymond
|
||||
|
||||
import "reflect"
|
||||
|
||||
// DataFrame represents a private data frame.
|
||||
//
|
||||
// Cf. private variables documentation at: http://handlebarsjs.com/block_helpers.html
|
||||
type DataFrame struct {
|
||||
parent *DataFrame
|
||||
data map[string]interface{}
|
||||
}
|
||||
|
||||
// NewDataFrame instanciates a new private data frame.
|
||||
func NewDataFrame() *DataFrame {
|
||||
return &DataFrame{
|
||||
data: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Copy instanciates a new private data frame with receiver as parent.
|
||||
func (p *DataFrame) Copy() *DataFrame {
|
||||
result := NewDataFrame()
|
||||
|
||||
for k, v := range p.data {
|
||||
result.data[k] = v
|
||||
}
|
||||
|
||||
result.parent = p
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// newIterDataFrame instanciates a new private data frame with receiver as parent and with iteration data set (@index, @key, @first, @last)
|
||||
func (p *DataFrame) newIterDataFrame(length int, i int, key interface{}) *DataFrame {
|
||||
result := p.Copy()
|
||||
|
||||
result.Set("index", i)
|
||||
result.Set("key", key)
|
||||
result.Set("first", i == 0)
|
||||
result.Set("last", i == length-1)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Set sets a data value.
|
||||
func (p *DataFrame) Set(key string, val interface{}) {
|
||||
p.data[key] = val
|
||||
}
|
||||
|
||||
// Get gets a data value.
|
||||
func (p *DataFrame) Get(key string) interface{} {
|
||||
return p.find([]string{key})
|
||||
}
|
||||
|
||||
// find gets a deep data value
|
||||
//
|
||||
// @todo This is NOT consistent with the way we resolve data in template (cf. `evalDataPathExpression()`) ! FIX THAT !
|
||||
func (p *DataFrame) find(parts []string) interface{} {
|
||||
data := p.data
|
||||
|
||||
for i, part := range parts {
|
||||
val := data[part]
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i == len(parts)-1 {
|
||||
// found
|
||||
return val
|
||||
}
|
||||
|
||||
valValue := reflect.ValueOf(val)
|
||||
if valValue.Kind() != reflect.Map {
|
||||
// not found
|
||||
return nil
|
||||
}
|
||||
|
||||
// continue
|
||||
data = mapStringInterface(valValue)
|
||||
}
|
||||
|
||||
// not found
|
||||
return nil
|
||||
}
|
||||
|
||||
// mapStringInterface converts any `map` to `map[string]interface{}`
|
||||
func mapStringInterface(value reflect.Value) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
|
||||
for _, key := range value.MapKeys() {
|
||||
result[strValue(key)] = value.MapIndex(key).Interface()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package raymond
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//
|
||||
// That whole file is borrowed from https://github.com/golang/go/tree/master/src/html/escape.go
|
||||
//
|
||||
// With changes:
|
||||
// ' => '
|
||||
// " => "
|
||||
//
|
||||
// To stay in sync with JS implementation, and make mustache tests pass.
|
||||
//
|
||||
|
||||
type writer interface {
|
||||
WriteString(string) (int, error)
|
||||
}
|
||||
|
||||
const escapedChars = `&'<>"`
|
||||
|
||||
func escape(w writer, s string) error {
|
||||
i := strings.IndexAny(s, escapedChars)
|
||||
for i != -1 {
|
||||
if _, err := w.WriteString(s[:i]); err != nil {
|
||||
return err
|
||||
}
|
||||
var esc string
|
||||
switch s[i] {
|
||||
case '&':
|
||||
esc = "&"
|
||||
case '\'':
|
||||
esc = "'"
|
||||
case '<':
|
||||
esc = "<"
|
||||
case '>':
|
||||
esc = ">"
|
||||
case '"':
|
||||
esc = """
|
||||
default:
|
||||
panic("unrecognized escape character")
|
||||
}
|
||||
s = s[i+1:]
|
||||
if _, err := w.WriteString(esc); err != nil {
|
||||
return err
|
||||
}
|
||||
i = strings.IndexAny(s, escapedChars)
|
||||
}
|
||||
_, err := w.WriteString(s)
|
||||
return err
|
||||
}
|
||||
|
||||
// Escape escapes special HTML characters.
|
||||
//
|
||||
// It can be used by helpers that return a SafeString and that need to escape some content by themselves.
|
||||
func Escape(s string) string {
|
||||
if strings.IndexAny(s, escapedChars) == -1 {
|
||||
return s
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
escape(&buf, s)
|
||||
return buf.String()
|
||||
}
|
|
@ -0,0 +1,984 @@
|
|||
package raymond
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aymerick/raymond/ast"
|
||||
)
|
||||
|
||||
var (
|
||||
// @note borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
|
||||
zero reflect.Value
|
||||
)
|
||||
|
||||
// evalVisitor evaluates a handlebars template with context
|
||||
type evalVisitor struct {
|
||||
tpl *Template
|
||||
|
||||
// contexts stack
|
||||
ctx []reflect.Value
|
||||
|
||||
// current data frame (chained with parent)
|
||||
dataFrame *DataFrame
|
||||
|
||||
// block parameters stack
|
||||
blockParams []map[string]interface{}
|
||||
|
||||
// block statements stack
|
||||
blocks []*ast.BlockStatement
|
||||
|
||||
// expressions stack
|
||||
exprs []*ast.Expression
|
||||
|
||||
// memoize expressions that were function calls
|
||||
exprFunc map[*ast.Expression]bool
|
||||
|
||||
// used for info on panic
|
||||
curNode ast.Node
|
||||
}
|
||||
|
||||
// NewEvalVisitor instanciate a new evaluation visitor with given context and initial private data frame
|
||||
//
|
||||
// If privData is nil, then a default data frame is created
|
||||
func newEvalVisitor(tpl *Template, ctx interface{}, privData *DataFrame) *evalVisitor {
|
||||
frame := privData
|
||||
if frame == nil {
|
||||
frame = NewDataFrame()
|
||||
}
|
||||
|
||||
return &evalVisitor{
|
||||
tpl: tpl,
|
||||
ctx: []reflect.Value{reflect.ValueOf(ctx)},
|
||||
dataFrame: frame,
|
||||
exprFunc: make(map[*ast.Expression]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// at sets current node
|
||||
func (v *evalVisitor) at(node ast.Node) {
|
||||
v.curNode = node
|
||||
}
|
||||
|
||||
//
|
||||
// Contexts stack
|
||||
//
|
||||
|
||||
// pushCtx pushes new context to the stack
|
||||
func (v *evalVisitor) pushCtx(ctx reflect.Value) {
|
||||
v.ctx = append(v.ctx, ctx)
|
||||
}
|
||||
|
||||
// popCtx pops last context from stack
|
||||
func (v *evalVisitor) popCtx() reflect.Value {
|
||||
if len(v.ctx) == 0 {
|
||||
return zero
|
||||
}
|
||||
|
||||
var result reflect.Value
|
||||
result, v.ctx = v.ctx[len(v.ctx)-1], v.ctx[:len(v.ctx)-1]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// rootCtx returns root context
|
||||
func (v *evalVisitor) rootCtx() reflect.Value {
|
||||
return v.ctx[0]
|
||||
}
|
||||
|
||||
// curCtx returns current context
|
||||
func (v *evalVisitor) curCtx() reflect.Value {
|
||||
return v.ancestorCtx(0)
|
||||
}
|
||||
|
||||
// ancestorCtx returns ancestor context
|
||||
func (v *evalVisitor) ancestorCtx(depth int) reflect.Value {
|
||||
index := len(v.ctx) - 1 - depth
|
||||
if index < 0 {
|
||||
return zero
|
||||
}
|
||||
|
||||
return v.ctx[index]
|
||||
}
|
||||
|
||||
//
|
||||
// Private data frame
|
||||
//
|
||||
|
||||
// setDataFrame sets new data frame
|
||||
func (v *evalVisitor) setDataFrame(frame *DataFrame) {
|
||||
v.dataFrame = frame
|
||||
}
|
||||
|
||||
// popDataFrame sets back parent data frame
|
||||
func (v *evalVisitor) popDataFrame() {
|
||||
v.dataFrame = v.dataFrame.parent
|
||||
}
|
||||
|
||||
//
|
||||
// Block Parameters stack
|
||||
//
|
||||
|
||||
// pushBlockParams pushes new block params to the stack
|
||||
func (v *evalVisitor) pushBlockParams(params map[string]interface{}) {
|
||||
v.blockParams = append(v.blockParams, params)
|
||||
}
|
||||
|
||||
// popBlockParams pops last block params from stack
|
||||
func (v *evalVisitor) popBlockParams() map[string]interface{} {
|
||||
var result map[string]interface{}
|
||||
|
||||
if len(v.blockParams) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
result, v.blockParams = v.blockParams[len(v.blockParams)-1], v.blockParams[:len(v.blockParams)-1]
|
||||
return result
|
||||
}
|
||||
|
||||
// blockParam iterates on stack to find given block parameter, and returns its value or nil if not founc
|
||||
func (v *evalVisitor) blockParam(name string) interface{} {
|
||||
for i := len(v.blockParams) - 1; i >= 0; i-- {
|
||||
for k, v := range v.blockParams[i] {
|
||||
if name == k {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
// Blocks stack
|
||||
//
|
||||
|
||||
// pushBlock pushes new block statement to stack
|
||||
func (v *evalVisitor) pushBlock(block *ast.BlockStatement) {
|
||||
v.blocks = append(v.blocks, block)
|
||||
}
|
||||
|
||||
// popBlock pops last block statement from stack
|
||||
func (v *evalVisitor) popBlock() *ast.BlockStatement {
|
||||
if len(v.blocks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result *ast.BlockStatement
|
||||
result, v.blocks = v.blocks[len(v.blocks)-1], v.blocks[:len(v.blocks)-1]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// curBlock returns current block statement
|
||||
func (v *evalVisitor) curBlock() *ast.BlockStatement {
|
||||
if len(v.blocks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return v.blocks[len(v.blocks)-1]
|
||||
}
|
||||
|
||||
//
|
||||
// Expressions stack
|
||||
//
|
||||
|
||||
// pushExpr pushes new expression to stack
|
||||
func (v *evalVisitor) pushExpr(expression *ast.Expression) {
|
||||
v.exprs = append(v.exprs, expression)
|
||||
}
|
||||
|
||||
// popExpr pops last expression from stack
|
||||
func (v *evalVisitor) popExpr() *ast.Expression {
|
||||
if len(v.exprs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result *ast.Expression
|
||||
result, v.exprs = v.exprs[len(v.exprs)-1], v.exprs[:len(v.exprs)-1]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// curExpr returns current expression
|
||||
func (v *evalVisitor) curExpr() *ast.Expression {
|
||||
if len(v.exprs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return v.exprs[len(v.exprs)-1]
|
||||
}
|
||||
|
||||
//
|
||||
// Error functions
|
||||
//
|
||||
|
||||
// errPanic panics
|
||||
func (v *evalVisitor) errPanic(err error) {
|
||||
panic(fmt.Errorf("Evaluation error: %s\nCurrent node:\n\t%s", err, v.curNode))
|
||||
}
|
||||
|
||||
// errorf panics with a custom message
|
||||
func (v *evalVisitor) errorf(format string, args ...interface{}) {
|
||||
v.errPanic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
//
|
||||
// Evaluation
|
||||
//
|
||||
|
||||
// evalProgram eEvaluates program with given context and returns string result
|
||||
func (v *evalVisitor) evalProgram(program *ast.Program, ctx interface{}, data *DataFrame, key interface{}) string {
|
||||
blockParams := make(map[string]interface{})
|
||||
|
||||
// compute block params
|
||||
if len(program.BlockParams) > 0 {
|
||||
blockParams[program.BlockParams[0]] = ctx
|
||||
}
|
||||
|
||||
if (len(program.BlockParams) > 1) && (key != nil) {
|
||||
blockParams[program.BlockParams[1]] = key
|
||||
}
|
||||
|
||||
// push contexts
|
||||
if len(blockParams) > 0 {
|
||||
v.pushBlockParams(blockParams)
|
||||
}
|
||||
|
||||
ctxVal := reflect.ValueOf(ctx)
|
||||
if ctxVal.IsValid() {
|
||||
v.pushCtx(ctxVal)
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
v.setDataFrame(data)
|
||||
}
|
||||
|
||||
// evaluate program
|
||||
result, _ := program.Accept(v).(string)
|
||||
|
||||
// pop contexts
|
||||
if data != nil {
|
||||
v.popDataFrame()
|
||||
}
|
||||
|
||||
if ctxVal.IsValid() {
|
||||
v.popCtx()
|
||||
}
|
||||
|
||||
if len(blockParams) > 0 {
|
||||
v.popBlockParams()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// evalPath evaluates all path parts with given context
|
||||
func (v *evalVisitor) evalPath(ctx reflect.Value, parts []string, exprRoot bool) (reflect.Value, bool) {
|
||||
partResolved := false
|
||||
|
||||
for i := 0; i < len(parts); i++ {
|
||||
part := parts[i]
|
||||
|
||||
// "[foo bar]"" => "foo bar"
|
||||
if (len(part) >= 2) && (part[0] == '[') && (part[len(part)-1] == ']') {
|
||||
part = part[1 : len(part)-1]
|
||||
}
|
||||
|
||||
ctx = v.evalField(ctx, part, exprRoot)
|
||||
if !ctx.IsValid() {
|
||||
break
|
||||
}
|
||||
|
||||
// we resolved at least one part of path
|
||||
partResolved = true
|
||||
}
|
||||
|
||||
return ctx, partResolved
|
||||
}
|
||||
|
||||
// evalField evaluates field with given context
|
||||
func (v *evalVisitor) evalField(ctx reflect.Value, fieldName string, exprRoot bool) reflect.Value {
|
||||
result := zero
|
||||
|
||||
ctx, _ = indirect(ctx)
|
||||
if !ctx.IsValid() {
|
||||
return result
|
||||
}
|
||||
|
||||
// check if this is a method call
|
||||
result, isMeth := v.evalMethod(ctx, fieldName, exprRoot)
|
||||
if !isMeth {
|
||||
switch ctx.Kind() {
|
||||
case reflect.Struct:
|
||||
// example: firstName => FirstName
|
||||
expFieldName := strings.Title(fieldName)
|
||||
|
||||
// check if struct have this field and that it is exported
|
||||
if tField, ok := ctx.Type().FieldByName(expFieldName); ok && (tField.PkgPath == "") {
|
||||
// struct field
|
||||
result = ctx.FieldByIndex(tField.Index)
|
||||
}
|
||||
case reflect.Map:
|
||||
nameVal := reflect.ValueOf(fieldName)
|
||||
if nameVal.Type().AssignableTo(ctx.Type().Key()) {
|
||||
// map key
|
||||
result = ctx.MapIndex(nameVal)
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
if i, err := strconv.Atoi(fieldName); (err == nil) && (i < ctx.Len()) {
|
||||
result = ctx.Index(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if result is a function
|
||||
result, _ = indirect(result)
|
||||
if result.Kind() == reflect.Func {
|
||||
result = v.evalFieldFunc(fieldName, result, exprRoot)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// evalFieldFunc tries to evaluate given method name, and a boolean to indicate if this was a method call
|
||||
func (v *evalVisitor) evalMethod(ctx reflect.Value, name string, exprRoot bool) (reflect.Value, bool) {
|
||||
if ctx.Kind() != reflect.Interface && ctx.CanAddr() {
|
||||
ctx = ctx.Addr()
|
||||
}
|
||||
|
||||
method := ctx.MethodByName(name)
|
||||
if !method.IsValid() {
|
||||
// example: subject() => Subject()
|
||||
method = ctx.MethodByName(strings.Title(name))
|
||||
}
|
||||
|
||||
if !method.IsValid() {
|
||||
return zero, false
|
||||
}
|
||||
|
||||
return v.evalFieldFunc(name, method, exprRoot), true
|
||||
}
|
||||
|
||||
// evalFieldFunc evaluates given function
|
||||
func (v *evalVisitor) evalFieldFunc(name string, funcVal reflect.Value, exprRoot bool) reflect.Value {
|
||||
ensureValidHelper(name, funcVal)
|
||||
|
||||
var options *Options
|
||||
if exprRoot {
|
||||
// create function arg with all params/hash
|
||||
expr := v.curExpr()
|
||||
options = v.helperOptions(expr)
|
||||
|
||||
// ok, that expression was a function call
|
||||
v.exprFunc[expr] = true
|
||||
} else {
|
||||
// we are not at root of expression, so we are a parameter... and we don't like
|
||||
// infinite loops caused by trying to parse ourself forever
|
||||
options = newEmptyOptions(v)
|
||||
}
|
||||
|
||||
return v.callFunc(name, funcVal, options)
|
||||
}
|
||||
|
||||
// findBlockParam returns node's block parameter
|
||||
func (v *evalVisitor) findBlockParam(node *ast.PathExpression) (string, interface{}) {
|
||||
if len(node.Parts) > 0 {
|
||||
name := node.Parts[0]
|
||||
if value := v.blockParam(name); value != nil {
|
||||
return name, value
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// evalPathExpression evaluates a path expression
|
||||
func (v *evalVisitor) evalPathExpression(node *ast.PathExpression, exprRoot bool) interface{} {
|
||||
var result interface{}
|
||||
|
||||
if name, value := v.findBlockParam(node); value != nil {
|
||||
// block parameter value
|
||||
|
||||
// We push a new context so we can evaluate the path expression (note: this may be a bad idea).
|
||||
//
|
||||
// Example:
|
||||
// {{#foo as |bar|}}
|
||||
// {{bar.baz}}
|
||||
// {{/foo}}
|
||||
//
|
||||
// With data:
|
||||
// {"foo": {"baz": "bat"}}
|
||||
newCtx := map[string]interface{}{name: value}
|
||||
|
||||
v.pushCtx(reflect.ValueOf(newCtx))
|
||||
result = v.evalCtxPathExpression(node, exprRoot)
|
||||
v.popCtx()
|
||||
} else {
|
||||
ctxTried := false
|
||||
|
||||
if node.IsDataRoot() {
|
||||
// context path
|
||||
result = v.evalCtxPathExpression(node, exprRoot)
|
||||
|
||||
ctxTried = true
|
||||
}
|
||||
|
||||
if (result == nil) && node.Data {
|
||||
// if it is @root, then we tried to evaluate with root context but nothing was found
|
||||
// so let's try with private data
|
||||
|
||||
// private data
|
||||
result = v.evalDataPathExpression(node, exprRoot)
|
||||
}
|
||||
|
||||
if (result == nil) && !ctxTried {
|
||||
// context path
|
||||
result = v.evalCtxPathExpression(node, exprRoot)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// evalDataPathExpression evaluates a private data path expression
|
||||
func (v *evalVisitor) evalDataPathExpression(node *ast.PathExpression, exprRoot bool) interface{} {
|
||||
// find data frame
|
||||
frame := v.dataFrame
|
||||
for i := node.Depth; i > 0; i-- {
|
||||
if frame.parent == nil {
|
||||
return nil
|
||||
}
|
||||
frame = frame.parent
|
||||
}
|
||||
|
||||
// resolve data
|
||||
// @note Can be changed to v.evalCtx() as context can't be an array
|
||||
result, _ := v.evalCtxPath(reflect.ValueOf(frame.data), node.Parts, exprRoot)
|
||||
return result
|
||||
}
|
||||
|
||||
// evalCtxPathExpression evaluates a context path expression
|
||||
func (v *evalVisitor) evalCtxPathExpression(node *ast.PathExpression, exprRoot bool) interface{} {
|
||||
v.at(node)
|
||||
|
||||
if node.IsDataRoot() {
|
||||
// `@root` - remove the first part
|
||||
parts := node.Parts[1:len(node.Parts)]
|
||||
|
||||
result, _ := v.evalCtxPath(v.rootCtx(), parts, exprRoot)
|
||||
return result
|
||||
}
|
||||
|
||||
return v.evalDepthPath(node.Depth, node.Parts, exprRoot)
|
||||
}
|
||||
|
||||
// evalDepthPath iterates on contexts, starting at given depth, until there is one that resolve given path parts
|
||||
func (v *evalVisitor) evalDepthPath(depth int, parts []string, exprRoot bool) interface{} {
|
||||
var result interface{}
|
||||
partResolved := false
|
||||
|
||||
ctx := v.ancestorCtx(depth)
|
||||
|
||||
for (result == nil) && ctx.IsValid() && (depth <= len(v.ctx) && !partResolved) {
|
||||
// try with context
|
||||
result, partResolved = v.evalCtxPath(ctx, parts, exprRoot)
|
||||
|
||||
// As soon as we find the first part of a path, we must not try to resolve with parent context if result is finally `nil`
|
||||
// Reference: "Dotted Names - Context Precedence" mustache test
|
||||
if !partResolved && (result == nil) {
|
||||
// try with previous context
|
||||
depth++
|
||||
ctx = v.ancestorCtx(depth)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// evalCtxPath evaluates path with given context
|
||||
func (v *evalVisitor) evalCtxPath(ctx reflect.Value, parts []string, exprRoot bool) (interface{}, bool) {
|
||||
var result interface{}
|
||||
partResolved := false
|
||||
|
||||
switch ctx.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
// Array context
|
||||
var results []interface{}
|
||||
|
||||
for i := 0; i < ctx.Len(); i++ {
|
||||
value, _ := v.evalPath(ctx.Index(i), parts, exprRoot)
|
||||
if value.IsValid() {
|
||||
results = append(results, value.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
result = results
|
||||
default:
|
||||
// NOT array context
|
||||
var value reflect.Value
|
||||
|
||||
value, partResolved = v.evalPath(ctx, parts, exprRoot)
|
||||
if value.IsValid() {
|
||||
result = value.Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return result, partResolved
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
// isHelperCall returns true if given expression is a helper call
|
||||
func (v *evalVisitor) isHelperCall(node *ast.Expression) bool {
|
||||
if helperName := node.HelperName(); helperName != "" {
|
||||
return v.findHelper(helperName) != zero
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// findHelper finds given helper
|
||||
func (v *evalVisitor) findHelper(name string) reflect.Value {
|
||||
// check template helpers
|
||||
if h := v.tpl.findHelper(name); h != zero {
|
||||
return h
|
||||
}
|
||||
|
||||
// check global helpers
|
||||
return findHelper(name)
|
||||
}
|
||||
|
||||
// callFunc calls function with given options
|
||||
func (v *evalVisitor) callFunc(name string, funcVal reflect.Value, options *Options) reflect.Value {
|
||||
params := options.Params()
|
||||
|
||||
funcType := funcVal.Type()
|
||||
|
||||
// @todo Is there a better way to do that ?
|
||||
strType := reflect.TypeOf("")
|
||||
boolType := reflect.TypeOf(true)
|
||||
|
||||
// check parameters number
|
||||
addOptions := false
|
||||
numIn := funcType.NumIn()
|
||||
|
||||
if numIn == len(params)+1 {
|
||||
lastArgType := funcType.In(numIn - 1)
|
||||
if reflect.TypeOf(options).AssignableTo(lastArgType) {
|
||||
addOptions = true
|
||||
}
|
||||
}
|
||||
|
||||
if !addOptions && (len(params) != numIn) {
|
||||
v.errorf("Helper '%s' called with wrong number of arguments, needed %d but got %d", name, numIn, len(params))
|
||||
}
|
||||
|
||||
// check and collect arguments
|
||||
args := make([]reflect.Value, numIn)
|
||||
for i, param := range params {
|
||||
arg := reflect.ValueOf(param)
|
||||
argType := funcType.In(i)
|
||||
|
||||
if !arg.IsValid() {
|
||||
if canBeNil(argType) {
|
||||
arg = reflect.Zero(argType)
|
||||
} else if argType.Kind() == reflect.String {
|
||||
arg = reflect.ValueOf("")
|
||||
} else {
|
||||
// @todo Maybe we can panic on that
|
||||
return reflect.Zero(strType)
|
||||
}
|
||||
}
|
||||
|
||||
if !arg.Type().AssignableTo(argType) {
|
||||
if strType.AssignableTo(argType) {
|
||||
// convert parameter to string
|
||||
arg = reflect.ValueOf(strValue(arg))
|
||||
} else if boolType.AssignableTo(argType) {
|
||||
// convert parameter to bool
|
||||
val, _ := isTrueValue(arg)
|
||||
arg = reflect.ValueOf(val)
|
||||
} else {
|
||||
v.errorf("Helper %s called with argument %d with type %s but it should be %s", name, i, arg.Type(), argType)
|
||||
}
|
||||
}
|
||||
|
||||
args[i] = arg
|
||||
}
|
||||
|
||||
if addOptions {
|
||||
args[numIn-1] = reflect.ValueOf(options)
|
||||
}
|
||||
|
||||
result := funcVal.Call(args)
|
||||
|
||||
return result[0]
|
||||
}
|
||||
|
||||
// callHelper invoqs helper function for given expression node
|
||||
func (v *evalVisitor) callHelper(name string, helper reflect.Value, node *ast.Expression) interface{} {
|
||||
result := v.callFunc(name, helper, v.helperOptions(node))
|
||||
if !result.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// @todo We maybe want to ensure here that helper returned a string or a SafeString
|
||||
return result.Interface()
|
||||
}
|
||||
|
||||
// helperOptions computes helper options argument from an expression
|
||||
func (v *evalVisitor) helperOptions(node *ast.Expression) *Options {
|
||||
var params []interface{}
|
||||
var hash map[string]interface{}
|
||||
|
||||
for _, paramNode := range node.Params {
|
||||
param := paramNode.Accept(v)
|
||||
params = append(params, param)
|
||||
}
|
||||
|
||||
if node.Hash != nil {
|
||||
hash, _ = node.Hash.Accept(v).(map[string]interface{})
|
||||
}
|
||||
|
||||
return newOptions(v, params, hash)
|
||||
}
|
||||
|
||||
//
|
||||
// Partials
|
||||
//
|
||||
|
||||
// findPartial finds given partial
|
||||
func (v *evalVisitor) findPartial(name string) *partial {
|
||||
// check template partials
|
||||
if p := v.tpl.findPartial(name); p != nil {
|
||||
return p
|
||||
}
|
||||
|
||||
// check global partials
|
||||
return findPartial(name)
|
||||
}
|
||||
|
||||
// partialContext computes partial context
|
||||
func (v *evalVisitor) partialContext(node *ast.PartialStatement) reflect.Value {
|
||||
if nb := len(node.Params); nb > 1 {
|
||||
v.errorf("Unsupported number of partial arguments: %d", nb)
|
||||
}
|
||||
|
||||
if (len(node.Params) > 0) && (node.Hash != nil) {
|
||||
v.errorf("Passing both context and named parameters to a partial is not allowed")
|
||||
}
|
||||
|
||||
if len(node.Params) == 1 {
|
||||
return reflect.ValueOf(node.Params[0].Accept(v))
|
||||
}
|
||||
|
||||
if node.Hash != nil {
|
||||
hash, _ := node.Hash.Accept(v).(map[string]interface{})
|
||||
return reflect.ValueOf(hash)
|
||||
}
|
||||
|
||||
return zero
|
||||
}
|
||||
|
||||
// evalPartial evaluates a partial
|
||||
func (v *evalVisitor) evalPartial(p *partial, node *ast.PartialStatement) string {
|
||||
// get partial template
|
||||
partialTpl, err := p.template()
|
||||
if err != nil {
|
||||
v.errPanic(err)
|
||||
}
|
||||
|
||||
// push partial context
|
||||
ctx := v.partialContext(node)
|
||||
if ctx.IsValid() {
|
||||
v.pushCtx(ctx)
|
||||
}
|
||||
|
||||
// evaluate partial template
|
||||
result, _ := partialTpl.program.Accept(v).(string)
|
||||
|
||||
// ident partial
|
||||
result = indentLines(result, node.Indent)
|
||||
|
||||
if ctx.IsValid() {
|
||||
v.popCtx()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// indentLines indents all lines of given string
|
||||
func indentLines(str string, indent string) string {
|
||||
if indent == "" {
|
||||
return str
|
||||
}
|
||||
|
||||
var indented []string
|
||||
|
||||
lines := strings.Split(str, "\n")
|
||||
for i, line := range lines {
|
||||
if (i == (len(lines) - 1)) && (line == "") {
|
||||
// input string ends with a new line
|
||||
indented = append(indented, line)
|
||||
} else {
|
||||
indented = append(indented, indent+line)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(indented, "\n")
|
||||
}
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
// wasFuncCall returns true if given expression was a function call
|
||||
func (v *evalVisitor) wasFuncCall(node *ast.Expression) bool {
|
||||
// check if expression was tagged as a function call
|
||||
return v.exprFunc[node]
|
||||
}
|
||||
|
||||
//
|
||||
// Visitor interface
|
||||
//
|
||||
|
||||
// Statements
|
||||
|
||||
// VisitProgram implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitProgram(node *ast.Program) interface{} {
|
||||
v.at(node)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
for _, n := range node.Body {
|
||||
if str := Str(n.Accept(v)); str != "" {
|
||||
if _, err := buf.Write([]byte(str)); err != nil {
|
||||
v.errPanic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// VisitMustache implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitMustache(node *ast.MustacheStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
// evaluate expression
|
||||
expr := node.Expression.Accept(v)
|
||||
|
||||
// check if this is a safe string
|
||||
isSafe := isSafeString(expr)
|
||||
|
||||
// get string value
|
||||
str := Str(expr)
|
||||
if !isSafe && !node.Unescaped {
|
||||
// escape html
|
||||
str = Escape(str)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// VisitBlock implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitBlock(node *ast.BlockStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
v.pushBlock(node)
|
||||
|
||||
var result interface{}
|
||||
|
||||
// evaluate expression
|
||||
expr := node.Expression.Accept(v)
|
||||
|
||||
if v.isHelperCall(node.Expression) || v.wasFuncCall(node.Expression) {
|
||||
// it is the responsability of the helper/function to evaluate block
|
||||
result = expr
|
||||
} else {
|
||||
val := reflect.ValueOf(expr)
|
||||
|
||||
truth, _ := isTrueValue(val)
|
||||
if truth {
|
||||
if node.Program != nil {
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
concat := ""
|
||||
|
||||
// Array context
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
// Computes new private data frame
|
||||
frame := v.dataFrame.newIterDataFrame(val.Len(), i, nil)
|
||||
|
||||
// Evaluate program
|
||||
concat += v.evalProgram(node.Program, val.Index(i).Interface(), frame, i)
|
||||
}
|
||||
|
||||
result = concat
|
||||
default:
|
||||
// NOT array
|
||||
result = v.evalProgram(node.Program, expr, nil, nil)
|
||||
}
|
||||
}
|
||||
} else if node.Inverse != nil {
|
||||
result, _ = node.Inverse.Accept(v).(string)
|
||||
}
|
||||
}
|
||||
|
||||
v.popBlock()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// VisitPartial implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitPartial(node *ast.PartialStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
// partialName: helperName | sexpr
|
||||
name, ok := ast.HelperNameStr(node.Name)
|
||||
if !ok {
|
||||
if subExpr, ok := node.Name.(*ast.SubExpression); ok {
|
||||
name, _ = subExpr.Accept(v).(string)
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
v.errorf("Unexpected partial name: %q", node.Name)
|
||||
}
|
||||
|
||||
partial := v.findPartial(name)
|
||||
if partial == nil {
|
||||
v.errorf("Partial not found: %s", name)
|
||||
}
|
||||
|
||||
return v.evalPartial(partial, node)
|
||||
}
|
||||
|
||||
// VisitContent implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitContent(node *ast.ContentStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
// write content as is
|
||||
return node.Value
|
||||
}
|
||||
|
||||
// VisitComment implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitComment(node *ast.CommentStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
// ignore comments
|
||||
return ""
|
||||
}
|
||||
|
||||
// Expressions
|
||||
|
||||
// VisitExpression implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitExpression(node *ast.Expression) interface{} {
|
||||
v.at(node)
|
||||
|
||||
var result interface{}
|
||||
done := false
|
||||
|
||||
v.pushExpr(node)
|
||||
|
||||
// helper call
|
||||
if helperName := node.HelperName(); helperName != "" {
|
||||
if helper := v.findHelper(helperName); helper != zero {
|
||||
result = v.callHelper(helperName, helper, node)
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
// literal
|
||||
if literal, ok := node.LiteralStr(); ok {
|
||||
if val := v.evalField(v.curCtx(), literal, true); val.IsValid() {
|
||||
result = val.Interface()
|
||||
done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
// field path
|
||||
if path := node.FieldPath(); path != nil {
|
||||
// @todo Find a cleaner way ! Don't break the pattern !
|
||||
// this is an exception to visitor pattern, because we need to pass the info
|
||||
// that this path is at root of current expression
|
||||
if val := v.evalPathExpression(path, true); val != nil {
|
||||
result = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v.popExpr()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// VisitSubExpression implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitSubExpression(node *ast.SubExpression) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Expression.Accept(v)
|
||||
}
|
||||
|
||||
// VisitPath implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitPath(node *ast.PathExpression) interface{} {
|
||||
return v.evalPathExpression(node, false)
|
||||
}
|
||||
|
||||
// Literals
|
||||
|
||||
// VisitString implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitString(node *ast.StringLiteral) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Value
|
||||
}
|
||||
|
||||
// VisitBoolean implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Value
|
||||
}
|
||||
|
||||
// VisitNumber implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitNumber(node *ast.NumberLiteral) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Number()
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
|
||||
// VisitHash implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitHash(node *ast.Hash) interface{} {
|
||||
v.at(node)
|
||||
|
||||
result := make(map[string]interface{})
|
||||
|
||||
for _, pair := range node.Pairs {
|
||||
if value := pair.Accept(v); value != nil {
|
||||
result[pair.Key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// VisitHashPair implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitHashPair(node *ast.HashPair) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Val.Accept(v)
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Options represents the options argument provided to helpers and context functions.
|
||||
type Options struct {
|
||||
// evaluation visitor
|
||||
eval *evalVisitor
|
||||
|
||||
// params
|
||||
params []interface{}
|
||||
hash map[string]interface{}
|
||||
}
|
||||
|
||||
// helpers stores all globally registered helpers
|
||||
var helpers = make(map[string]reflect.Value)
|
||||
|
||||
// protects global helpers
|
||||
var helpersMutex sync.RWMutex
|
||||
|
||||
func init() {
|
||||
// register builtin helpers
|
||||
RegisterHelper("if", ifHelper)
|
||||
RegisterHelper("unless", unlessHelper)
|
||||
RegisterHelper("with", withHelper)
|
||||
RegisterHelper("each", eachHelper)
|
||||
RegisterHelper("log", logHelper)
|
||||
RegisterHelper("lookup", lookupHelper)
|
||||
}
|
||||
|
||||
// RegisterHelper registers a global helper. That helper will be available to all templates.
|
||||
func RegisterHelper(name string, helper interface{}) {
|
||||
helpersMutex.Lock()
|
||||
defer helpersMutex.Unlock()
|
||||
|
||||
if helpers[name] != zero {
|
||||
panic(fmt.Errorf("Helper already registered: %s", name))
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(helper)
|
||||
ensureValidHelper(name, val)
|
||||
|
||||
helpers[name] = val
|
||||
}
|
||||
|
||||
// RegisterHelpers registers several global helpers. Those helpers will be available to all templates.
|
||||
func RegisterHelpers(helpers map[string]interface{}) {
|
||||
for name, helper := range helpers {
|
||||
RegisterHelper(name, helper)
|
||||
}
|
||||
}
|
||||
|
||||
// ensureValidHelper panics if given helper is not valid
|
||||
func ensureValidHelper(name string, funcValue reflect.Value) {
|
||||
if funcValue.Kind() != reflect.Func {
|
||||
panic(fmt.Errorf("Helper must be a function: %s", name))
|
||||
}
|
||||
|
||||
funcType := funcValue.Type()
|
||||
|
||||
if funcType.NumOut() != 1 {
|
||||
panic(fmt.Errorf("Helper function must return a string or a SafeString: %s", name))
|
||||
}
|
||||
|
||||
// @todo Check if first returned value is a string, SafeString or interface{} ?
|
||||
}
|
||||
|
||||
// findHelper finds a globally registered helper
|
||||
func findHelper(name string) reflect.Value {
|
||||
helpersMutex.RLock()
|
||||
defer helpersMutex.RUnlock()
|
||||
|
||||
return helpers[name]
|
||||
}
|
||||
|
||||
// newOptions instanciates a new Options
|
||||
func newOptions(eval *evalVisitor, params []interface{}, hash map[string]interface{}) *Options {
|
||||
return &Options{
|
||||
eval: eval,
|
||||
params: params,
|
||||
hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
// newEmptyOptions instanciates a new empty Options
|
||||
func newEmptyOptions(eval *evalVisitor) *Options {
|
||||
return &Options{
|
||||
eval: eval,
|
||||
hash: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Context Values
|
||||
//
|
||||
|
||||
// Value returns field value from current context.
|
||||
func (options *Options) Value(name string) interface{} {
|
||||
value := options.eval.evalField(options.eval.curCtx(), name, false)
|
||||
if !value.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return value.Interface()
|
||||
}
|
||||
|
||||
// ValueStr returns string representation of field value from current context.
|
||||
func (options *Options) ValueStr(name string) string {
|
||||
return Str(options.Value(name))
|
||||
}
|
||||
|
||||
// Ctx returns current evaluation context.
|
||||
func (options *Options) Ctx() interface{} {
|
||||
return options.eval.curCtx().Interface()
|
||||
}
|
||||
|
||||
//
|
||||
// Hash Arguments
|
||||
//
|
||||
|
||||
// HashProp returns hash property.
|
||||
func (options *Options) HashProp(name string) interface{} {
|
||||
return options.hash[name]
|
||||
}
|
||||
|
||||
// HashStr returns string representation of hash property.
|
||||
func (options *Options) HashStr(name string) string {
|
||||
return Str(options.hash[name])
|
||||
}
|
||||
|
||||
// Hash returns entire hash.
|
||||
func (options *Options) Hash() map[string]interface{} {
|
||||
return options.hash
|
||||
}
|
||||
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
|
||||
// Param returns parameter at given position.
|
||||
func (options *Options) Param(pos int) interface{} {
|
||||
if len(options.params) > pos {
|
||||
return options.params[pos]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParamStr returns string representation of parameter at given position.
|
||||
func (options *Options) ParamStr(pos int) string {
|
||||
return Str(options.Param(pos))
|
||||
}
|
||||
|
||||
// Params returns all parameters.
|
||||
func (options *Options) Params() []interface{} {
|
||||
return options.params
|
||||
}
|
||||
|
||||
//
|
||||
// Private data
|
||||
//
|
||||
|
||||
// Data returns private data value.
|
||||
func (options *Options) Data(name string) interface{} {
|
||||
return options.eval.dataFrame.Get(name)
|
||||
}
|
||||
|
||||
// DataStr returns string representation of private data value.
|
||||
func (options *Options) DataStr(name string) string {
|
||||
return Str(options.eval.dataFrame.Get(name))
|
||||
}
|
||||
|
||||
// DataFrame returns current private data frame.
|
||||
func (options *Options) DataFrame() *DataFrame {
|
||||
return options.eval.dataFrame
|
||||
}
|
||||
|
||||
// NewDataFrame instanciates a new data frame that is a copy of current evaluation data frame.
|
||||
//
|
||||
// Parent of returned data frame is set to current evaluation data frame.
|
||||
func (options *Options) NewDataFrame() *DataFrame {
|
||||
return options.eval.dataFrame.Copy()
|
||||
}
|
||||
|
||||
// newIterDataFrame instanciates a new data frame and set iteration specific vars
|
||||
func (options *Options) newIterDataFrame(length int, i int, key interface{}) *DataFrame {
|
||||
return options.eval.dataFrame.newIterDataFrame(length, i, key)
|
||||
}
|
||||
|
||||
//
|
||||
// Evaluation
|
||||
//
|
||||
|
||||
// evalBlock evaluates block with given context, private data and iteration key
|
||||
func (options *Options) evalBlock(ctx interface{}, data *DataFrame, key interface{}) string {
|
||||
result := ""
|
||||
|
||||
if block := options.eval.curBlock(); (block != nil) && (block.Program != nil) {
|
||||
result = options.eval.evalProgram(block.Program, ctx, data, key)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Fn evaluates block with current evaluation context.
|
||||
func (options *Options) Fn() string {
|
||||
return options.evalBlock(nil, nil, nil)
|
||||
}
|
||||
|
||||
// FnCtxData evaluates block with given context and private data frame.
|
||||
func (options *Options) FnCtxData(ctx interface{}, data *DataFrame) string {
|
||||
return options.evalBlock(ctx, data, nil)
|
||||
}
|
||||
|
||||
// FnWith evaluates block with given context.
|
||||
func (options *Options) FnWith(ctx interface{}) string {
|
||||
return options.evalBlock(ctx, nil, nil)
|
||||
}
|
||||
|
||||
// FnData evaluates block with given private data frame.
|
||||
func (options *Options) FnData(data *DataFrame) string {
|
||||
return options.evalBlock(nil, data, nil)
|
||||
}
|
||||
|
||||
// Inverse evaluates "else block".
|
||||
func (options *Options) Inverse() string {
|
||||
result := ""
|
||||
if block := options.eval.curBlock(); (block != nil) && (block.Inverse != nil) {
|
||||
result, _ = block.Inverse.Accept(options.eval).(string)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Eval evaluates field for given context.
|
||||
func (options *Options) Eval(ctx interface{}, field string) interface{} {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if field == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
val := options.eval.evalField(reflect.ValueOf(ctx), field, false)
|
||||
if !val.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return val.Interface()
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
//
|
||||
|
||||
// isIncludableZero returns true if 'includeZero' option is set and first param is the number 0
|
||||
func (options *Options) isIncludableZero() bool {
|
||||
b, ok := options.HashProp("includeZero").(bool)
|
||||
if ok && b {
|
||||
nb, ok := options.Param(0).(int)
|
||||
if ok && nb == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
//
|
||||
// Builtin helpers
|
||||
//
|
||||
|
||||
// #if block helper
|
||||
func ifHelper(conditional interface{}, options *Options) interface{} {
|
||||
if options.isIncludableZero() || IsTrue(conditional) {
|
||||
return options.Fn()
|
||||
}
|
||||
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
// #unless block helper
|
||||
func unlessHelper(conditional interface{}, options *Options) interface{} {
|
||||
if options.isIncludableZero() || IsTrue(conditional) {
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
return options.Fn()
|
||||
}
|
||||
|
||||
// #with block helper
|
||||
func withHelper(context interface{}, options *Options) interface{} {
|
||||
if IsTrue(context) {
|
||||
return options.FnWith(context)
|
||||
}
|
||||
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
// #each block helper
|
||||
func eachHelper(context interface{}, options *Options) interface{} {
|
||||
if !IsTrue(context) {
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
result := ""
|
||||
|
||||
val := reflect.ValueOf(context)
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
// computes private data
|
||||
data := options.newIterDataFrame(val.Len(), i, nil)
|
||||
|
||||
// evaluates block
|
||||
result += options.evalBlock(val.Index(i).Interface(), data, i)
|
||||
}
|
||||
case reflect.Map:
|
||||
// note: a go hash is not ordered, so result may vary, this behaviour differs from the JS implementation
|
||||
keys := val.MapKeys()
|
||||
for i := 0; i < len(keys); i++ {
|
||||
key := keys[i].Interface()
|
||||
ctx := val.MapIndex(keys[i]).Interface()
|
||||
|
||||
// computes private data
|
||||
data := options.newIterDataFrame(len(keys), i, key)
|
||||
|
||||
// evaluates block
|
||||
result += options.evalBlock(ctx, data, key)
|
||||
}
|
||||
case reflect.Struct:
|
||||
var exportedFields []int
|
||||
|
||||
// collect exported fields only
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
if tField := val.Type().Field(i); tField.PkgPath == "" {
|
||||
exportedFields = append(exportedFields, i)
|
||||
}
|
||||
}
|
||||
|
||||
for i, fieldIndex := range exportedFields {
|
||||
key := val.Type().Field(fieldIndex).Name
|
||||
ctx := val.Field(fieldIndex).Interface()
|
||||
|
||||
// computes private data
|
||||
data := options.newIterDataFrame(len(exportedFields), i, key)
|
||||
|
||||
// evaluates block
|
||||
result += options.evalBlock(ctx, data, key)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// #log helper
|
||||
func logHelper(message string) interface{} {
|
||||
log.Print(message)
|
||||
return ""
|
||||
}
|
||||
|
||||
// #lookup helper
|
||||
func lookupHelper(obj interface{}, field string, options *Options) interface{} {
|
||||
return Str(options.Eval(obj, field))
|
||||
}
|
|
@ -0,0 +1,639 @@
|
|||
// Package lexer provides a handlebars tokenizer.
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// References:
|
||||
// - https://github.com/wycats/handlebars.js/blob/master/src/handlebars.l
|
||||
// - https://github.com/golang/go/blob/master/src/text/template/parse/lex.go
|
||||
|
||||
const (
|
||||
// Mustaches detection
|
||||
escapedEscapedOpenMustache = "\\\\{{"
|
||||
escapedOpenMustache = "\\{{"
|
||||
openMustache = "{{"
|
||||
closeMustache = "}}"
|
||||
closeStripMustache = "~}}"
|
||||
closeUnescapedStripMustache = "}~}}"
|
||||
)
|
||||
|
||||
const eof = -1
|
||||
|
||||
// lexFunc represents a function that returns the next lexer function.
|
||||
type lexFunc func(*Lexer) lexFunc
|
||||
|
||||
// Lexer is a lexical analyzer.
|
||||
type Lexer struct {
|
||||
input string // input to scan
|
||||
name string // lexer name, used for testing purpose
|
||||
tokens chan Token // channel of scanned tokens
|
||||
nextFunc lexFunc // the next function to execute
|
||||
|
||||
pos int // current byte position in input string
|
||||
line int // current line position in input string
|
||||
width int // size of last rune scanned from input string
|
||||
start int // start position of the token we are scanning
|
||||
|
||||
// the shameful contextual properties needed because `nextFunc` is not enough
|
||||
closeComment *regexp.Regexp // regexp to scan close of current comment
|
||||
rawBlock bool // are we parsing a raw block content ?
|
||||
}
|
||||
|
||||
var (
|
||||
lookheadChars = `[\s` + regexp.QuoteMeta("=~}/)|") + `]`
|
||||
literalLookheadChars = `[\s` + regexp.QuoteMeta("~})") + `]`
|
||||
|
||||
// characters not allowed in an identifier
|
||||
unallowedIDChars = " \n\t!\"#%&'()*+,./;<=>@[\\]^`{|}~"
|
||||
|
||||
// regular expressions
|
||||
rID = regexp.MustCompile(`^[^` + regexp.QuoteMeta(unallowedIDChars) + `]+`)
|
||||
rDotID = regexp.MustCompile(`^\.` + lookheadChars)
|
||||
rTrue = regexp.MustCompile(`^true` + literalLookheadChars)
|
||||
rFalse = regexp.MustCompile(`^false` + literalLookheadChars)
|
||||
rOpenRaw = regexp.MustCompile(`^\{\{\{\{`)
|
||||
rCloseRaw = regexp.MustCompile(`^\}\}\}\}`)
|
||||
rOpenEndRaw = regexp.MustCompile(`^\{\{\{\{/`)
|
||||
rOpenEndRawLookAhead = regexp.MustCompile(`\{\{\{\{/`)
|
||||
rOpenUnescaped = regexp.MustCompile(`^\{\{~?\{`)
|
||||
rCloseUnescaped = regexp.MustCompile(`^\}~?\}\}`)
|
||||
rOpenBlock = regexp.MustCompile(`^\{\{~?#`)
|
||||
rOpenEndBlock = regexp.MustCompile(`^\{\{~?/`)
|
||||
rOpenPartial = regexp.MustCompile(`^\{\{~?>`)
|
||||
// {{^}} or {{else}}
|
||||
rInverse = regexp.MustCompile(`^(\{\{~?\^\s*~?\}\}|\{\{~?\s*else\s*~?\}\})`)
|
||||
rOpenInverse = regexp.MustCompile(`^\{\{~?\^`)
|
||||
rOpenInverseChain = regexp.MustCompile(`^\{\{~?\s*else`)
|
||||
// {{ or {{&
|
||||
rOpen = regexp.MustCompile(`^\{\{~?&?`)
|
||||
rClose = regexp.MustCompile(`^~?\}\}`)
|
||||
rOpenBlockParams = regexp.MustCompile(`^as\s+\|`)
|
||||
// {{!-- ... --}}
|
||||
rOpenCommentDash = regexp.MustCompile(`^\{\{~?!--\s*`)
|
||||
rCloseCommentDash = regexp.MustCompile(`^\s*--~?\}\}`)
|
||||
// {{! ... }}
|
||||
rOpenComment = regexp.MustCompile(`^\{\{~?!\s*`)
|
||||
rCloseComment = regexp.MustCompile(`^\s*~?\}\}`)
|
||||
)
|
||||
|
||||
// Scan scans given input.
|
||||
//
|
||||
// Tokens can then be fetched sequentially thanks to NextToken() function on returned lexer.
|
||||
func Scan(input string) *Lexer {
|
||||
return scanWithName(input, "")
|
||||
}
|
||||
|
||||
// scanWithName scans given input, with a name used for testing
|
||||
//
|
||||
// Tokens can then be fetched sequentially thanks to NextToken() function on returned lexer.
|
||||
func scanWithName(input string, name string) *Lexer {
|
||||
result := &Lexer{
|
||||
input: input,
|
||||
name: name,
|
||||
tokens: make(chan Token),
|
||||
line: 1,
|
||||
}
|
||||
|
||||
go result.run()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Collect scans and collect all tokens.
|
||||
//
|
||||
// This should be used for debugging purpose only. You should use Scan() and lexer.NextToken() functions instead.
|
||||
func Collect(input string) []Token {
|
||||
var result []Token
|
||||
|
||||
l := Scan(input)
|
||||
for {
|
||||
token := l.NextToken()
|
||||
result = append(result, token)
|
||||
|
||||
if token.Kind == TokenEOF || token.Kind == TokenError {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NextToken returns the next scanned token.
|
||||
func (l *Lexer) NextToken() Token {
|
||||
result := <-l.tokens
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// run starts lexical analysis
|
||||
func (l *Lexer) run() {
|
||||
for l.nextFunc = lexContent; l.nextFunc != nil; {
|
||||
l.nextFunc = l.nextFunc(l)
|
||||
}
|
||||
}
|
||||
|
||||
// next returns next character from input, or eof of there is nothing left to scan
|
||||
func (l *Lexer) next() rune {
|
||||
if l.pos >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = w
|
||||
l.pos += l.width
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *Lexer) produce(kind TokenKind, val string) {
|
||||
l.tokens <- Token{kind, val, l.start, l.line}
|
||||
|
||||
// scanning a new token
|
||||
l.start = l.pos
|
||||
|
||||
// update line number
|
||||
l.line += strings.Count(val, "\n")
|
||||
}
|
||||
|
||||
// emit emits a new scanned token
|
||||
func (l *Lexer) emit(kind TokenKind) {
|
||||
l.produce(kind, l.input[l.start:l.pos])
|
||||
}
|
||||
|
||||
// emitContent emits scanned content
|
||||
func (l *Lexer) emitContent() {
|
||||
if l.pos > l.start {
|
||||
l.emit(TokenContent)
|
||||
}
|
||||
}
|
||||
|
||||
// emitString emits a scanned string
|
||||
func (l *Lexer) emitString(delimiter rune) {
|
||||
str := l.input[l.start:l.pos]
|
||||
|
||||
// replace escaped delimiters
|
||||
str = strings.Replace(str, "\\"+string(delimiter), string(delimiter), -1)
|
||||
|
||||
l.produce(TokenString, str)
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next character in the input
|
||||
func (l *Lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one character
|
||||
//
|
||||
// WARNING: Can only be called once per call of next
|
||||
func (l *Lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// ignoreskips all characters that have been scanned up to current position
|
||||
func (l *Lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept scans the next character if it is included in given string
|
||||
func (l *Lexer) accept(valid string) bool {
|
||||
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
l.backup()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun scans all following characters that are part of given string
|
||||
func (l *Lexer) acceptRun(valid string) {
|
||||
for strings.IndexRune(valid, l.next()) >= 0 {
|
||||
}
|
||||
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// errorf emits an error token
|
||||
func (l *Lexer) errorf(format string, args ...interface{}) lexFunc {
|
||||
l.tokens <- Token{TokenError, fmt.Sprintf(format, args...), l.start, l.line}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isString returns true if content at current scanning position starts with given string
|
||||
func (l *Lexer) isString(str string) bool {
|
||||
return strings.HasPrefix(l.input[l.pos:], str)
|
||||
}
|
||||
|
||||
// findRegexp returns the first string from current scanning position that matches given regular expression
|
||||
func (l *Lexer) findRegexp(r *regexp.Regexp) string {
|
||||
return r.FindString(l.input[l.pos:])
|
||||
}
|
||||
|
||||
// indexRegexp returns the index of the first string from current scanning position that matches given regular expression
|
||||
//
|
||||
// It returns -1 if not found
|
||||
func (l *Lexer) indexRegexp(r *regexp.Regexp) int {
|
||||
loc := r.FindStringIndex(l.input[l.pos:])
|
||||
if loc == nil {
|
||||
return -1
|
||||
}
|
||||
return loc[0]
|
||||
}
|
||||
|
||||
// lexContent scans content (ie: not between mustaches)
|
||||
func lexContent(l *Lexer) lexFunc {
|
||||
var next lexFunc
|
||||
|
||||
if l.rawBlock {
|
||||
if i := l.indexRegexp(rOpenEndRawLookAhead); i != -1 {
|
||||
// {{{{/
|
||||
l.rawBlock = false
|
||||
l.pos += i
|
||||
|
||||
next = lexOpenMustache
|
||||
} else {
|
||||
return l.errorf("Unclosed raw block")
|
||||
}
|
||||
} else if l.isString(escapedEscapedOpenMustache) {
|
||||
// \\{{
|
||||
|
||||
// emit content with only one escaped escape
|
||||
l.next()
|
||||
l.emitContent()
|
||||
|
||||
// ignore second escaped escape
|
||||
l.next()
|
||||
l.ignore()
|
||||
|
||||
next = lexContent
|
||||
} else if l.isString(escapedOpenMustache) {
|
||||
// \{{
|
||||
next = lexEscapedOpenMustache
|
||||
} else if str := l.findRegexp(rOpenCommentDash); str != "" {
|
||||
// {{!--
|
||||
l.closeComment = rCloseCommentDash
|
||||
|
||||
next = lexComment
|
||||
} else if str := l.findRegexp(rOpenComment); str != "" {
|
||||
// {{!
|
||||
l.closeComment = rCloseComment
|
||||
|
||||
next = lexComment
|
||||
} else if l.isString(openMustache) {
|
||||
// {{
|
||||
next = lexOpenMustache
|
||||
}
|
||||
|
||||
if next != nil {
|
||||
// emit scanned content
|
||||
l.emitContent()
|
||||
|
||||
// scan next token
|
||||
return next
|
||||
}
|
||||
|
||||
// scan next rune
|
||||
if l.next() == eof {
|
||||
// emit scanned content
|
||||
l.emitContent()
|
||||
|
||||
// this is over
|
||||
l.emit(TokenEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// continue content scanning
|
||||
return lexContent
|
||||
}
|
||||
|
||||
// lexEscapedOpenMustache scans \{{
|
||||
func lexEscapedOpenMustache(l *Lexer) lexFunc {
|
||||
// ignore escape character
|
||||
l.next()
|
||||
l.ignore()
|
||||
|
||||
// scan mustaches
|
||||
for l.peek() == '{' {
|
||||
l.next()
|
||||
}
|
||||
|
||||
return lexContent
|
||||
}
|
||||
|
||||
// lexOpenMustache scans {{
|
||||
func lexOpenMustache(l *Lexer) lexFunc {
|
||||
var str string
|
||||
var tok TokenKind
|
||||
|
||||
nextFunc := lexExpression
|
||||
|
||||
if str = l.findRegexp(rOpenEndRaw); str != "" {
|
||||
tok = TokenOpenEndRawBlock
|
||||
} else if str = l.findRegexp(rOpenRaw); str != "" {
|
||||
tok = TokenOpenRawBlock
|
||||
l.rawBlock = true
|
||||
} else if str = l.findRegexp(rOpenUnescaped); str != "" {
|
||||
tok = TokenOpenUnescaped
|
||||
} else if str = l.findRegexp(rOpenBlock); str != "" {
|
||||
tok = TokenOpenBlock
|
||||
} else if str = l.findRegexp(rOpenEndBlock); str != "" {
|
||||
tok = TokenOpenEndBlock
|
||||
} else if str = l.findRegexp(rOpenPartial); str != "" {
|
||||
tok = TokenOpenPartial
|
||||
} else if str = l.findRegexp(rInverse); str != "" {
|
||||
tok = TokenInverse
|
||||
nextFunc = lexContent
|
||||
} else if str = l.findRegexp(rOpenInverse); str != "" {
|
||||
tok = TokenOpenInverse
|
||||
} else if str = l.findRegexp(rOpenInverseChain); str != "" {
|
||||
tok = TokenOpenInverseChain
|
||||
} else if str = l.findRegexp(rOpen); str != "" {
|
||||
tok = TokenOpen
|
||||
} else {
|
||||
// this is rotten
|
||||
panic("Current pos MUST be an opening mustache")
|
||||
}
|
||||
|
||||
l.pos += len(str)
|
||||
l.emit(tok)
|
||||
|
||||
return nextFunc
|
||||
}
|
||||
|
||||
// lexCloseMustache scans }} or ~}}
|
||||
func lexCloseMustache(l *Lexer) lexFunc {
|
||||
var str string
|
||||
var tok TokenKind
|
||||
|
||||
if str = l.findRegexp(rCloseRaw); str != "" {
|
||||
// }}}}
|
||||
tok = TokenCloseRawBlock
|
||||
} else if str = l.findRegexp(rCloseUnescaped); str != "" {
|
||||
// }}}
|
||||
tok = TokenCloseUnescaped
|
||||
} else if str = l.findRegexp(rClose); str != "" {
|
||||
// }}
|
||||
tok = TokenClose
|
||||
} else {
|
||||
// this is rotten
|
||||
panic("Current pos MUST be a closing mustache")
|
||||
}
|
||||
|
||||
l.pos += len(str)
|
||||
l.emit(tok)
|
||||
|
||||
return lexContent
|
||||
}
|
||||
|
||||
// lexExpression scans inside mustaches
|
||||
func lexExpression(l *Lexer) lexFunc {
|
||||
// search close mustache delimiter
|
||||
if l.isString(closeMustache) || l.isString(closeStripMustache) || l.isString(closeUnescapedStripMustache) {
|
||||
return lexCloseMustache
|
||||
}
|
||||
|
||||
// search some patterns before advancing scanning position
|
||||
|
||||
// "as |"
|
||||
if str := l.findRegexp(rOpenBlockParams); str != "" {
|
||||
l.pos += len(str)
|
||||
l.emit(TokenOpenBlockParams)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// ..
|
||||
if l.isString("..") {
|
||||
l.pos += len("..")
|
||||
l.emit(TokenID)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// .
|
||||
if str := l.findRegexp(rDotID); str != "" {
|
||||
l.pos += len(".")
|
||||
l.emit(TokenID)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// true
|
||||
if str := l.findRegexp(rTrue); str != "" {
|
||||
l.pos += len("true")
|
||||
l.emit(TokenBoolean)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// false
|
||||
if str := l.findRegexp(rFalse); str != "" {
|
||||
l.pos += len("false")
|
||||
l.emit(TokenBoolean)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// let's scan next character
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
return l.errorf("Unclosed expression")
|
||||
case isIgnorable(r):
|
||||
return lexIgnorable
|
||||
case r == '(':
|
||||
l.emit(TokenOpenSexpr)
|
||||
case r == ')':
|
||||
l.emit(TokenCloseSexpr)
|
||||
case r == '=':
|
||||
l.emit(TokenEquals)
|
||||
case r == '@':
|
||||
l.emit(TokenData)
|
||||
case r == '"' || r == '\'':
|
||||
l.backup()
|
||||
return lexString
|
||||
case r == '/' || r == '.':
|
||||
l.emit(TokenSep)
|
||||
case r == '|':
|
||||
l.emit(TokenCloseBlockParams)
|
||||
case r == '+' || r == '-' || (r >= '0' && r <= '9'):
|
||||
l.backup()
|
||||
return lexNumber
|
||||
case r == '[':
|
||||
return lexPathLiteral
|
||||
case strings.IndexRune(unallowedIDChars, r) < 0:
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
default:
|
||||
return l.errorf("Unexpected character in expression: '%c'", r)
|
||||
}
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// lexComment scans {{!-- or {{!
|
||||
func lexComment(l *Lexer) lexFunc {
|
||||
if str := l.findRegexp(l.closeComment); str != "" {
|
||||
l.pos += len(str)
|
||||
l.emit(TokenComment)
|
||||
|
||||
return lexContent
|
||||
}
|
||||
|
||||
if r := l.next(); r == eof {
|
||||
return l.errorf("Unclosed comment")
|
||||
}
|
||||
|
||||
return lexComment
|
||||
}
|
||||
|
||||
// lexIgnorable scans all following ignorable characters
|
||||
func lexIgnorable(l *Lexer) lexFunc {
|
||||
for isIgnorable(l.peek()) {
|
||||
l.next()
|
||||
}
|
||||
l.ignore()
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// lexString scans a string
|
||||
func lexString(l *Lexer) lexFunc {
|
||||
// get string delimiter
|
||||
delim := l.next()
|
||||
var prev rune
|
||||
|
||||
// ignore delimiter
|
||||
l.ignore()
|
||||
|
||||
for {
|
||||
r := l.next()
|
||||
if r == eof || r == '\n' {
|
||||
return l.errorf("Unterminated string")
|
||||
}
|
||||
|
||||
if (r == delim) && (prev != '\\') {
|
||||
break
|
||||
}
|
||||
|
||||
prev = r
|
||||
}
|
||||
|
||||
// remove end delimiter
|
||||
l.backup()
|
||||
|
||||
// emit string
|
||||
l.emitString(delim)
|
||||
|
||||
// skip end delimiter
|
||||
l.next()
|
||||
l.ignore()
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||
// strconv) will notice.
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/parse/lex.go
|
||||
func lexNumber(l *Lexer) lexFunc {
|
||||
if !l.scanNumber() {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
if sign := l.peek(); sign == '+' || sign == '-' {
|
||||
// Complex: 1+2i. No spaces, must end in 'i'.
|
||||
if !l.scanNumber() || l.input[l.pos-1] != 'i' {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(TokenNumber)
|
||||
} else {
|
||||
l.emit(TokenNumber)
|
||||
}
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// scanNumber scans a number
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/parse/lex.go
|
||||
func (l *Lexer) scanNumber() bool {
|
||||
// Optional leading sign.
|
||||
l.accept("+-")
|
||||
|
||||
// Is it hex?
|
||||
digits := "0123456789"
|
||||
|
||||
if l.accept("0") && l.accept("xX") {
|
||||
digits = "0123456789abcdefABCDEF"
|
||||
}
|
||||
|
||||
l.acceptRun(digits)
|
||||
|
||||
if l.accept(".") {
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
l.acceptRun("0123456789")
|
||||
}
|
||||
|
||||
// Is it imaginary?
|
||||
l.accept("i")
|
||||
|
||||
// Next thing mustn't be alphanumeric.
|
||||
if isAlphaNumeric(l.peek()) {
|
||||
l.next()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// lexIdentifier scans an ID
|
||||
func lexIdentifier(l *Lexer) lexFunc {
|
||||
str := l.findRegexp(rID)
|
||||
if len(str) == 0 {
|
||||
// this is rotten
|
||||
panic("Identifier expected")
|
||||
}
|
||||
|
||||
l.pos += len(str)
|
||||
l.emit(TokenID)
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// lexPathLiteral scans an [ID]
|
||||
func lexPathLiteral(l *Lexer) lexFunc {
|
||||
for {
|
||||
r := l.next()
|
||||
if r == eof || r == '\n' {
|
||||
return l.errorf("Unterminated path literal")
|
||||
}
|
||||
|
||||
if r == ']' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
l.emit(TokenID)
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// isIgnorable returns true if given character is ignorable (ie. whitespace of line feed)
|
||||
func isIgnorable(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
//
|
||||
// NOTE borrowed from https://github.com/golang/go/tree/master/src/text/template/parse/lex.go
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package lexer
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
// TokenError represents an error
|
||||
TokenError TokenKind = iota
|
||||
|
||||
// TokenEOF represents an End Of File
|
||||
TokenEOF
|
||||
|
||||
//
|
||||
// Mustache delimiters
|
||||
//
|
||||
|
||||
// TokenOpen is the OPEN token
|
||||
TokenOpen
|
||||
|
||||
// TokenClose is the CLOSE token
|
||||
TokenClose
|
||||
|
||||
// TokenOpenRawBlock is the OPEN_RAW_BLOCK token
|
||||
TokenOpenRawBlock
|
||||
|
||||
// TokenCloseRawBlock is the CLOSE_RAW_BLOCK token
|
||||
TokenCloseRawBlock
|
||||
|
||||
// TokenOpenEndRawBlock is the END_RAW_BLOCK token
|
||||
TokenOpenEndRawBlock
|
||||
|
||||
// TokenOpenUnescaped is the OPEN_UNESCAPED token
|
||||
TokenOpenUnescaped
|
||||
|
||||
// TokenCloseUnescaped is the CLOSE_UNESCAPED token
|
||||
TokenCloseUnescaped
|
||||
|
||||
// TokenOpenBlock is the OPEN_BLOCK token
|
||||
TokenOpenBlock
|
||||
|
||||
// TokenOpenEndBlock is the OPEN_ENDBLOCK token
|
||||
TokenOpenEndBlock
|
||||
|
||||
// TokenInverse is the INVERSE token
|
||||
TokenInverse
|
||||
|
||||
// TokenOpenInverse is the OPEN_INVERSE token
|
||||
TokenOpenInverse
|
||||
|
||||
// TokenOpenInverseChain is the OPEN_INVERSE_CHAIN token
|
||||
TokenOpenInverseChain
|
||||
|
||||
// TokenOpenPartial is the OPEN_PARTIAL token
|
||||
TokenOpenPartial
|
||||
|
||||
// TokenComment is the COMMENT token
|
||||
TokenComment
|
||||
|
||||
//
|
||||
// Inside mustaches
|
||||
//
|
||||
|
||||
// TokenOpenSexpr is the OPEN_SEXPR token
|
||||
TokenOpenSexpr
|
||||
|
||||
// TokenCloseSexpr is the CLOSE_SEXPR token
|
||||
TokenCloseSexpr
|
||||
|
||||
// TokenEquals is the EQUALS token
|
||||
TokenEquals
|
||||
|
||||
// TokenData is the DATA token
|
||||
TokenData
|
||||
|
||||
// TokenSep is the SEP token
|
||||
TokenSep
|
||||
|
||||
// TokenOpenBlockParams is the OPEN_BLOCK_PARAMS token
|
||||
TokenOpenBlockParams
|
||||
|
||||
// TokenCloseBlockParams is the CLOSE_BLOCK_PARAMS token
|
||||
TokenCloseBlockParams
|
||||
|
||||
//
|
||||
// Tokens with content
|
||||
//
|
||||
|
||||
// TokenContent is the CONTENT token
|
||||
TokenContent
|
||||
|
||||
// TokenID is the ID token
|
||||
TokenID
|
||||
|
||||
// TokenString is the STRING token
|
||||
TokenString
|
||||
|
||||
// TokenNumber is the NUMBER token
|
||||
TokenNumber
|
||||
|
||||
// TokenBoolean is the BOOLEAN token
|
||||
TokenBoolean
|
||||
)
|
||||
|
||||
const (
|
||||
// Option to generate token position in its string representation
|
||||
dumpTokenPos = false
|
||||
|
||||
// Option to generate values for all token kinds for their string representations
|
||||
dumpAllTokensVal = true
|
||||
)
|
||||
|
||||
// TokenKind represents a Token type.
|
||||
type TokenKind int
|
||||
|
||||
// Token represents a scanned token.
|
||||
type Token struct {
|
||||
Kind TokenKind // Token kind
|
||||
Val string // Token value
|
||||
|
||||
Pos int // Byte position in input string
|
||||
Line int // Line number in input string
|
||||
}
|
||||
|
||||
// tokenName permits to display token name given token type
|
||||
var tokenName = map[TokenKind]string{
|
||||
TokenError: "Error",
|
||||
TokenEOF: "EOF",
|
||||
TokenContent: "Content",
|
||||
TokenComment: "Comment",
|
||||
TokenOpen: "Open",
|
||||
TokenClose: "Close",
|
||||
TokenOpenUnescaped: "OpenUnescaped",
|
||||
TokenCloseUnescaped: "CloseUnescaped",
|
||||
TokenOpenBlock: "OpenBlock",
|
||||
TokenOpenEndBlock: "OpenEndBlock",
|
||||
TokenOpenRawBlock: "OpenRawBlock",
|
||||
TokenCloseRawBlock: "CloseRawBlock",
|
||||
TokenOpenEndRawBlock: "OpenEndRawBlock",
|
||||
TokenOpenBlockParams: "OpenBlockParams",
|
||||
TokenCloseBlockParams: "CloseBlockParams",
|
||||
TokenInverse: "Inverse",
|
||||
TokenOpenInverse: "OpenInverse",
|
||||
TokenOpenInverseChain: "OpenInverseChain",
|
||||
TokenOpenPartial: "OpenPartial",
|
||||
TokenOpenSexpr: "OpenSexpr",
|
||||
TokenCloseSexpr: "CloseSexpr",
|
||||
TokenID: "ID",
|
||||
TokenEquals: "Equals",
|
||||
TokenString: "String",
|
||||
TokenNumber: "Number",
|
||||
TokenBoolean: "Boolean",
|
||||
TokenData: "Data",
|
||||
TokenSep: "Sep",
|
||||
}
|
||||
|
||||
// String returns the token kind string representation for debugging.
|
||||
func (k TokenKind) String() string {
|
||||
s := tokenName[k]
|
||||
if s == "" {
|
||||
return fmt.Sprintf("Token-%d", int(k))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// String returns the token string representation for debugging.
|
||||
func (t Token) String() string {
|
||||
result := ""
|
||||
|
||||
if dumpTokenPos {
|
||||
result += fmt.Sprintf("%d:", t.Pos)
|
||||
}
|
||||
|
||||
result += fmt.Sprintf("%s", t.Kind)
|
||||
|
||||
if (dumpAllTokensVal || (t.Kind >= TokenContent)) && len(t.Val) > 0 {
|
||||
if len(t.Val) > 100 {
|
||||
result += fmt.Sprintf("{%.20q...}", t.Val)
|
||||
} else {
|
||||
result += fmt.Sprintf("{%q}", t.Val)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,846 @@
|
|||
// Package parser provides a handlebars syntax analyser. It consumes the tokens provided by the lexer to build an AST.
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/aymerick/raymond/ast"
|
||||
"github.com/aymerick/raymond/lexer"
|
||||
)
|
||||
|
||||
// References:
|
||||
// - https://github.com/wycats/handlebars.js/blob/master/src/handlebars.yy
|
||||
// - https://github.com/golang/go/blob/master/src/text/template/parse/parse.go
|
||||
|
||||
// parser is a syntax analyzer.
|
||||
type parser struct {
|
||||
// Lexer
|
||||
lex *lexer.Lexer
|
||||
|
||||
// Root node
|
||||
root ast.Node
|
||||
|
||||
// Tokens parsed but not consumed yet
|
||||
tokens []*lexer.Token
|
||||
|
||||
// All tokens have been retreieved from lexer
|
||||
lexOver bool
|
||||
}
|
||||
|
||||
var (
|
||||
rOpenComment = regexp.MustCompile(`^\{\{~?!-?-?`)
|
||||
rCloseComment = regexp.MustCompile(`-?-?~?\}\}$`)
|
||||
rOpenAmp = regexp.MustCompile(`^\{\{~?&`)
|
||||
)
|
||||
|
||||
// new instanciates a new parser
|
||||
func new(input string) *parser {
|
||||
return &parser{
|
||||
lex: lexer.Scan(input),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse analyzes given input and returns the AST root node.
|
||||
func Parse(input string) (result *ast.Program, err error) {
|
||||
// recover error
|
||||
defer errRecover(&err)
|
||||
|
||||
parser := new(input)
|
||||
|
||||
// parse
|
||||
result = parser.parseProgram()
|
||||
|
||||
// check last token
|
||||
token := parser.shift()
|
||||
if token.Kind != lexer.TokenEOF {
|
||||
// Parsing ended before EOF
|
||||
errToken(token, "Syntax error")
|
||||
}
|
||||
|
||||
// fix whitespaces
|
||||
processWhitespaces(result)
|
||||
|
||||
// named returned values
|
||||
return
|
||||
}
|
||||
|
||||
// errRecover recovers parsing panic
|
||||
func errRecover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
switch err := e.(type) {
|
||||
case runtime.Error:
|
||||
panic(e)
|
||||
case error:
|
||||
*errp = err
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// errPanic panics
|
||||
func errPanic(err error, line int) {
|
||||
panic(fmt.Errorf("Parse error on line %d:\n%s", line, err))
|
||||
}
|
||||
|
||||
// errNode panics with given node infos
|
||||
func errNode(node ast.Node, msg string) {
|
||||
errPanic(fmt.Errorf("%s\nNode: %s", msg, node), node.Location().Line)
|
||||
}
|
||||
|
||||
// errNode panics with given Token infos
|
||||
func errToken(tok *lexer.Token, msg string) {
|
||||
errPanic(fmt.Errorf("%s\nToken: %s", msg, tok), tok.Line)
|
||||
}
|
||||
|
||||
// errNode panics because of an unexpected Token kind
|
||||
func errExpected(expect lexer.TokenKind, tok *lexer.Token) {
|
||||
errPanic(fmt.Errorf("Expecting %s, got: '%s'", expect, tok), tok.Line)
|
||||
}
|
||||
|
||||
// program : statement*
|
||||
func (p *parser) parseProgram() *ast.Program {
|
||||
result := ast.NewProgram(p.next().Pos, p.next().Line)
|
||||
|
||||
for p.isStatement() {
|
||||
result.AddStatement(p.parseStatement())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// statement : mustache | block | rawBlock | partial | content | COMMENT
|
||||
func (p *parser) parseStatement() ast.Node {
|
||||
var result ast.Node
|
||||
|
||||
tok := p.next()
|
||||
|
||||
switch tok.Kind {
|
||||
case lexer.TokenOpen, lexer.TokenOpenUnescaped:
|
||||
// mustache
|
||||
result = p.parseMustache()
|
||||
case lexer.TokenOpenBlock:
|
||||
// block
|
||||
result = p.parseBlock()
|
||||
case lexer.TokenOpenInverse:
|
||||
// block
|
||||
result = p.parseInverse()
|
||||
case lexer.TokenOpenRawBlock:
|
||||
// rawBlock
|
||||
result = p.parseRawBlock()
|
||||
case lexer.TokenOpenPartial:
|
||||
// partial
|
||||
result = p.parsePartial()
|
||||
case lexer.TokenContent:
|
||||
// content
|
||||
result = p.parseContent()
|
||||
case lexer.TokenComment:
|
||||
// COMMENT
|
||||
result = p.parseComment()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// isStatement returns true if next token starts a statement
|
||||
func (p *parser) isStatement() bool {
|
||||
if !p.have(1) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch p.next().Kind {
|
||||
case lexer.TokenOpen, lexer.TokenOpenUnescaped, lexer.TokenOpenBlock,
|
||||
lexer.TokenOpenInverse, lexer.TokenOpenRawBlock, lexer.TokenOpenPartial,
|
||||
lexer.TokenContent, lexer.TokenComment:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// content : CONTENT
|
||||
func (p *parser) parseContent() *ast.ContentStatement {
|
||||
// CONTENT
|
||||
tok := p.shift()
|
||||
if tok.Kind != lexer.TokenContent {
|
||||
// @todo This check can be removed if content is optional in a raw block
|
||||
errExpected(lexer.TokenContent, tok)
|
||||
}
|
||||
|
||||
return ast.NewContentStatement(tok.Pos, tok.Line, tok.Val)
|
||||
}
|
||||
|
||||
// COMMENT
|
||||
func (p *parser) parseComment() *ast.CommentStatement {
|
||||
// COMMENT
|
||||
tok := p.shift()
|
||||
|
||||
value := rOpenComment.ReplaceAllString(tok.Val, "")
|
||||
value = rCloseComment.ReplaceAllString(value, "")
|
||||
|
||||
result := ast.NewCommentStatement(tok.Pos, tok.Line, value)
|
||||
result.Strip = ast.NewStripForStr(tok.Val)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// param* hash?
|
||||
func (p *parser) parseExpressionParamsHash() ([]ast.Node, *ast.Hash) {
|
||||
var params []ast.Node
|
||||
var hash *ast.Hash
|
||||
|
||||
// params*
|
||||
if p.isParam() {
|
||||
params = p.parseParams()
|
||||
}
|
||||
|
||||
// hash?
|
||||
if p.isHashSegment() {
|
||||
hash = p.parseHash()
|
||||
}
|
||||
|
||||
return params, hash
|
||||
}
|
||||
|
||||
// helperName param* hash?
|
||||
func (p *parser) parseExpression(tok *lexer.Token) *ast.Expression {
|
||||
result := ast.NewExpression(tok.Pos, tok.Line)
|
||||
|
||||
// helperName
|
||||
result.Path = p.parseHelperName()
|
||||
|
||||
// param* hash?
|
||||
result.Params, result.Hash = p.parseExpressionParamsHash()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// rawBlock : openRawBlock content endRawBlock
|
||||
// openRawBlock : OPEN_RAW_BLOCK helperName param* hash? CLOSE_RAW_BLOCK
|
||||
// endRawBlock : OPEN_END_RAW_BLOCK helperName CLOSE_RAW_BLOCK
|
||||
func (p *parser) parseRawBlock() *ast.BlockStatement {
|
||||
// OPEN_RAW_BLOCK
|
||||
tok := p.shift()
|
||||
|
||||
result := ast.NewBlockStatement(tok.Pos, tok.Line)
|
||||
|
||||
// helperName param* hash?
|
||||
result.Expression = p.parseExpression(tok)
|
||||
|
||||
openName := result.Expression.Canonical()
|
||||
|
||||
// CLOSE_RAW_BLOCK
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenCloseRawBlock {
|
||||
errExpected(lexer.TokenCloseRawBlock, tok)
|
||||
}
|
||||
|
||||
// content
|
||||
// @todo Is content mandatory in a raw block ?
|
||||
content := p.parseContent()
|
||||
|
||||
program := ast.NewProgram(tok.Pos, tok.Line)
|
||||
program.AddStatement(content)
|
||||
|
||||
result.Program = program
|
||||
|
||||
// OPEN_END_RAW_BLOCK
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenOpenEndRawBlock {
|
||||
// should never happen as it is caught by lexer
|
||||
errExpected(lexer.TokenOpenEndRawBlock, tok)
|
||||
}
|
||||
|
||||
// helperName
|
||||
endID := p.parseHelperName()
|
||||
|
||||
closeName, ok := ast.HelperNameStr(endID)
|
||||
if !ok {
|
||||
errNode(endID, "Erroneous closing expression")
|
||||
}
|
||||
|
||||
if openName != closeName {
|
||||
errNode(endID, fmt.Sprintf("%s doesn't match %s", openName, closeName))
|
||||
}
|
||||
|
||||
// CLOSE_RAW_BLOCK
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenCloseRawBlock {
|
||||
errExpected(lexer.TokenCloseRawBlock, tok)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// block : openBlock program inverseChain? closeBlock
|
||||
func (p *parser) parseBlock() *ast.BlockStatement {
|
||||
// openBlock
|
||||
result, blockParams := p.parseOpenBlock()
|
||||
|
||||
// program
|
||||
program := p.parseProgram()
|
||||
program.BlockParams = blockParams
|
||||
result.Program = program
|
||||
|
||||
// inverseChain?
|
||||
if p.isInverseChain() {
|
||||
result.Inverse = p.parseInverseChain()
|
||||
}
|
||||
|
||||
// closeBlock
|
||||
p.parseCloseBlock(result)
|
||||
|
||||
setBlockInverseStrip(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// setBlockInverseStrip is called when parsing `block` (openBlock | openInverse) and `inverseChain`
|
||||
//
|
||||
// TODO: This was totally cargo culted ! CHECK THAT !
|
||||
//
|
||||
// cf. prepareBlock() in:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/helper.js
|
||||
func setBlockInverseStrip(block *ast.BlockStatement) {
|
||||
if block.Inverse == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if block.Inverse.Chained {
|
||||
b, _ := block.Inverse.Body[0].(*ast.BlockStatement)
|
||||
b.CloseStrip = block.CloseStrip
|
||||
}
|
||||
|
||||
block.InverseStrip = block.Inverse.Strip
|
||||
}
|
||||
|
||||
// block : openInverse program inverseAndProgram? closeBlock
|
||||
func (p *parser) parseInverse() *ast.BlockStatement {
|
||||
// openInverse
|
||||
result, blockParams := p.parseOpenBlock()
|
||||
|
||||
// program
|
||||
program := p.parseProgram()
|
||||
|
||||
program.BlockParams = blockParams
|
||||
result.Inverse = program
|
||||
|
||||
// inverseAndProgram?
|
||||
if p.isInverse() {
|
||||
result.Program = p.parseInverseAndProgram()
|
||||
}
|
||||
|
||||
// closeBlock
|
||||
p.parseCloseBlock(result)
|
||||
|
||||
setBlockInverseStrip(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// helperName param* hash? blockParams?
|
||||
func (p *parser) parseOpenBlockExpression(tok *lexer.Token) (*ast.BlockStatement, []string) {
|
||||
var blockParams []string
|
||||
|
||||
result := ast.NewBlockStatement(tok.Pos, tok.Line)
|
||||
|
||||
// helperName param* hash?
|
||||
result.Expression = p.parseExpression(tok)
|
||||
|
||||
// blockParams?
|
||||
if p.isBlockParams() {
|
||||
blockParams = p.parseBlockParams()
|
||||
}
|
||||
|
||||
// named returned values
|
||||
return result, blockParams
|
||||
}
|
||||
|
||||
// inverseChain : openInverseChain program inverseChain?
|
||||
// | inverseAndProgram
|
||||
func (p *parser) parseInverseChain() *ast.Program {
|
||||
if p.isInverse() {
|
||||
// inverseAndProgram
|
||||
return p.parseInverseAndProgram()
|
||||
}
|
||||
|
||||
result := ast.NewProgram(p.next().Pos, p.next().Line)
|
||||
|
||||
// openInverseChain
|
||||
block, blockParams := p.parseOpenBlock()
|
||||
|
||||
// program
|
||||
program := p.parseProgram()
|
||||
|
||||
program.BlockParams = blockParams
|
||||
block.Program = program
|
||||
|
||||
// inverseChain?
|
||||
if p.isInverseChain() {
|
||||
block.Inverse = p.parseInverseChain()
|
||||
}
|
||||
|
||||
setBlockInverseStrip(block)
|
||||
|
||||
result.Chained = true
|
||||
result.AddStatement(block)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns true if current token starts an inverse chain
|
||||
func (p *parser) isInverseChain() bool {
|
||||
return p.isOpenInverseChain() || p.isInverse()
|
||||
}
|
||||
|
||||
// inverseAndProgram : INVERSE program
|
||||
func (p *parser) parseInverseAndProgram() *ast.Program {
|
||||
// INVERSE
|
||||
tok := p.shift()
|
||||
|
||||
// program
|
||||
result := p.parseProgram()
|
||||
result.Strip = ast.NewStripForStr(tok.Val)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// openBlock : OPEN_BLOCK helperName param* hash? blockParams? CLOSE
|
||||
// openInverse : OPEN_INVERSE helperName param* hash? blockParams? CLOSE
|
||||
// openInverseChain: OPEN_INVERSE_CHAIN helperName param* hash? blockParams? CLOSE
|
||||
func (p *parser) parseOpenBlock() (*ast.BlockStatement, []string) {
|
||||
// OPEN_BLOCK | OPEN_INVERSE | OPEN_INVERSE_CHAIN
|
||||
tok := p.shift()
|
||||
|
||||
// helperName param* hash? blockParams?
|
||||
result, blockParams := p.parseOpenBlockExpression(tok)
|
||||
|
||||
// CLOSE
|
||||
tokClose := p.shift()
|
||||
if tokClose.Kind != lexer.TokenClose {
|
||||
errExpected(lexer.TokenClose, tokClose)
|
||||
}
|
||||
|
||||
result.OpenStrip = ast.NewStrip(tok.Val, tokClose.Val)
|
||||
|
||||
// named returned values
|
||||
return result, blockParams
|
||||
}
|
||||
|
||||
// closeBlock : OPEN_ENDBLOCK helperName CLOSE
|
||||
func (p *parser) parseCloseBlock(block *ast.BlockStatement) {
|
||||
// OPEN_ENDBLOCK
|
||||
tok := p.shift()
|
||||
if tok.Kind != lexer.TokenOpenEndBlock {
|
||||
errExpected(lexer.TokenOpenEndBlock, tok)
|
||||
}
|
||||
|
||||
// helperName
|
||||
endID := p.parseHelperName()
|
||||
|
||||
closeName, ok := ast.HelperNameStr(endID)
|
||||
if !ok {
|
||||
errNode(endID, "Erroneous closing expression")
|
||||
}
|
||||
|
||||
openName := block.Expression.Canonical()
|
||||
if openName != closeName {
|
||||
errNode(endID, fmt.Sprintf("%s doesn't match %s", openName, closeName))
|
||||
}
|
||||
|
||||
// CLOSE
|
||||
tokClose := p.shift()
|
||||
if tokClose.Kind != lexer.TokenClose {
|
||||
errExpected(lexer.TokenClose, tokClose)
|
||||
}
|
||||
|
||||
block.CloseStrip = ast.NewStrip(tok.Val, tokClose.Val)
|
||||
}
|
||||
|
||||
// mustache : OPEN helperName param* hash? CLOSE
|
||||
// | OPEN_UNESCAPED helperName param* hash? CLOSE_UNESCAPED
|
||||
func (p *parser) parseMustache() *ast.MustacheStatement {
|
||||
// OPEN | OPEN_UNESCAPED
|
||||
tok := p.shift()
|
||||
|
||||
closeToken := lexer.TokenClose
|
||||
if tok.Kind == lexer.TokenOpenUnescaped {
|
||||
closeToken = lexer.TokenCloseUnescaped
|
||||
}
|
||||
|
||||
unescaped := false
|
||||
if (tok.Kind == lexer.TokenOpenUnescaped) || (rOpenAmp.MatchString(tok.Val)) {
|
||||
unescaped = true
|
||||
}
|
||||
|
||||
result := ast.NewMustacheStatement(tok.Pos, tok.Line, unescaped)
|
||||
|
||||
// helperName param* hash?
|
||||
result.Expression = p.parseExpression(tok)
|
||||
|
||||
// CLOSE | CLOSE_UNESCAPED
|
||||
tokClose := p.shift()
|
||||
if tokClose.Kind != closeToken {
|
||||
errExpected(closeToken, tokClose)
|
||||
}
|
||||
|
||||
result.Strip = ast.NewStrip(tok.Val, tokClose.Val)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// partial : OPEN_PARTIAL partialName param* hash? CLOSE
|
||||
func (p *parser) parsePartial() *ast.PartialStatement {
|
||||
// OPEN_PARTIAL
|
||||
tok := p.shift()
|
||||
|
||||
result := ast.NewPartialStatement(tok.Pos, tok.Line)
|
||||
|
||||
// partialName
|
||||
result.Name = p.parsePartialName()
|
||||
|
||||
// param* hash?
|
||||
result.Params, result.Hash = p.parseExpressionParamsHash()
|
||||
|
||||
// CLOSE
|
||||
tokClose := p.shift()
|
||||
if tokClose.Kind != lexer.TokenClose {
|
||||
errExpected(lexer.TokenClose, tokClose)
|
||||
}
|
||||
|
||||
result.Strip = ast.NewStrip(tok.Val, tokClose.Val)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// helperName | sexpr
|
||||
func (p *parser) parseHelperNameOrSexpr() ast.Node {
|
||||
if p.isSexpr() {
|
||||
// sexpr
|
||||
return p.parseSexpr()
|
||||
}
|
||||
|
||||
// helperName
|
||||
return p.parseHelperName()
|
||||
}
|
||||
|
||||
// param : helperName | sexpr
|
||||
func (p *parser) parseParam() ast.Node {
|
||||
return p.parseHelperNameOrSexpr()
|
||||
}
|
||||
|
||||
// Returns true if next tokens represent a `param`
|
||||
func (p *parser) isParam() bool {
|
||||
return (p.isSexpr() || p.isHelperName()) && !p.isHashSegment()
|
||||
}
|
||||
|
||||
// param*
|
||||
func (p *parser) parseParams() []ast.Node {
|
||||
var result []ast.Node
|
||||
|
||||
for p.isParam() {
|
||||
result = append(result, p.parseParam())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// sexpr : OPEN_SEXPR helperName param* hash? CLOSE_SEXPR
|
||||
func (p *parser) parseSexpr() *ast.SubExpression {
|
||||
// OPEN_SEXPR
|
||||
tok := p.shift()
|
||||
|
||||
result := ast.NewSubExpression(tok.Pos, tok.Line)
|
||||
|
||||
// helperName param* hash?
|
||||
result.Expression = p.parseExpression(tok)
|
||||
|
||||
// CLOSE_SEXPR
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenCloseSexpr {
|
||||
errExpected(lexer.TokenCloseSexpr, tok)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// hash : hashSegment+
|
||||
func (p *parser) parseHash() *ast.Hash {
|
||||
var pairs []*ast.HashPair
|
||||
|
||||
for p.isHashSegment() {
|
||||
pairs = append(pairs, p.parseHashSegment())
|
||||
}
|
||||
|
||||
firstLoc := pairs[0].Location()
|
||||
|
||||
result := ast.NewHash(firstLoc.Pos, firstLoc.Line)
|
||||
result.Pairs = pairs
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// returns true if next tokens represents a `hashSegment`
|
||||
func (p *parser) isHashSegment() bool {
|
||||
return p.have(2) && (p.next().Kind == lexer.TokenID) && (p.nextAt(1).Kind == lexer.TokenEquals)
|
||||
}
|
||||
|
||||
// hashSegment : ID EQUALS param
|
||||
func (p *parser) parseHashSegment() *ast.HashPair {
|
||||
// ID
|
||||
tok := p.shift()
|
||||
|
||||
// EQUALS
|
||||
p.shift()
|
||||
|
||||
// param
|
||||
param := p.parseParam()
|
||||
|
||||
result := ast.NewHashPair(tok.Pos, tok.Line)
|
||||
result.Key = tok.Val
|
||||
result.Val = param
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// blockParams : OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS
|
||||
func (p *parser) parseBlockParams() []string {
|
||||
var result []string
|
||||
|
||||
// OPEN_BLOCK_PARAMS
|
||||
tok := p.shift()
|
||||
|
||||
// ID+
|
||||
for p.isID() {
|
||||
result = append(result, p.shift().Val)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
errExpected(lexer.TokenID, p.next())
|
||||
}
|
||||
|
||||
// CLOSE_BLOCK_PARAMS
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenCloseBlockParams {
|
||||
errExpected(lexer.TokenCloseBlockParams, tok)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// helperName : path | dataName | STRING | NUMBER | BOOLEAN | UNDEFINED | NULL
|
||||
func (p *parser) parseHelperName() ast.Node {
|
||||
var result ast.Node
|
||||
|
||||
tok := p.next()
|
||||
|
||||
switch tok.Kind {
|
||||
case lexer.TokenBoolean:
|
||||
// BOOLEAN
|
||||
p.shift()
|
||||
result = ast.NewBooleanLiteral(tok.Pos, tok.Line, (tok.Val == "true"), tok.Val)
|
||||
case lexer.TokenNumber:
|
||||
// NUMBER
|
||||
p.shift()
|
||||
|
||||
val, isInt := parseNumber(tok)
|
||||
result = ast.NewNumberLiteral(tok.Pos, tok.Line, val, isInt, tok.Val)
|
||||
case lexer.TokenString:
|
||||
// STRING
|
||||
p.shift()
|
||||
result = ast.NewStringLiteral(tok.Pos, tok.Line, tok.Val)
|
||||
case lexer.TokenData:
|
||||
// dataName
|
||||
result = p.parseDataName()
|
||||
default:
|
||||
// path
|
||||
result = p.parsePath(false)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// parseNumber parses a number
|
||||
func parseNumber(tok *lexer.Token) (result float64, isInt bool) {
|
||||
var valInt int
|
||||
var err error
|
||||
|
||||
valInt, err = strconv.Atoi(tok.Val)
|
||||
if err == nil {
|
||||
isInt = true
|
||||
|
||||
result = float64(valInt)
|
||||
} else {
|
||||
isInt = false
|
||||
|
||||
result, err = strconv.ParseFloat(tok.Val, 64)
|
||||
if err != nil {
|
||||
errToken(tok, fmt.Sprintf("Failed to parse number: %s", tok.Val))
|
||||
}
|
||||
}
|
||||
|
||||
// named returned values
|
||||
return
|
||||
}
|
||||
|
||||
// Returns true if next tokens represent a `helperName`
|
||||
func (p *parser) isHelperName() bool {
|
||||
switch p.next().Kind {
|
||||
case lexer.TokenBoolean, lexer.TokenNumber, lexer.TokenString, lexer.TokenData, lexer.TokenID:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// partialName : helperName | sexpr
|
||||
func (p *parser) parsePartialName() ast.Node {
|
||||
return p.parseHelperNameOrSexpr()
|
||||
}
|
||||
|
||||
// dataName : DATA pathSegments
|
||||
func (p *parser) parseDataName() *ast.PathExpression {
|
||||
// DATA
|
||||
p.shift()
|
||||
|
||||
// pathSegments
|
||||
return p.parsePath(true)
|
||||
}
|
||||
|
||||
// path : pathSegments
|
||||
// pathSegments : pathSegments SEP ID
|
||||
// | ID
|
||||
func (p *parser) parsePath(data bool) *ast.PathExpression {
|
||||
var tok *lexer.Token
|
||||
|
||||
// ID
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenID {
|
||||
errExpected(lexer.TokenID, tok)
|
||||
}
|
||||
|
||||
result := ast.NewPathExpression(tok.Pos, tok.Line, data)
|
||||
result.Part(tok.Val)
|
||||
|
||||
for p.isPathSep() {
|
||||
// SEP
|
||||
tok = p.shift()
|
||||
result.Sep(tok.Val)
|
||||
|
||||
// ID
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenID {
|
||||
errExpected(lexer.TokenID, tok)
|
||||
}
|
||||
|
||||
result.Part(tok.Val)
|
||||
|
||||
if len(result.Parts) > 0 {
|
||||
switch tok.Val {
|
||||
case "..", ".", "this":
|
||||
errToken(tok, "Invalid path: "+result.Original)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Ensures there is token to parse at given index
|
||||
func (p *parser) ensure(index int) {
|
||||
if p.lexOver {
|
||||
// nothing more to grab
|
||||
return
|
||||
}
|
||||
|
||||
nb := index + 1
|
||||
|
||||
for len(p.tokens) < nb {
|
||||
// fetch next token
|
||||
tok := p.lex.NextToken()
|
||||
|
||||
// queue it
|
||||
p.tokens = append(p.tokens, &tok)
|
||||
|
||||
if (tok.Kind == lexer.TokenEOF) || (tok.Kind == lexer.TokenError) {
|
||||
p.lexOver = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// have returns true is there are a list given number of tokens to consume left
|
||||
func (p *parser) have(nb int) bool {
|
||||
p.ensure(nb - 1)
|
||||
|
||||
return len(p.tokens) >= nb
|
||||
}
|
||||
|
||||
// nextAt returns next token at given index, without consuming it
|
||||
func (p *parser) nextAt(index int) *lexer.Token {
|
||||
p.ensure(index)
|
||||
|
||||
return p.tokens[index]
|
||||
}
|
||||
|
||||
// next returns next token without consuming it
|
||||
func (p *parser) next() *lexer.Token {
|
||||
return p.nextAt(0)
|
||||
}
|
||||
|
||||
// shift returns next token and remove it from the tokens buffer
|
||||
//
|
||||
// Panics if next token is `TokenError`
|
||||
func (p *parser) shift() *lexer.Token {
|
||||
var result *lexer.Token
|
||||
|
||||
p.ensure(0)
|
||||
|
||||
result, p.tokens = p.tokens[0], p.tokens[1:]
|
||||
|
||||
// check error token
|
||||
if result.Kind == lexer.TokenError {
|
||||
errToken(result, "Lexer error")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// isToken returns true if next token is of given type
|
||||
func (p *parser) isToken(kind lexer.TokenKind) bool {
|
||||
return p.have(1) && p.next().Kind == kind
|
||||
}
|
||||
|
||||
// isSexpr returns true if next token starts a sexpr
|
||||
func (p *parser) isSexpr() bool {
|
||||
return p.isToken(lexer.TokenOpenSexpr)
|
||||
}
|
||||
|
||||
// isPathSep returns true if next token is a path separator
|
||||
func (p *parser) isPathSep() bool {
|
||||
return p.isToken(lexer.TokenSep)
|
||||
}
|
||||
|
||||
// isID returns true if next token is an ID
|
||||
func (p *parser) isID() bool {
|
||||
return p.isToken(lexer.TokenID)
|
||||
}
|
||||
|
||||
// isBlockParams returns true if next token starts a block params
|
||||
func (p *parser) isBlockParams() bool {
|
||||
return p.isToken(lexer.TokenOpenBlockParams)
|
||||
}
|
||||
|
||||
// isInverse returns true if next token starts an INVERSE sequence
|
||||
func (p *parser) isInverse() bool {
|
||||
return p.isToken(lexer.TokenInverse)
|
||||
}
|
||||
|
||||
// isOpenInverseChain returns true if next token is OPEN_INVERSE_CHAIN
|
||||
func (p *parser) isOpenInverseChain() bool {
|
||||
return p.isToken(lexer.TokenOpenInverseChain)
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/aymerick/raymond/ast"
|
||||
)
|
||||
|
||||
// whitespaceVisitor walks through the AST to perform whitespace control
|
||||
//
|
||||
// The logic was shamelessly borrowed from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/whitespace-control.js
|
||||
type whitespaceVisitor struct {
|
||||
isRootSeen bool
|
||||
}
|
||||
|
||||
var (
|
||||
rTrimLeft = regexp.MustCompile(`^[ \t]*\r?\n?`)
|
||||
rTrimLeftMultiple = regexp.MustCompile(`^\s+`)
|
||||
|
||||
rTrimRight = regexp.MustCompile(`[ \t]+$`)
|
||||
rTrimRightMultiple = regexp.MustCompile(`\s+$`)
|
||||
|
||||
rPrevWhitespace = regexp.MustCompile(`\r?\n\s*?$`)
|
||||
rPrevWhitespaceStart = regexp.MustCompile(`(^|\r?\n)\s*?$`)
|
||||
|
||||
rNextWhitespace = regexp.MustCompile(`^\s*?\r?\n`)
|
||||
rNextWhitespaceEnd = regexp.MustCompile(`^\s*?(\r?\n|$)`)
|
||||
|
||||
rPartialIndent = regexp.MustCompile(`([ \t]+$)`)
|
||||
)
|
||||
|
||||
// newWhitespaceVisitor instanciates a new whitespaceVisitor
|
||||
func newWhitespaceVisitor() *whitespaceVisitor {
|
||||
return &whitespaceVisitor{}
|
||||
}
|
||||
|
||||
// processWhitespaces performs whitespace control on given AST
|
||||
//
|
||||
// WARNING: It must be called only once on AST.
|
||||
func processWhitespaces(node ast.Node) {
|
||||
node.Accept(newWhitespaceVisitor())
|
||||
}
|
||||
|
||||
func omitRightFirst(body []ast.Node, multiple bool) {
|
||||
omitRight(body, -1, multiple)
|
||||
}
|
||||
|
||||
func omitRight(body []ast.Node, i int, multiple bool) {
|
||||
if i+1 >= len(body) {
|
||||
return
|
||||
}
|
||||
|
||||
current := body[i+1]
|
||||
|
||||
node, ok := current.(*ast.ContentStatement)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !multiple && node.RightStripped {
|
||||
return
|
||||
}
|
||||
|
||||
original := node.Value
|
||||
|
||||
r := rTrimLeft
|
||||
if multiple {
|
||||
r = rTrimLeftMultiple
|
||||
}
|
||||
|
||||
node.Value = r.ReplaceAllString(node.Value, "")
|
||||
|
||||
node.RightStripped = (original != node.Value)
|
||||
}
|
||||
|
||||
func omitLeftLast(body []ast.Node, multiple bool) {
|
||||
omitLeft(body, len(body), multiple)
|
||||
}
|
||||
|
||||
func omitLeft(body []ast.Node, i int, multiple bool) bool {
|
||||
if i-1 < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
current := body[i-1]
|
||||
|
||||
node, ok := current.(*ast.ContentStatement)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !multiple && node.LeftStripped {
|
||||
return false
|
||||
}
|
||||
|
||||
original := node.Value
|
||||
|
||||
r := rTrimRight
|
||||
if multiple {
|
||||
r = rTrimRightMultiple
|
||||
}
|
||||
|
||||
node.Value = r.ReplaceAllString(node.Value, "")
|
||||
|
||||
node.LeftStripped = (original != node.Value)
|
||||
|
||||
return node.LeftStripped
|
||||
}
|
||||
|
||||
func isPrevWhitespace(body []ast.Node) bool {
|
||||
return isPrevWhitespaceProgram(body, len(body), false)
|
||||
}
|
||||
|
||||
func isPrevWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool {
|
||||
if i < 1 {
|
||||
return isRoot
|
||||
}
|
||||
|
||||
prev := body[i-1]
|
||||
|
||||
if node, ok := prev.(*ast.ContentStatement); ok {
|
||||
if (node.Value == "") && node.RightStripped {
|
||||
// already stripped, so it may be an empty string not catched by regexp
|
||||
return true
|
||||
}
|
||||
|
||||
r := rPrevWhitespaceStart
|
||||
if (i > 1) || !isRoot {
|
||||
r = rPrevWhitespace
|
||||
}
|
||||
|
||||
return r.MatchString(node.Value)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isNextWhitespace(body []ast.Node) bool {
|
||||
return isNextWhitespaceProgram(body, -1, false)
|
||||
}
|
||||
|
||||
func isNextWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool {
|
||||
if i+1 >= len(body) {
|
||||
return isRoot
|
||||
}
|
||||
|
||||
next := body[i+1]
|
||||
|
||||
if node, ok := next.(*ast.ContentStatement); ok {
|
||||
if (node.Value == "") && node.LeftStripped {
|
||||
// already stripped, so it may be an empty string not catched by regexp
|
||||
return true
|
||||
}
|
||||
|
||||
r := rNextWhitespaceEnd
|
||||
if (i+2 > len(body)) || !isRoot {
|
||||
r = rNextWhitespace
|
||||
}
|
||||
|
||||
return r.MatchString(node.Value)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
//
|
||||
// Visitor interface
|
||||
//
|
||||
|
||||
func (v *whitespaceVisitor) VisitProgram(program *ast.Program) interface{} {
|
||||
isRoot := !v.isRootSeen
|
||||
v.isRootSeen = true
|
||||
|
||||
body := program.Body
|
||||
for i, current := range body {
|
||||
strip, _ := current.Accept(v).(*ast.Strip)
|
||||
if strip == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_isPrevWhitespace := isPrevWhitespaceProgram(body, i, isRoot)
|
||||
_isNextWhitespace := isNextWhitespaceProgram(body, i, isRoot)
|
||||
|
||||
openStandalone := strip.OpenStandalone && _isPrevWhitespace
|
||||
closeStandalone := strip.CloseStandalone && _isNextWhitespace
|
||||
inlineStandalone := strip.InlineStandalone && _isPrevWhitespace && _isNextWhitespace
|
||||
|
||||
if strip.Close {
|
||||
omitRight(body, i, true)
|
||||
}
|
||||
|
||||
if strip.Open && (i > 0) {
|
||||
omitLeft(body, i, true)
|
||||
}
|
||||
|
||||
if inlineStandalone {
|
||||
omitRight(body, i, false)
|
||||
|
||||
if omitLeft(body, i, false) {
|
||||
// If we are on a standalone node, save the indent info for partials
|
||||
if partial, ok := current.(*ast.PartialStatement); ok {
|
||||
// Pull out the whitespace from the final line
|
||||
if i > 0 {
|
||||
if prevContent, ok := body[i-1].(*ast.ContentStatement); ok {
|
||||
partial.Indent = rPartialIndent.FindString(prevContent.Original)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b, ok := current.(*ast.BlockStatement); ok {
|
||||
if openStandalone {
|
||||
prog := b.Program
|
||||
if prog == nil {
|
||||
prog = b.Inverse
|
||||
}
|
||||
|
||||
omitRightFirst(prog.Body, false)
|
||||
|
||||
// Strip out the previous content node if it's whitespace only
|
||||
omitLeft(body, i, false)
|
||||
}
|
||||
|
||||
if closeStandalone {
|
||||
prog := b.Inverse
|
||||
if prog == nil {
|
||||
prog = b.Program
|
||||
}
|
||||
|
||||
// Always strip the next node
|
||||
omitRight(body, i, false)
|
||||
|
||||
omitLeftLast(prog.Body, false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *whitespaceVisitor) VisitBlock(block *ast.BlockStatement) interface{} {
|
||||
if block.Program != nil {
|
||||
block.Program.Accept(v)
|
||||
}
|
||||
|
||||
if block.Inverse != nil {
|
||||
block.Inverse.Accept(v)
|
||||
}
|
||||
|
||||
program := block.Program
|
||||
inverse := block.Inverse
|
||||
|
||||
if program == nil {
|
||||
program = inverse
|
||||
inverse = nil
|
||||
}
|
||||
|
||||
firstInverse := inverse
|
||||
lastInverse := inverse
|
||||
|
||||
if (inverse != nil) && inverse.Chained {
|
||||
b, _ := inverse.Body[0].(*ast.BlockStatement)
|
||||
firstInverse = b.Program
|
||||
|
||||
for lastInverse.Chained {
|
||||
b, _ := lastInverse.Body[len(lastInverse.Body)-1].(*ast.BlockStatement)
|
||||
lastInverse = b.Program
|
||||
}
|
||||
}
|
||||
|
||||
closeProg := firstInverse
|
||||
if closeProg == nil {
|
||||
closeProg = program
|
||||
}
|
||||
|
||||
strip := &ast.Strip{
|
||||
Open: (block.OpenStrip != nil) && block.OpenStrip.Open,
|
||||
Close: (block.CloseStrip != nil) && block.CloseStrip.Close,
|
||||
|
||||
OpenStandalone: isNextWhitespace(program.Body),
|
||||
CloseStandalone: isPrevWhitespace(closeProg.Body),
|
||||
}
|
||||
|
||||
if (block.OpenStrip != nil) && block.OpenStrip.Close {
|
||||
omitRightFirst(program.Body, true)
|
||||
}
|
||||
|
||||
if inverse != nil {
|
||||
if block.InverseStrip != nil {
|
||||
inverseStrip := block.InverseStrip
|
||||
|
||||
if inverseStrip.Open {
|
||||
omitLeftLast(program.Body, true)
|
||||
}
|
||||
|
||||
if inverseStrip.Close {
|
||||
omitRightFirst(firstInverse.Body, true)
|
||||
}
|
||||
}
|
||||
|
||||
if (block.CloseStrip != nil) && block.CloseStrip.Open {
|
||||
omitLeftLast(lastInverse.Body, true)
|
||||
}
|
||||
|
||||
// Find standalone else statements
|
||||
if isPrevWhitespace(program.Body) && isNextWhitespace(firstInverse.Body) {
|
||||
omitLeftLast(program.Body, false)
|
||||
|
||||
omitRightFirst(firstInverse.Body, false)
|
||||
}
|
||||
} else if (block.CloseStrip != nil) && block.CloseStrip.Open {
|
||||
omitLeftLast(program.Body, true)
|
||||
}
|
||||
|
||||
return strip
|
||||
}
|
||||
|
||||
func (v *whitespaceVisitor) VisitMustache(mustache *ast.MustacheStatement) interface{} {
|
||||
return mustache.Strip
|
||||
}
|
||||
|
||||
func _inlineStandalone(strip *ast.Strip) interface{} {
|
||||
return &ast.Strip{
|
||||
Open: strip.Open,
|
||||
Close: strip.Close,
|
||||
InlineStandalone: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *whitespaceVisitor) VisitPartial(node *ast.PartialStatement) interface{} {
|
||||
strip := node.Strip
|
||||
if strip == nil {
|
||||
strip = &ast.Strip{}
|
||||
}
|
||||
|
||||
return _inlineStandalone(strip)
|
||||
}
|
||||
|
||||
func (v *whitespaceVisitor) VisitComment(node *ast.CommentStatement) interface{} {
|
||||
strip := node.Strip
|
||||
if strip == nil {
|
||||
strip = &ast.Strip{}
|
||||
}
|
||||
|
||||
return _inlineStandalone(strip)
|
||||
}
|
||||
|
||||
// NOOP
|
||||
func (v *whitespaceVisitor) VisitContent(node *ast.ContentStatement) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitExpression(node *ast.Expression) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitSubExpression(node *ast.SubExpression) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitPath(node *ast.PathExpression) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitString(node *ast.StringLiteral) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitNumber(node *ast.NumberLiteral) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitHash(node *ast.Hash) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitHashPair(node *ast.HashPair) interface{} { return nil }
|
|
@ -0,0 +1,85 @@
|
|||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// partial represents a partial template
|
||||
type partial struct {
|
||||
name string
|
||||
source string
|
||||
tpl *Template
|
||||
}
|
||||
|
||||
// partials stores all global partials
|
||||
var partials map[string]*partial
|
||||
|
||||
// protects global partials
|
||||
var partialsMutex sync.RWMutex
|
||||
|
||||
func init() {
|
||||
partials = make(map[string]*partial)
|
||||
}
|
||||
|
||||
// newPartial instanciates a new partial
|
||||
func newPartial(name string, source string, tpl *Template) *partial {
|
||||
return &partial{
|
||||
name: name,
|
||||
source: source,
|
||||
tpl: tpl,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterPartial registers a global partial. That partial will be available to all templates.
|
||||
func RegisterPartial(name string, source string) {
|
||||
partialsMutex.Lock()
|
||||
defer partialsMutex.Unlock()
|
||||
|
||||
if partials[name] != nil {
|
||||
panic(fmt.Errorf("Partial already registered: %s", name))
|
||||
}
|
||||
|
||||
partials[name] = newPartial(name, source, nil)
|
||||
}
|
||||
|
||||
// RegisterPartials registers several global partials. Those partials will be available to all templates.
|
||||
func RegisterPartials(partials map[string]string) {
|
||||
for name, p := range partials {
|
||||
RegisterPartial(name, p)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterPartialTemplate registers a global partial with given parsed template. That partial will be available to all templates.
|
||||
func RegisterPartialTemplate(name string, tpl *Template) {
|
||||
partialsMutex.Lock()
|
||||
defer partialsMutex.Unlock()
|
||||
|
||||
if partials[name] != nil {
|
||||
panic(fmt.Errorf("Partial already registered: %s", name))
|
||||
}
|
||||
|
||||
partials[name] = newPartial(name, "", tpl)
|
||||
}
|
||||
|
||||
// findPartial finds a registered global partial
|
||||
func findPartial(name string) *partial {
|
||||
partialsMutex.RLock()
|
||||
defer partialsMutex.RUnlock()
|
||||
|
||||
return partials[name]
|
||||
}
|
||||
|
||||
// template returns parsed partial template
|
||||
func (p *partial) template() (*Template, error) {
|
||||
if p.tpl == nil {
|
||||
var err error
|
||||
|
||||
p.tpl, err = Parse(p.source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return p.tpl, nil
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Package raymond provides handlebars evaluation
|
||||
package raymond
|
||||
|
||||
// Render parses a template and evaluates it with given context
|
||||
//
|
||||
// Note that this function call is not optimal as your template is parsed everytime you call it. You should use Parse() function instead.
|
||||
func Render(source string, ctx interface{}) (string, error) {
|
||||
// parse template
|
||||
tpl, err := Parse(source)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// renders template
|
||||
str, err := tpl.Exec(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// MustRender parses a template and evaluates it with given context. It panics on error.
|
||||
//
|
||||
// Note that this function call is not optimal as your template is parsed everytime you call it. You should use Parse() function instead.
|
||||
func MustRender(source string, ctx interface{}) string {
|
||||
return MustParse(source).MustExec(ctx)
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,84 @@
|
|||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SafeString represents a string that must not be escaped.
|
||||
//
|
||||
// A SafeString can be returned by helpers to disable escaping.
|
||||
type SafeString string
|
||||
|
||||
// isSafeString returns true if argument is a SafeString
|
||||
func isSafeString(value interface{}) bool {
|
||||
if _, ok := value.(SafeString); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Str returns string representation of any basic type value.
|
||||
func Str(value interface{}) string {
|
||||
return strValue(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
// strValue returns string representation of a reflect.Value
|
||||
func strValue(value reflect.Value) string {
|
||||
result := ""
|
||||
|
||||
ival, ok := printableValue(value)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("Can't print value: %q", value))
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(ival)
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
result += strValue(val.Index(i))
|
||||
}
|
||||
case reflect.Bool:
|
||||
result = "false"
|
||||
if val.Bool() {
|
||||
result = "true"
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
result = fmt.Sprintf("%d", ival)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
result = strconv.FormatFloat(val.Float(), 'f', -1, 64)
|
||||
case reflect.Invalid:
|
||||
result = ""
|
||||
default:
|
||||
result = fmt.Sprintf("%s", ival)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// printableValue returns the, possibly indirected, interface value inside v that
|
||||
// is best for a call to formatted printer.
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
func printableValue(v reflect.Value) (interface{}, bool) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||
}
|
||||
if !v.IsValid() {
|
||||
return "", true
|
||||
}
|
||||
|
||||
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
||||
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
||||
v = v.Addr()
|
||||
} else {
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return v.Interface(), true
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/aymerick/raymond/ast"
|
||||
"github.com/aymerick/raymond/parser"
|
||||
)
|
||||
|
||||
// Template represents a handlebars template.
|
||||
type Template struct {
|
||||
source string
|
||||
program *ast.Program
|
||||
helpers map[string]reflect.Value
|
||||
partials map[string]*partial
|
||||
mutex sync.RWMutex // protects helpers and partials
|
||||
}
|
||||
|
||||
// newTemplate instanciate a new template without parsing it
|
||||
func newTemplate(source string) *Template {
|
||||
return &Template{
|
||||
source: source,
|
||||
helpers: make(map[string]reflect.Value),
|
||||
partials: make(map[string]*partial),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse instanciates a template by parsing given source.
|
||||
func Parse(source string) (*Template, error) {
|
||||
tpl := newTemplate(source)
|
||||
|
||||
// parse template
|
||||
if err := tpl.parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tpl, nil
|
||||
}
|
||||
|
||||
// MustParse instanciates a template by parsing given source. It panics on error.
|
||||
func MustParse(source string) *Template {
|
||||
result, err := Parse(source)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ParseFile reads given file and returns parsed template.
|
||||
func ParseFile(filePath string) (*Template, error) {
|
||||
b, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Parse(string(b))
|
||||
}
|
||||
|
||||
// parse parses the template
|
||||
//
|
||||
// It can be called several times, the parsing will be done only once.
|
||||
func (tpl *Template) parse() error {
|
||||
if tpl.program == nil {
|
||||
var err error
|
||||
|
||||
tpl.program, err = parser.Parse(tpl.source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a copy of that template.
|
||||
func (tpl *Template) Clone() *Template {
|
||||
result := newTemplate(tpl.source)
|
||||
|
||||
result.program = tpl.program
|
||||
|
||||
tpl.mutex.RLock()
|
||||
defer tpl.mutex.RUnlock()
|
||||
|
||||
for name, helper := range tpl.helpers {
|
||||
result.RegisterHelper(name, helper.Interface())
|
||||
}
|
||||
|
||||
for name, partial := range tpl.partials {
|
||||
result.addPartial(name, partial.source, partial.tpl)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (tpl *Template) findHelper(name string) reflect.Value {
|
||||
tpl.mutex.RLock()
|
||||
defer tpl.mutex.RUnlock()
|
||||
|
||||
return tpl.helpers[name]
|
||||
}
|
||||
|
||||
// RegisterHelper registers a helper for that template.
|
||||
func (tpl *Template) RegisterHelper(name string, helper interface{}) {
|
||||
tpl.mutex.Lock()
|
||||
defer tpl.mutex.Unlock()
|
||||
|
||||
if tpl.helpers[name] != zero {
|
||||
panic(fmt.Sprintf("Helper %s already registered", name))
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(helper)
|
||||
ensureValidHelper(name, val)
|
||||
|
||||
tpl.helpers[name] = val
|
||||
}
|
||||
|
||||
// RegisterHelpers registers several helpers for that template.
|
||||
func (tpl *Template) RegisterHelpers(helpers map[string]interface{}) {
|
||||
for name, helper := range helpers {
|
||||
tpl.RegisterHelper(name, helper)
|
||||
}
|
||||
}
|
||||
|
||||
func (tpl *Template) addPartial(name string, source string, template *Template) {
|
||||
tpl.mutex.Lock()
|
||||
defer tpl.mutex.Unlock()
|
||||
|
||||
if tpl.partials[name] != nil {
|
||||
panic(fmt.Sprintf("Partial %s already registered", name))
|
||||
}
|
||||
|
||||
tpl.partials[name] = newPartial(name, source, template)
|
||||
}
|
||||
|
||||
func (tpl *Template) findPartial(name string) *partial {
|
||||
tpl.mutex.RLock()
|
||||
defer tpl.mutex.RUnlock()
|
||||
|
||||
return tpl.partials[name]
|
||||
}
|
||||
|
||||
// RegisterPartial registers a partial for that template.
|
||||
func (tpl *Template) RegisterPartial(name string, source string) {
|
||||
tpl.addPartial(name, source, nil)
|
||||
}
|
||||
|
||||
// RegisterPartials registers several partials for that template.
|
||||
func (tpl *Template) RegisterPartials(partials map[string]string) {
|
||||
for name, partial := range partials {
|
||||
tpl.RegisterPartial(name, partial)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterPartialFile reads given file and registers its content as a partial with given name.
|
||||
func (tpl *Template) RegisterPartialFile(filePath string, name string) error {
|
||||
b, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tpl.RegisterPartial(name, string(b))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterPartialFiles reads several files and registers them as partials, the filename base is used as the partial name.
|
||||
func (tpl *Template) RegisterPartialFiles(filePaths ...string) error {
|
||||
if len(filePaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, filePath := range filePaths {
|
||||
name := fileBase(filePath)
|
||||
|
||||
if err := tpl.RegisterPartialFile(filePath, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterPartialTemplate registers an already parsed partial for that template.
|
||||
func (tpl *Template) RegisterPartialTemplate(name string, template *Template) {
|
||||
tpl.addPartial(name, "", template)
|
||||
}
|
||||
|
||||
// Exec evaluates template with given context.
|
||||
func (tpl *Template) Exec(ctx interface{}) (result string, err error) {
|
||||
return tpl.ExecWith(ctx, nil)
|
||||
}
|
||||
|
||||
// MustExec evaluates template with given context. It panics on error.
|
||||
func (tpl *Template) MustExec(ctx interface{}) string {
|
||||
result, err := tpl.Exec(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ExecWith evaluates template with given context and private data frame.
|
||||
func (tpl *Template) ExecWith(ctx interface{}, privData *DataFrame) (result string, err error) {
|
||||
defer errRecover(&err)
|
||||
|
||||
// parses template if necessary
|
||||
err = tpl.parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// setup visitor
|
||||
v := newEvalVisitor(tpl, ctx, privData)
|
||||
|
||||
// visit AST
|
||||
result, _ = tpl.program.Accept(v).(string)
|
||||
|
||||
// named return values
|
||||
return
|
||||
}
|
||||
|
||||
// errRecover recovers evaluation panic
|
||||
func errRecover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
switch err := e.(type) {
|
||||
case runtime.Error:
|
||||
panic(e)
|
||||
case error:
|
||||
*errp = err
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintAST returns string representation of parsed template.
|
||||
func (tpl *Template) PrintAST() string {
|
||||
if err := tpl.parse(); err != nil {
|
||||
return fmt.Sprintf("PARSER ERROR: %s", err)
|
||||
}
|
||||
|
||||
return ast.Print(tpl.program)
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package raymond
|
||||
|
||||
import (
|
||||
"path"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
||||
// We indirect through pointers and empty interfaces (only) because
|
||||
// non-empty interfaces have methods we might need.
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||
if v.IsNil() {
|
||||
return v, true
|
||||
}
|
||||
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v, false
|
||||
}
|
||||
|
||||
// IsTrue returns true if obj is a truthy value.
|
||||
func IsTrue(obj interface{}) bool {
|
||||
thruth, ok := isTrueValue(reflect.ValueOf(obj))
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return thruth
|
||||
}
|
||||
|
||||
// isTrueValue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||
// and whether the value has a meaningful truth value
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
func isTrueValue(val reflect.Value) (truth, ok bool) {
|
||||
if !val.IsValid() {
|
||||
// Something like var x interface{}, never set. It's a form of nil.
|
||||
return false, true
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
truth = val.Len() > 0
|
||||
case reflect.Bool:
|
||||
truth = val.Bool()
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
truth = val.Complex() != 0
|
||||
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
||||
truth = !val.IsNil()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
truth = val.Int() != 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
truth = val.Float() != 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
truth = val.Uint() != 0
|
||||
case reflect.Struct:
|
||||
truth = true // Struct values are always true.
|
||||
default:
|
||||
return
|
||||
}
|
||||
return truth, true
|
||||
}
|
||||
|
||||
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
func canBeNil(typ reflect.Type) bool {
|
||||
switch typ.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// fileBase returns base file name
|
||||
//
|
||||
// example: /foo/bar/baz.png => baz
|
||||
func fileBase(filePath string) string {
|
||||
fileName := path.Base(filePath)
|
||||
fileExt := path.Ext(filePath)
|
||||
|
||||
return fileName[:len(fileName)-len(fileExt)]
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# drone-go
|
||||
|
||||
[![Build Status](http://beta.drone.io/api/badges/drone/drone-go/status.svg)](http://beta.drone.io/drone/drone-go)
|
||||
|
||||
drone-go is a Go client library for accessing the Drone [API](http://readme.drone.io/devs/api/builds/) and writing [plugins](http://readme.drone.io/plugins/).
|
||||
|
||||
Download the package using `go get`:
|
||||
|
||||
```Go
|
||||
go get "github.com/drone/drone-go/drone"
|
||||
go get "github.com/drone/drone-go/plugin"
|
||||
```
|
||||
|
||||
Import the package:
|
||||
|
||||
```Go
|
||||
import "github.com/drone/drone-go/drone"
|
||||
```
|
||||
|
||||
Create the client:
|
||||
|
||||
```Go
|
||||
const (
|
||||
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
|
||||
host = "http://drone.company.com"
|
||||
)
|
||||
|
||||
client := drone.NewClientToken(host, token)
|
||||
```
|
||||
|
||||
Get the current user:
|
||||
|
||||
```Go
|
||||
user, err := client.Self()
|
||||
fmt.Println(user)
|
||||
```
|
||||
|
||||
Get the repository list:
|
||||
|
||||
```Go
|
||||
repos, err := client.RepoList()
|
||||
fmt.Println(repos)
|
||||
```
|
||||
|
||||
Get the named repository:
|
||||
|
||||
```Go
|
||||
repo, err := client.Repo("drone", "drone-go")
|
||||
fmt.Println(repo)
|
||||
```
|
|
@ -0,0 +1,404 @@
|
|||
package drone
|
||||
|
||||
//go:generate mockery -all
|
||||
//go:generate mv mocks/Client.go mocks/client.go
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
pathSelf = "%s/api/user"
|
||||
pathFeed = "%s/api/user/feed"
|
||||
pathRepos = "%s/api/user/repos"
|
||||
pathRepo = "%s/api/repos/%s/%s"
|
||||
pathEncrypt = "%s/api/repos/%s/%s/encrypt"
|
||||
pathBuilds = "%s/api/repos/%s/%s/builds"
|
||||
pathBuild = "%s/api/repos/%s/%s/builds/%v"
|
||||
pathJob = "%s/api/repos/%s/%s/builds/%d/%d"
|
||||
pathLog = "%s/api/repos/%s/%s/logs/%d/%d"
|
||||
pathKey = "%s/api/repos/%s/%s/key"
|
||||
pathSign = "%s/api/repos/%s/%s/sign"
|
||||
pathSecrets = "%s/api/repos/%s/%s/secrets"
|
||||
pathSecret = "%s/api/repos/%s/%s/secrets/%s"
|
||||
pathNodes = "%s/api/nodes"
|
||||
pathNode = "%s/api/nodes/%d"
|
||||
pathUsers = "%s/api/users"
|
||||
pathUser = "%s/api/users/%s"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
client *http.Client
|
||||
base string // base url
|
||||
}
|
||||
|
||||
// NewClient returns a client at the specified url.
|
||||
func NewClient(uri string) Client {
|
||||
return &client{http.DefaultClient, uri}
|
||||
}
|
||||
|
||||
// NewClientToken returns a client at the specified url that
|
||||
// authenticates all outbound requests with the given token.
|
||||
func NewClientToken(uri, token string) Client {
|
||||
config := new(oauth2.Config)
|
||||
auther := config.Client(oauth2.NoContext, &oauth2.Token{AccessToken: token})
|
||||
return &client{auther, uri}
|
||||
}
|
||||
|
||||
// NewClientTokenTLS returns a client at the specified url that
|
||||
// authenticates all outbound requests with the given token and
|
||||
// tls.Config if provided.
|
||||
func NewClientTokenTLS(uri, token string, c *tls.Config) Client {
|
||||
config := new(oauth2.Config)
|
||||
auther := config.Client(oauth2.NoContext, &oauth2.Token{AccessToken: token})
|
||||
if c != nil {
|
||||
auther.Transport.(*oauth2.Transport).Base = &http.Transport{TLSClientConfig: c}
|
||||
}
|
||||
return &client{auther, uri}
|
||||
}
|
||||
|
||||
// SetClient sets the default http client. This should be
|
||||
// used in conjunction with golang.org/x/oauth2 to
|
||||
// authenticate requests to the Drone server.
|
||||
func (c *client) SetClient(client *http.Client) {
|
||||
c.client = client
|
||||
}
|
||||
|
||||
// Self returns the currently authenticated user.
|
||||
func (c *client) Self() (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathSelf, c.base)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// User returns a user by login.
|
||||
func (c *client) User(login string) (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathUser, c.base, login)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserList returns a list of all registered users.
|
||||
func (c *client) UserList() ([]*User, error) {
|
||||
var out []*User
|
||||
uri := fmt.Sprintf(pathUsers, c.base)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserPost creates a new user account.
|
||||
func (c *client) UserPost(in *User) (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathUsers, c.base)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserPatch updates a user account.
|
||||
func (c *client) UserPatch(in *User) (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathUser, c.base, in.Login)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserDel deletes a user account.
|
||||
func (c *client) UserDel(login string) error {
|
||||
uri := fmt.Sprintf(pathUser, c.base, login)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// UserFeed returns the user's activity feed.
|
||||
func (c *client) UserFeed() ([]*Activity, error) {
|
||||
var out []*Activity
|
||||
uri := fmt.Sprintf(pathFeed, c.base)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Repo returns a repository by name.
|
||||
func (c *client) Repo(owner string, name string) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepo, c.base, owner, name)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoList returns a list of all repositories to which
|
||||
// the user has explicit access in the host system.
|
||||
func (c *client) RepoList() ([]*Repo, error) {
|
||||
var out []*Repo
|
||||
uri := fmt.Sprintf(pathRepos, c.base)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoPost activates a repository.
|
||||
func (c *client) RepoPost(owner string, name string) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepo, c.base, owner, name)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoPatch updates a repository.
|
||||
func (c *client) RepoPatch(in *Repo) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepo, c.base, in.Owner, in.Name)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoDel deletes a repository.
|
||||
func (c *client) RepoDel(owner, name string) error {
|
||||
uri := fmt.Sprintf(pathRepo, c.base, owner, name)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// RepoKey returns a repository public key.
|
||||
func (c *client) RepoKey(owner, name string) (*Key, error) {
|
||||
out := new(Key)
|
||||
uri := fmt.Sprintf(pathKey, c.base, owner, name)
|
||||
rc, err := c.stream(uri, "GET", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
raw, _ := ioutil.ReadAll(rc)
|
||||
out.Public = string(raw)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Build returns a repository build by number.
|
||||
func (c *client) Build(owner, name string, num int) (*Build, error) {
|
||||
out := new(Build)
|
||||
uri := fmt.Sprintf(pathBuild, c.base, owner, name, num)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Build returns the latest repository build by branch.
|
||||
func (c *client) BuildLast(owner, name, branch string) (*Build, error) {
|
||||
out := new(Build)
|
||||
uri := fmt.Sprintf(pathBuild, c.base, owner, name, "latest")
|
||||
if len(branch) != 0 {
|
||||
uri += "?branch=" + branch
|
||||
}
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// BuildList returns a list of recent builds for the
|
||||
// the specified repository.
|
||||
func (c *client) BuildList(owner, name string) ([]*Build, error) {
|
||||
var out []*Build
|
||||
uri := fmt.Sprintf(pathBuilds, c.base, owner, name)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// BuildStart re-starts a stopped build.
|
||||
func (c *client) BuildStart(owner, name string, num int) (*Build, error) {
|
||||
out := new(Build)
|
||||
uri := fmt.Sprintf(pathBuild, c.base, owner, name, num)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// BuildStop cancels the running job.
|
||||
func (c *client) BuildStop(owner, name string, num, job int) error {
|
||||
uri := fmt.Sprintf(pathJob, c.base, owner, name, num, job)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// BuildFork re-starts a stopped build with a new build number,
|
||||
// preserving the prior history.
|
||||
func (c *client) BuildFork(owner, name string, num int) (*Build, error) {
|
||||
out := new(Build)
|
||||
uri := fmt.Sprintf(pathBuild+"?fork=true", c.base, owner, name, num)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// BuildLogs returns the build logs for the specified job.
|
||||
func (c *client) BuildLogs(owner, name string, num, job int) (io.ReadCloser, error) {
|
||||
uri := fmt.Sprintf(pathLog, c.base, owner, name, num, job)
|
||||
return c.stream(uri, "GET", nil, nil)
|
||||
}
|
||||
|
||||
// Deploy triggers a deployment for an existing build using the
|
||||
// specified target environment.
|
||||
func (c *client) Deploy(owner, name string, num int, env string) (*Build, error) {
|
||||
out := new(Build)
|
||||
val := url.Values{}
|
||||
val.Set("fork", "true")
|
||||
val.Set("event", "deployment")
|
||||
val.Set("deploy_to", env)
|
||||
uri := fmt.Sprintf(pathBuild+"?"+val.Encode(), c.base, owner, name, num)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// SecretPost create or updates a repository secret.
|
||||
func (c *client) SecretPost(owner, name string, secret *Secret) error {
|
||||
uri := fmt.Sprintf(pathSecrets, c.base, owner, name)
|
||||
return c.post(uri, secret, nil)
|
||||
}
|
||||
|
||||
// SecretDel deletes a named repository secret.
|
||||
func (c *client) SecretDel(owner, name, secret string) error {
|
||||
uri := fmt.Sprintf(pathSecret, c.base, owner, name, secret)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// Sign returns a cryptographic signature for the input string.
|
||||
func (c *client) Sign(owner, name string, in []byte) ([]byte, error) {
|
||||
buf := bytes.Buffer{}
|
||||
buf.Write(in)
|
||||
uri := fmt.Sprintf(pathSign, c.base, owner, name)
|
||||
rc, err := c.stream(uri, "POST", buf, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
return ioutil.ReadAll(rc)
|
||||
}
|
||||
|
||||
// Node returns a node by id.
|
||||
func (c *client) Node(id int64) (*Node, error) {
|
||||
out := new(Node)
|
||||
uri := fmt.Sprintf(pathNode, c.base, id)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// NodeList returns a list of all registered worker nodes.
|
||||
func (c *client) NodeList() ([]*Node, error) {
|
||||
var out []*Node
|
||||
uri := fmt.Sprintf(pathNodes, c.base)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// NodePost registers a new worker node.
|
||||
func (c *client) NodePost(in *Node) (*Node, error) {
|
||||
out := new(Node)
|
||||
uri := fmt.Sprintf(pathNodes, c.base)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// NodeDel deletes a worker node.
|
||||
func (c *client) NodeDel(id int64) error {
|
||||
uri := fmt.Sprintf(pathNode, c.base, id)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// http request helper functions
|
||||
//
|
||||
|
||||
// helper function for making an http GET request.
|
||||
func (c *client) get(rawurl string, out interface{}) error {
|
||||
return c.do(rawurl, "GET", nil, out)
|
||||
}
|
||||
|
||||
// helper function for making an http POST request.
|
||||
func (c *client) post(rawurl string, in, out interface{}) error {
|
||||
return c.do(rawurl, "POST", in, out)
|
||||
}
|
||||
|
||||
// helper function for making an http PUT request.
|
||||
func (c *client) put(rawurl string, in, out interface{}) error {
|
||||
return c.do(rawurl, "PUT", in, out)
|
||||
}
|
||||
|
||||
// helper function for making an http PATCH request.
|
||||
func (c *client) patch(rawurl string, in, out interface{}) error {
|
||||
return c.do(rawurl, "PATCH", in, out)
|
||||
}
|
||||
|
||||
// helper function for making an http DELETE request.
|
||||
func (c *client) delete(rawurl string) error {
|
||||
return c.do(rawurl, "DELETE", nil, nil)
|
||||
}
|
||||
|
||||
// helper function to make an http request
|
||||
func (c *client) do(rawurl, method string, in, out interface{}) error {
|
||||
// executes the http request and returns the body as
|
||||
// and io.ReadCloser
|
||||
body, err := c.stream(rawurl, method, in, out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
// if a json response is expected, parse and return
|
||||
// the json response.
|
||||
if out != nil {
|
||||
return json.NewDecoder(body).Decode(out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// helper function to stream an http request
|
||||
func (c *client) stream(rawurl, method string, in, out interface{}) (io.ReadCloser, error) {
|
||||
uri, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if we are posting or putting data, we need to
|
||||
// write it to the body of the request.
|
||||
var buf io.ReadWriter
|
||||
if in == nil {
|
||||
// nothing
|
||||
} else if rw, ok := in.(io.ReadWriter); ok {
|
||||
buf = rw
|
||||
} else {
|
||||
buf = new(bytes.Buffer)
|
||||
err := json.NewEncoder(buf).Encode(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// creates a new http request to bitbucket.
|
||||
req, err := http.NewRequest(method, uri.String(), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in == nil {
|
||||
// nothing
|
||||
} else if _, ok := in.(io.ReadWriter); ok {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
} else {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode > http.StatusPartialContent {
|
||||
defer resp.Body.Close()
|
||||
out, _ := ioutil.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf(string(out))
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package drone
|
||||
|
||||
// Events
|
||||
const (
|
||||
EventPush = "push"
|
||||
EventPull = "pull_request"
|
||||
EventTag = "tag"
|
||||
EventDeploy = "deployment"
|
||||
)
|
||||
|
||||
// Statuses
|
||||
const (
|
||||
StatusSkipped = "skipped"
|
||||
StatusPending = "pending"
|
||||
StatusRunning = "running"
|
||||
StatusSuccess = "success"
|
||||
StatusFailure = "failure"
|
||||
StatusKilled = "killed"
|
||||
StatusError = "error"
|
||||
)
|
||||
|
||||
// Architectures
|
||||
const (
|
||||
Freebsd_386 uint = iota
|
||||
Freebsd_amd64
|
||||
Freebsd_arm
|
||||
Linux_386
|
||||
Linux_amd64
|
||||
Linux_arm
|
||||
Linux_arm64
|
||||
Solaris_amd64
|
||||
Windows_386
|
||||
Windows_amd64
|
||||
)
|
||||
|
||||
// Architecture Map
|
||||
var Archs = map[string]uint{
|
||||
"freebsd_386": Freebsd_386,
|
||||
"freebsd_amd64": Freebsd_amd64,
|
||||
"freebsd_arm": Freebsd_arm,
|
||||
"linux_386": Linux_386,
|
||||
"linux_amd64": Linux_amd64,
|
||||
"linux_arm": Linux_arm,
|
||||
"linux_arm64": Linux_arm64,
|
||||
"solaris_amd64": Solaris_amd64,
|
||||
"windows_386": Windows_386,
|
||||
"windows_amd64": Windows_amd64,
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package drone
|
||||
|
||||
import "io"
|
||||
|
||||
// Client describes a drone client.
|
||||
type Client interface {
|
||||
// Self returns the currently authenticated user.
|
||||
Self() (*User, error)
|
||||
|
||||
// User returns a user by login.
|
||||
User(string) (*User, error)
|
||||
|
||||
// UserList returns a list of all registered users.
|
||||
UserList() ([]*User, error)
|
||||
|
||||
// UserPost creates a new user account.
|
||||
UserPost(*User) (*User, error)
|
||||
|
||||
// UserPatch updates a user account.
|
||||
UserPatch(*User) (*User, error)
|
||||
|
||||
// UserDel deletes a user account.
|
||||
UserDel(string) error
|
||||
|
||||
// UserFeed returns the user's activity feed.
|
||||
UserFeed() ([]*Activity, error)
|
||||
|
||||
// Repo returns a repository by name.
|
||||
Repo(string, string) (*Repo, error)
|
||||
|
||||
// RepoList returns a list of all repositories to which
|
||||
// the user has explicit access in the host system.
|
||||
RepoList() ([]*Repo, error)
|
||||
|
||||
// RepoPost activates a repository.
|
||||
RepoPost(string, string) (*Repo, error)
|
||||
|
||||
// RepoPatch updates a repository.
|
||||
RepoPatch(*Repo) (*Repo, error)
|
||||
|
||||
// RepoDel deletes a repository.
|
||||
RepoDel(string, string) error
|
||||
|
||||
// RepoKey returns a repository public key.
|
||||
RepoKey(string, string) (*Key, error)
|
||||
|
||||
// Build returns a repository build by number.
|
||||
Build(string, string, int) (*Build, error)
|
||||
|
||||
// BuildLast returns the latest repository build by branch.
|
||||
// An empty branch will result in the default branch.
|
||||
BuildLast(string, string, string) (*Build, error)
|
||||
|
||||
// BuildList returns a list of recent builds for the
|
||||
// the specified repository.
|
||||
BuildList(string, string) ([]*Build, error)
|
||||
|
||||
// BuildStart re-starts a stopped build.
|
||||
BuildStart(string, string, int) (*Build, error)
|
||||
|
||||
// BuildStop stops the specified running job for given build.
|
||||
BuildStop(string, string, int, int) error
|
||||
|
||||
// BuildFork re-starts a stopped build with a new build number,
|
||||
// preserving the prior history.
|
||||
BuildFork(string, string, int) (*Build, error)
|
||||
|
||||
// BuildLogs returns the build logs for the specified job.
|
||||
BuildLogs(string, string, int, int) (io.ReadCloser, error)
|
||||
|
||||
// Deploy triggers a deployment for an existing build using the
|
||||
// specified target environment.
|
||||
Deploy(string, string, int, string) (*Build, error)
|
||||
|
||||
// Sign returns a cryptographic signature for the input string.
|
||||
Sign(string, string, []byte) ([]byte, error)
|
||||
|
||||
// SecretPost create or updates a repository secret.
|
||||
SecretPost(string, string, *Secret) error
|
||||
|
||||
// SecretDel deletes a named repository secret.
|
||||
SecretDel(string, string, string) error
|
||||
|
||||
// Node returns a node by id.
|
||||
Node(int64) (*Node, error)
|
||||
|
||||
// NodeList returns a list of all registered worker nodes.
|
||||
NodeList() ([]*Node, error)
|
||||
|
||||
// NodePost registers a new worker node.
|
||||
NodePost(*Node) (*Node, error)
|
||||
|
||||
// NodeDel deletes a worker node.
|
||||
NodeDel(int64) error
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
package drone
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// User represents a user account.
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar_url"`
|
||||
Active bool `json:"active"`
|
||||
Admin bool `json:"admin"`
|
||||
}
|
||||
|
||||
// Repo represents a version control repository.
|
||||
type Repo struct {
|
||||
ID int64 `json:"id"`
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Avatar string `json:"avatar_url"`
|
||||
Link string `json:"link_url"`
|
||||
Clone string `json:"clone_url"`
|
||||
Branch string `json:"default_branch"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
IsPrivate bool `json:"private"`
|
||||
IsTrusted bool `json:"trusted"`
|
||||
AllowPull bool `json:"allow_pr"`
|
||||
AllowPush bool `json:"allow_push"`
|
||||
AllowDeploy bool `json:"allow_deploys"`
|
||||
AllowTag bool `json:"allow_tags"`
|
||||
}
|
||||
|
||||
// Build represents the process of compiling and testing a changeset,
|
||||
// typically triggered by the remote system (ie GitHub).
|
||||
type Build struct {
|
||||
ID int64 `json:"id"`
|
||||
Number int `json:"number"`
|
||||
Event string `json:"event"`
|
||||
Status string `json:"status"`
|
||||
Enqueued int64 `json:"enqueued_at"`
|
||||
Created int64 `json:"created_at"`
|
||||
Started int64 `json:"started_at"`
|
||||
Finished int64 `json:"finished_at"`
|
||||
Deploy string `json:"deploy_to"`
|
||||
Commit string `json:"commit"`
|
||||
Branch string `json:"branch"`
|
||||
Ref string `json:"ref"`
|
||||
Refspec string `json:"refspec"`
|
||||
Remote string `json:"remote"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Author string `json:"author"`
|
||||
Avatar string `json:"author_avatar"`
|
||||
Email string `json:"author_email"`
|
||||
Link string `json:"link_url"`
|
||||
}
|
||||
|
||||
// Job represents a single job that is being executed as part
|
||||
// of a Build.
|
||||
type Job struct {
|
||||
ID int64 `json:"id"`
|
||||
Number int `json:"number"`
|
||||
Status string `json:"status"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
Enqueued int64 `json:"enqueued_at"`
|
||||
Started int64 `json:"started_at"`
|
||||
Finished int64 `json:"finished_at"`
|
||||
|
||||
Environment map[string]string `json:"environment"`
|
||||
}
|
||||
|
||||
// Secret represents a repository secret.
|
||||
type Secret struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Image []string `json:"image"`
|
||||
Event []string `json:"event"`
|
||||
}
|
||||
|
||||
// Activity represents a build activity. It combines the
|
||||
// build details with summary Repository information.
|
||||
type Activity struct {
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Number int `json:"number"`
|
||||
Event string `json:"event"`
|
||||
Status string `json:"status"`
|
||||
Enqueued int64 `json:"enqueued_at"`
|
||||
Created int64 `json:"created_at"`
|
||||
Started int64 `json:"started_at"`
|
||||
Finished int64 `json:"finished_at"`
|
||||
Commit string `json:"commit"`
|
||||
Branch string `json:"branch"`
|
||||
Ref string `json:"ref"`
|
||||
Refspec string `json:"refspec"`
|
||||
Remote string `json:"remote"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Author string `json:"author"`
|
||||
Avatar string `json:"author_avatar"`
|
||||
Email string `json:"author_email"`
|
||||
Link string `json:"link_url"`
|
||||
}
|
||||
|
||||
// Node represents a local or remote Docker daemon that is
|
||||
// responsible for running jobs.
|
||||
type Node struct {
|
||||
ID int64 `json:"id"`
|
||||
Addr string `json:"address"`
|
||||
Arch string `json:"architecture"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
CA string `json:"ca"`
|
||||
}
|
||||
|
||||
// Key represents an RSA public and private key assigned to a
|
||||
// repository. It may be used to clone private repositories, or as
|
||||
// a deployment key.
|
||||
type Key struct {
|
||||
Public string `json:"public"`
|
||||
Private string `json:"private"`
|
||||
}
|
||||
|
||||
// Netrc defines a default .netrc file that should be injected
|
||||
// into the build environment. It will be used to authorize access
|
||||
// to https resources, such as git+https clones.
|
||||
type Netrc struct {
|
||||
Machine string `json:"machine"`
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// netrc is only used to allow compatibility with older plugins that rely on
|
||||
// the JSON "user" attribute as a password value. In Drone 0.6 breaking
|
||||
// changes will be introduced and this netrc struct and the Netrc UnmarshalJSON
|
||||
// and MarshalJSON will be removed.
|
||||
type netrc struct {
|
||||
Machine string `json:"machine"`
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
PasswordOld string `json:"user"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (n *Netrc) UnmarshalJSON(b []byte) error {
|
||||
x := &netrc{}
|
||||
err := json.Unmarshal(b, x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.Machine = x.Machine
|
||||
n.Login = x.Login
|
||||
n.Password = x.Password
|
||||
if x.PasswordOld != "" {
|
||||
n.Password = x.PasswordOld
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (n Netrc) MarshalJSON() ([]byte, error) {
|
||||
x := &netrc{
|
||||
Machine: n.Machine,
|
||||
Login: n.Login,
|
||||
Password: n.Password,
|
||||
PasswordOld: n.Password,
|
||||
}
|
||||
return json.Marshal(x)
|
||||
}
|
||||
|
||||
// System represents the drone system.
|
||||
type System struct {
|
||||
Version string `json:"version"`
|
||||
Link string `json:"link_url"`
|
||||
Plugins []string `json:"plugins"`
|
||||
Globals []string `json:"globals"`
|
||||
Escalates []string `json:"privileged_plugins"`
|
||||
}
|
||||
|
||||
// Workspace defines the build's workspace inside the
|
||||
// container. This helps the plugin locate the source
|
||||
// code directory.
|
||||
type Workspace struct {
|
||||
Root string `json:"root"`
|
||||
Path string `json:"path"`
|
||||
|
||||
Netrc *Netrc `json:"netrc"`
|
||||
Keys *Key `json:"keys"`
|
||||
}
|
||||
|
||||
// Payload defines the full payload send to plugins.
|
||||
type Payload struct {
|
||||
Yaml string `json:"config"`
|
||||
YamlEnc string `json:"secret"`
|
||||
Repo *Repo `json:"repo"`
|
||||
Build *Build `json:"build"`
|
||||
BuildLast *Build `json:"build_last"`
|
||||
Job *Job `json:"job"`
|
||||
Netrc *Netrc `json:"netrc"`
|
||||
Keys *Key `json:"keys"`
|
||||
System *System `json:"system"`
|
||||
Workspace *Workspace `json:"workspace"`
|
||||
Vargs interface{} `json:"vargs"`
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package drone
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// StringSlice representes a string or an array of strings.
|
||||
type StringSlice struct {
|
||||
parts []string
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals bytes into a StringSlice.
|
||||
func (e *StringSlice) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
p := make([]string, 0, 1)
|
||||
if err := json.Unmarshal(b, &p); err != nil {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
p = append(p, s)
|
||||
}
|
||||
|
||||
e.parts = p
|
||||
return nil
|
||||
}
|
||||
|
||||
// Len returns the number of strings in a StringSlice.
|
||||
func (e *StringSlice) Len() int {
|
||||
if e == nil {
|
||||
return 0
|
||||
}
|
||||
return len(e.parts)
|
||||
}
|
||||
|
||||
// Slice returns the slice of strings in a StringSlice.
|
||||
func (e *StringSlice) Slice() []string {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.parts
|
||||
}
|
||||
|
||||
// NewStringSlice takes a slice of strings and returns a StringSlice.
|
||||
func NewStringSlice(parts []string) StringSlice {
|
||||
return StringSlice{parts}
|
||||
}
|
||||
|
||||
// StringMap representes a string or a map of strings.
|
||||
type StringMap struct {
|
||||
parts map[string]string
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals bytes into a StringMap.
|
||||
func (e *StringMap) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
p := map[string]string{}
|
||||
if err := json.Unmarshal(b, &p); err != nil {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
p[""] = s
|
||||
}
|
||||
|
||||
e.parts = p
|
||||
return nil
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the StringMap.
|
||||
func (e *StringMap) Len() int {
|
||||
if e == nil {
|
||||
return 0
|
||||
}
|
||||
return len(e.parts)
|
||||
}
|
||||
|
||||
// String satisfies the fmt.Stringer interface.
|
||||
func (e *StringMap) String() (str string) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
for _, val := range e.parts {
|
||||
return val // returns the first string value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Map returns StringMap as a map of string to string.
|
||||
func (e *StringMap) Map() map[string]string {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.parts
|
||||
}
|
||||
|
||||
// NewStringMap takes a map of string to string and returns a StringMap.
|
||||
func NewStringMap(parts map[string]string) StringMap {
|
||||
return StringMap{parts}
|
||||
}
|
||||
|
||||
// StringInt representes a string or an integer value.
|
||||
type StringInt struct {
|
||||
value string
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals bytes into a StringInt.
|
||||
func (e *StringInt) UnmarshalJSON(b []byte) error {
|
||||
var num int
|
||||
err := json.Unmarshal(b, &num)
|
||||
if err == nil {
|
||||
e.value = strconv.Itoa(num)
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(b, &e.value)
|
||||
}
|
||||
|
||||
func (e StringInt) String() string {
|
||||
return e.value
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Standard Input
|
||||
var Stdin *ParamSet
|
||||
|
||||
func init() {
|
||||
// defaults to stdin
|
||||
Stdin = NewParamSet(os.Stdin)
|
||||
|
||||
// check for params after the double dash
|
||||
// in the command string
|
||||
for i, argv := range os.Args {
|
||||
if argv == "--" {
|
||||
arg := os.Args[i+1]
|
||||
buf := bytes.NewBufferString(arg)
|
||||
Stdin = NewParamSet(buf)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this init function is deprecated, but I'm keeping it
|
||||
// around just in case it proves useful in the future.
|
||||
func deprecated_init() {
|
||||
// if piping from stdin we can just exit
|
||||
// and use the default Stdin value
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// check for params after the double dash
|
||||
// in the command string
|
||||
for i, argv := range os.Args {
|
||||
if argv == "--" {
|
||||
arg := os.Args[i+1]
|
||||
buf := bytes.NewBufferString(arg)
|
||||
Stdin = NewParamSet(buf)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// else use the first variable in the list
|
||||
if len(os.Args) > 1 {
|
||||
buf := bytes.NewBufferString(os.Args[1])
|
||||
Stdin = NewParamSet(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// ParamSet describes a parameter set.
|
||||
type ParamSet struct {
|
||||
reader io.Reader
|
||||
params map[string]interface{}
|
||||
}
|
||||
|
||||
// NewParamSet takes a io.Reader and returns a ParamSet.
|
||||
func NewParamSet(reader io.Reader) *ParamSet {
|
||||
var p = new(ParamSet)
|
||||
p.reader = reader
|
||||
p.params = map[string]interface{}{}
|
||||
return p
|
||||
}
|
||||
|
||||
// Param defines a parameter with the specified name.
|
||||
func (p ParamSet) Param(name string, value interface{}) {
|
||||
p.params[name] = value
|
||||
}
|
||||
|
||||
// Parse parses parameter definitions from the map.
|
||||
func (p ParamSet) Parse() error {
|
||||
raw := map[string]json.RawMessage{}
|
||||
err := json.NewDecoder(p.reader).Decode(&raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key, val := range p.params {
|
||||
data, ok := raw[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
err := json.Unmarshal(data, val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to unarmshal %s. %s", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal parses the JSON payload from the command
|
||||
// arguments and unmarshal into a value pointed to by v.
|
||||
func (p ParamSet) Unmarshal(v interface{}) error {
|
||||
return json.NewDecoder(p.reader).Decode(v)
|
||||
}
|
||||
|
||||
// Param defines a parameter with the specified name.
|
||||
func Param(name string, value interface{}) {
|
||||
Stdin.Param(name, value)
|
||||
}
|
||||
|
||||
// Parse parses parameter definitions from the map.
|
||||
func Parse() error {
|
||||
return Stdin.Parse()
|
||||
}
|
||||
|
||||
// Unmarshal parses the JSON payload from the command
|
||||
// arguments and unmarshal into a value pointed to by v.
|
||||
func Unmarshal(v interface{}) error {
|
||||
return Stdin.Unmarshal(v)
|
||||
}
|
||||
|
||||
// MustUnmarshal parses the JSON payload from the command
|
||||
// arguments and unmarshal into a value pointed to by v.
|
||||
func MustUnmarshal(v interface{}) error {
|
||||
return Stdin.Unmarshal(v)
|
||||
}
|
||||
|
||||
// MustParse parses parameter definitions from the map
|
||||
// and panics if there is a parsing error.
|
||||
func MustParse() {
|
||||
err := Parse()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/aymerick/raymond"
|
||||
"github.com/drone/drone-go/drone"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func init() {
|
||||
raymond.RegisterHelpers(funcs)
|
||||
}
|
||||
|
||||
// Render parses and executes a template, returning the results
|
||||
// in string format.
|
||||
func Render(template string, payload *drone.Payload) (string, error) {
|
||||
if strings.HasPrefix(template, "http") {
|
||||
resp, err := http.Get(template)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to fetch remote template: %s", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to read remote template: %s", err)
|
||||
}
|
||||
|
||||
template = string(content)
|
||||
}
|
||||
|
||||
return raymond.Render(template, normalize(payload))
|
||||
}
|
||||
|
||||
// RenderTrim parses and executes a template, returning the results
|
||||
// in string format. The result is trimmed to remove left and right
|
||||
// padding and newlines that may be added unintentially in the
|
||||
// template markup.
|
||||
func RenderTrim(template string, payload *drone.Payload) (string, error) {
|
||||
out, err := Render(template, payload)
|
||||
return strings.Trim(out, " \n"), err
|
||||
}
|
||||
|
||||
// Write parses and executes a template, writing the results to
|
||||
// writer w.
|
||||
func Write(w io.Writer, template string, payload *drone.Payload) error {
|
||||
out, err := Render(template, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.WriteString(w, out)
|
||||
return err
|
||||
}
|
||||
|
||||
var funcs = map[string]interface{}{
|
||||
"uppercasefirst": uppercaseFirst,
|
||||
"uppercase": strings.ToUpper,
|
||||
"lowercase": strings.ToLower,
|
||||
"duration": toDuration,
|
||||
"datetime": toDatetime,
|
||||
"success": isSuccess,
|
||||
"failure": isFailure,
|
||||
"truncate": truncate,
|
||||
"urlencode": urlencode,
|
||||
}
|
||||
|
||||
// truncate is a helper function that truncates a string by a particular length.
|
||||
func truncate(s string, len int) string {
|
||||
if utf8.RuneCountInString(s) <= len {
|
||||
return s
|
||||
}
|
||||
runes := []rune(s)
|
||||
return string(runes[:len])
|
||||
|
||||
}
|
||||
|
||||
// uppercaseFirst is a helper function that takes a string and capitalizes
|
||||
// the first letter.
|
||||
func uppercaseFirst(s string) string {
|
||||
a := []rune(s)
|
||||
a[0] = unicode.ToUpper(a[0])
|
||||
s = string(a)
|
||||
return s
|
||||
}
|
||||
|
||||
// toDuration is a helper function that calculates a duration for a start and
|
||||
// and end time, and returns the duration in string format.
|
||||
func toDuration(started, finished float64) string {
|
||||
return fmt.Sprintln(time.Duration(finished-started) * time.Second)
|
||||
}
|
||||
|
||||
// toDatetime is a helper function that converts a unix timestamp to a string.
|
||||
func toDatetime(timestamp float64, layout, zone string) string {
|
||||
if len(zone) == 0 {
|
||||
return time.Unix(int64(timestamp), 0).Format(layout)
|
||||
}
|
||||
loc, err := time.LoadLocation(zone)
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing timezone, defaulting to local timezone. %s\n", err)
|
||||
return time.Unix(int64(timestamp), 0).Local().Format(layout)
|
||||
}
|
||||
return time.Unix(int64(timestamp), 0).In(loc).Format(layout)
|
||||
}
|
||||
|
||||
// isSuccess is a helper function that executes a block iff the status
|
||||
// is success, else it executes the else block.
|
||||
func isSuccess(conditional bool, options *raymond.Options) string {
|
||||
if !conditional {
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
switch options.ParamStr(0) {
|
||||
case "success":
|
||||
return options.Fn()
|
||||
default:
|
||||
return options.Inverse()
|
||||
}
|
||||
}
|
||||
|
||||
// isFailure is a helper function that executes a block iff the status
|
||||
// is a form of failure, else it executes the else block.
|
||||
func isFailure(conditional bool, options *raymond.Options) string {
|
||||
if !conditional {
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
switch options.ParamStr(0) {
|
||||
case "failure", "error", "killed":
|
||||
return options.Fn()
|
||||
default:
|
||||
return options.Inverse()
|
||||
}
|
||||
}
|
||||
|
||||
// normalize takes a Go representation of the variable, marshals
|
||||
// to json and then unmarshals to a map[string]interfacce{}. This
|
||||
// is important because it let's us use the JSON variable names
|
||||
// in our template
|
||||
func normalize(in interface{}) map[string]interface{} {
|
||||
data, _ := json.Marshal(in) // we own the types, so this should never fail
|
||||
|
||||
out := map[string]interface{}{}
|
||||
json.Unmarshal(data, &out)
|
||||
return out
|
||||
}
|
||||
|
||||
// urlencode is a helper function that encode a block
|
||||
// to url safe string.
|
||||
func urlencode(options *raymond.Options) string {
|
||||
return url.QueryEscape(options.Fn())
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
|
@ -0,0 +1,3 @@
|
|||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
@ -0,0 +1,31 @@
|
|||
Go support for Protocol Buffers - Google's data interchange format
|
||||
|
||||
Copyright 2010 The Go Authors. All rights reserved.
|
||||
https://github.com/golang/protobuf
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# Includable Makefile to add a rule for generating .pb.go files from .proto files
|
||||
# (Google protocol buffer descriptions).
|
||||
# Typical use if myproto.proto is a file in package mypackage in this directory:
|
||||
#
|
||||
# include $(GOROOT)/src/pkg/github.com/golang/protobuf/Make.protobuf
|
||||
|
||||
%.pb.go: %.proto
|
||||
protoc --go_out=. $<
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
all: install
|
||||
|
||||
install:
|
||||
go install ./proto ./jsonpb ./ptypes
|
||||
go install ./protoc-gen-go
|
||||
|
||||
test:
|
||||
go test ./proto ./jsonpb ./ptypes
|
||||
make -C protoc-gen-go/testdata test
|
||||
|
||||
clean:
|
||||
go clean ./...
|
||||
|
||||
nuke:
|
||||
go clean -i ./...
|
||||
|
||||
regenerate:
|
||||
make -C protoc-gen-go/descriptor regenerate
|
||||
make -C protoc-gen-go/plugin regenerate
|
||||
make -C protoc-gen-go/testdata regenerate
|
||||
make -C proto/testdata regenerate
|
||||
make -C jsonpb/jsonpb_test_proto regenerate
|
|
@ -0,0 +1,199 @@
|
|||
# Go support for Protocol Buffers
|
||||
|
||||
Google's data interchange format.
|
||||
Copyright 2010 The Go Authors.
|
||||
https://github.com/golang/protobuf
|
||||
|
||||
This package and the code it generates requires at least Go 1.4.
|
||||
|
||||
This software implements Go bindings for protocol buffers. For
|
||||
information about protocol buffers themselves, see
|
||||
https://developers.google.com/protocol-buffers/
|
||||
|
||||
## Installation ##
|
||||
|
||||
To use this software, you must:
|
||||
- Install the standard C++ implementation of protocol buffers from
|
||||
https://developers.google.com/protocol-buffers/
|
||||
- Of course, install the Go compiler and tools from
|
||||
https://golang.org/
|
||||
See
|
||||
https://golang.org/doc/install
|
||||
for details or, if you are using gccgo, follow the instructions at
|
||||
https://golang.org/doc/install/gccgo
|
||||
- Grab the code from the repository and install the proto package.
|
||||
The simplest way is to run `go get -u github.com/golang/protobuf/{proto,protoc-gen-go}`.
|
||||
The compiler plugin, protoc-gen-go, will be installed in $GOBIN,
|
||||
defaulting to $GOPATH/bin. It must be in your $PATH for the protocol
|
||||
compiler, protoc, to find it.
|
||||
|
||||
This software has two parts: a 'protocol compiler plugin' that
|
||||
generates Go source files that, once compiled, can access and manage
|
||||
protocol buffers; and a library that implements run-time support for
|
||||
encoding (marshaling), decoding (unmarshaling), and accessing protocol
|
||||
buffers.
|
||||
|
||||
There is support for gRPC in Go using protocol buffers.
|
||||
See the note at the bottom of this file for details.
|
||||
|
||||
There are no insertion points in the plugin.
|
||||
|
||||
|
||||
## Using protocol buffers with Go ##
|
||||
|
||||
Once the software is installed, there are two steps to using it.
|
||||
First you must compile the protocol buffer definitions and then import
|
||||
them, with the support library, into your program.
|
||||
|
||||
To compile the protocol buffer definition, run protoc with the --go_out
|
||||
parameter set to the directory you want to output the Go code to.
|
||||
|
||||
protoc --go_out=. *.proto
|
||||
|
||||
The generated files will be suffixed .pb.go. See the Test code below
|
||||
for an example using such a file.
|
||||
|
||||
|
||||
The package comment for the proto library contains text describing
|
||||
the interface provided in Go for protocol buffers. Here is an edited
|
||||
version.
|
||||
|
||||
==========
|
||||
|
||||
The proto package converts data structures to and from the
|
||||
wire format of protocol buffers. It works in concert with the
|
||||
Go source code generated for .proto files by the protocol compiler.
|
||||
|
||||
A summary of the properties of the protocol buffer interface
|
||||
for a protocol buffer variable v:
|
||||
|
||||
- Names are turned from camel_case to CamelCase for export.
|
||||
- There are no methods on v to set fields; just treat
|
||||
them as structure fields.
|
||||
- There are getters that return a field's value if set,
|
||||
and return the field's default value if unset.
|
||||
The getters work even if the receiver is a nil message.
|
||||
- The zero value for a struct is its correct initialization state.
|
||||
All desired fields must be set before marshaling.
|
||||
- A Reset() method will restore a protobuf struct to its zero state.
|
||||
- Non-repeated fields are pointers to the values; nil means unset.
|
||||
That is, optional or required field int32 f becomes F *int32.
|
||||
- Repeated fields are slices.
|
||||
- Helper functions are available to aid the setting of fields.
|
||||
Helpers for getting values are superseded by the
|
||||
GetFoo methods and their use is deprecated.
|
||||
msg.Foo = proto.String("hello") // set field
|
||||
- Constants are defined to hold the default values of all fields that
|
||||
have them. They have the form Default_StructName_FieldName.
|
||||
Because the getter methods handle defaulted values,
|
||||
direct use of these constants should be rare.
|
||||
- Enums are given type names and maps from names to values.
|
||||
Enum values are prefixed with the enum's type name. Enum types have
|
||||
a String method, and a Enum method to assist in message construction.
|
||||
- Nested groups and enums have type names prefixed with the name of
|
||||
the surrounding message type.
|
||||
- Extensions are given descriptor names that start with E_,
|
||||
followed by an underscore-delimited list of the nested messages
|
||||
that contain it (if any) followed by the CamelCased name of the
|
||||
extension field itself. HasExtension, ClearExtension, GetExtension
|
||||
and SetExtension are functions for manipulating extensions.
|
||||
- Oneof field sets are given a single field in their message,
|
||||
with distinguished wrapper types for each possible field value.
|
||||
- Marshal and Unmarshal are functions to encode and decode the wire format.
|
||||
|
||||
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||
|
||||
- Non-repeated fields of non-message type are values instead of pointers.
|
||||
- Getters are only generated for message and oneof fields.
|
||||
- Enum types do not get an Enum method.
|
||||
|
||||
Consider file test.proto, containing
|
||||
|
||||
```proto
|
||||
package example;
|
||||
|
||||
enum FOO { X = 17; };
|
||||
|
||||
message Test {
|
||||
required string label = 1;
|
||||
optional int32 type = 2 [default=77];
|
||||
repeated int64 reps = 3;
|
||||
optional group OptionalGroup = 4 {
|
||||
required string RequiredField = 5;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To create and play with a Test object from the example package,
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"path/to/example"
|
||||
)
|
||||
|
||||
func main() {
|
||||
test := &example.Test {
|
||||
Label: proto.String("hello"),
|
||||
Type: proto.Int32(17),
|
||||
Reps: []int64{1, 2, 3},
|
||||
Optionalgroup: &example.Test_OptionalGroup {
|
||||
RequiredField: proto.String("good bye"),
|
||||
},
|
||||
}
|
||||
data, err := proto.Marshal(test)
|
||||
if err != nil {
|
||||
log.Fatal("marshaling error: ", err)
|
||||
}
|
||||
newTest := &example.Test{}
|
||||
err = proto.Unmarshal(data, newTest)
|
||||
if err != nil {
|
||||
log.Fatal("unmarshaling error: ", err)
|
||||
}
|
||||
// Now test and newTest contain the same data.
|
||||
if test.GetLabel() != newTest.GetLabel() {
|
||||
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
|
||||
}
|
||||
// etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters ##
|
||||
|
||||
To pass extra parameters to the plugin, use a comma-separated
|
||||
parameter list separated from the output directory by a colon:
|
||||
|
||||
|
||||
protoc --go_out=plugins=grpc,import_path=mypackage:. *.proto
|
||||
|
||||
|
||||
- `import_prefix=xxx` - a prefix that is added onto the beginning of
|
||||
all imports. Useful for things like generating protos in a
|
||||
subdirectory, or regenerating vendored protobufs in-place.
|
||||
- `import_path=foo/bar` - used as the package if no input files
|
||||
declare `go_package`. If it contains slashes, everything up to the
|
||||
rightmost slash is ignored.
|
||||
- `plugins=plugin1+plugin2` - specifies the list of sub-plugins to
|
||||
load. The only plugin in this repo is `grpc`.
|
||||
- `Mfoo/bar.proto=quux/shme` - declares that foo/bar.proto is
|
||||
associated with Go package quux/shme. This is subject to the
|
||||
import_prefix parameter.
|
||||
|
||||
## gRPC Support ##
|
||||
|
||||
If a proto file specifies RPC services, protoc-gen-go can be instructed to
|
||||
generate code compatible with gRPC (http://www.grpc.io/). To do this, pass
|
||||
the `plugins` parameter to protoc-gen-go; the usual way is to insert it into
|
||||
the --go_out argument to protoc:
|
||||
|
||||
protoc --go_out=plugins=grpc:. *.proto
|
||||
|
||||
## Plugins ##
|
||||
|
||||
The `protoc-gen-go/generator` package exposes a plugin interface,
|
||||
which is used by the gRPC code generation. This interface is not
|
||||
supported and is subject to incompatible changes without notice.
|
|
@ -0,0 +1,43 @@
|
|||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
install:
|
||||
go install
|
||||
|
||||
test: install generate-test-pbs
|
||||
go test
|
||||
|
||||
|
||||
generate-test-pbs:
|
||||
make install
|
||||
make -C testdata
|
||||
protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. proto3_proto/proto3.proto
|
||||
make
|
|
@ -0,0 +1,229 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Protocol buffer deep copy and merge.
|
||||
// TODO: RawMessage.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Clone returns a deep copy of a protocol buffer.
|
||||
func Clone(pb Message) Message {
|
||||
in := reflect.ValueOf(pb)
|
||||
if in.IsNil() {
|
||||
return pb
|
||||
}
|
||||
|
||||
out := reflect.New(in.Type().Elem())
|
||||
// out is empty so a merge is a deep copy.
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
return out.Interface().(Message)
|
||||
}
|
||||
|
||||
// Merge merges src into dst.
|
||||
// Required and optional fields that are set in src will be set to that value in dst.
|
||||
// Elements of repeated fields will be appended.
|
||||
// Merge panics if src and dst are not the same type, or if dst is nil.
|
||||
func Merge(dst, src Message) {
|
||||
in := reflect.ValueOf(src)
|
||||
out := reflect.ValueOf(dst)
|
||||
if out.IsNil() {
|
||||
panic("proto: nil destination")
|
||||
}
|
||||
if in.Type() != out.Type() {
|
||||
// Explicit test prior to mergeStruct so that mistyped nils will fail
|
||||
panic("proto: type mismatch")
|
||||
}
|
||||
if in.IsNil() {
|
||||
// Merging nil into non-nil is a quiet no-op
|
||||
return
|
||||
}
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
}
|
||||
|
||||
func mergeStruct(out, in reflect.Value) {
|
||||
sprop := GetProperties(in.Type())
|
||||
for i := 0; i < in.NumField(); i++ {
|
||||
f := in.Type().Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i])
|
||||
}
|
||||
|
||||
if emIn, ok := extendable(in.Addr().Interface()); ok {
|
||||
emOut, _ := extendable(out.Addr().Interface())
|
||||
mIn, muIn := emIn.extensionsRead()
|
||||
if mIn != nil {
|
||||
mOut := emOut.extensionsWrite()
|
||||
muIn.Lock()
|
||||
mergeExtension(mOut, mIn)
|
||||
muIn.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
uf := in.FieldByName("XXX_unrecognized")
|
||||
if !uf.IsValid() {
|
||||
return
|
||||
}
|
||||
uin := uf.Bytes()
|
||||
if len(uin) > 0 {
|
||||
out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...))
|
||||
}
|
||||
}
|
||||
|
||||
// mergeAny performs a merge between two values of the same type.
|
||||
// viaPtr indicates whether the values were indirected through a pointer (implying proto2).
|
||||
// prop is set if this is a struct field (it may be nil).
|
||||
func mergeAny(out, in reflect.Value, viaPtr bool, prop *Properties) {
|
||||
if in.Type() == protoMessageType {
|
||||
if !in.IsNil() {
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.ValueOf(Clone(in.Interface().(Message))))
|
||||
} else {
|
||||
Merge(out.Interface().(Message), in.Interface().(Message))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
switch in.Kind() {
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||
if !viaPtr && isProto3Zero(in) {
|
||||
return
|
||||
}
|
||||
out.Set(in)
|
||||
case reflect.Interface:
|
||||
// Probably a oneof field; copy non-nil values.
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
// Allocate destination if it is not set, or set to a different type.
|
||||
// Otherwise we will merge as normal.
|
||||
if out.IsNil() || out.Elem().Type() != in.Elem().Type() {
|
||||
out.Set(reflect.New(in.Elem().Elem().Type())) // interface -> *T -> T -> new(T)
|
||||
}
|
||||
mergeAny(out.Elem(), in.Elem(), false, nil)
|
||||
case reflect.Map:
|
||||
if in.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(in.Type()))
|
||||
}
|
||||
// For maps with value types of *T or []byte we need to deep copy each value.
|
||||
elemKind := in.Type().Elem().Kind()
|
||||
for _, key := range in.MapKeys() {
|
||||
var val reflect.Value
|
||||
switch elemKind {
|
||||
case reflect.Ptr:
|
||||
val = reflect.New(in.Type().Elem().Elem())
|
||||
mergeAny(val, in.MapIndex(key), false, nil)
|
||||
case reflect.Slice:
|
||||
val = in.MapIndex(key)
|
||||
val = reflect.ValueOf(append([]byte{}, val.Bytes()...))
|
||||
default:
|
||||
val = in.MapIndex(key)
|
||||
}
|
||||
out.SetMapIndex(key, val)
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.New(in.Elem().Type()))
|
||||
}
|
||||
mergeAny(out.Elem(), in.Elem(), true, nil)
|
||||
case reflect.Slice:
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
if in.Type().Elem().Kind() == reflect.Uint8 {
|
||||
// []byte is a scalar bytes field, not a repeated field.
|
||||
|
||||
// Edge case: if this is in a proto3 message, a zero length
|
||||
// bytes field is considered the zero value, and should not
|
||||
// be merged.
|
||||
if prop != nil && prop.proto3 && in.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Make a deep copy.
|
||||
// Append to []byte{} instead of []byte(nil) so that we never end up
|
||||
// with a nil result.
|
||||
out.SetBytes(append([]byte{}, in.Bytes()...))
|
||||
return
|
||||
}
|
||||
n := in.Len()
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeSlice(in.Type(), 0, n))
|
||||
}
|
||||
switch in.Type().Elem().Kind() {
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||
out.Set(reflect.AppendSlice(out, in))
|
||||
default:
|
||||
for i := 0; i < n; i++ {
|
||||
x := reflect.Indirect(reflect.New(in.Type().Elem()))
|
||||
mergeAny(x, in.Index(i), false, nil)
|
||||
out.Set(reflect.Append(out, x))
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
mergeStruct(out, in)
|
||||
default:
|
||||
// unknown type, so not a protocol buffer
|
||||
log.Printf("proto: don't know how to copy %v", in)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeExtension(out, in map[int32]Extension) {
|
||||
for extNum, eIn := range in {
|
||||
eOut := Extension{desc: eIn.desc}
|
||||
if eIn.value != nil {
|
||||
v := reflect.New(reflect.TypeOf(eIn.value)).Elem()
|
||||
mergeAny(v, reflect.ValueOf(eIn.value), false, nil)
|
||||
eOut.value = v.Interface()
|
||||
}
|
||||
if eIn.enc != nil {
|
||||
eOut.enc = make([]byte, len(eIn.enc))
|
||||
copy(eOut.enc, eIn.enc)
|
||||
}
|
||||
|
||||
out[extNum] = eOut
|
||||
}
|
||||
}
|
|
@ -0,0 +1,869 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Routines for decoding protocol buffer data to construct in-memory representations.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// errOverflow is returned when an integer is too large to be represented.
|
||||
var errOverflow = errors.New("proto: integer overflow")
|
||||
|
||||
// ErrInternalBadWireType is returned by generated code when an incorrect
|
||||
// wire type is encountered. It does not get returned to user code.
|
||||
var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
||||
|
||||
// The fundamental decoders that interpret bytes on the wire.
|
||||
// Those that take integer types all return uint64 and are
|
||||
// therefore of type valueDecoder.
|
||||
|
||||
// DecodeVarint reads a varint-encoded integer from the slice.
|
||||
// It returns the integer and the number of bytes consumed, or
|
||||
// zero if there is not enough.
|
||||
// This is the format for the
|
||||
// int32, int64, uint32, uint64, bool, and enum
|
||||
// protocol buffer types.
|
||||
func DecodeVarint(buf []byte) (x uint64, n int) {
|
||||
// x, n already 0
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
if n >= len(buf) {
|
||||
return 0, 0
|
||||
}
|
||||
b := uint64(buf[n])
|
||||
n++
|
||||
x |= (b & 0x7F) << shift
|
||||
if (b & 0x80) == 0 {
|
||||
return x, n
|
||||
}
|
||||
}
|
||||
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// DecodeVarint reads a varint-encoded integer from the Buffer.
|
||||
// This is the format for the
|
||||
// int32, int64, uint32, uint64, bool, and enum
|
||||
// protocol buffer types.
|
||||
func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
|
||||
i := p.index
|
||||
l := len(p.buf)
|
||||
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
if i >= l {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
b := p.buf[i]
|
||||
i++
|
||||
x |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
p.index = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
err = errOverflow
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeFixed64 reads a 64-bit integer from the Buffer.
|
||||
// This is the format for the
|
||||
// fixed64, sfixed64, and double protocol buffer types.
|
||||
func (p *Buffer) DecodeFixed64() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
i := p.index + 8
|
||||
if i < 0 || i > len(p.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
p.index = i
|
||||
|
||||
x = uint64(p.buf[i-8])
|
||||
x |= uint64(p.buf[i-7]) << 8
|
||||
x |= uint64(p.buf[i-6]) << 16
|
||||
x |= uint64(p.buf[i-5]) << 24
|
||||
x |= uint64(p.buf[i-4]) << 32
|
||||
x |= uint64(p.buf[i-3]) << 40
|
||||
x |= uint64(p.buf[i-2]) << 48
|
||||
x |= uint64(p.buf[i-1]) << 56
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeFixed32 reads a 32-bit integer from the Buffer.
|
||||
// This is the format for the
|
||||
// fixed32, sfixed32, and float protocol buffer types.
|
||||
func (p *Buffer) DecodeFixed32() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
i := p.index + 4
|
||||
if i < 0 || i > len(p.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
p.index = i
|
||||
|
||||
x = uint64(p.buf[i-4])
|
||||
x |= uint64(p.buf[i-3]) << 8
|
||||
x |= uint64(p.buf[i-2]) << 16
|
||||
x |= uint64(p.buf[i-1]) << 24
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeZigzag64 reads a zigzag-encoded 64-bit integer
|
||||
// from the Buffer.
|
||||
// This is the format used for the sint64 protocol buffer type.
|
||||
func (p *Buffer) DecodeZigzag64() (x uint64, err error) {
|
||||
x, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63)
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeZigzag32 reads a zigzag-encoded 32-bit integer
|
||||
// from the Buffer.
|
||||
// This is the format used for the sint32 protocol buffer type.
|
||||
func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
|
||||
x, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31))
|
||||
return
|
||||
}
|
||||
|
||||
// These are not ValueDecoders: they produce an array of bytes or a string.
|
||||
// bytes, embedded messages
|
||||
|
||||
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
|
||||
// This is the format used for the bytes protocol buffer
|
||||
// type and for embedded messages.
|
||||
func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
|
||||
n, err := p.DecodeVarint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nb := int(n)
|
||||
if nb < 0 {
|
||||
return nil, fmt.Errorf("proto: bad byte length %d", nb)
|
||||
}
|
||||
end := p.index + nb
|
||||
if end < p.index || end > len(p.buf) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
if !alloc {
|
||||
// todo: check if can get more uses of alloc=false
|
||||
buf = p.buf[p.index:end]
|
||||
p.index += nb
|
||||
return
|
||||
}
|
||||
|
||||
buf = make([]byte, nb)
|
||||
copy(buf, p.buf[p.index:])
|
||||
p.index += nb
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeStringBytes reads an encoded string from the Buffer.
|
||||
// This is the format used for the proto2 string type.
|
||||
func (p *Buffer) DecodeStringBytes() (s string, err error) {
|
||||
buf, err := p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
// If the protocol buffer has extensions, and the field matches, add it as an extension.
|
||||
// Otherwise, if the XXX_unrecognized field exists, append the skipped data there.
|
||||
func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error {
|
||||
oi := o.index
|
||||
|
||||
err := o.skip(t, tag, wire)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !unrecField.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ptr := structPointer_Bytes(base, unrecField)
|
||||
|
||||
// Add the skipped field to struct field
|
||||
obuf := o.buf
|
||||
|
||||
o.buf = *ptr
|
||||
o.EncodeVarint(uint64(tag<<3 | wire))
|
||||
*ptr = append(o.buf, obuf[oi:o.index]...)
|
||||
|
||||
o.buf = obuf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
func (o *Buffer) skip(t reflect.Type, tag, wire int) error {
|
||||
|
||||
var u uint64
|
||||
var err error
|
||||
|
||||
switch wire {
|
||||
case WireVarint:
|
||||
_, err = o.DecodeVarint()
|
||||
case WireFixed64:
|
||||
_, err = o.DecodeFixed64()
|
||||
case WireBytes:
|
||||
_, err = o.DecodeRawBytes(false)
|
||||
case WireFixed32:
|
||||
_, err = o.DecodeFixed32()
|
||||
case WireStartGroup:
|
||||
for {
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fwire := int(u & 0x7)
|
||||
if fwire == WireEndGroup {
|
||||
break
|
||||
}
|
||||
ftag := int(u >> 3)
|
||||
err = o.skip(t, ftag, fwire)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface representing objects that can
|
||||
// unmarshal themselves. The method should reset the receiver before
|
||||
// decoding starts. The argument points to data that may be
|
||||
// overwritten, so implementations should not keep references to the
|
||||
// buffer.
|
||||
type Unmarshaler interface {
|
||||
Unmarshal([]byte) error
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in buf and places the
|
||||
// decoded result in pb. If the struct underlying pb does not match
|
||||
// the data in buf, the results can be unpredictable.
|
||||
//
|
||||
// Unmarshal resets pb before starting to unmarshal, so any
|
||||
// existing data in pb is always removed. Use UnmarshalMerge
|
||||
// to preserve and append to existing data.
|
||||
func Unmarshal(buf []byte, pb Message) error {
|
||||
pb.Reset()
|
||||
return UnmarshalMerge(buf, pb)
|
||||
}
|
||||
|
||||
// UnmarshalMerge parses the protocol buffer representation in buf and
|
||||
// writes the decoded result to pb. If the struct underlying pb does not match
|
||||
// the data in buf, the results can be unpredictable.
|
||||
//
|
||||
// UnmarshalMerge merges into existing data in pb.
|
||||
// Most code should use Unmarshal instead.
|
||||
func UnmarshalMerge(buf []byte, pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
return u.Unmarshal(buf)
|
||||
}
|
||||
return NewBuffer(buf).Unmarshal(pb)
|
||||
}
|
||||
|
||||
// DecodeMessage reads a count-delimited message from the Buffer.
|
||||
func (p *Buffer) DecodeMessage(pb Message) error {
|
||||
enc, err := p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return NewBuffer(enc).Unmarshal(pb)
|
||||
}
|
||||
|
||||
// DecodeGroup reads a tag-delimited group from the Buffer.
|
||||
func (p *Buffer) DecodeGroup(pb Message) error {
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base)
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in the
|
||||
// Buffer and places the decoded result in pb. If the struct
|
||||
// underlying pb does not match the data in the buffer, the results can be
|
||||
// unpredictable.
|
||||
func (p *Buffer) Unmarshal(pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
err := u.Unmarshal(p.buf[p.index:])
|
||||
p.index = len(p.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
|
||||
|
||||
if collectStats {
|
||||
stats.Decode++
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// unmarshalType does the work of unmarshaling a structure.
|
||||
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
|
||||
var state errorState
|
||||
required, reqFields := prop.reqCount, uint64(0)
|
||||
|
||||
var err error
|
||||
for err == nil && o.index < len(o.buf) {
|
||||
oi := o.index
|
||||
var u uint64
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
wire := int(u & 0x7)
|
||||
if wire == WireEndGroup {
|
||||
if is_group {
|
||||
return nil // input is satisfied
|
||||
}
|
||||
return fmt.Errorf("proto: %s: wiretype end group for non-group", st)
|
||||
}
|
||||
tag := int(u >> 3)
|
||||
if tag <= 0 {
|
||||
return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire)
|
||||
}
|
||||
fieldnum, ok := prop.decoderTags.get(tag)
|
||||
if !ok {
|
||||
// Maybe it's an extension?
|
||||
if prop.extendable {
|
||||
if e, _ := extendable(structPointer_Interface(base, st)); isExtensionField(e, int32(tag)) {
|
||||
if err = o.skip(st, tag, wire); err == nil {
|
||||
extmap := e.extensionsWrite()
|
||||
ext := extmap[int32(tag)] // may be missing
|
||||
ext.enc = append(ext.enc, o.buf[oi:o.index]...)
|
||||
extmap[int32(tag)] = ext
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Maybe it's a oneof?
|
||||
if prop.oneofUnmarshaler != nil {
|
||||
m := structPointer_Interface(base, st).(Message)
|
||||
// First return value indicates whether tag is a oneof field.
|
||||
ok, err = prop.oneofUnmarshaler(m, tag, wire, o)
|
||||
if err == ErrInternalBadWireType {
|
||||
// Map the error to something more descriptive.
|
||||
// Do the formatting here to save generated code space.
|
||||
err = fmt.Errorf("bad wiretype for oneof field in %T", m)
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = o.skipAndSave(st, tag, wire, base, prop.unrecField)
|
||||
continue
|
||||
}
|
||||
p := prop.Prop[fieldnum]
|
||||
|
||||
if p.dec == nil {
|
||||
fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name)
|
||||
continue
|
||||
}
|
||||
dec := p.dec
|
||||
if wire != WireStartGroup && wire != p.WireType {
|
||||
if wire == WireBytes && p.packedDec != nil {
|
||||
// a packable field
|
||||
dec = p.packedDec
|
||||
} else {
|
||||
err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType)
|
||||
continue
|
||||
}
|
||||
}
|
||||
decErr := dec(o, p, base)
|
||||
if decErr != nil && !state.shouldContinue(decErr, p) {
|
||||
err = decErr
|
||||
}
|
||||
if err == nil && p.Required {
|
||||
// Successfully decoded a required field.
|
||||
if tag <= 64 {
|
||||
// use bitmap for fields 1-64 to catch field reuse.
|
||||
var mask uint64 = 1 << uint64(tag-1)
|
||||
if reqFields&mask == 0 {
|
||||
// new required field
|
||||
reqFields |= mask
|
||||
required--
|
||||
}
|
||||
} else {
|
||||
// This is imprecise. It can be fooled by a required field
|
||||
// with a tag > 64 that is encoded twice; that's very rare.
|
||||
// A fully correct implementation would require allocating
|
||||
// a data structure, which we would like to avoid.
|
||||
required--
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
if is_group {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if state.err != nil {
|
||||
return state.err
|
||||
}
|
||||
if required > 0 {
|
||||
// Not enough information to determine the exact field. If we use extra
|
||||
// CPU, we could determine the field only if the missing required field
|
||||
// has a tag <= 64 and we check reqFields.
|
||||
return &RequiredNotSetError{"{Unknown}"}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Individual type decoders
|
||||
// For each,
|
||||
// u is the decoded value,
|
||||
// v is a pointer to the field (pointer) in the struct
|
||||
|
||||
// Sizes of the pools to allocate inside the Buffer.
|
||||
// The goal is modest amortization and allocation
|
||||
// on at least 16-byte boundaries.
|
||||
const (
|
||||
boolPoolSize = 16
|
||||
uint32PoolSize = 8
|
||||
uint64PoolSize = 4
|
||||
)
|
||||
|
||||
// Decode a bool.
|
||||
func (o *Buffer) dec_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(o.bools) == 0 {
|
||||
o.bools = make([]bool, boolPoolSize)
|
||||
}
|
||||
o.bools[0] = u != 0
|
||||
*structPointer_Bool(base, p.field) = &o.bools[0]
|
||||
o.bools = o.bools[1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_BoolVal(base, p.field) = u != 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int32.
|
||||
func (o *Buffer) dec_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32_Set(structPointer_Word32(base, p.field), o, uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int64.
|
||||
func (o *Buffer) dec_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64_Set(structPointer_Word64(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64Val_Set(structPointer_Word64Val(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a string.
|
||||
func (o *Buffer) dec_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_String(base, p.field) = &s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_StringVal(base, p.field) = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bytes ([]byte).
|
||||
func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_Bytes(base, p.field) = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool).
|
||||
func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
*v = append(*v, u != 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error {
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded bools
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
|
||||
y := *v
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
y = append(y, u != 0)
|
||||
}
|
||||
|
||||
*v = y
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32).
|
||||
func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
structPointer_Word32Slice(base, p.field).Append(uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word32Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int32s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(uint32(u))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64).
|
||||
func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
structPointer_Word64Slice(base, p.field).Append(u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word64Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int64s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of strings ([]string).
|
||||
func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_StringSlice(base, p.field)
|
||||
*v = append(*v, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of slice of bytes ([][]byte).
|
||||
func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BytesSlice(base, p.field)
|
||||
*v = append(*v, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a map field.
|
||||
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oi := o.index // index at the end of this map entry
|
||||
o.index -= len(raw) // move buffer back to start of map entry
|
||||
|
||||
mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V
|
||||
if mptr.Elem().IsNil() {
|
||||
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
|
||||
}
|
||||
v := mptr.Elem() // map[K]V
|
||||
|
||||
// Prepare addressable doubly-indirect placeholders for the key and value types.
|
||||
// See enc_new_map for why.
|
||||
keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K
|
||||
keybase := toStructPointer(keyptr.Addr()) // **K
|
||||
|
||||
var valbase structPointer
|
||||
var valptr reflect.Value
|
||||
switch p.mtype.Elem().Kind() {
|
||||
case reflect.Slice:
|
||||
// []byte
|
||||
var dummy []byte
|
||||
valptr = reflect.ValueOf(&dummy) // *[]byte
|
||||
valbase = toStructPointer(valptr) // *[]byte
|
||||
case reflect.Ptr:
|
||||
// message; valptr is **Msg; need to allocate the intermediate pointer
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valptr.Set(reflect.New(valptr.Type().Elem()))
|
||||
valbase = toStructPointer(valptr)
|
||||
default:
|
||||
// everything else
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valbase = toStructPointer(valptr.Addr()) // **V
|
||||
}
|
||||
|
||||
// Decode.
|
||||
// This parses a restricted wire format, namely the encoding of a message
|
||||
// with two fields. See enc_new_map for the format.
|
||||
for o.index < oi {
|
||||
// tagcode for key and value properties are always a single byte
|
||||
// because they have tags 1 and 2.
|
||||
tagcode := o.buf[o.index]
|
||||
o.index++
|
||||
switch tagcode {
|
||||
case p.mkeyprop.tagcode[0]:
|
||||
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
|
||||
return err
|
||||
}
|
||||
case p.mvalprop.tagcode[0]:
|
||||
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// TODO: Should we silently skip this instead?
|
||||
return fmt.Errorf("proto: bad map data tag %d", raw[0])
|
||||
}
|
||||
}
|
||||
keyelem, valelem := keyptr.Elem(), valptr.Elem()
|
||||
if !keyelem.IsValid() {
|
||||
keyelem = reflect.Zero(p.mtype.Key())
|
||||
}
|
||||
if !valelem.IsValid() {
|
||||
valelem = reflect.Zero(p.mtype.Elem())
|
||||
}
|
||||
|
||||
v.SetMapIndex(keyelem, valelem)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a group.
|
||||
func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error {
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
return o.unmarshalType(p.stype, p.sprop, true, bas)
|
||||
}
|
||||
|
||||
// Decode an embedded message.
|
||||
func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) {
|
||||
raw, e := o.DecodeRawBytes(false)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := structPointer_Interface(bas, p.stype)
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, false, bas)
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode a slice of embedded messages.
|
||||
func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, false, base)
|
||||
}
|
||||
|
||||
// Decode a slice of embedded groups.
|
||||
func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, true, base)
|
||||
}
|
||||
|
||||
// Decode a slice of structs ([]*struct).
|
||||
func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error {
|
||||
v := reflect.New(p.stype)
|
||||
bas := toStructPointer(v)
|
||||
structPointer_StructPointerSlice(base, p.field).Append(bas)
|
||||
|
||||
if is_group {
|
||||
err := o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
return err
|
||||
}
|
||||
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := v.Interface()
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
return err
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,296 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Protocol buffer comparison.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Equal returns true iff protocol buffers a and b are equal.
|
||||
The arguments must both be pointers to protocol buffer structs.
|
||||
|
||||
Equality is defined in this way:
|
||||
- Two messages are equal iff they are the same type,
|
||||
corresponding fields are equal, unknown field sets
|
||||
are equal, and extensions sets are equal.
|
||||
- Two set scalar fields are equal iff their values are equal.
|
||||
If the fields are of a floating-point type, remember that
|
||||
NaN != x for all x, including NaN. If the message is defined
|
||||
in a proto3 .proto file, fields are not "set"; specifically,
|
||||
zero length proto3 "bytes" fields are equal (nil == {}).
|
||||
- Two repeated fields are equal iff their lengths are the same,
|
||||
and their corresponding elements are equal (a "bytes" field,
|
||||
although represented by []byte, is not a repeated field)
|
||||
- Two unset fields are equal.
|
||||
- Two unknown field sets are equal if their current
|
||||
encoded state is equal.
|
||||
- Two extension sets are equal iff they have corresponding
|
||||
elements that are pairwise equal.
|
||||
- Every other combination of things are not equal.
|
||||
|
||||
The return value is undefined if a and b are not protocol buffers.
|
||||
*/
|
||||
func Equal(a, b Message) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == b
|
||||
}
|
||||
v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b)
|
||||
if v1.Type() != v2.Type() {
|
||||
return false
|
||||
}
|
||||
if v1.Kind() == reflect.Ptr {
|
||||
if v1.IsNil() {
|
||||
return v2.IsNil()
|
||||
}
|
||||
if v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
v1, v2 = v1.Elem(), v2.Elem()
|
||||
}
|
||||
if v1.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
return equalStruct(v1, v2)
|
||||
}
|
||||
|
||||
// v1 and v2 are known to have the same type.
|
||||
func equalStruct(v1, v2 reflect.Value) bool {
|
||||
sprop := GetProperties(v1.Type())
|
||||
for i := 0; i < v1.NumField(); i++ {
|
||||
f := v1.Type().Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
f1, f2 := v1.Field(i), v2.Field(i)
|
||||
if f.Type.Kind() == reflect.Ptr {
|
||||
if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 {
|
||||
// both unset
|
||||
continue
|
||||
} else if n1 != n2 {
|
||||
// set/unset mismatch
|
||||
return false
|
||||
}
|
||||
b1, ok := f1.Interface().(raw)
|
||||
if ok {
|
||||
b2 := f2.Interface().(raw)
|
||||
// RawMessage
|
||||
if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
f1, f2 = f1.Elem(), f2.Elem()
|
||||
}
|
||||
if !equalAny(f1, f2, sprop.Prop[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if em1 := v1.FieldByName("XXX_InternalExtensions"); em1.IsValid() {
|
||||
em2 := v2.FieldByName("XXX_InternalExtensions")
|
||||
if !equalExtensions(v1.Type(), em1.Interface().(XXX_InternalExtensions), em2.Interface().(XXX_InternalExtensions)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() {
|
||||
em2 := v2.FieldByName("XXX_extensions")
|
||||
if !equalExtMap(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
uf := v1.FieldByName("XXX_unrecognized")
|
||||
if !uf.IsValid() {
|
||||
return true
|
||||
}
|
||||
|
||||
u1 := uf.Bytes()
|
||||
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
|
||||
if !bytes.Equal(u1, u2) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// v1 and v2 are known to have the same type.
|
||||
// prop may be nil.
|
||||
func equalAny(v1, v2 reflect.Value, prop *Properties) bool {
|
||||
if v1.Type() == protoMessageType {
|
||||
m1, _ := v1.Interface().(Message)
|
||||
m2, _ := v2.Interface().(Message)
|
||||
return Equal(m1, m2)
|
||||
}
|
||||
switch v1.Kind() {
|
||||
case reflect.Bool:
|
||||
return v1.Bool() == v2.Bool()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v1.Float() == v2.Float()
|
||||
case reflect.Int32, reflect.Int64:
|
||||
return v1.Int() == v2.Int()
|
||||
case reflect.Interface:
|
||||
// Probably a oneof field; compare the inner values.
|
||||
n1, n2 := v1.IsNil(), v2.IsNil()
|
||||
if n1 || n2 {
|
||||
return n1 == n2
|
||||
}
|
||||
e1, e2 := v1.Elem(), v2.Elem()
|
||||
if e1.Type() != e2.Type() {
|
||||
return false
|
||||
}
|
||||
return equalAny(e1, e2, nil)
|
||||
case reflect.Map:
|
||||
if v1.Len() != v2.Len() {
|
||||
return false
|
||||
}
|
||||
for _, key := range v1.MapKeys() {
|
||||
val2 := v2.MapIndex(key)
|
||||
if !val2.IsValid() {
|
||||
// This key was not found in the second map.
|
||||
return false
|
||||
}
|
||||
if !equalAny(v1.MapIndex(key), val2, nil) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Ptr:
|
||||
// Maps may have nil values in them, so check for nil.
|
||||
if v1.IsNil() && v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
return equalAny(v1.Elem(), v2.Elem(), prop)
|
||||
case reflect.Slice:
|
||||
if v1.Type().Elem().Kind() == reflect.Uint8 {
|
||||
// short circuit: []byte
|
||||
|
||||
// Edge case: if this is in a proto3 message, a zero length
|
||||
// bytes field is considered the zero value.
|
||||
if prop != nil && prop.proto3 && v1.Len() == 0 && v2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte))
|
||||
}
|
||||
|
||||
if v1.Len() != v2.Len() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
if !equalAny(v1.Index(i), v2.Index(i), prop) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.String:
|
||||
return v1.Interface().(string) == v2.Interface().(string)
|
||||
case reflect.Struct:
|
||||
return equalStruct(v1, v2)
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
return v1.Uint() == v2.Uint()
|
||||
}
|
||||
|
||||
// unknown type, so not a protocol buffer
|
||||
log.Printf("proto: don't know how to compare %v", v1)
|
||||
return false
|
||||
}
|
||||
|
||||
// base is the struct type that the extensions are based on.
|
||||
// x1 and x2 are InternalExtensions.
|
||||
func equalExtensions(base reflect.Type, x1, x2 XXX_InternalExtensions) bool {
|
||||
em1, _ := x1.extensionsRead()
|
||||
em2, _ := x2.extensionsRead()
|
||||
return equalExtMap(base, em1, em2)
|
||||
}
|
||||
|
||||
func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool {
|
||||
if len(em1) != len(em2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for extNum, e1 := range em1 {
|
||||
e2, ok := em2[extNum]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
m1, m2 := e1.value, e2.value
|
||||
|
||||
if m1 != nil && m2 != nil {
|
||||
// Both are unencoded.
|
||||
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// At least one is encoded. To do a semantically correct comparison
|
||||
// we need to unmarshal them first.
|
||||
var desc *ExtensionDesc
|
||||
if m := extensionMaps[base]; m != nil {
|
||||
desc = m[extNum]
|
||||
}
|
||||
if desc == nil {
|
||||
log.Printf("proto: don't know how to compare extension %d of %v", extNum, base)
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
if m1 == nil {
|
||||
m1, err = decodeExtension(e1.enc, desc)
|
||||
}
|
||||
if m2 == nil && err == nil {
|
||||
m2, err = decodeExtension(e2.enc, desc)
|
||||
}
|
||||
if err != nil {
|
||||
// The encoded form is invalid.
|
||||
log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err)
|
||||
return false
|
||||
}
|
||||
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,555 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Types and routines for supporting protocol buffer extensions.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message.
|
||||
var ErrMissingExtension = errors.New("proto: missing extension")
|
||||
|
||||
// ExtensionRange represents a range of message extensions for a protocol buffer.
|
||||
// Used in code generated by the protocol compiler.
|
||||
type ExtensionRange struct {
|
||||
Start, End int32 // both inclusive
|
||||
}
|
||||
|
||||
// extendableProto is an interface implemented by any protocol buffer generated by the current
|
||||
// proto compiler that may be extended.
|
||||
type extendableProto interface {
|
||||
Message
|
||||
ExtensionRangeArray() []ExtensionRange
|
||||
extensionsWrite() map[int32]Extension
|
||||
extensionsRead() (map[int32]Extension, sync.Locker)
|
||||
}
|
||||
|
||||
// extendableProtoV1 is an interface implemented by a protocol buffer generated by the previous
|
||||
// version of the proto compiler that may be extended.
|
||||
type extendableProtoV1 interface {
|
||||
Message
|
||||
ExtensionRangeArray() []ExtensionRange
|
||||
ExtensionMap() map[int32]Extension
|
||||
}
|
||||
|
||||
// extensionAdapter is a wrapper around extendableProtoV1 that implements extendableProto.
|
||||
type extensionAdapter struct {
|
||||
extendableProtoV1
|
||||
}
|
||||
|
||||
func (e extensionAdapter) extensionsWrite() map[int32]Extension {
|
||||
return e.ExtensionMap()
|
||||
}
|
||||
|
||||
func (e extensionAdapter) extensionsRead() (map[int32]Extension, sync.Locker) {
|
||||
return e.ExtensionMap(), notLocker{}
|
||||
}
|
||||
|
||||
// notLocker is a sync.Locker whose Lock and Unlock methods are nops.
|
||||
type notLocker struct{}
|
||||
|
||||
func (n notLocker) Lock() {}
|
||||
func (n notLocker) Unlock() {}
|
||||
|
||||
// extendable returns the extendableProto interface for the given generated proto message.
|
||||
// If the proto message has the old extension format, it returns a wrapper that implements
|
||||
// the extendableProto interface.
|
||||
func extendable(p interface{}) (extendableProto, bool) {
|
||||
if ep, ok := p.(extendableProto); ok {
|
||||
return ep, ok
|
||||
}
|
||||
if ep, ok := p.(extendableProtoV1); ok {
|
||||
return extensionAdapter{ep}, ok
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// XXX_InternalExtensions is an internal representation of proto extensions.
|
||||
//
|
||||
// Each generated message struct type embeds an anonymous XXX_InternalExtensions field,
|
||||
// thus gaining the unexported 'extensions' method, which can be called only from the proto package.
|
||||
//
|
||||
// The methods of XXX_InternalExtensions are not concurrency safe in general,
|
||||
// but calls to logically read-only methods such as has and get may be executed concurrently.
|
||||
type XXX_InternalExtensions struct {
|
||||
// The struct must be indirect so that if a user inadvertently copies a
|
||||
// generated message and its embedded XXX_InternalExtensions, they
|
||||
// avoid the mayhem of a copied mutex.
|
||||
//
|
||||
// The mutex serializes all logically read-only operations to p.extensionMap.
|
||||
// It is up to the client to ensure that write operations to p.extensionMap are
|
||||
// mutually exclusive with other accesses.
|
||||
p *struct {
|
||||
mu sync.Mutex
|
||||
extensionMap map[int32]Extension
|
||||
}
|
||||
}
|
||||
|
||||
// extensionsWrite returns the extension map, creating it on first use.
|
||||
func (e *XXX_InternalExtensions) extensionsWrite() map[int32]Extension {
|
||||
if e.p == nil {
|
||||
e.p = new(struct {
|
||||
mu sync.Mutex
|
||||
extensionMap map[int32]Extension
|
||||
})
|
||||
e.p.extensionMap = make(map[int32]Extension)
|
||||
}
|
||||
return e.p.extensionMap
|
||||
}
|
||||
|
||||
// extensionsRead returns the extensions map for read-only use. It may be nil.
|
||||
// The caller must hold the returned mutex's lock when accessing Elements within the map.
|
||||
func (e *XXX_InternalExtensions) extensionsRead() (map[int32]Extension, sync.Locker) {
|
||||
if e.p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return e.p.extensionMap, &e.p.mu
|
||||
}
|
||||
|
||||
var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem()
|
||||
var extendableProtoV1Type = reflect.TypeOf((*extendableProtoV1)(nil)).Elem()
|
||||
|
||||
// ExtensionDesc represents an extension specification.
|
||||
// Used in generated code from the protocol compiler.
|
||||
type ExtensionDesc struct {
|
||||
ExtendedType Message // nil pointer to the type that is being extended
|
||||
ExtensionType interface{} // nil pointer to the extension type
|
||||
Field int32 // field number
|
||||
Name string // fully-qualified name of extension, for text formatting
|
||||
Tag string // protobuf tag style
|
||||
}
|
||||
|
||||
func (ed *ExtensionDesc) repeated() bool {
|
||||
t := reflect.TypeOf(ed.ExtensionType)
|
||||
return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
|
||||
}
|
||||
|
||||
// Extension represents an extension in a message.
|
||||
type Extension struct {
|
||||
// When an extension is stored in a message using SetExtension
|
||||
// only desc and value are set. When the message is marshaled
|
||||
// enc will be set to the encoded form of the message.
|
||||
//
|
||||
// When a message is unmarshaled and contains extensions, each
|
||||
// extension will have only enc set. When such an extension is
|
||||
// accessed using GetExtension (or GetExtensions) desc and value
|
||||
// will be set.
|
||||
desc *ExtensionDesc
|
||||
value interface{}
|
||||
enc []byte
|
||||
}
|
||||
|
||||
// SetRawExtension is for testing only.
|
||||
func SetRawExtension(base Message, id int32, b []byte) {
|
||||
epb, ok := extendable(base)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
extmap := epb.extensionsWrite()
|
||||
extmap[id] = Extension{enc: b}
|
||||
}
|
||||
|
||||
// isExtensionField returns true iff the given field number is in an extension range.
|
||||
func isExtensionField(pb extendableProto, field int32) bool {
|
||||
for _, er := range pb.ExtensionRangeArray() {
|
||||
if er.Start <= field && field <= er.End {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkExtensionTypes checks that the given extension is valid for pb.
|
||||
func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
|
||||
var pbi interface{} = pb
|
||||
// Check the extended type.
|
||||
if ea, ok := pbi.(extensionAdapter); ok {
|
||||
pbi = ea.extendableProtoV1
|
||||
}
|
||||
if a, b := reflect.TypeOf(pbi), reflect.TypeOf(extension.ExtendedType); a != b {
|
||||
return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String())
|
||||
}
|
||||
// Check the range.
|
||||
if !isExtensionField(pb, extension.Field) {
|
||||
return errors.New("proto: bad extension number; not in declared ranges")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extPropKey is sufficient to uniquely identify an extension.
|
||||
type extPropKey struct {
|
||||
base reflect.Type
|
||||
field int32
|
||||
}
|
||||
|
||||
var extProp = struct {
|
||||
sync.RWMutex
|
||||
m map[extPropKey]*Properties
|
||||
}{
|
||||
m: make(map[extPropKey]*Properties),
|
||||
}
|
||||
|
||||
func extensionProperties(ed *ExtensionDesc) *Properties {
|
||||
key := extPropKey{base: reflect.TypeOf(ed.ExtendedType), field: ed.Field}
|
||||
|
||||
extProp.RLock()
|
||||
if prop, ok := extProp.m[key]; ok {
|
||||
extProp.RUnlock()
|
||||
return prop
|
||||
}
|
||||
extProp.RUnlock()
|
||||
|
||||
extProp.Lock()
|
||||
defer extProp.Unlock()
|
||||
// Check again.
|
||||
if prop, ok := extProp.m[key]; ok {
|
||||
return prop
|
||||
}
|
||||
|
||||
prop := new(Properties)
|
||||
prop.Init(reflect.TypeOf(ed.ExtensionType), "unknown_name", ed.Tag, nil)
|
||||
extProp.m[key] = prop
|
||||
return prop
|
||||
}
|
||||
|
||||
// encode encodes any unmarshaled (unencoded) extensions in e.
|
||||
func encodeExtensions(e *XXX_InternalExtensions) error {
|
||||
m, mu := e.extensionsRead()
|
||||
if m == nil {
|
||||
return nil // fast path
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return encodeExtensionsMap(m)
|
||||
}
|
||||
|
||||
// encode encodes any unmarshaled (unencoded) extensions in e.
|
||||
func encodeExtensionsMap(m map[int32]Extension) error {
|
||||
for k, e := range m {
|
||||
if e.value == nil || e.desc == nil {
|
||||
// Extension is only in its encoded form.
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't skip extensions that have an encoded form set,
|
||||
// because the extension value may have been mutated after
|
||||
// the last time this function was called.
|
||||
|
||||
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||
props := extensionProperties(e.desc)
|
||||
|
||||
p := NewBuffer(nil)
|
||||
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||
// Pass a *T with a zero field and hope it all works out.
|
||||
x := reflect.New(et)
|
||||
x.Elem().Set(reflect.ValueOf(e.value))
|
||||
if err := props.enc(p, props, toStructPointer(x)); err != nil {
|
||||
return err
|
||||
}
|
||||
e.enc = p.buf
|
||||
m[k] = e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extensionsSize(e *XXX_InternalExtensions) (n int) {
|
||||
m, mu := e.extensionsRead()
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return extensionsMapSize(m)
|
||||
}
|
||||
|
||||
func extensionsMapSize(m map[int32]Extension) (n int) {
|
||||
for _, e := range m {
|
||||
if e.value == nil || e.desc == nil {
|
||||
// Extension is only in its encoded form.
|
||||
n += len(e.enc)
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't skip extensions that have an encoded form set,
|
||||
// because the extension value may have been mutated after
|
||||
// the last time this function was called.
|
||||
|
||||
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||
props := extensionProperties(e.desc)
|
||||
|
||||
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||
// Pass a *T with a zero field and hope it all works out.
|
||||
x := reflect.New(et)
|
||||
x.Elem().Set(reflect.ValueOf(e.value))
|
||||
n += props.size(props, toStructPointer(x))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasExtension returns whether the given extension is present in pb.
|
||||
func HasExtension(pb Message, extension *ExtensionDesc) bool {
|
||||
// TODO: Check types, field numbers, etc.?
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
extmap, mu := epb.extensionsRead()
|
||||
if extmap == nil {
|
||||
return false
|
||||
}
|
||||
mu.Lock()
|
||||
_, ok = extmap[extension.Field]
|
||||
mu.Unlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// ClearExtension removes the given extension from pb.
|
||||
func ClearExtension(pb Message, extension *ExtensionDesc) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// TODO: Check types, field numbers, etc.?
|
||||
extmap := epb.extensionsWrite()
|
||||
delete(extmap, extension.Field)
|
||||
}
|
||||
|
||||
// GetExtension parses and returns the given extension of pb.
|
||||
// If the extension is not present and has no default value it returns ErrMissingExtension.
|
||||
func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return nil, errors.New("proto: not an extendable proto")
|
||||
}
|
||||
|
||||
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
emap, mu := epb.extensionsRead()
|
||||
if emap == nil {
|
||||
return defaultExtensionValue(extension)
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
e, ok := emap[extension.Field]
|
||||
if !ok {
|
||||
// defaultExtensionValue returns the default value or
|
||||
// ErrMissingExtension if there is no default.
|
||||
return defaultExtensionValue(extension)
|
||||
}
|
||||
|
||||
if e.value != nil {
|
||||
// Already decoded. Check the descriptor, though.
|
||||
if e.desc != extension {
|
||||
// This shouldn't happen. If it does, it means that
|
||||
// GetExtension was called twice with two different
|
||||
// descriptors with the same field number.
|
||||
return nil, errors.New("proto: descriptor conflict")
|
||||
}
|
||||
return e.value, nil
|
||||
}
|
||||
|
||||
v, err := decodeExtension(e.enc, extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remember the decoded version and drop the encoded version.
|
||||
// That way it is safe to mutate what we return.
|
||||
e.value = v
|
||||
e.desc = extension
|
||||
e.enc = nil
|
||||
emap[extension.Field] = e
|
||||
return e.value, nil
|
||||
}
|
||||
|
||||
// defaultExtensionValue returns the default value for extension.
|
||||
// If no default for an extension is defined ErrMissingExtension is returned.
|
||||
func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
|
||||
t := reflect.TypeOf(extension.ExtensionType)
|
||||
props := extensionProperties(extension)
|
||||
|
||||
sf, _, err := fieldDefault(t, props)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sf == nil || sf.value == nil {
|
||||
// There is no default value.
|
||||
return nil, ErrMissingExtension
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Ptr {
|
||||
// We do not need to return a Ptr, we can directly return sf.value.
|
||||
return sf.value, nil
|
||||
}
|
||||
|
||||
// We need to return an interface{} that is a pointer to sf.value.
|
||||
value := reflect.New(t).Elem()
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
if sf.kind == reflect.Int32 {
|
||||
// We may have an int32 or an enum, but the underlying data is int32.
|
||||
// Since we can't set an int32 into a non int32 reflect.value directly
|
||||
// set it as a int32.
|
||||
value.Elem().SetInt(int64(sf.value.(int32)))
|
||||
} else {
|
||||
value.Elem().Set(reflect.ValueOf(sf.value))
|
||||
}
|
||||
return value.Interface(), nil
|
||||
}
|
||||
|
||||
// decodeExtension decodes an extension encoded in b.
|
||||
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
|
||||
o := NewBuffer(b)
|
||||
|
||||
t := reflect.TypeOf(extension.ExtensionType)
|
||||
|
||||
props := extensionProperties(extension)
|
||||
|
||||
// t is a pointer to a struct, pointer to basic type or a slice.
|
||||
// Allocate a "field" to store the pointer/slice itself; the
|
||||
// pointer/slice will be stored here. We pass
|
||||
// the address of this field to props.dec.
|
||||
// This passes a zero field and a *t and lets props.dec
|
||||
// interpret it as a *struct{ x t }.
|
||||
value := reflect.New(t).Elem()
|
||||
|
||||
for {
|
||||
// Discard wire type and field number varint. It isn't needed.
|
||||
if _, err := o.DecodeVarint(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if o.index >= len(o.buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return value.Interface(), nil
|
||||
}
|
||||
|
||||
// GetExtensions returns a slice of the extensions present in pb that are also listed in es.
|
||||
// The returned slice has the same length as es; missing extensions will appear as nil elements.
|
||||
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return nil, errors.New("proto: not an extendable proto")
|
||||
}
|
||||
extensions = make([]interface{}, len(es))
|
||||
for i, e := range es {
|
||||
extensions[i], err = GetExtension(epb, e)
|
||||
if err == ErrMissingExtension {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetExtension sets the specified extension of pb to the specified value.
|
||||
func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return errors.New("proto: not an extendable proto")
|
||||
}
|
||||
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||
return err
|
||||
}
|
||||
typ := reflect.TypeOf(extension.ExtensionType)
|
||||
if typ != reflect.TypeOf(value) {
|
||||
return errors.New("proto: bad extension value type")
|
||||
}
|
||||
// nil extension values need to be caught early, because the
|
||||
// encoder can't distinguish an ErrNil due to a nil extension
|
||||
// from an ErrNil due to a missing field. Extensions are
|
||||
// always optional, so the encoder would just swallow the error
|
||||
// and drop all the extensions from the encoded message.
|
||||
if reflect.ValueOf(value).IsNil() {
|
||||
return fmt.Errorf("proto: SetExtension called with nil value of type %T", value)
|
||||
}
|
||||
|
||||
extmap := epb.extensionsWrite()
|
||||
extmap[extension.Field] = Extension{desc: extension, value: value}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearAllExtensions clears all extensions from pb.
|
||||
func ClearAllExtensions(pb Message) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
m := epb.extensionsWrite()
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
// A global registry of extensions.
|
||||
// The generated code will register the generated descriptors by calling RegisterExtension.
|
||||
|
||||
var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc)
|
||||
|
||||
// RegisterExtension is called from the generated code.
|
||||
func RegisterExtension(desc *ExtensionDesc) {
|
||||
st := reflect.TypeOf(desc.ExtendedType).Elem()
|
||||
m := extensionMaps[st]
|
||||
if m == nil {
|
||||
m = make(map[int32]*ExtensionDesc)
|
||||
extensionMaps[st] = m
|
||||
}
|
||||
if _, ok := m[desc.Field]; ok {
|
||||
panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))
|
||||
}
|
||||
m[desc.Field] = desc
|
||||
}
|
||||
|
||||
// RegisteredExtensions returns a map of the registered extensions of a
|
||||
// protocol buffer struct, indexed by the extension number.
|
||||
// The argument pb should be a nil pointer to the struct type.
|
||||
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
|
||||
return extensionMaps[reflect.TypeOf(pb).Elem()]
|
||||
}
|
|
@ -0,0 +1,898 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/*
|
||||
Package proto converts data structures to and from the wire format of
|
||||
protocol buffers. It works in concert with the Go source code generated
|
||||
for .proto files by the protocol compiler.
|
||||
|
||||
A summary of the properties of the protocol buffer interface
|
||||
for a protocol buffer variable v:
|
||||
|
||||
- Names are turned from camel_case to CamelCase for export.
|
||||
- There are no methods on v to set fields; just treat
|
||||
them as structure fields.
|
||||
- There are getters that return a field's value if set,
|
||||
and return the field's default value if unset.
|
||||
The getters work even if the receiver is a nil message.
|
||||
- The zero value for a struct is its correct initialization state.
|
||||
All desired fields must be set before marshaling.
|
||||
- A Reset() method will restore a protobuf struct to its zero state.
|
||||
- Non-repeated fields are pointers to the values; nil means unset.
|
||||
That is, optional or required field int32 f becomes F *int32.
|
||||
- Repeated fields are slices.
|
||||
- Helper functions are available to aid the setting of fields.
|
||||
msg.Foo = proto.String("hello") // set field
|
||||
- Constants are defined to hold the default values of all fields that
|
||||
have them. They have the form Default_StructName_FieldName.
|
||||
Because the getter methods handle defaulted values,
|
||||
direct use of these constants should be rare.
|
||||
- Enums are given type names and maps from names to values.
|
||||
Enum values are prefixed by the enclosing message's name, or by the
|
||||
enum's type name if it is a top-level enum. Enum types have a String
|
||||
method, and a Enum method to assist in message construction.
|
||||
- Nested messages, groups and enums have type names prefixed with the name of
|
||||
the surrounding message type.
|
||||
- Extensions are given descriptor names that start with E_,
|
||||
followed by an underscore-delimited list of the nested messages
|
||||
that contain it (if any) followed by the CamelCased name of the
|
||||
extension field itself. HasExtension, ClearExtension, GetExtension
|
||||
and SetExtension are functions for manipulating extensions.
|
||||
- Oneof field sets are given a single field in their message,
|
||||
with distinguished wrapper types for each possible field value.
|
||||
- Marshal and Unmarshal are functions to encode and decode the wire format.
|
||||
|
||||
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||
|
||||
- Non-repeated fields of non-message type are values instead of pointers.
|
||||
- Getters are only generated for message and oneof fields.
|
||||
- Enum types do not get an Enum method.
|
||||
|
||||
The simplest way to describe this is to see an example.
|
||||
Given file test.proto, containing
|
||||
|
||||
package example;
|
||||
|
||||
enum FOO { X = 17; }
|
||||
|
||||
message Test {
|
||||
required string label = 1;
|
||||
optional int32 type = 2 [default=77];
|
||||
repeated int64 reps = 3;
|
||||
optional group OptionalGroup = 4 {
|
||||
required string RequiredField = 5;
|
||||
}
|
||||
oneof union {
|
||||
int32 number = 6;
|
||||
string name = 7;
|
||||
}
|
||||
}
|
||||
|
||||
The resulting file, test.pb.go, is:
|
||||
|
||||
package example
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import math "math"
|
||||
|
||||
type FOO int32
|
||||
const (
|
||||
FOO_X FOO = 17
|
||||
)
|
||||
var FOO_name = map[int32]string{
|
||||
17: "X",
|
||||
}
|
||||
var FOO_value = map[string]int32{
|
||||
"X": 17,
|
||||
}
|
||||
|
||||
func (x FOO) Enum() *FOO {
|
||||
p := new(FOO)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
func (x FOO) String() string {
|
||||
return proto.EnumName(FOO_name, int32(x))
|
||||
}
|
||||
func (x *FOO) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(FOO_value, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = FOO(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
|
||||
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
|
||||
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
|
||||
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
|
||||
// Types that are valid to be assigned to Union:
|
||||
// *Test_Number
|
||||
// *Test_Name
|
||||
Union isTest_Union `protobuf_oneof:"union"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
func (m *Test) Reset() { *m = Test{} }
|
||||
func (m *Test) String() string { return proto.CompactTextString(m) }
|
||||
func (*Test) ProtoMessage() {}
|
||||
|
||||
type isTest_Union interface {
|
||||
isTest_Union()
|
||||
}
|
||||
|
||||
type Test_Number struct {
|
||||
Number int32 `protobuf:"varint,6,opt,name=number"`
|
||||
}
|
||||
type Test_Name struct {
|
||||
Name string `protobuf:"bytes,7,opt,name=name"`
|
||||
}
|
||||
|
||||
func (*Test_Number) isTest_Union() {}
|
||||
func (*Test_Name) isTest_Union() {}
|
||||
|
||||
func (m *Test) GetUnion() isTest_Union {
|
||||
if m != nil {
|
||||
return m.Union
|
||||
}
|
||||
return nil
|
||||
}
|
||||
const Default_Test_Type int32 = 77
|
||||
|
||||
func (m *Test) GetLabel() string {
|
||||
if m != nil && m.Label != nil {
|
||||
return *m.Label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Test) GetType() int32 {
|
||||
if m != nil && m.Type != nil {
|
||||
return *m.Type
|
||||
}
|
||||
return Default_Test_Type
|
||||
}
|
||||
|
||||
func (m *Test) GetOptionalgroup() *Test_OptionalGroup {
|
||||
if m != nil {
|
||||
return m.Optionalgroup
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Test_OptionalGroup struct {
|
||||
RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"`
|
||||
}
|
||||
func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} }
|
||||
func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) }
|
||||
|
||||
func (m *Test_OptionalGroup) GetRequiredField() string {
|
||||
if m != nil && m.RequiredField != nil {
|
||||
return *m.RequiredField
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Test) GetNumber() int32 {
|
||||
if x, ok := m.GetUnion().(*Test_Number); ok {
|
||||
return x.Number
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Test) GetName() string {
|
||||
if x, ok := m.GetUnion().(*Test_Name); ok {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("example.FOO", FOO_name, FOO_value)
|
||||
}
|
||||
|
||||
To create and play with a Test object:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
pb "./example.pb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
test := &pb.Test{
|
||||
Label: proto.String("hello"),
|
||||
Type: proto.Int32(17),
|
||||
Reps: []int64{1, 2, 3},
|
||||
Optionalgroup: &pb.Test_OptionalGroup{
|
||||
RequiredField: proto.String("good bye"),
|
||||
},
|
||||
Union: &pb.Test_Name{"fred"},
|
||||
}
|
||||
data, err := proto.Marshal(test)
|
||||
if err != nil {
|
||||
log.Fatal("marshaling error: ", err)
|
||||
}
|
||||
newTest := &pb.Test{}
|
||||
err = proto.Unmarshal(data, newTest)
|
||||
if err != nil {
|
||||
log.Fatal("unmarshaling error: ", err)
|
||||
}
|
||||
// Now test and newTest contain the same data.
|
||||
if test.GetLabel() != newTest.GetLabel() {
|
||||
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
|
||||
}
|
||||
// Use a type switch to determine which oneof was set.
|
||||
switch u := test.Union.(type) {
|
||||
case *pb.Test_Number: // u.Number contains the number.
|
||||
case *pb.Test_Name: // u.Name contains the string.
|
||||
}
|
||||
// etc.
|
||||
}
|
||||
*/
|
||||
package proto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Message is implemented by generated protocol buffer messages.
|
||||
type Message interface {
|
||||
Reset()
|
||||
String() string
|
||||
ProtoMessage()
|
||||
}
|
||||
|
||||
// Stats records allocation details about the protocol buffer encoders
|
||||
// and decoders. Useful for tuning the library itself.
|
||||
type Stats struct {
|
||||
Emalloc uint64 // mallocs in encode
|
||||
Dmalloc uint64 // mallocs in decode
|
||||
Encode uint64 // number of encodes
|
||||
Decode uint64 // number of decodes
|
||||
Chit uint64 // number of cache hits
|
||||
Cmiss uint64 // number of cache misses
|
||||
Size uint64 // number of sizes
|
||||
}
|
||||
|
||||
// Set to true to enable stats collection.
|
||||
const collectStats = false
|
||||
|
||||
var stats Stats
|
||||
|
||||
// GetStats returns a copy of the global Stats structure.
|
||||
func GetStats() Stats { return stats }
|
||||
|
||||
// A Buffer is a buffer manager for marshaling and unmarshaling
|
||||
// protocol buffers. It may be reused between invocations to
|
||||
// reduce memory usage. It is not necessary to use a Buffer;
|
||||
// the global functions Marshal and Unmarshal create a
|
||||
// temporary Buffer and are fine for most applications.
|
||||
type Buffer struct {
|
||||
buf []byte // encode/decode byte stream
|
||||
index int // write point
|
||||
|
||||
// pools of basic types to amortize allocation.
|
||||
bools []bool
|
||||
uint32s []uint32
|
||||
uint64s []uint64
|
||||
|
||||
// extra pools, only used with pointer_reflect.go
|
||||
int32s []int32
|
||||
int64s []int64
|
||||
float32s []float32
|
||||
float64s []float64
|
||||
}
|
||||
|
||||
// NewBuffer allocates a new Buffer and initializes its internal data to
|
||||
// the contents of the argument slice.
|
||||
func NewBuffer(e []byte) *Buffer {
|
||||
return &Buffer{buf: e}
|
||||
}
|
||||
|
||||
// Reset resets the Buffer, ready for marshaling a new protocol buffer.
|
||||
func (p *Buffer) Reset() {
|
||||
p.buf = p.buf[0:0] // for reading/writing
|
||||
p.index = 0 // for reading
|
||||
}
|
||||
|
||||
// SetBuf replaces the internal buffer with the slice,
|
||||
// ready for unmarshaling the contents of the slice.
|
||||
func (p *Buffer) SetBuf(s []byte) {
|
||||
p.buf = s
|
||||
p.index = 0
|
||||
}
|
||||
|
||||
// Bytes returns the contents of the Buffer.
|
||||
func (p *Buffer) Bytes() []byte { return p.buf }
|
||||
|
||||
/*
|
||||
* Helper routines for simplifying the creation of optional fields of basic type.
|
||||
*/
|
||||
|
||||
// Bool is a helper routine that allocates a new bool value
|
||||
// to store v and returns a pointer to it.
|
||||
func Bool(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Int32 is a helper routine that allocates a new int32 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Int32(v int32) *int32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Int is a helper routine that allocates a new int32 value
|
||||
// to store v and returns a pointer to it, but unlike Int32
|
||||
// its argument value is an int.
|
||||
func Int(v int) *int32 {
|
||||
p := new(int32)
|
||||
*p = int32(v)
|
||||
return p
|
||||
}
|
||||
|
||||
// Int64 is a helper routine that allocates a new int64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Int64(v int64) *int64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Float32 is a helper routine that allocates a new float32 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Float32(v float32) *float32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Float64 is a helper routine that allocates a new float64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Float64(v float64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Uint32 is a helper routine that allocates a new uint32 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Uint32(v uint32) *uint32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Uint64 is a helper routine that allocates a new uint64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Uint64(v uint64) *uint64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// String is a helper routine that allocates a new string value
|
||||
// to store v and returns a pointer to it.
|
||||
func String(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
// EnumName is a helper function to simplify printing protocol buffer enums
|
||||
// by name. Given an enum map and a value, it returns a useful string.
|
||||
func EnumName(m map[int32]string, v int32) string {
|
||||
s, ok := m[v]
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
return strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
// UnmarshalJSONEnum is a helper function to simplify recovering enum int values
|
||||
// from their JSON-encoded representation. Given a map from the enum's symbolic
|
||||
// names to its int values, and a byte buffer containing the JSON-encoded
|
||||
// value, it returns an int32 that can be cast to the enum type by the caller.
|
||||
//
|
||||
// The function can deal with both JSON representations, numeric and symbolic.
|
||||
func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) {
|
||||
if data[0] == '"' {
|
||||
// New style: enums are strings.
|
||||
var repr string
|
||||
if err := json.Unmarshal(data, &repr); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
val, ok := m[repr]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
// Old style: enums are ints.
|
||||
var val int32
|
||||
if err := json.Unmarshal(data, &val); err != nil {
|
||||
return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// DebugPrint dumps the encoded data in b in a debugging format with a header
|
||||
// including the string s. Used in testing but made available for general debugging.
|
||||
func (p *Buffer) DebugPrint(s string, b []byte) {
|
||||
var u uint64
|
||||
|
||||
obuf := p.buf
|
||||
index := p.index
|
||||
p.buf = b
|
||||
p.index = 0
|
||||
depth := 0
|
||||
|
||||
fmt.Printf("\n--- %s ---\n", s)
|
||||
|
||||
out:
|
||||
for {
|
||||
for i := 0; i < depth; i++ {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
|
||||
index := p.index
|
||||
if index == len(p.buf) {
|
||||
break
|
||||
}
|
||||
|
||||
op, err := p.DecodeVarint()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: fetching op err %v\n", index, err)
|
||||
break out
|
||||
}
|
||||
tag := op >> 3
|
||||
wire := op & 7
|
||||
|
||||
switch wire {
|
||||
default:
|
||||
fmt.Printf("%3d: t=%3d unknown wire=%d\n",
|
||||
index, tag, wire)
|
||||
break out
|
||||
|
||||
case WireBytes:
|
||||
var r []byte
|
||||
|
||||
r, err = p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d bytes [%d]", index, tag, len(r))
|
||||
if len(r) <= 6 {
|
||||
for i := 0; i < len(r); i++ {
|
||||
fmt.Printf(" %.2x", r[i])
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < 3; i++ {
|
||||
fmt.Printf(" %.2x", r[i])
|
||||
}
|
||||
fmt.Printf(" ..")
|
||||
for i := len(r) - 3; i < len(r); i++ {
|
||||
fmt.Printf(" %.2x", r[i])
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
case WireFixed32:
|
||||
u, err = p.DecodeFixed32()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err)
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u)
|
||||
|
||||
case WireFixed64:
|
||||
u, err = p.DecodeFixed64()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err)
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d fix64 %d\n", index, tag, u)
|
||||
|
||||
case WireVarint:
|
||||
u, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err)
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d varint %d\n", index, tag, u)
|
||||
|
||||
case WireStartGroup:
|
||||
fmt.Printf("%3d: t=%3d start\n", index, tag)
|
||||
depth++
|
||||
|
||||
case WireEndGroup:
|
||||
depth--
|
||||
fmt.Printf("%3d: t=%3d end\n", index, tag)
|
||||
}
|
||||
}
|
||||
|
||||
if depth != 0 {
|
||||
fmt.Printf("%3d: start-end not balanced %d\n", p.index, depth)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
p.buf = obuf
|
||||
p.index = index
|
||||
}
|
||||
|
||||
// SetDefaults sets unset protocol buffer fields to their default values.
|
||||
// It only modifies fields that are both unset and have defined defaults.
|
||||
// It recursively sets default values in any non-nil sub-messages.
|
||||
func SetDefaults(pb Message) {
|
||||
setDefaults(reflect.ValueOf(pb), true, false)
|
||||
}
|
||||
|
||||
// v is a pointer to a struct.
|
||||
func setDefaults(v reflect.Value, recur, zeros bool) {
|
||||
v = v.Elem()
|
||||
|
||||
defaultMu.RLock()
|
||||
dm, ok := defaults[v.Type()]
|
||||
defaultMu.RUnlock()
|
||||
if !ok {
|
||||
dm = buildDefaultMessage(v.Type())
|
||||
defaultMu.Lock()
|
||||
defaults[v.Type()] = dm
|
||||
defaultMu.Unlock()
|
||||
}
|
||||
|
||||
for _, sf := range dm.scalars {
|
||||
f := v.Field(sf.index)
|
||||
if !f.IsNil() {
|
||||
// field already set
|
||||
continue
|
||||
}
|
||||
dv := sf.value
|
||||
if dv == nil && !zeros {
|
||||
// no explicit default, and don't want to set zeros
|
||||
continue
|
||||
}
|
||||
fptr := f.Addr().Interface() // **T
|
||||
// TODO: Consider batching the allocations we do here.
|
||||
switch sf.kind {
|
||||
case reflect.Bool:
|
||||
b := new(bool)
|
||||
if dv != nil {
|
||||
*b = dv.(bool)
|
||||
}
|
||||
*(fptr.(**bool)) = b
|
||||
case reflect.Float32:
|
||||
f := new(float32)
|
||||
if dv != nil {
|
||||
*f = dv.(float32)
|
||||
}
|
||||
*(fptr.(**float32)) = f
|
||||
case reflect.Float64:
|
||||
f := new(float64)
|
||||
if dv != nil {
|
||||
*f = dv.(float64)
|
||||
}
|
||||
*(fptr.(**float64)) = f
|
||||
case reflect.Int32:
|
||||
// might be an enum
|
||||
if ft := f.Type(); ft != int32PtrType {
|
||||
// enum
|
||||
f.Set(reflect.New(ft.Elem()))
|
||||
if dv != nil {
|
||||
f.Elem().SetInt(int64(dv.(int32)))
|
||||
}
|
||||
} else {
|
||||
// int32 field
|
||||
i := new(int32)
|
||||
if dv != nil {
|
||||
*i = dv.(int32)
|
||||
}
|
||||
*(fptr.(**int32)) = i
|
||||
}
|
||||
case reflect.Int64:
|
||||
i := new(int64)
|
||||
if dv != nil {
|
||||
*i = dv.(int64)
|
||||
}
|
||||
*(fptr.(**int64)) = i
|
||||
case reflect.String:
|
||||
s := new(string)
|
||||
if dv != nil {
|
||||
*s = dv.(string)
|
||||
}
|
||||
*(fptr.(**string)) = s
|
||||
case reflect.Uint8:
|
||||
// exceptional case: []byte
|
||||
var b []byte
|
||||
if dv != nil {
|
||||
db := dv.([]byte)
|
||||
b = make([]byte, len(db))
|
||||
copy(b, db)
|
||||
} else {
|
||||
b = []byte{}
|
||||
}
|
||||
*(fptr.(*[]byte)) = b
|
||||
case reflect.Uint32:
|
||||
u := new(uint32)
|
||||
if dv != nil {
|
||||
*u = dv.(uint32)
|
||||
}
|
||||
*(fptr.(**uint32)) = u
|
||||
case reflect.Uint64:
|
||||
u := new(uint64)
|
||||
if dv != nil {
|
||||
*u = dv.(uint64)
|
||||
}
|
||||
*(fptr.(**uint64)) = u
|
||||
default:
|
||||
log.Printf("proto: can't set default for field %v (sf.kind=%v)", f, sf.kind)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ni := range dm.nested {
|
||||
f := v.Field(ni)
|
||||
// f is *T or []*T or map[T]*T
|
||||
switch f.Kind() {
|
||||
case reflect.Ptr:
|
||||
if f.IsNil() {
|
||||
continue
|
||||
}
|
||||
setDefaults(f, recur, zeros)
|
||||
|
||||
case reflect.Slice:
|
||||
for i := 0; i < f.Len(); i++ {
|
||||
e := f.Index(i)
|
||||
if e.IsNil() {
|
||||
continue
|
||||
}
|
||||
setDefaults(e, recur, zeros)
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
for _, k := range f.MapKeys() {
|
||||
e := f.MapIndex(k)
|
||||
if e.IsNil() {
|
||||
continue
|
||||
}
|
||||
setDefaults(e, recur, zeros)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// defaults maps a protocol buffer struct type to a slice of the fields,
|
||||
// with its scalar fields set to their proto-declared non-zero default values.
|
||||
defaultMu sync.RWMutex
|
||||
defaults = make(map[reflect.Type]defaultMessage)
|
||||
|
||||
int32PtrType = reflect.TypeOf((*int32)(nil))
|
||||
)
|
||||
|
||||
// defaultMessage represents information about the default values of a message.
|
||||
type defaultMessage struct {
|
||||
scalars []scalarField
|
||||
nested []int // struct field index of nested messages
|
||||
}
|
||||
|
||||
type scalarField struct {
|
||||
index int // struct field index
|
||||
kind reflect.Kind // element type (the T in *T or []T)
|
||||
value interface{} // the proto-declared default value, or nil
|
||||
}
|
||||
|
||||
// t is a struct type.
|
||||
func buildDefaultMessage(t reflect.Type) (dm defaultMessage) {
|
||||
sprop := GetProperties(t)
|
||||
for _, prop := range sprop.Prop {
|
||||
fi, ok := sprop.decoderTags.get(prop.Tag)
|
||||
if !ok {
|
||||
// XXX_unrecognized
|
||||
continue
|
||||
}
|
||||
ft := t.Field(fi).Type
|
||||
|
||||
sf, nested, err := fieldDefault(ft, prop)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Print(err)
|
||||
case nested:
|
||||
dm.nested = append(dm.nested, fi)
|
||||
case sf != nil:
|
||||
sf.index = fi
|
||||
dm.scalars = append(dm.scalars, *sf)
|
||||
}
|
||||
}
|
||||
|
||||
return dm
|
||||
}
|
||||
|
||||
// fieldDefault returns the scalarField for field type ft.
|
||||
// sf will be nil if the field can not have a default.
|
||||
// nestedMessage will be true if this is a nested message.
|
||||
// Note that sf.index is not set on return.
|
||||
func fieldDefault(ft reflect.Type, prop *Properties) (sf *scalarField, nestedMessage bool, err error) {
|
||||
var canHaveDefault bool
|
||||
switch ft.Kind() {
|
||||
case reflect.Ptr:
|
||||
if ft.Elem().Kind() == reflect.Struct {
|
||||
nestedMessage = true
|
||||
} else {
|
||||
canHaveDefault = true // proto2 scalar field
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
switch ft.Elem().Kind() {
|
||||
case reflect.Ptr:
|
||||
nestedMessage = true // repeated message
|
||||
case reflect.Uint8:
|
||||
canHaveDefault = true // bytes field
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
if ft.Elem().Kind() == reflect.Ptr {
|
||||
nestedMessage = true // map with message values
|
||||
}
|
||||
}
|
||||
|
||||
if !canHaveDefault {
|
||||
if nestedMessage {
|
||||
return nil, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// We now know that ft is a pointer or slice.
|
||||
sf = &scalarField{kind: ft.Elem().Kind()}
|
||||
|
||||
// scalar fields without defaults
|
||||
if !prop.HasDefault {
|
||||
return sf, false, nil
|
||||
}
|
||||
|
||||
// a scalar field: either *T or []byte
|
||||
switch ft.Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
x, err := strconv.ParseBool(prop.Default)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default bool %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
case reflect.Float32:
|
||||
x, err := strconv.ParseFloat(prop.Default, 32)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default float32 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = float32(x)
|
||||
case reflect.Float64:
|
||||
x, err := strconv.ParseFloat(prop.Default, 64)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default float64 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
case reflect.Int32:
|
||||
x, err := strconv.ParseInt(prop.Default, 10, 32)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default int32 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = int32(x)
|
||||
case reflect.Int64:
|
||||
x, err := strconv.ParseInt(prop.Default, 10, 64)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default int64 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
case reflect.String:
|
||||
sf.value = prop.Default
|
||||
case reflect.Uint8:
|
||||
// []byte (not *uint8)
|
||||
sf.value = []byte(prop.Default)
|
||||
case reflect.Uint32:
|
||||
x, err := strconv.ParseUint(prop.Default, 10, 32)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default uint32 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = uint32(x)
|
||||
case reflect.Uint64:
|
||||
x, err := strconv.ParseUint(prop.Default, 10, 64)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default uint64 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
default:
|
||||
return nil, false, fmt.Errorf("proto: unhandled def kind %v", ft.Elem().Kind())
|
||||
}
|
||||
|
||||
return sf, false, nil
|
||||
}
|
||||
|
||||
// Map fields may have key types of non-float scalars, strings and enums.
|
||||
// The easiest way to sort them in some deterministic order is to use fmt.
|
||||
// If this turns out to be inefficient we can always consider other options,
|
||||
// such as doing a Schwartzian transform.
|
||||
|
||||
func mapKeys(vs []reflect.Value) sort.Interface {
|
||||
s := mapKeySorter{
|
||||
vs: vs,
|
||||
// default Less function: textual comparison
|
||||
less: func(a, b reflect.Value) bool {
|
||||
return fmt.Sprint(a.Interface()) < fmt.Sprint(b.Interface())
|
||||
},
|
||||
}
|
||||
|
||||
// Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps;
|
||||
// numeric keys are sorted numerically.
|
||||
if len(vs) == 0 {
|
||||
return s
|
||||
}
|
||||
switch vs[0].Kind() {
|
||||
case reflect.Int32, reflect.Int64:
|
||||
s.less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
s.less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
type mapKeySorter struct {
|
||||
vs []reflect.Value
|
||||
less func(a, b reflect.Value) bool
|
||||
}
|
||||
|
||||
func (s mapKeySorter) Len() int { return len(s.vs) }
|
||||
func (s mapKeySorter) Swap(i, j int) { s.vs[i], s.vs[j] = s.vs[j], s.vs[i] }
|
||||
func (s mapKeySorter) Less(i, j int) bool {
|
||||
return s.less(s.vs[i], s.vs[j])
|
||||
}
|
||||
|
||||
// isProto3Zero reports whether v is a zero proto3 value.
|
||||
func isProto3Zero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.String:
|
||||
return v.String() == ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ProtoPackageIsVersion2 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
const ProtoPackageIsVersion2 = true
|
||||
|
||||
// ProtoPackageIsVersion1 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
const ProtoPackageIsVersion1 = true
|
|
@ -0,0 +1,311 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Support for message sets.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID.
|
||||
// A message type ID is required for storing a protocol buffer in a message set.
|
||||
var errNoMessageTypeID = errors.New("proto does not have a message type ID")
|
||||
|
||||
// The first two types (_MessageSet_Item and messageSet)
|
||||
// model what the protocol compiler produces for the following protocol message:
|
||||
// message MessageSet {
|
||||
// repeated group Item = 1 {
|
||||
// required int32 type_id = 2;
|
||||
// required string message = 3;
|
||||
// };
|
||||
// }
|
||||
// That is the MessageSet wire format. We can't use a proto to generate these
|
||||
// because that would introduce a circular dependency between it and this package.
|
||||
|
||||
type _MessageSet_Item struct {
|
||||
TypeId *int32 `protobuf:"varint,2,req,name=type_id"`
|
||||
Message []byte `protobuf:"bytes,3,req,name=message"`
|
||||
}
|
||||
|
||||
type messageSet struct {
|
||||
Item []*_MessageSet_Item `protobuf:"group,1,rep"`
|
||||
XXX_unrecognized []byte
|
||||
// TODO: caching?
|
||||
}
|
||||
|
||||
// Make sure messageSet is a Message.
|
||||
var _ Message = (*messageSet)(nil)
|
||||
|
||||
// messageTypeIder is an interface satisfied by a protocol buffer type
|
||||
// that may be stored in a MessageSet.
|
||||
type messageTypeIder interface {
|
||||
MessageTypeId() int32
|
||||
}
|
||||
|
||||
func (ms *messageSet) find(pb Message) *_MessageSet_Item {
|
||||
mti, ok := pb.(messageTypeIder)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
id := mti.MessageTypeId()
|
||||
for _, item := range ms.Item {
|
||||
if *item.TypeId == id {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *messageSet) Has(pb Message) bool {
|
||||
if ms.find(pb) != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ms *messageSet) Unmarshal(pb Message) error {
|
||||
if item := ms.find(pb); item != nil {
|
||||
return Unmarshal(item.Message, pb)
|
||||
}
|
||||
if _, ok := pb.(messageTypeIder); !ok {
|
||||
return errNoMessageTypeID
|
||||
}
|
||||
return nil // TODO: return error instead?
|
||||
}
|
||||
|
||||
func (ms *messageSet) Marshal(pb Message) error {
|
||||
msg, err := Marshal(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item := ms.find(pb); item != nil {
|
||||
// reuse existing item
|
||||
item.Message = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
mti, ok := pb.(messageTypeIder)
|
||||
if !ok {
|
||||
return errNoMessageTypeID
|
||||
}
|
||||
|
||||
mtid := mti.MessageTypeId()
|
||||
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||
TypeId: &mtid,
|
||||
Message: msg,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *messageSet) Reset() { *ms = messageSet{} }
|
||||
func (ms *messageSet) String() string { return CompactTextString(ms) }
|
||||
func (*messageSet) ProtoMessage() {}
|
||||
|
||||
// Support for the message_set_wire_format message option.
|
||||
|
||||
func skipVarint(buf []byte) []byte {
|
||||
i := 0
|
||||
for ; buf[i]&0x80 != 0; i++ {
|
||||
}
|
||||
return buf[i+1:]
|
||||
}
|
||||
|
||||
// MarshalMessageSet encodes the extension map represented by m in the message set wire format.
|
||||
// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSet(exts interface{}) ([]byte, error) {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
if err := encodeExtensions(exts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, _ = exts.extensionsRead()
|
||||
case map[int32]Extension:
|
||||
if err := encodeExtensionsMap(exts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m = exts
|
||||
default:
|
||||
return nil, errors.New("proto: not an extension map")
|
||||
}
|
||||
|
||||
// Sort extension IDs to provide a deterministic encoding.
|
||||
// See also enc_map in encode.go.
|
||||
ids := make([]int, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, int(id))
|
||||
}
|
||||
sort.Ints(ids)
|
||||
|
||||
ms := &messageSet{Item: make([]*_MessageSet_Item, 0, len(m))}
|
||||
for _, id := range ids {
|
||||
e := m[int32(id)]
|
||||
// Remove the wire type and field number varint, as well as the length varint.
|
||||
msg := skipVarint(skipVarint(e.enc))
|
||||
|
||||
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||
TypeId: Int32(int32(id)),
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
return Marshal(ms)
|
||||
}
|
||||
|
||||
// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||
// It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSet(buf []byte, exts interface{}) error {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
m = exts.extensionsWrite()
|
||||
case map[int32]Extension:
|
||||
m = exts
|
||||
default:
|
||||
return errors.New("proto: not an extension map")
|
||||
}
|
||||
|
||||
ms := new(messageSet)
|
||||
if err := Unmarshal(buf, ms); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range ms.Item {
|
||||
id := *item.TypeId
|
||||
msg := item.Message
|
||||
|
||||
// Restore wire type and field number varint, plus length varint.
|
||||
// Be careful to preserve duplicate items.
|
||||
b := EncodeVarint(uint64(id)<<3 | WireBytes)
|
||||
if ext, ok := m[id]; ok {
|
||||
// Existing data; rip off the tag and length varint
|
||||
// so we join the new data correctly.
|
||||
// We can assume that ext.enc is set because we are unmarshaling.
|
||||
o := ext.enc[len(b):] // skip wire type and field number
|
||||
_, n := DecodeVarint(o) // calculate length of length varint
|
||||
o = o[n:] // skip length varint
|
||||
msg = append(o, msg...) // join old data and new data
|
||||
}
|
||||
b = append(b, EncodeVarint(uint64(len(msg)))...)
|
||||
b = append(b, msg...)
|
||||
|
||||
m[id] = Extension{enc: b}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalMessageSetJSON encodes the extension map represented by m in JSON format.
|
||||
// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSetJSON(exts interface{}) ([]byte, error) {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
m, _ = exts.extensionsRead()
|
||||
case map[int32]Extension:
|
||||
m = exts
|
||||
default:
|
||||
return nil, errors.New("proto: not an extension map")
|
||||
}
|
||||
var b bytes.Buffer
|
||||
b.WriteByte('{')
|
||||
|
||||
// Process the map in key order for deterministic output.
|
||||
ids := make([]int32, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Sort(int32Slice(ids)) // int32Slice defined in text.go
|
||||
|
||||
for i, id := range ids {
|
||||
ext := m[id]
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
|
||||
msd, ok := messageSetMap[id]
|
||||
if !ok {
|
||||
// Unknown type; we can't render it, so skip it.
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, `"[%s]":`, msd.name)
|
||||
|
||||
x := ext.value
|
||||
if x == nil {
|
||||
x = reflect.New(msd.t.Elem()).Interface()
|
||||
if err := Unmarshal(ext.enc, x.(Message)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.Write(d)
|
||||
}
|
||||
b.WriteByte('}')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format.
|
||||
// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSetJSON(buf []byte, exts interface{}) error {
|
||||
// Common-case fast path.
|
||||
if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is fairly tricky, and it's not clear that it is needed.
|
||||
return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented")
|
||||
}
|
||||
|
||||
// A global registry of types that can be used in a MessageSet.
|
||||
|
||||
var messageSetMap = make(map[int32]messageSetDesc)
|
||||
|
||||
type messageSetDesc struct {
|
||||
t reflect.Type // pointer to struct
|
||||
name string
|
||||
}
|
||||
|
||||
// RegisterMessageSetType is called from the generated code.
|
||||
func RegisterMessageSetType(m Message, fieldNum int32, name string) {
|
||||
messageSetMap[fieldNum] = messageSetDesc{
|
||||
t: reflect.TypeOf(m),
|
||||
name: name,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,484 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build appengine js
|
||||
|
||||
// This file contains an implementation of proto field accesses using package reflect.
|
||||
// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can
|
||||
// be used on App Engine.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// A structPointer is a pointer to a struct.
|
||||
type structPointer struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||
// The reflect value must itself be a pointer to a struct.
|
||||
func toStructPointer(v reflect.Value) structPointer {
|
||||
return structPointer{v}
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func structPointer_IsNil(p structPointer) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
// Interface returns the struct pointer as an interface value.
|
||||
func structPointer_Interface(p structPointer, _ reflect.Type) interface{} {
|
||||
return p.v.Interface()
|
||||
}
|
||||
|
||||
// A field identifies a field in a struct, accessible from a structPointer.
|
||||
// In this implementation, a field is identified by the sequence of field indices
|
||||
// passed to reflect's FieldByIndex.
|
||||
type field []int
|
||||
|
||||
// toField returns a field equivalent to the given reflect field.
|
||||
func toField(f *reflect.StructField) field {
|
||||
return f.Index
|
||||
}
|
||||
|
||||
// invalidField is an invalid field identifier.
|
||||
var invalidField = field(nil)
|
||||
|
||||
// IsValid reports whether the field identifier is valid.
|
||||
func (f field) IsValid() bool { return f != nil }
|
||||
|
||||
// field returns the given field in the struct as a reflect value.
|
||||
func structPointer_field(p structPointer, f field) reflect.Value {
|
||||
// Special case: an extension map entry with a value of type T
|
||||
// passes a *T to the struct-handling code with a zero field,
|
||||
// expecting that it will be treated as equivalent to *struct{ X T },
|
||||
// which has the same memory layout. We have to handle that case
|
||||
// specially, because reflect will panic if we call FieldByIndex on a
|
||||
// non-struct.
|
||||
if f == nil {
|
||||
return p.v.Elem()
|
||||
}
|
||||
|
||||
return p.v.Elem().FieldByIndex(f)
|
||||
}
|
||||
|
||||
// ifield returns the given field in the struct as an interface value.
|
||||
func structPointer_ifield(p structPointer, f field) interface{} {
|
||||
return structPointer_field(p, f).Addr().Interface()
|
||||
}
|
||||
|
||||
// Bytes returns the address of a []byte field in the struct.
|
||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||
return structPointer_ifield(p, f).(*[]byte)
|
||||
}
|
||||
|
||||
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||
return structPointer_ifield(p, f).(*[][]byte)
|
||||
}
|
||||
|
||||
// Bool returns the address of a *bool field in the struct.
|
||||
func structPointer_Bool(p structPointer, f field) **bool {
|
||||
return structPointer_ifield(p, f).(**bool)
|
||||
}
|
||||
|
||||
// BoolVal returns the address of a bool field in the struct.
|
||||
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||
return structPointer_ifield(p, f).(*bool)
|
||||
}
|
||||
|
||||
// BoolSlice returns the address of a []bool field in the struct.
|
||||
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||
return structPointer_ifield(p, f).(*[]bool)
|
||||
}
|
||||
|
||||
// String returns the address of a *string field in the struct.
|
||||
func structPointer_String(p structPointer, f field) **string {
|
||||
return structPointer_ifield(p, f).(**string)
|
||||
}
|
||||
|
||||
// StringVal returns the address of a string field in the struct.
|
||||
func structPointer_StringVal(p structPointer, f field) *string {
|
||||
return structPointer_ifield(p, f).(*string)
|
||||
}
|
||||
|
||||
// StringSlice returns the address of a []string field in the struct.
|
||||
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||
return structPointer_ifield(p, f).(*[]string)
|
||||
}
|
||||
|
||||
// Extensions returns the address of an extension map field in the struct.
|
||||
func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions {
|
||||
return structPointer_ifield(p, f).(*XXX_InternalExtensions)
|
||||
}
|
||||
|
||||
// ExtMap returns the address of an extension map field in the struct.
|
||||
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||
return structPointer_ifield(p, f).(*map[int32]Extension)
|
||||
}
|
||||
|
||||
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||
return structPointer_field(p, f).Addr()
|
||||
}
|
||||
|
||||
// SetStructPointer writes a *struct field in the struct.
|
||||
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||
structPointer_field(p, f).Set(q.v)
|
||||
}
|
||||
|
||||
// GetStructPointer reads a *struct field in the struct.
|
||||
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||
return structPointer{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// StructPointerSlice the address of a []*struct field in the struct.
|
||||
func structPointer_StructPointerSlice(p structPointer, f field) structPointerSlice {
|
||||
return structPointerSlice{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A structPointerSlice represents the address of a slice of pointers to structs
|
||||
// (themselves messages or groups). That is, v.Type() is *[]*struct{...}.
|
||||
type structPointerSlice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p structPointerSlice) Len() int { return p.v.Len() }
|
||||
func (p structPointerSlice) Index(i int) structPointer { return structPointer{p.v.Index(i)} }
|
||||
func (p structPointerSlice) Append(q structPointer) {
|
||||
p.v.Set(reflect.Append(p.v, q.v))
|
||||
}
|
||||
|
||||
var (
|
||||
int32Type = reflect.TypeOf(int32(0))
|
||||
uint32Type = reflect.TypeOf(uint32(0))
|
||||
float32Type = reflect.TypeOf(float32(0))
|
||||
int64Type = reflect.TypeOf(int64(0))
|
||||
uint64Type = reflect.TypeOf(uint64(0))
|
||||
float64Type = reflect.TypeOf(float64(0))
|
||||
)
|
||||
|
||||
// A word32 represents a field of type *int32, *uint32, *float32, or *enum.
|
||||
// That is, v.Type() is *int32, *uint32, *float32, or *enum and v is assignable.
|
||||
type word32 struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func word32_IsNil(p word32) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
// Set sets p to point at a newly allocated word with bits set to x.
|
||||
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||
t := p.v.Type().Elem()
|
||||
switch t {
|
||||
case int32Type:
|
||||
if len(o.int32s) == 0 {
|
||||
o.int32s = make([]int32, uint32PoolSize)
|
||||
}
|
||||
o.int32s[0] = int32(x)
|
||||
p.v.Set(reflect.ValueOf(&o.int32s[0]))
|
||||
o.int32s = o.int32s[1:]
|
||||
return
|
||||
case uint32Type:
|
||||
if len(o.uint32s) == 0 {
|
||||
o.uint32s = make([]uint32, uint32PoolSize)
|
||||
}
|
||||
o.uint32s[0] = x
|
||||
p.v.Set(reflect.ValueOf(&o.uint32s[0]))
|
||||
o.uint32s = o.uint32s[1:]
|
||||
return
|
||||
case float32Type:
|
||||
if len(o.float32s) == 0 {
|
||||
o.float32s = make([]float32, uint32PoolSize)
|
||||
}
|
||||
o.float32s[0] = math.Float32frombits(x)
|
||||
p.v.Set(reflect.ValueOf(&o.float32s[0]))
|
||||
o.float32s = o.float32s[1:]
|
||||
return
|
||||
}
|
||||
|
||||
// must be enum
|
||||
p.v.Set(reflect.New(t))
|
||||
p.v.Elem().SetInt(int64(int32(x)))
|
||||
}
|
||||
|
||||
// Get gets the bits pointed at by p, as a uint32.
|
||||
func word32_Get(p word32) uint32 {
|
||||
elem := p.v.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32 returns a reference to a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32(p structPointer, f field) word32 {
|
||||
return word32{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A word32Val represents a field of type int32, uint32, float32, or enum.
|
||||
// That is, v.Type() is int32, uint32, float32, or enum and v is assignable.
|
||||
type word32Val struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// Set sets *p to x.
|
||||
func word32Val_Set(p word32Val, x uint32) {
|
||||
switch p.v.Type() {
|
||||
case int32Type:
|
||||
p.v.SetInt(int64(x))
|
||||
return
|
||||
case uint32Type:
|
||||
p.v.SetUint(uint64(x))
|
||||
return
|
||||
case float32Type:
|
||||
p.v.SetFloat(float64(math.Float32frombits(x)))
|
||||
return
|
||||
}
|
||||
|
||||
// must be enum
|
||||
p.v.SetInt(int64(int32(x)))
|
||||
}
|
||||
|
||||
// Get gets the bits pointed at by p, as a uint32.
|
||||
func word32Val_Get(p word32Val) uint32 {
|
||||
elem := p.v
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32Val returns a reference to a int32, uint32, float32, or enum field in the struct.
|
||||
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||
return word32Val{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A word32Slice is a slice of 32-bit values.
|
||||
// That is, v.Type() is []int32, []uint32, []float32, or []enum.
|
||||
type word32Slice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p word32Slice) Append(x uint32) {
|
||||
n, m := p.v.Len(), p.v.Cap()
|
||||
if n < m {
|
||||
p.v.SetLen(n + 1)
|
||||
} else {
|
||||
t := p.v.Type().Elem()
|
||||
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||
}
|
||||
elem := p.v.Index(n)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
elem.SetInt(int64(int32(x)))
|
||||
case reflect.Uint32:
|
||||
elem.SetUint(uint64(x))
|
||||
case reflect.Float32:
|
||||
elem.SetFloat(float64(math.Float32frombits(x)))
|
||||
}
|
||||
}
|
||||
|
||||
func (p word32Slice) Len() int {
|
||||
return p.v.Len()
|
||||
}
|
||||
|
||||
func (p word32Slice) Index(i int) uint32 {
|
||||
elem := p.v.Index(i)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32Slice returns a reference to a []int32, []uint32, []float32, or []enum field in the struct.
|
||||
func structPointer_Word32Slice(p structPointer, f field) word32Slice {
|
||||
return word32Slice{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// word64 is like word32 but for 64-bit values.
|
||||
type word64 struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||
t := p.v.Type().Elem()
|
||||
switch t {
|
||||
case int64Type:
|
||||
if len(o.int64s) == 0 {
|
||||
o.int64s = make([]int64, uint64PoolSize)
|
||||
}
|
||||
o.int64s[0] = int64(x)
|
||||
p.v.Set(reflect.ValueOf(&o.int64s[0]))
|
||||
o.int64s = o.int64s[1:]
|
||||
return
|
||||
case uint64Type:
|
||||
if len(o.uint64s) == 0 {
|
||||
o.uint64s = make([]uint64, uint64PoolSize)
|
||||
}
|
||||
o.uint64s[0] = x
|
||||
p.v.Set(reflect.ValueOf(&o.uint64s[0]))
|
||||
o.uint64s = o.uint64s[1:]
|
||||
return
|
||||
case float64Type:
|
||||
if len(o.float64s) == 0 {
|
||||
o.float64s = make([]float64, uint64PoolSize)
|
||||
}
|
||||
o.float64s[0] = math.Float64frombits(x)
|
||||
p.v.Set(reflect.ValueOf(&o.float64s[0]))
|
||||
o.float64s = o.float64s[1:]
|
||||
return
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func word64_IsNil(p word64) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
func word64_Get(p word64) uint64 {
|
||||
elem := p.v.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return elem.Uint()
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(elem.Float())
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func structPointer_Word64(p structPointer, f field) word64 {
|
||||
return word64{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// word64Val is like word32Val but for 64-bit values.
|
||||
type word64Val struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||
switch p.v.Type() {
|
||||
case int64Type:
|
||||
p.v.SetInt(int64(x))
|
||||
return
|
||||
case uint64Type:
|
||||
p.v.SetUint(x)
|
||||
return
|
||||
case float64Type:
|
||||
p.v.SetFloat(math.Float64frombits(x))
|
||||
return
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func word64Val_Get(p word64Val) uint64 {
|
||||
elem := p.v
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return elem.Uint()
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(elem.Float())
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||
return word64Val{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
type word64Slice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p word64Slice) Append(x uint64) {
|
||||
n, m := p.v.Len(), p.v.Cap()
|
||||
if n < m {
|
||||
p.v.SetLen(n + 1)
|
||||
} else {
|
||||
t := p.v.Type().Elem()
|
||||
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||
}
|
||||
elem := p.v.Index(n)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
elem.SetInt(int64(int64(x)))
|
||||
case reflect.Uint64:
|
||||
elem.SetUint(uint64(x))
|
||||
case reflect.Float64:
|
||||
elem.SetFloat(float64(math.Float64frombits(x)))
|
||||
}
|
||||
}
|
||||
|
||||
func (p word64Slice) Len() int {
|
||||
return p.v.Len()
|
||||
}
|
||||
|
||||
func (p word64Slice) Index(i int) uint64 {
|
||||
elem := p.v.Index(i)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return uint64(elem.Uint())
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(float64(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func structPointer_Word64Slice(p structPointer, f field) word64Slice {
|
||||
return word64Slice{structPointer_field(p, f)}
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build !appengine,!js
|
||||
|
||||
// This file contains the implementation of the proto field accesses using package unsafe.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// NOTE: These type_Foo functions would more idiomatically be methods,
|
||||
// but Go does not allow methods on pointer types, and we must preserve
|
||||
// some pointer type for the garbage collector. We use these
|
||||
// funcs with clunky names as our poor approximation to methods.
|
||||
//
|
||||
// An alternative would be
|
||||
// type structPointer struct { p unsafe.Pointer }
|
||||
// but that does not registerize as well.
|
||||
|
||||
// A structPointer is a pointer to a struct.
|
||||
type structPointer unsafe.Pointer
|
||||
|
||||
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||
func toStructPointer(v reflect.Value) structPointer {
|
||||
return structPointer(unsafe.Pointer(v.Pointer()))
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func structPointer_IsNil(p structPointer) bool {
|
||||
return p == nil
|
||||
}
|
||||
|
||||
// Interface returns the struct pointer, assumed to have element type t,
|
||||
// as an interface value.
|
||||
func structPointer_Interface(p structPointer, t reflect.Type) interface{} {
|
||||
return reflect.NewAt(t, unsafe.Pointer(p)).Interface()
|
||||
}
|
||||
|
||||
// A field identifies a field in a struct, accessible from a structPointer.
|
||||
// In this implementation, a field is identified by its byte offset from the start of the struct.
|
||||
type field uintptr
|
||||
|
||||
// toField returns a field equivalent to the given reflect field.
|
||||
func toField(f *reflect.StructField) field {
|
||||
return field(f.Offset)
|
||||
}
|
||||
|
||||
// invalidField is an invalid field identifier.
|
||||
const invalidField = ^field(0)
|
||||
|
||||
// IsValid reports whether the field identifier is valid.
|
||||
func (f field) IsValid() bool {
|
||||
return f != ^field(0)
|
||||
}
|
||||
|
||||
// Bytes returns the address of a []byte field in the struct.
|
||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||
return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||
return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// Bool returns the address of a *bool field in the struct.
|
||||
func structPointer_Bool(p structPointer, f field) **bool {
|
||||
return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BoolVal returns the address of a bool field in the struct.
|
||||
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||
return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BoolSlice returns the address of a []bool field in the struct.
|
||||
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||
return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// String returns the address of a *string field in the struct.
|
||||
func structPointer_String(p structPointer, f field) **string {
|
||||
return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StringVal returns the address of a string field in the struct.
|
||||
func structPointer_StringVal(p structPointer, f field) *string {
|
||||
return (*string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StringSlice returns the address of a []string field in the struct.
|
||||
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||
return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// ExtMap returns the address of an extension map field in the struct.
|
||||
func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions {
|
||||
return (*XXX_InternalExtensions)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||
return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||
return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f)))
|
||||
}
|
||||
|
||||
// SetStructPointer writes a *struct field in the struct.
|
||||
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||
*(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q
|
||||
}
|
||||
|
||||
// GetStructPointer reads a *struct field in the struct.
|
||||
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||
return *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StructPointerSlice the address of a []*struct field in the struct.
|
||||
func structPointer_StructPointerSlice(p structPointer, f field) *structPointerSlice {
|
||||
return (*structPointerSlice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// A structPointerSlice represents a slice of pointers to structs (themselves submessages or groups).
|
||||
type structPointerSlice []structPointer
|
||||
|
||||
func (v *structPointerSlice) Len() int { return len(*v) }
|
||||
func (v *structPointerSlice) Index(i int) structPointer { return (*v)[i] }
|
||||
func (v *structPointerSlice) Append(p structPointer) { *v = append(*v, p) }
|
||||
|
||||
// A word32 is the address of a "pointer to 32-bit value" field.
|
||||
type word32 **uint32
|
||||
|
||||
// IsNil reports whether *v is nil.
|
||||
func word32_IsNil(p word32) bool {
|
||||
return *p == nil
|
||||
}
|
||||
|
||||
// Set sets *v to point at a newly allocated word set to x.
|
||||
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||
if len(o.uint32s) == 0 {
|
||||
o.uint32s = make([]uint32, uint32PoolSize)
|
||||
}
|
||||
o.uint32s[0] = x
|
||||
*p = &o.uint32s[0]
|
||||
o.uint32s = o.uint32s[1:]
|
||||
}
|
||||
|
||||
// Get gets the value pointed at by *v.
|
||||
func word32_Get(p word32) uint32 {
|
||||
return **p
|
||||
}
|
||||
|
||||
// Word32 returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32(p structPointer, f field) word32 {
|
||||
return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// A word32Val is the address of a 32-bit value field.
|
||||
type word32Val *uint32
|
||||
|
||||
// Set sets *p to x.
|
||||
func word32Val_Set(p word32Val, x uint32) {
|
||||
*p = x
|
||||
}
|
||||
|
||||
// Get gets the value pointed at by p.
|
||||
func word32Val_Get(p word32Val) uint32 {
|
||||
return *p
|
||||
}
|
||||
|
||||
// Word32Val returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||
return word32Val((*uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// A word32Slice is a slice of 32-bit values.
|
||||
type word32Slice []uint32
|
||||
|
||||
func (v *word32Slice) Append(x uint32) { *v = append(*v, x) }
|
||||
func (v *word32Slice) Len() int { return len(*v) }
|
||||
func (v *word32Slice) Index(i int) uint32 { return (*v)[i] }
|
||||
|
||||
// Word32Slice returns the address of a []int32, []uint32, []float32, or []enum field in the struct.
|
||||
func structPointer_Word32Slice(p structPointer, f field) *word32Slice {
|
||||
return (*word32Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// word64 is like word32 but for 64-bit values.
|
||||
type word64 **uint64
|
||||
|
||||
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||
if len(o.uint64s) == 0 {
|
||||
o.uint64s = make([]uint64, uint64PoolSize)
|
||||
}
|
||||
o.uint64s[0] = x
|
||||
*p = &o.uint64s[0]
|
||||
o.uint64s = o.uint64s[1:]
|
||||
}
|
||||
|
||||
func word64_IsNil(p word64) bool {
|
||||
return *p == nil
|
||||
}
|
||||
|
||||
func word64_Get(p word64) uint64 {
|
||||
return **p
|
||||
}
|
||||
|
||||
func structPointer_Word64(p structPointer, f field) word64 {
|
||||
return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// word64Val is like word32Val but for 64-bit values.
|
||||
type word64Val *uint64
|
||||
|
||||
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||
*p = x
|
||||
}
|
||||
|
||||
func word64Val_Get(p word64Val) uint64 {
|
||||
return *p
|
||||
}
|
||||
|
||||
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||
return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// word64Slice is like word32Slice but for 64-bit values.
|
||||
type word64Slice []uint64
|
||||
|
||||
func (v *word64Slice) Append(x uint64) { *v = append(*v, x) }
|
||||
func (v *word64Slice) Len() int { return len(*v) }
|
||||
func (v *word64Slice) Index(i int) uint64 { return (*v)[i] }
|
||||
|
||||
func structPointer_Word64Slice(p structPointer, f field) *word64Slice {
|
||||
return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
|
@ -0,0 +1,864 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Routines for encoding data into the wire format for protocol buffers.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const debug bool = false
|
||||
|
||||
// Constants that identify the encoding of a value on the wire.
|
||||
const (
|
||||
WireVarint = 0
|
||||
WireFixed64 = 1
|
||||
WireBytes = 2
|
||||
WireStartGroup = 3
|
||||
WireEndGroup = 4
|
||||
WireFixed32 = 5
|
||||
)
|
||||
|
||||
const startSize = 10 // initial slice/string sizes
|
||||
|
||||
// Encoders are defined in encode.go
|
||||
// An encoder outputs the full representation of a field, including its
|
||||
// tag and encoder type.
|
||||
type encoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||
|
||||
// A valueEncoder encodes a single integer in a particular encoding.
|
||||
type valueEncoder func(o *Buffer, x uint64) error
|
||||
|
||||
// Sizers are defined in encode.go
|
||||
// A sizer returns the encoded size of a field, including its tag and encoder
|
||||
// type.
|
||||
type sizer func(prop *Properties, base structPointer) int
|
||||
|
||||
// A valueSizer returns the encoded size of a single integer in a particular
|
||||
// encoding.
|
||||
type valueSizer func(x uint64) int
|
||||
|
||||
// Decoders are defined in decode.go
|
||||
// A decoder creates a value from its wire representation.
|
||||
// Unrecognized subelements are saved in unrec.
|
||||
type decoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||
|
||||
// A valueDecoder decodes a single integer in a particular encoding.
|
||||
type valueDecoder func(o *Buffer) (x uint64, err error)
|
||||
|
||||
// A oneofMarshaler does the marshaling for all oneof fields in a message.
|
||||
type oneofMarshaler func(Message, *Buffer) error
|
||||
|
||||
// A oneofUnmarshaler does the unmarshaling for a oneof field in a message.
|
||||
type oneofUnmarshaler func(Message, int, int, *Buffer) (bool, error)
|
||||
|
||||
// A oneofSizer does the sizing for all oneof fields in a message.
|
||||
type oneofSizer func(Message) int
|
||||
|
||||
// tagMap is an optimization over map[int]int for typical protocol buffer
|
||||
// use-cases. Encoded protocol buffers are often in tag order with small tag
|
||||
// numbers.
|
||||
type tagMap struct {
|
||||
fastTags []int
|
||||
slowTags map[int]int
|
||||
}
|
||||
|
||||
// tagMapFastLimit is the upper bound on the tag number that will be stored in
|
||||
// the tagMap slice rather than its map.
|
||||
const tagMapFastLimit = 1024
|
||||
|
||||
func (p *tagMap) get(t int) (int, bool) {
|
||||
if t > 0 && t < tagMapFastLimit {
|
||||
if t >= len(p.fastTags) {
|
||||
return 0, false
|
||||
}
|
||||
fi := p.fastTags[t]
|
||||
return fi, fi >= 0
|
||||
}
|
||||
fi, ok := p.slowTags[t]
|
||||
return fi, ok
|
||||
}
|
||||
|
||||
func (p *tagMap) put(t int, fi int) {
|
||||
if t > 0 && t < tagMapFastLimit {
|
||||
for len(p.fastTags) < t+1 {
|
||||
p.fastTags = append(p.fastTags, -1)
|
||||
}
|
||||
p.fastTags[t] = fi
|
||||
return
|
||||
}
|
||||
if p.slowTags == nil {
|
||||
p.slowTags = make(map[int]int)
|
||||
}
|
||||
p.slowTags[t] = fi
|
||||
}
|
||||
|
||||
// StructProperties represents properties for all the fields of a struct.
|
||||
// decoderTags and decoderOrigNames should only be used by the decoder.
|
||||
type StructProperties struct {
|
||||
Prop []*Properties // properties for each field
|
||||
reqCount int // required count
|
||||
decoderTags tagMap // map from proto tag to struct field number
|
||||
decoderOrigNames map[string]int // map from original name to struct field number
|
||||
order []int // list of struct field numbers in tag order
|
||||
unrecField field // field id of the XXX_unrecognized []byte field
|
||||
extendable bool // is this an extendable proto
|
||||
|
||||
oneofMarshaler oneofMarshaler
|
||||
oneofUnmarshaler oneofUnmarshaler
|
||||
oneofSizer oneofSizer
|
||||
stype reflect.Type
|
||||
|
||||
// OneofTypes contains information about the oneof fields in this message.
|
||||
// It is keyed by the original name of a field.
|
||||
OneofTypes map[string]*OneofProperties
|
||||
}
|
||||
|
||||
// OneofProperties represents information about a specific field in a oneof.
|
||||
type OneofProperties struct {
|
||||
Type reflect.Type // pointer to generated struct type for this oneof field
|
||||
Field int // struct field number of the containing oneof in the message
|
||||
Prop *Properties
|
||||
}
|
||||
|
||||
// Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec.
|
||||
// See encode.go, (*Buffer).enc_struct.
|
||||
|
||||
func (sp *StructProperties) Len() int { return len(sp.order) }
|
||||
func (sp *StructProperties) Less(i, j int) bool {
|
||||
return sp.Prop[sp.order[i]].Tag < sp.Prop[sp.order[j]].Tag
|
||||
}
|
||||
func (sp *StructProperties) Swap(i, j int) { sp.order[i], sp.order[j] = sp.order[j], sp.order[i] }
|
||||
|
||||
// Properties represents the protocol-specific behavior of a single struct field.
|
||||
type Properties struct {
|
||||
Name string // name of the field, for error messages
|
||||
OrigName string // original name before protocol compiler (always set)
|
||||
JSONName string // name to use for JSON; determined by protoc
|
||||
Wire string
|
||||
WireType int
|
||||
Tag int
|
||||
Required bool
|
||||
Optional bool
|
||||
Repeated bool
|
||||
Packed bool // relevant for repeated primitives only
|
||||
Enum string // set for enum types only
|
||||
proto3 bool // whether this is known to be a proto3 field; set for []byte only
|
||||
oneof bool // whether this is a oneof field
|
||||
|
||||
Default string // default value
|
||||
HasDefault bool // whether an explicit default was provided
|
||||
def_uint64 uint64
|
||||
|
||||
enc encoder
|
||||
valEnc valueEncoder // set for bool and numeric types only
|
||||
field field
|
||||
tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType)
|
||||
tagbuf [8]byte
|
||||
stype reflect.Type // set for struct types only
|
||||
sprop *StructProperties // set for struct types only
|
||||
isMarshaler bool
|
||||
isUnmarshaler bool
|
||||
|
||||
mtype reflect.Type // set for map types only
|
||||
mkeyprop *Properties // set for map types only
|
||||
mvalprop *Properties // set for map types only
|
||||
|
||||
size sizer
|
||||
valSize valueSizer // set for bool and numeric types only
|
||||
|
||||
dec decoder
|
||||
valDec valueDecoder // set for bool and numeric types only
|
||||
|
||||
// If this is a packable field, this will be the decoder for the packed version of the field.
|
||||
packedDec decoder
|
||||
}
|
||||
|
||||
// String formats the properties in the protobuf struct field tag style.
|
||||
func (p *Properties) String() string {
|
||||
s := p.Wire
|
||||
s = ","
|
||||
s += strconv.Itoa(p.Tag)
|
||||
if p.Required {
|
||||
s += ",req"
|
||||
}
|
||||
if p.Optional {
|
||||
s += ",opt"
|
||||
}
|
||||
if p.Repeated {
|
||||
s += ",rep"
|
||||
}
|
||||
if p.Packed {
|
||||
s += ",packed"
|
||||
}
|
||||
s += ",name=" + p.OrigName
|
||||
if p.JSONName != p.OrigName {
|
||||
s += ",json=" + p.JSONName
|
||||
}
|
||||
if p.proto3 {
|
||||
s += ",proto3"
|
||||
}
|
||||
if p.oneof {
|
||||
s += ",oneof"
|
||||
}
|
||||
if len(p.Enum) > 0 {
|
||||
s += ",enum=" + p.Enum
|
||||
}
|
||||
if p.HasDefault {
|
||||
s += ",def=" + p.Default
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Parse populates p by parsing a string in the protobuf struct field tag style.
|
||||
func (p *Properties) Parse(s string) {
|
||||
// "bytes,49,opt,name=foo,def=hello!"
|
||||
fields := strings.Split(s, ",") // breaks def=, but handled below.
|
||||
if len(fields) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s)
|
||||
return
|
||||
}
|
||||
|
||||
p.Wire = fields[0]
|
||||
switch p.Wire {
|
||||
case "varint":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeVarint
|
||||
p.valDec = (*Buffer).DecodeVarint
|
||||
p.valSize = sizeVarint
|
||||
case "fixed32":
|
||||
p.WireType = WireFixed32
|
||||
p.valEnc = (*Buffer).EncodeFixed32
|
||||
p.valDec = (*Buffer).DecodeFixed32
|
||||
p.valSize = sizeFixed32
|
||||
case "fixed64":
|
||||
p.WireType = WireFixed64
|
||||
p.valEnc = (*Buffer).EncodeFixed64
|
||||
p.valDec = (*Buffer).DecodeFixed64
|
||||
p.valSize = sizeFixed64
|
||||
case "zigzag32":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeZigzag32
|
||||
p.valDec = (*Buffer).DecodeZigzag32
|
||||
p.valSize = sizeZigzag32
|
||||
case "zigzag64":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeZigzag64
|
||||
p.valDec = (*Buffer).DecodeZigzag64
|
||||
p.valSize = sizeZigzag64
|
||||
case "bytes", "group":
|
||||
p.WireType = WireBytes
|
||||
// no numeric converter for non-numeric types
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
p.Tag, err = strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 2; i < len(fields); i++ {
|
||||
f := fields[i]
|
||||
switch {
|
||||
case f == "req":
|
||||
p.Required = true
|
||||
case f == "opt":
|
||||
p.Optional = true
|
||||
case f == "rep":
|
||||
p.Repeated = true
|
||||
case f == "packed":
|
||||
p.Packed = true
|
||||
case strings.HasPrefix(f, "name="):
|
||||
p.OrigName = f[5:]
|
||||
case strings.HasPrefix(f, "json="):
|
||||
p.JSONName = f[5:]
|
||||
case strings.HasPrefix(f, "enum="):
|
||||
p.Enum = f[5:]
|
||||
case f == "proto3":
|
||||
p.proto3 = true
|
||||
case f == "oneof":
|
||||
p.oneof = true
|
||||
case strings.HasPrefix(f, "def="):
|
||||
p.HasDefault = true
|
||||
p.Default = f[4:] // rest of string
|
||||
if i+1 < len(fields) {
|
||||
// Commas aren't escaped, and def is always last.
|
||||
p.Default += "," + strings.Join(fields[i+1:], ",")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logNoSliceEnc(t1, t2 reflect.Type) {
|
||||
fmt.Fprintf(os.Stderr, "proto: no slice oenc for %T = []%T\n", t1, t2)
|
||||
}
|
||||
|
||||
var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem()
|
||||
|
||||
// Initialize the fields for encoding and decoding.
|
||||
func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
|
||||
p.enc = nil
|
||||
p.dec = nil
|
||||
p.size = nil
|
||||
|
||||
switch t1 := typ; t1.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1)
|
||||
|
||||
// proto3 scalar types
|
||||
|
||||
case reflect.Bool:
|
||||
p.enc = (*Buffer).enc_proto3_bool
|
||||
p.dec = (*Buffer).dec_proto3_bool
|
||||
p.size = size_proto3_bool
|
||||
case reflect.Int32:
|
||||
p.enc = (*Buffer).enc_proto3_int32
|
||||
p.dec = (*Buffer).dec_proto3_int32
|
||||
p.size = size_proto3_int32
|
||||
case reflect.Uint32:
|
||||
p.enc = (*Buffer).enc_proto3_uint32
|
||||
p.dec = (*Buffer).dec_proto3_int32 // can reuse
|
||||
p.size = size_proto3_uint32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
p.enc = (*Buffer).enc_proto3_int64
|
||||
p.dec = (*Buffer).dec_proto3_int64
|
||||
p.size = size_proto3_int64
|
||||
case reflect.Float32:
|
||||
p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_proto3_int32
|
||||
p.size = size_proto3_uint32
|
||||
case reflect.Float64:
|
||||
p.enc = (*Buffer).enc_proto3_int64 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_proto3_int64
|
||||
p.size = size_proto3_int64
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_proto3_string
|
||||
p.dec = (*Buffer).dec_proto3_string
|
||||
p.size = size_proto3_string
|
||||
|
||||
case reflect.Ptr:
|
||||
switch t2 := t1.Elem(); t2.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no encoder function for %v -> %v\n", t1, t2)
|
||||
break
|
||||
case reflect.Bool:
|
||||
p.enc = (*Buffer).enc_bool
|
||||
p.dec = (*Buffer).dec_bool
|
||||
p.size = size_bool
|
||||
case reflect.Int32:
|
||||
p.enc = (*Buffer).enc_int32
|
||||
p.dec = (*Buffer).dec_int32
|
||||
p.size = size_int32
|
||||
case reflect.Uint32:
|
||||
p.enc = (*Buffer).enc_uint32
|
||||
p.dec = (*Buffer).dec_int32 // can reuse
|
||||
p.size = size_uint32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
p.enc = (*Buffer).enc_int64
|
||||
p.dec = (*Buffer).dec_int64
|
||||
p.size = size_int64
|
||||
case reflect.Float32:
|
||||
p.enc = (*Buffer).enc_uint32 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_int32
|
||||
p.size = size_uint32
|
||||
case reflect.Float64:
|
||||
p.enc = (*Buffer).enc_int64 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_int64
|
||||
p.size = size_int64
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_string
|
||||
p.dec = (*Buffer).dec_string
|
||||
p.size = size_string
|
||||
case reflect.Struct:
|
||||
p.stype = t1.Elem()
|
||||
p.isMarshaler = isMarshaler(t1)
|
||||
p.isUnmarshaler = isUnmarshaler(t1)
|
||||
if p.Wire == "bytes" {
|
||||
p.enc = (*Buffer).enc_struct_message
|
||||
p.dec = (*Buffer).dec_struct_message
|
||||
p.size = size_struct_message
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_struct_group
|
||||
p.dec = (*Buffer).dec_struct_group
|
||||
p.size = size_struct_group
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
switch t2 := t1.Elem(); t2.Kind() {
|
||||
default:
|
||||
logNoSliceEnc(t1, t2)
|
||||
break
|
||||
case reflect.Bool:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_bool
|
||||
p.size = size_slice_packed_bool
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_bool
|
||||
p.size = size_slice_bool
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_bool
|
||||
p.packedDec = (*Buffer).dec_slice_packed_bool
|
||||
case reflect.Int32:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int32
|
||||
p.size = size_slice_packed_int32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int32
|
||||
p.size = size_slice_int32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case reflect.Uint32:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||
p.size = size_slice_packed_uint32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_uint32
|
||||
p.size = size_slice_uint32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int64
|
||||
p.size = size_slice_packed_int64
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int64
|
||||
p.size = size_slice_int64
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int64
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||
case reflect.Uint8:
|
||||
p.dec = (*Buffer).dec_slice_byte
|
||||
if p.proto3 {
|
||||
p.enc = (*Buffer).enc_proto3_slice_byte
|
||||
p.size = size_proto3_slice_byte
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_byte
|
||||
p.size = size_slice_byte
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch t2.Bits() {
|
||||
case 32:
|
||||
// can just treat them as bits
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||
p.size = size_slice_packed_uint32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_uint32
|
||||
p.size = size_slice_uint32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case 64:
|
||||
// can just treat them as bits
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int64
|
||||
p.size = size_slice_packed_int64
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int64
|
||||
p.size = size_slice_int64
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int64
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||
default:
|
||||
logNoSliceEnc(t1, t2)
|
||||
break
|
||||
}
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_slice_string
|
||||
p.dec = (*Buffer).dec_slice_string
|
||||
p.size = size_slice_string
|
||||
case reflect.Ptr:
|
||||
switch t3 := t2.Elem(); t3.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no ptr oenc for %T -> %T -> %T\n", t1, t2, t3)
|
||||
break
|
||||
case reflect.Struct:
|
||||
p.stype = t2.Elem()
|
||||
p.isMarshaler = isMarshaler(t2)
|
||||
p.isUnmarshaler = isUnmarshaler(t2)
|
||||
if p.Wire == "bytes" {
|
||||
p.enc = (*Buffer).enc_slice_struct_message
|
||||
p.dec = (*Buffer).dec_slice_struct_message
|
||||
p.size = size_slice_struct_message
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_struct_group
|
||||
p.dec = (*Buffer).dec_slice_struct_group
|
||||
p.size = size_slice_struct_group
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
switch t2.Elem().Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no slice elem oenc for %T -> %T -> %T\n", t1, t2, t2.Elem())
|
||||
break
|
||||
case reflect.Uint8:
|
||||
p.enc = (*Buffer).enc_slice_slice_byte
|
||||
p.dec = (*Buffer).dec_slice_slice_byte
|
||||
p.size = size_slice_slice_byte
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
p.enc = (*Buffer).enc_new_map
|
||||
p.dec = (*Buffer).dec_new_map
|
||||
p.size = size_new_map
|
||||
|
||||
p.mtype = t1
|
||||
p.mkeyprop = &Properties{}
|
||||
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||
p.mvalprop = &Properties{}
|
||||
vtype := p.mtype.Elem()
|
||||
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
|
||||
// The value type is not a message (*T) or bytes ([]byte),
|
||||
// so we need encoders for the pointer to this type.
|
||||
vtype = reflect.PtrTo(vtype)
|
||||
}
|
||||
p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
|
||||
}
|
||||
|
||||
// precalculate tag code
|
||||
wire := p.WireType
|
||||
if p.Packed {
|
||||
wire = WireBytes
|
||||
}
|
||||
x := uint32(p.Tag)<<3 | uint32(wire)
|
||||
i := 0
|
||||
for i = 0; x > 127; i++ {
|
||||
p.tagbuf[i] = 0x80 | uint8(x&0x7F)
|
||||
x >>= 7
|
||||
}
|
||||
p.tagbuf[i] = uint8(x)
|
||||
p.tagcode = p.tagbuf[0 : i+1]
|
||||
|
||||
if p.stype != nil {
|
||||
if lockGetProp {
|
||||
p.sprop = GetProperties(p.stype)
|
||||
} else {
|
||||
p.sprop = getPropertiesLocked(p.stype)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
// isMarshaler reports whether type t implements Marshaler.
|
||||
func isMarshaler(t reflect.Type) bool {
|
||||
// We're checking for (likely) pointer-receiver methods
|
||||
// so if t is not a pointer, something is very wrong.
|
||||
// The calls above only invoke isMarshaler on pointer types.
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic("proto: misuse of isMarshaler")
|
||||
}
|
||||
return t.Implements(marshalerType)
|
||||
}
|
||||
|
||||
// isUnmarshaler reports whether type t implements Unmarshaler.
|
||||
func isUnmarshaler(t reflect.Type) bool {
|
||||
// We're checking for (likely) pointer-receiver methods
|
||||
// so if t is not a pointer, something is very wrong.
|
||||
// The calls above only invoke isUnmarshaler on pointer types.
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic("proto: misuse of isUnmarshaler")
|
||||
}
|
||||
return t.Implements(unmarshalerType)
|
||||
}
|
||||
|
||||
// Init populates the properties from a protocol buffer struct tag.
|
||||
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
||||
p.init(typ, name, tag, f, true)
|
||||
}
|
||||
|
||||
func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructField, lockGetProp bool) {
|
||||
// "bytes,49,opt,def=hello!"
|
||||
p.Name = name
|
||||
p.OrigName = name
|
||||
if f != nil {
|
||||
p.field = toField(f)
|
||||
}
|
||||
if tag == "" {
|
||||
return
|
||||
}
|
||||
p.Parse(tag)
|
||||
p.setEncAndDec(typ, f, lockGetProp)
|
||||
}
|
||||
|
||||
var (
|
||||
propertiesMu sync.RWMutex
|
||||
propertiesMap = make(map[reflect.Type]*StructProperties)
|
||||
)
|
||||
|
||||
// GetProperties returns the list of properties for the type represented by t.
|
||||
// t must represent a generated struct type of a protocol message.
|
||||
func GetProperties(t reflect.Type) *StructProperties {
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic("proto: type must have kind struct")
|
||||
}
|
||||
|
||||
// Most calls to GetProperties in a long-running program will be
|
||||
// retrieving details for types we have seen before.
|
||||
propertiesMu.RLock()
|
||||
sprop, ok := propertiesMap[t]
|
||||
propertiesMu.RUnlock()
|
||||
if ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return sprop
|
||||
}
|
||||
|
||||
propertiesMu.Lock()
|
||||
sprop = getPropertiesLocked(t)
|
||||
propertiesMu.Unlock()
|
||||
return sprop
|
||||
}
|
||||
|
||||
// getPropertiesLocked requires that propertiesMu is held.
|
||||
func getPropertiesLocked(t reflect.Type) *StructProperties {
|
||||
if prop, ok := propertiesMap[t]; ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return prop
|
||||
}
|
||||
if collectStats {
|
||||
stats.Cmiss++
|
||||
}
|
||||
|
||||
prop := new(StructProperties)
|
||||
// in case of recursive protos, fill this in now.
|
||||
propertiesMap[t] = prop
|
||||
|
||||
// build properties
|
||||
prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType) ||
|
||||
reflect.PtrTo(t).Implements(extendableProtoV1Type)
|
||||
prop.unrecField = invalidField
|
||||
prop.Prop = make([]*Properties, t.NumField())
|
||||
prop.order = make([]int, t.NumField())
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
p := new(Properties)
|
||||
name := f.Name
|
||||
p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false)
|
||||
|
||||
if f.Name == "XXX_InternalExtensions" { // special case
|
||||
p.enc = (*Buffer).enc_exts
|
||||
p.dec = nil // not needed
|
||||
p.size = size_exts
|
||||
} else if f.Name == "XXX_extensions" { // special case
|
||||
p.enc = (*Buffer).enc_map
|
||||
p.dec = nil // not needed
|
||||
p.size = size_map
|
||||
} else if f.Name == "XXX_unrecognized" { // special case
|
||||
prop.unrecField = toField(&f)
|
||||
}
|
||||
oneof := f.Tag.Get("protobuf_oneof") // special case
|
||||
if oneof != "" {
|
||||
// Oneof fields don't use the traditional protobuf tag.
|
||||
p.OrigName = oneof
|
||||
}
|
||||
prop.Prop[i] = p
|
||||
prop.order[i] = i
|
||||
if debug {
|
||||
print(i, " ", f.Name, " ", t.String(), " ")
|
||||
if p.Tag > 0 {
|
||||
print(p.String())
|
||||
}
|
||||
print("\n")
|
||||
}
|
||||
if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") && oneof == "" {
|
||||
fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]")
|
||||
}
|
||||
}
|
||||
|
||||
// Re-order prop.order.
|
||||
sort.Sort(prop)
|
||||
|
||||
type oneofMessage interface {
|
||||
XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{})
|
||||
}
|
||||
if om, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(oneofMessage); ok {
|
||||
var oots []interface{}
|
||||
prop.oneofMarshaler, prop.oneofUnmarshaler, prop.oneofSizer, oots = om.XXX_OneofFuncs()
|
||||
prop.stype = t
|
||||
|
||||
// Interpret oneof metadata.
|
||||
prop.OneofTypes = make(map[string]*OneofProperties)
|
||||
for _, oot := range oots {
|
||||
oop := &OneofProperties{
|
||||
Type: reflect.ValueOf(oot).Type(), // *T
|
||||
Prop: new(Properties),
|
||||
}
|
||||
sft := oop.Type.Elem().Field(0)
|
||||
oop.Prop.Name = sft.Name
|
||||
oop.Prop.Parse(sft.Tag.Get("protobuf"))
|
||||
// There will be exactly one interface field that
|
||||
// this new value is assignable to.
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.Type.Kind() != reflect.Interface {
|
||||
continue
|
||||
}
|
||||
if !oop.Type.AssignableTo(f.Type) {
|
||||
continue
|
||||
}
|
||||
oop.Field = i
|
||||
break
|
||||
}
|
||||
prop.OneofTypes[oop.Prop.OrigName] = oop
|
||||
}
|
||||
}
|
||||
|
||||
// build required counts
|
||||
// build tags
|
||||
reqCount := 0
|
||||
prop.decoderOrigNames = make(map[string]int)
|
||||
for i, p := range prop.Prop {
|
||||
if strings.HasPrefix(p.Name, "XXX_") {
|
||||
// Internal fields should not appear in tags/origNames maps.
|
||||
// They are handled specially when encoding and decoding.
|
||||
continue
|
||||
}
|
||||
if p.Required {
|
||||
reqCount++
|
||||
}
|
||||
prop.decoderTags.put(p.Tag, i)
|
||||
prop.decoderOrigNames[p.OrigName] = i
|
||||
}
|
||||
prop.reqCount = reqCount
|
||||
|
||||
return prop
|
||||
}
|
||||
|
||||
// Return the Properties object for the x[0]'th field of the structure.
|
||||
func propByIndex(t reflect.Type, x []int) *Properties {
|
||||
if len(x) != 1 {
|
||||
fmt.Fprintf(os.Stderr, "proto: field index dimension %d (not 1) for type %s\n", len(x), t)
|
||||
return nil
|
||||
}
|
||||
prop := GetProperties(t)
|
||||
return prop.Prop[x[0]]
|
||||
}
|
||||
|
||||
// Get the address and type of a pointer to a struct from an interface.
|
||||
func getbase(pb Message) (t reflect.Type, b structPointer, err error) {
|
||||
if pb == nil {
|
||||
err = ErrNil
|
||||
return
|
||||
}
|
||||
// get the reflect type of the pointer to the struct.
|
||||
t = reflect.TypeOf(pb)
|
||||
// get the address of the struct.
|
||||
value := reflect.ValueOf(pb)
|
||||
b = toStructPointer(value)
|
||||
return
|
||||
}
|
||||
|
||||
// A global registry of enum types.
|
||||
// The generated code will register the generated maps by calling RegisterEnum.
|
||||
|
||||
var enumValueMaps = make(map[string]map[string]int32)
|
||||
|
||||
// RegisterEnum is called from the generated code to install the enum descriptor
|
||||
// maps into the global table to aid parsing text format protocol buffers.
|
||||
func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[string]int32) {
|
||||
if _, ok := enumValueMaps[typeName]; ok {
|
||||
panic("proto: duplicate enum registered: " + typeName)
|
||||
}
|
||||
enumValueMaps[typeName] = valueMap
|
||||
}
|
||||
|
||||
// EnumValueMap returns the mapping from names to integers of the
|
||||
// enum type enumType, or a nil if not found.
|
||||
func EnumValueMap(enumType string) map[string]int32 {
|
||||
return enumValueMaps[enumType]
|
||||
}
|
||||
|
||||
// A registry of all linked message types.
|
||||
// The string is a fully-qualified proto name ("pkg.Message").
|
||||
var (
|
||||
protoTypes = make(map[string]reflect.Type)
|
||||
revProtoTypes = make(map[reflect.Type]string)
|
||||
)
|
||||
|
||||
// RegisterType is called from generated code and maps from the fully qualified
|
||||
// proto name to the type (pointer to struct) of the protocol buffer.
|
||||
func RegisterType(x Message, name string) {
|
||||
if _, ok := protoTypes[name]; ok {
|
||||
// TODO: Some day, make this a panic.
|
||||
log.Printf("proto: duplicate proto type registered: %s", name)
|
||||
return
|
||||
}
|
||||
t := reflect.TypeOf(x)
|
||||
protoTypes[name] = t
|
||||
revProtoTypes[t] = name
|
||||
}
|
||||
|
||||
// MessageName returns the fully-qualified proto name for the given message type.
|
||||
func MessageName(x Message) string { return revProtoTypes[reflect.TypeOf(x)] }
|
||||
|
||||
// MessageType returns the message type (pointer to struct) for a named message.
|
||||
func MessageType(name string) reflect.Type { return protoTypes[name] }
|
||||
|
||||
// A registry of all linked proto files.
|
||||
var (
|
||||
protoFiles = make(map[string][]byte) // file name => fileDescriptor
|
||||
)
|
||||
|
||||
// RegisterFile is called from generated code and maps from the
|
||||
// full file name of a .proto file to its compressed FileDescriptorProto.
|
||||
func RegisterFile(filename string, fileDescriptor []byte) {
|
||||
protoFiles[filename] = fileDescriptor
|
||||
}
|
||||
|
||||
// FileDescriptor returns the compressed FileDescriptorProto for a .proto file.
|
||||
func FileDescriptor(filename string) []byte { return protoFiles[filename] }
|
|
@ -0,0 +1,854 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
// Functions for writing the text protocol buffer format.
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
newline = []byte("\n")
|
||||
spaces = []byte(" ")
|
||||
gtNewline = []byte(">\n")
|
||||
endBraceNewline = []byte("}\n")
|
||||
backslashN = []byte{'\\', 'n'}
|
||||
backslashR = []byte{'\\', 'r'}
|
||||
backslashT = []byte{'\\', 't'}
|
||||
backslashDQ = []byte{'\\', '"'}
|
||||
backslashBS = []byte{'\\', '\\'}
|
||||
posInf = []byte("inf")
|
||||
negInf = []byte("-inf")
|
||||
nan = []byte("nan")
|
||||
)
|
||||
|
||||
type writer interface {
|
||||
io.Writer
|
||||
WriteByte(byte) error
|
||||
}
|
||||
|
||||
// textWriter is an io.Writer that tracks its indentation level.
|
||||
type textWriter struct {
|
||||
ind int
|
||||
complete bool // if the current position is a complete line
|
||||
compact bool // whether to write out as a one-liner
|
||||
w writer
|
||||
}
|
||||
|
||||
func (w *textWriter) WriteString(s string) (n int, err error) {
|
||||
if !strings.Contains(s, "\n") {
|
||||
if !w.compact && w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
w.complete = false
|
||||
return io.WriteString(w.w, s)
|
||||
}
|
||||
// WriteString is typically called without newlines, so this
|
||||
// codepath and its copy are rare. We copy to avoid
|
||||
// duplicating all of Write's logic here.
|
||||
return w.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (w *textWriter) Write(p []byte) (n int, err error) {
|
||||
newlines := bytes.Count(p, newline)
|
||||
if newlines == 0 {
|
||||
if !w.compact && w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
n, err = w.w.Write(p)
|
||||
w.complete = false
|
||||
return n, err
|
||||
}
|
||||
|
||||
frags := bytes.SplitN(p, newline, newlines+1)
|
||||
if w.compact {
|
||||
for i, frag := range frags {
|
||||
if i > 0 {
|
||||
if err := w.w.WriteByte(' '); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
nn, err := w.w.Write(frag)
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
for i, frag := range frags {
|
||||
if w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
nn, err := w.w.Write(frag)
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if i+1 < len(frags) {
|
||||
if err := w.w.WriteByte('\n'); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
w.complete = len(frags[len(frags)-1]) == 0
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *textWriter) WriteByte(c byte) error {
|
||||
if w.compact && c == '\n' {
|
||||
c = ' '
|
||||
}
|
||||
if !w.compact && w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
err := w.w.WriteByte(c)
|
||||
w.complete = c == '\n'
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *textWriter) indent() { w.ind++ }
|
||||
|
||||
func (w *textWriter) unindent() {
|
||||
if w.ind == 0 {
|
||||
log.Print("proto: textWriter unindented too far")
|
||||
return
|
||||
}
|
||||
w.ind--
|
||||
}
|
||||
|
||||
func writeName(w *textWriter, props *Properties) error {
|
||||
if _, err := w.WriteString(props.OrigName); err != nil {
|
||||
return err
|
||||
}
|
||||
if props.Wire != "group" {
|
||||
return w.WriteByte(':')
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// raw is the interface satisfied by RawMessage.
|
||||
type raw interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
func requiresQuotes(u string) bool {
|
||||
// When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
||||
for _, ch := range u {
|
||||
switch {
|
||||
case ch == '.' || ch == '/' || ch == '_':
|
||||
continue
|
||||
case '0' <= ch && ch <= '9':
|
||||
continue
|
||||
case 'A' <= ch && ch <= 'Z':
|
||||
continue
|
||||
case 'a' <= ch && ch <= 'z':
|
||||
continue
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isAny reports whether sv is a google.protobuf.Any message
|
||||
func isAny(sv reflect.Value) bool {
|
||||
type wkt interface {
|
||||
XXX_WellKnownType() string
|
||||
}
|
||||
t, ok := sv.Addr().Interface().(wkt)
|
||||
return ok && t.XXX_WellKnownType() == "Any"
|
||||
}
|
||||
|
||||
// writeProto3Any writes an expanded google.protobuf.Any message.
|
||||
//
|
||||
// It returns (false, nil) if sv value can't be unmarshaled (e.g. because
|
||||
// required messages are not linked in).
|
||||
//
|
||||
// It returns (true, error) when sv was written in expanded format or an error
|
||||
// was encountered.
|
||||
func (tm *TextMarshaler) writeProto3Any(w *textWriter, sv reflect.Value) (bool, error) {
|
||||
turl := sv.FieldByName("TypeUrl")
|
||||
val := sv.FieldByName("Value")
|
||||
if !turl.IsValid() || !val.IsValid() {
|
||||
return true, errors.New("proto: invalid google.protobuf.Any message")
|
||||
}
|
||||
|
||||
b, ok := val.Interface().([]byte)
|
||||
if !ok {
|
||||
return true, errors.New("proto: invalid google.protobuf.Any message")
|
||||
}
|
||||
|
||||
parts := strings.Split(turl.String(), "/")
|
||||
mt := MessageType(parts[len(parts)-1])
|
||||
if mt == nil {
|
||||
return false, nil
|
||||
}
|
||||
m := reflect.New(mt.Elem())
|
||||
if err := Unmarshal(b, m.Interface().(Message)); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
w.Write([]byte("["))
|
||||
u := turl.String()
|
||||
if requiresQuotes(u) {
|
||||
writeString(w, u)
|
||||
} else {
|
||||
w.Write([]byte(u))
|
||||
}
|
||||
if w.compact {
|
||||
w.Write([]byte("]:<"))
|
||||
} else {
|
||||
w.Write([]byte("]: <\n"))
|
||||
w.ind++
|
||||
}
|
||||
if err := tm.writeStruct(w, m.Elem()); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if w.compact {
|
||||
w.Write([]byte("> "))
|
||||
} else {
|
||||
w.ind--
|
||||
w.Write([]byte(">\n"))
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
||||
if tm.ExpandAny && isAny(sv) {
|
||||
if canExpand, err := tm.writeProto3Any(w, sv); canExpand {
|
||||
return err
|
||||
}
|
||||
}
|
||||
st := sv.Type()
|
||||
sprops := GetProperties(st)
|
||||
for i := 0; i < sv.NumField(); i++ {
|
||||
fv := sv.Field(i)
|
||||
props := sprops.Prop[i]
|
||||
name := st.Field(i).Name
|
||||
|
||||
if strings.HasPrefix(name, "XXX_") {
|
||||
// There are two XXX_ fields:
|
||||
// XXX_unrecognized []byte
|
||||
// XXX_extensions map[int32]proto.Extension
|
||||
// The first is handled here;
|
||||
// the second is handled at the bottom of this function.
|
||||
if name == "XXX_unrecognized" && !fv.IsNil() {
|
||||
if err := writeUnknownStruct(w, fv.Interface().([]byte)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||
// Field not filled in. This could be an optional field or
|
||||
// a required field that wasn't filled in. Either way, there
|
||||
// isn't anything we can show for it.
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Slice && fv.IsNil() {
|
||||
// Repeated field that is empty, or a bytes field that is unused.
|
||||
continue
|
||||
}
|
||||
|
||||
if props.Repeated && fv.Kind() == reflect.Slice {
|
||||
// Repeated field.
|
||||
for j := 0; j < fv.Len(); j++ {
|
||||
if err := writeName(w, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
v := fv.Index(j)
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
// A nil message in a repeated field is not valid,
|
||||
// but we can handle that more gracefully than panicking.
|
||||
if _, err := w.Write([]byte("<nil>\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := tm.writeAny(w, v, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Map {
|
||||
// Map fields are rendered as a repeated struct with key/value fields.
|
||||
keys := fv.MapKeys()
|
||||
sort.Sort(mapKeys(keys))
|
||||
for _, key := range keys {
|
||||
val := fv.MapIndex(key)
|
||||
if err := writeName(w, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// open struct
|
||||
if err := w.WriteByte('<'); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
// key
|
||||
if _, err := w.WriteString("key:"); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, key, props.mkeyprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
// nil values aren't legal, but we can avoid panicking because of them.
|
||||
if val.Kind() != reflect.Ptr || !val.IsNil() {
|
||||
// value
|
||||
if _, err := w.WriteString("value:"); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, val, props.mvalprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// close struct
|
||||
w.unindent()
|
||||
if err := w.WriteByte('>'); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if props.proto3 && fv.Kind() == reflect.Slice && fv.Len() == 0 {
|
||||
// empty bytes field
|
||||
continue
|
||||
}
|
||||
if fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice {
|
||||
// proto3 non-repeated scalar field; skip if zero value
|
||||
if isProto3Zero(fv) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if fv.Kind() == reflect.Interface {
|
||||
// Check if it is a oneof.
|
||||
if st.Field(i).Tag.Get("protobuf_oneof") != "" {
|
||||
// fv is nil, or holds a pointer to generated struct.
|
||||
// That generated struct has exactly one field,
|
||||
// which has a protobuf struct tag.
|
||||
if fv.IsNil() {
|
||||
continue
|
||||
}
|
||||
inner := fv.Elem().Elem() // interface -> *T -> T
|
||||
tag := inner.Type().Field(0).Tag.Get("protobuf")
|
||||
props = new(Properties) // Overwrite the outer props var, but not its pointee.
|
||||
props.Parse(tag)
|
||||
// Write the value in the oneof, not the oneof itself.
|
||||
fv = inner.Field(0)
|
||||
|
||||
// Special case to cope with malformed messages gracefully:
|
||||
// If the value in the oneof is a nil pointer, don't panic
|
||||
// in writeAny.
|
||||
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||
// Use errors.New so writeAny won't render quotes.
|
||||
msg := errors.New("/* nil */")
|
||||
fv = reflect.ValueOf(&msg).Elem()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeName(w, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if b, ok := fv.Interface().(raw); ok {
|
||||
if err := writeRaw(w, b.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Enums have a String method, so writeAny will work fine.
|
||||
if err := tm.writeAny(w, fv, props); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Extensions (the XXX_extensions field).
|
||||
pv := sv.Addr()
|
||||
if _, ok := extendable(pv.Interface()); ok {
|
||||
if err := tm.writeExtensions(w, pv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeRaw writes an uninterpreted raw message.
|
||||
func writeRaw(w *textWriter, b []byte) error {
|
||||
if err := w.WriteByte('<'); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
if err := writeUnknownStruct(w, b); err != nil {
|
||||
return err
|
||||
}
|
||||
w.unindent()
|
||||
if err := w.WriteByte('>'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeAny writes an arbitrary field.
|
||||
func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error {
|
||||
v = reflect.Indirect(v)
|
||||
|
||||
// Floats have special cases.
|
||||
if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 {
|
||||
x := v.Float()
|
||||
var b []byte
|
||||
switch {
|
||||
case math.IsInf(x, 1):
|
||||
b = posInf
|
||||
case math.IsInf(x, -1):
|
||||
b = negInf
|
||||
case math.IsNaN(x):
|
||||
b = nan
|
||||
}
|
||||
if b != nil {
|
||||
_, err := w.Write(b)
|
||||
return err
|
||||
}
|
||||
// Other values are handled below.
|
||||
}
|
||||
|
||||
// We don't attempt to serialise every possible value type; only those
|
||||
// that can occur in protocol buffers.
|
||||
switch v.Kind() {
|
||||
case reflect.Slice:
|
||||
// Should only be a []byte; repeated fields are handled in writeStruct.
|
||||
if err := writeString(w, string(v.Bytes())); err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.String:
|
||||
if err := writeString(w, v.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Struct:
|
||||
// Required/optional group/message.
|
||||
var bra, ket byte = '<', '>'
|
||||
if props != nil && props.Wire == "group" {
|
||||
bra, ket = '{', '}'
|
||||
}
|
||||
if err := w.WriteByte(bra); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
if etm, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||
text, err := etm.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write(text); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := tm.writeStruct(w, v); err != nil {
|
||||
return err
|
||||
}
|
||||
w.unindent()
|
||||
if err := w.WriteByte(ket); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
_, err := fmt.Fprint(w, v.Interface())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// equivalent to C's isprint.
|
||||
func isprint(c byte) bool {
|
||||
return c >= 0x20 && c < 0x7f
|
||||
}
|
||||
|
||||
// writeString writes a string in the protocol buffer text format.
|
||||
// It is similar to strconv.Quote except we don't use Go escape sequences,
|
||||
// we treat the string as a byte sequence, and we use octal escapes.
|
||||
// These differences are to maintain interoperability with the other
|
||||
// languages' implementations of the text format.
|
||||
func writeString(w *textWriter, s string) error {
|
||||
// use WriteByte here to get any needed indent
|
||||
if err := w.WriteByte('"'); err != nil {
|
||||
return err
|
||||
}
|
||||
// Loop over the bytes, not the runes.
|
||||
for i := 0; i < len(s); i++ {
|
||||
var err error
|
||||
// Divergence from C++: we don't escape apostrophes.
|
||||
// There's no need to escape them, and the C++ parser
|
||||
// copes with a naked apostrophe.
|
||||
switch c := s[i]; c {
|
||||
case '\n':
|
||||
_, err = w.w.Write(backslashN)
|
||||
case '\r':
|
||||
_, err = w.w.Write(backslashR)
|
||||
case '\t':
|
||||
_, err = w.w.Write(backslashT)
|
||||
case '"':
|
||||
_, err = w.w.Write(backslashDQ)
|
||||
case '\\':
|
||||
_, err = w.w.Write(backslashBS)
|
||||
default:
|
||||
if isprint(c) {
|
||||
err = w.w.WriteByte(c)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w.w, "\\%03o", c)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.WriteByte('"')
|
||||
}
|
||||
|
||||
func writeUnknownStruct(w *textWriter, data []byte) (err error) {
|
||||
if !w.compact {
|
||||
if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
b := NewBuffer(data)
|
||||
for b.index < len(b.buf) {
|
||||
x, err := b.DecodeVarint()
|
||||
if err != nil {
|
||||
_, err := fmt.Fprintf(w, "/* %v */\n", err)
|
||||
return err
|
||||
}
|
||||
wire, tag := x&7, x>>3
|
||||
if wire == WireEndGroup {
|
||||
w.unindent()
|
||||
if _, err := w.Write(endBraceNewline); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err := fmt.Fprint(w, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
if wire != WireStartGroup {
|
||||
if err := w.WriteByte(':'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !w.compact || wire == WireStartGroup {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch wire {
|
||||
case WireBytes:
|
||||
buf, e := b.DecodeRawBytes(false)
|
||||
if e == nil {
|
||||
_, err = fmt.Fprintf(w, "%q", buf)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w, "/* %v */", e)
|
||||
}
|
||||
case WireFixed32:
|
||||
x, err = b.DecodeFixed32()
|
||||
err = writeUnknownInt(w, x, err)
|
||||
case WireFixed64:
|
||||
x, err = b.DecodeFixed64()
|
||||
err = writeUnknownInt(w, x, err)
|
||||
case WireStartGroup:
|
||||
err = w.WriteByte('{')
|
||||
w.indent()
|
||||
case WireVarint:
|
||||
x, err = b.DecodeVarint()
|
||||
err = writeUnknownInt(w, x, err)
|
||||
default:
|
||||
_, err = fmt.Fprintf(w, "/* unknown wire type %d */", wire)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeUnknownInt(w *textWriter, x uint64, err error) error {
|
||||
if err == nil {
|
||||
_, err = fmt.Fprint(w, x)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w, "/* %v */", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type int32Slice []int32
|
||||
|
||||
func (s int32Slice) Len() int { return len(s) }
|
||||
func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] }
|
||||
func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// writeExtensions writes all the extensions in pv.
|
||||
// pv is assumed to be a pointer to a protocol message struct that is extendable.
|
||||
func (tm *TextMarshaler) writeExtensions(w *textWriter, pv reflect.Value) error {
|
||||
emap := extensionMaps[pv.Type().Elem()]
|
||||
ep, _ := extendable(pv.Interface())
|
||||
|
||||
// Order the extensions by ID.
|
||||
// This isn't strictly necessary, but it will give us
|
||||
// canonical output, which will also make testing easier.
|
||||
m, mu := ep.extensionsRead()
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
mu.Lock()
|
||||
ids := make([]int32, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Sort(int32Slice(ids))
|
||||
mu.Unlock()
|
||||
|
||||
for _, extNum := range ids {
|
||||
ext := m[extNum]
|
||||
var desc *ExtensionDesc
|
||||
if emap != nil {
|
||||
desc = emap[extNum]
|
||||
}
|
||||
if desc == nil {
|
||||
// Unknown extension.
|
||||
if err := writeUnknownStruct(w, ext.enc); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
pb, err := GetExtension(ep, desc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting extension: %v", err)
|
||||
}
|
||||
|
||||
// Repeated extensions will appear as a slice.
|
||||
if !desc.repeated() {
|
||||
if err := tm.writeExtension(w, desc.Name, pb); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
v := reflect.ValueOf(pb)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if err := tm.writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tm *TextMarshaler) writeExtension(w *textWriter, name string, pb interface{}) error {
|
||||
if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, reflect.ValueOf(pb), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *textWriter) writeIndent() {
|
||||
if !w.complete {
|
||||
return
|
||||
}
|
||||
remain := w.ind * 2
|
||||
for remain > 0 {
|
||||
n := remain
|
||||
if n > len(spaces) {
|
||||
n = len(spaces)
|
||||
}
|
||||
w.w.Write(spaces[:n])
|
||||
remain -= n
|
||||
}
|
||||
w.complete = false
|
||||
}
|
||||
|
||||
// TextMarshaler is a configurable text format marshaler.
|
||||
type TextMarshaler struct {
|
||||
Compact bool // use compact text format (one line).
|
||||
ExpandAny bool // expand google.protobuf.Any messages of known types
|
||||
}
|
||||
|
||||
// Marshal writes a given protocol buffer in text format.
|
||||
// The only errors returned are from w.
|
||||
func (tm *TextMarshaler) Marshal(w io.Writer, pb Message) error {
|
||||
val := reflect.ValueOf(pb)
|
||||
if pb == nil || val.IsNil() {
|
||||
w.Write([]byte("<nil>"))
|
||||
return nil
|
||||
}
|
||||
var bw *bufio.Writer
|
||||
ww, ok := w.(writer)
|
||||
if !ok {
|
||||
bw = bufio.NewWriter(w)
|
||||
ww = bw
|
||||
}
|
||||
aw := &textWriter{
|
||||
w: ww,
|
||||
complete: true,
|
||||
compact: tm.Compact,
|
||||
}
|
||||
|
||||
if etm, ok := pb.(encoding.TextMarshaler); ok {
|
||||
text, err := etm.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = aw.Write(text); err != nil {
|
||||
return err
|
||||
}
|
||||
if bw != nil {
|
||||
return bw.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Dereference the received pointer so we don't have outer < and >.
|
||||
v := reflect.Indirect(val)
|
||||
if err := tm.writeStruct(aw, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if bw != nil {
|
||||
return bw.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Text is the same as Marshal, but returns the string directly.
|
||||
func (tm *TextMarshaler) Text(pb Message) string {
|
||||
var buf bytes.Buffer
|
||||
tm.Marshal(&buf, pb)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
var (
|
||||
defaultTextMarshaler = TextMarshaler{}
|
||||
compactTextMarshaler = TextMarshaler{Compact: true}
|
||||
)
|
||||
|
||||
// TODO: consider removing some of the Marshal functions below.
|
||||
|
||||
// MarshalText writes a given protocol buffer in text format.
|
||||
// The only errors returned are from w.
|
||||
func MarshalText(w io.Writer, pb Message) error { return defaultTextMarshaler.Marshal(w, pb) }
|
||||
|
||||
// MarshalTextString is the same as MarshalText, but returns the string directly.
|
||||
func MarshalTextString(pb Message) string { return defaultTextMarshaler.Text(pb) }
|
||||
|
||||
// CompactText writes a given protocol buffer in compact text format (one line).
|
||||
func CompactText(w io.Writer, pb Message) error { return compactTextMarshaler.Marshal(w, pb) }
|
||||
|
||||
// CompactTextString is the same as CompactText, but returns the string directly.
|
||||
func CompactTextString(pb Message) string { return compactTextMarshaler.Text(pb) }
|
|
@ -0,0 +1,880 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
// Functions for parsing the Text protocol buffer format.
|
||||
// TODO: message sets.
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type ParseError struct {
|
||||
Message string
|
||||
Line int // 1-based line number
|
||||
Offset int // 0-based byte offset from start of input
|
||||
}
|
||||
|
||||
func (p *ParseError) Error() string {
|
||||
if p.Line == 1 {
|
||||
// show offset only for first line
|
||||
return fmt.Sprintf("line 1.%d: %v", p.Offset, p.Message)
|
||||
}
|
||||
return fmt.Sprintf("line %d: %v", p.Line, p.Message)
|
||||
}
|
||||
|
||||
type token struct {
|
||||
value string
|
||||
err *ParseError
|
||||
line int // line number
|
||||
offset int // byte number from start of input, not start of line
|
||||
unquoted string // the unquoted version of value, if it was a quoted string
|
||||
}
|
||||
|
||||
func (t *token) String() string {
|
||||
if t.err == nil {
|
||||
return fmt.Sprintf("%q (line=%d, offset=%d)", t.value, t.line, t.offset)
|
||||
}
|
||||
return fmt.Sprintf("parse error: %v", t.err)
|
||||
}
|
||||
|
||||
type textParser struct {
|
||||
s string // remaining input
|
||||
done bool // whether the parsing is finished (success or error)
|
||||
backed bool // whether back() was called
|
||||
offset, line int
|
||||
cur token
|
||||
}
|
||||
|
||||
func newTextParser(s string) *textParser {
|
||||
p := new(textParser)
|
||||
p.s = s
|
||||
p.line = 1
|
||||
p.cur.line = 1
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *textParser) errorf(format string, a ...interface{}) *ParseError {
|
||||
pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset}
|
||||
p.cur.err = pe
|
||||
p.done = true
|
||||
return pe
|
||||
}
|
||||
|
||||
// Numbers and identifiers are matched by [-+._A-Za-z0-9]
|
||||
func isIdentOrNumberChar(c byte) bool {
|
||||
switch {
|
||||
case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z':
|
||||
return true
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
}
|
||||
switch c {
|
||||
case '-', '+', '.', '_':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isWhitespace(c byte) bool {
|
||||
switch c {
|
||||
case ' ', '\t', '\n', '\r':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isQuote(c byte) bool {
|
||||
switch c {
|
||||
case '"', '\'':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *textParser) skipWhitespace() {
|
||||
i := 0
|
||||
for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') {
|
||||
if p.s[i] == '#' {
|
||||
// comment; skip to end of line or input
|
||||
for i < len(p.s) && p.s[i] != '\n' {
|
||||
i++
|
||||
}
|
||||
if i == len(p.s) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.s[i] == '\n' {
|
||||
p.line++
|
||||
}
|
||||
i++
|
||||
}
|
||||
p.offset += i
|
||||
p.s = p.s[i:len(p.s)]
|
||||
if len(p.s) == 0 {
|
||||
p.done = true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *textParser) advance() {
|
||||
// Skip whitespace
|
||||
p.skipWhitespace()
|
||||
if p.done {
|
||||
return
|
||||
}
|
||||
|
||||
// Start of non-whitespace
|
||||
p.cur.err = nil
|
||||
p.cur.offset, p.cur.line = p.offset, p.line
|
||||
p.cur.unquoted = ""
|
||||
switch p.s[0] {
|
||||
case '<', '>', '{', '}', ':', '[', ']', ';', ',', '/':
|
||||
// Single symbol
|
||||
p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)]
|
||||
case '"', '\'':
|
||||
// Quoted string
|
||||
i := 1
|
||||
for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' {
|
||||
if p.s[i] == '\\' && i+1 < len(p.s) {
|
||||
// skip escaped char
|
||||
i++
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i >= len(p.s) || p.s[i] != p.s[0] {
|
||||
p.errorf("unmatched quote")
|
||||
return
|
||||
}
|
||||
unq, err := unquoteC(p.s[1:i], rune(p.s[0]))
|
||||
if err != nil {
|
||||
p.errorf("invalid quoted string %s: %v", p.s[0:i+1], err)
|
||||
return
|
||||
}
|
||||
p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)]
|
||||
p.cur.unquoted = unq
|
||||
default:
|
||||
i := 0
|
||||
for i < len(p.s) && isIdentOrNumberChar(p.s[i]) {
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
p.errorf("unexpected byte %#x", p.s[0])
|
||||
return
|
||||
}
|
||||
p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)]
|
||||
}
|
||||
p.offset += len(p.cur.value)
|
||||
}
|
||||
|
||||
var (
|
||||
errBadUTF8 = errors.New("proto: bad UTF-8")
|
||||
errBadHex = errors.New("proto: bad hexadecimal")
|
||||
)
|
||||
|
||||
func unquoteC(s string, quote rune) (string, error) {
|
||||
// This is based on C++'s tokenizer.cc.
|
||||
// Despite its name, this is *not* parsing C syntax.
|
||||
// For instance, "\0" is an invalid quoted string.
|
||||
|
||||
// Avoid allocation in trivial cases.
|
||||
simple := true
|
||||
for _, r := range s {
|
||||
if r == '\\' || r == quote {
|
||||
simple = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if simple {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 3*len(s)/2)
|
||||
for len(s) > 0 {
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
return "", errBadUTF8
|
||||
}
|
||||
s = s[n:]
|
||||
if r != '\\' {
|
||||
if r < utf8.RuneSelf {
|
||||
buf = append(buf, byte(r))
|
||||
} else {
|
||||
buf = append(buf, string(r)...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ch, tail, err := unescape(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf = append(buf, ch...)
|
||||
s = tail
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func unescape(s string) (ch string, tail string, err error) {
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
return "", "", errBadUTF8
|
||||
}
|
||||
s = s[n:]
|
||||
switch r {
|
||||
case 'a':
|
||||
return "\a", s, nil
|
||||
case 'b':
|
||||
return "\b", s, nil
|
||||
case 'f':
|
||||
return "\f", s, nil
|
||||
case 'n':
|
||||
return "\n", s, nil
|
||||
case 'r':
|
||||
return "\r", s, nil
|
||||
case 't':
|
||||
return "\t", s, nil
|
||||
case 'v':
|
||||
return "\v", s, nil
|
||||
case '?':
|
||||
return "?", s, nil // trigraph workaround
|
||||
case '\'', '"', '\\':
|
||||
return string(r), s, nil
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', 'x', 'X':
|
||||
if len(s) < 2 {
|
||||
return "", "", fmt.Errorf(`\%c requires 2 following digits`, r)
|
||||
}
|
||||
base := 8
|
||||
ss := s[:2]
|
||||
s = s[2:]
|
||||
if r == 'x' || r == 'X' {
|
||||
base = 16
|
||||
} else {
|
||||
ss = string(r) + ss
|
||||
}
|
||||
i, err := strconv.ParseUint(ss, base, 8)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return string([]byte{byte(i)}), s, nil
|
||||
case 'u', 'U':
|
||||
n := 4
|
||||
if r == 'U' {
|
||||
n = 8
|
||||
}
|
||||
if len(s) < n {
|
||||
return "", "", fmt.Errorf(`\%c requires %d digits`, r, n)
|
||||
}
|
||||
|
||||
bs := make([]byte, n/2)
|
||||
for i := 0; i < n; i += 2 {
|
||||
a, ok1 := unhex(s[i])
|
||||
b, ok2 := unhex(s[i+1])
|
||||
if !ok1 || !ok2 {
|
||||
return "", "", errBadHex
|
||||
}
|
||||
bs[i/2] = a<<4 | b
|
||||
}
|
||||
s = s[n:]
|
||||
return string(bs), s, nil
|
||||
}
|
||||
return "", "", fmt.Errorf(`unknown escape \%c`, r)
|
||||
}
|
||||
|
||||
// Adapted from src/pkg/strconv/quote.go.
|
||||
func unhex(b byte) (v byte, ok bool) {
|
||||
switch {
|
||||
case '0' <= b && b <= '9':
|
||||
return b - '0', true
|
||||
case 'a' <= b && b <= 'f':
|
||||
return b - 'a' + 10, true
|
||||
case 'A' <= b && b <= 'F':
|
||||
return b - 'A' + 10, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Back off the parser by one token. Can only be done between calls to next().
|
||||
// It makes the next advance() a no-op.
|
||||
func (p *textParser) back() { p.backed = true }
|
||||
|
||||
// Advances the parser and returns the new current token.
|
||||
func (p *textParser) next() *token {
|
||||
if p.backed || p.done {
|
||||
p.backed = false
|
||||
return &p.cur
|
||||
}
|
||||
p.advance()
|
||||
if p.done {
|
||||
p.cur.value = ""
|
||||
} else if len(p.cur.value) > 0 && isQuote(p.cur.value[0]) {
|
||||
// Look for multiple quoted strings separated by whitespace,
|
||||
// and concatenate them.
|
||||
cat := p.cur
|
||||
for {
|
||||
p.skipWhitespace()
|
||||
if p.done || !isQuote(p.s[0]) {
|
||||
break
|
||||
}
|
||||
p.advance()
|
||||
if p.cur.err != nil {
|
||||
return &p.cur
|
||||
}
|
||||
cat.value += " " + p.cur.value
|
||||
cat.unquoted += p.cur.unquoted
|
||||
}
|
||||
p.done = false // parser may have seen EOF, but we want to return cat
|
||||
p.cur = cat
|
||||
}
|
||||
return &p.cur
|
||||
}
|
||||
|
||||
func (p *textParser) consumeToken(s string) error {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value != s {
|
||||
p.back()
|
||||
return p.errorf("expected %q, found %q", s, tok.value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return a RequiredNotSetError indicating which required field was not set.
|
||||
func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSetError {
|
||||
st := sv.Type()
|
||||
sprops := GetProperties(st)
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
if !isNil(sv.Field(i)) {
|
||||
continue
|
||||
}
|
||||
|
||||
props := sprops.Prop[i]
|
||||
if props.Required {
|
||||
return &RequiredNotSetError{fmt.Sprintf("%v.%v", st, props.OrigName)}
|
||||
}
|
||||
}
|
||||
return &RequiredNotSetError{fmt.Sprintf("%v.<unknown field name>", st)} // should not happen
|
||||
}
|
||||
|
||||
// Returns the index in the struct for the named field, as well as the parsed tag properties.
|
||||
func structFieldByName(sprops *StructProperties, name string) (int, *Properties, bool) {
|
||||
i, ok := sprops.decoderOrigNames[name]
|
||||
if ok {
|
||||
return i, sprops.Prop[i], true
|
||||
}
|
||||
return -1, nil, false
|
||||
}
|
||||
|
||||
// Consume a ':' from the input stream (if the next token is a colon),
|
||||
// returning an error if a colon is needed but not present.
|
||||
func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseError {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value != ":" {
|
||||
// Colon is optional when the field is a group or message.
|
||||
needColon := true
|
||||
switch props.Wire {
|
||||
case "group":
|
||||
needColon = false
|
||||
case "bytes":
|
||||
// A "bytes" field is either a message, a string, or a repeated field;
|
||||
// those three become *T, *string and []T respectively, so we can check for
|
||||
// this field being a pointer to a non-string.
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
// *T or *string
|
||||
if typ.Elem().Kind() == reflect.String {
|
||||
break
|
||||
}
|
||||
} else if typ.Kind() == reflect.Slice {
|
||||
// []T or []*T
|
||||
if typ.Elem().Kind() != reflect.Ptr {
|
||||
break
|
||||
}
|
||||
} else if typ.Kind() == reflect.String {
|
||||
// The proto3 exception is for a string field,
|
||||
// which requires a colon.
|
||||
break
|
||||
}
|
||||
needColon = false
|
||||
}
|
||||
if needColon {
|
||||
return p.errorf("expected ':', found %q", tok.value)
|
||||
}
|
||||
p.back()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *textParser) readStruct(sv reflect.Value, terminator string) error {
|
||||
st := sv.Type()
|
||||
sprops := GetProperties(st)
|
||||
reqCount := sprops.reqCount
|
||||
var reqFieldErr error
|
||||
fieldSet := make(map[string]bool)
|
||||
// A struct is a sequence of "name: value", terminated by one of
|
||||
// '>' or '}', or the end of the input. A name may also be
|
||||
// "[extension]" or "[type/url]".
|
||||
//
|
||||
// The whole struct can also be an expanded Any message, like:
|
||||
// [type/url] < ... struct contents ... >
|
||||
for {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == terminator {
|
||||
break
|
||||
}
|
||||
if tok.value == "[" {
|
||||
// Looks like an extension or an Any.
|
||||
//
|
||||
// TODO: Check whether we need to handle
|
||||
// namespace rooted names (e.g. ".something.Foo").
|
||||
extName, err := p.consumeExtName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s := strings.LastIndex(extName, "/"); s >= 0 {
|
||||
// If it contains a slash, it's an Any type URL.
|
||||
messageName := extName[s+1:]
|
||||
mt := MessageType(messageName)
|
||||
if mt == nil {
|
||||
return p.errorf("unrecognized message %q in google.protobuf.Any", messageName)
|
||||
}
|
||||
tok = p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
// consume an optional colon
|
||||
if tok.value == ":" {
|
||||
tok = p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
}
|
||||
var terminator string
|
||||
switch tok.value {
|
||||
case "<":
|
||||
terminator = ">"
|
||||
case "{":
|
||||
terminator = "}"
|
||||
default:
|
||||
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||
}
|
||||
v := reflect.New(mt.Elem())
|
||||
if pe := p.readStruct(v.Elem(), terminator); pe != nil {
|
||||
return pe
|
||||
}
|
||||
b, err := Marshal(v.Interface().(Message))
|
||||
if err != nil {
|
||||
return p.errorf("failed to marshal message of type %q: %v", messageName, err)
|
||||
}
|
||||
sv.FieldByName("TypeUrl").SetString(extName)
|
||||
sv.FieldByName("Value").SetBytes(b)
|
||||
continue
|
||||
}
|
||||
|
||||
var desc *ExtensionDesc
|
||||
// This could be faster, but it's functional.
|
||||
// TODO: Do something smarter than a linear scan.
|
||||
for _, d := range RegisteredExtensions(reflect.New(st).Interface().(Message)) {
|
||||
if d.Name == extName {
|
||||
desc = d
|
||||
break
|
||||
}
|
||||
}
|
||||
if desc == nil {
|
||||
return p.errorf("unrecognized extension %q", extName)
|
||||
}
|
||||
|
||||
props := &Properties{}
|
||||
props.Parse(desc.Tag)
|
||||
|
||||
typ := reflect.TypeOf(desc.ExtensionType)
|
||||
if err := p.checkForColon(props, typ); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rep := desc.repeated()
|
||||
|
||||
// Read the extension structure, and set it in
|
||||
// the value we're constructing.
|
||||
var ext reflect.Value
|
||||
if !rep {
|
||||
ext = reflect.New(typ).Elem()
|
||||
} else {
|
||||
ext = reflect.New(typ.Elem()).Elem()
|
||||
}
|
||||
if err := p.readAny(ext, props); err != nil {
|
||||
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||
return err
|
||||
}
|
||||
reqFieldErr = err
|
||||
}
|
||||
ep := sv.Addr().Interface().(Message)
|
||||
if !rep {
|
||||
SetExtension(ep, desc, ext.Interface())
|
||||
} else {
|
||||
old, err := GetExtension(ep, desc)
|
||||
var sl reflect.Value
|
||||
if err == nil {
|
||||
sl = reflect.ValueOf(old) // existing slice
|
||||
} else {
|
||||
sl = reflect.MakeSlice(typ, 0, 1)
|
||||
}
|
||||
sl = reflect.Append(sl, ext)
|
||||
SetExtension(ep, desc, sl.Interface())
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// This is a normal, non-extension field.
|
||||
name := tok.value
|
||||
var dst reflect.Value
|
||||
fi, props, ok := structFieldByName(sprops, name)
|
||||
if ok {
|
||||
dst = sv.Field(fi)
|
||||
} else if oop, ok := sprops.OneofTypes[name]; ok {
|
||||
// It is a oneof.
|
||||
props = oop.Prop
|
||||
nv := reflect.New(oop.Type.Elem())
|
||||
dst = nv.Elem().Field(0)
|
||||
sv.Field(oop.Field).Set(nv)
|
||||
}
|
||||
if !dst.IsValid() {
|
||||
return p.errorf("unknown field name %q in %v", name, st)
|
||||
}
|
||||
|
||||
if dst.Kind() == reflect.Map {
|
||||
// Consume any colon.
|
||||
if err := p.checkForColon(props, dst.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Construct the map if it doesn't already exist.
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
}
|
||||
key := reflect.New(dst.Type().Key()).Elem()
|
||||
val := reflect.New(dst.Type().Elem()).Elem()
|
||||
|
||||
// The map entry should be this sequence of tokens:
|
||||
// < key : KEY value : VALUE >
|
||||
// However, implementations may omit key or value, and technically
|
||||
// we should support them in any order. See b/28924776 for a time
|
||||
// this went wrong.
|
||||
|
||||
tok := p.next()
|
||||
var terminator string
|
||||
switch tok.value {
|
||||
case "<":
|
||||
terminator = ">"
|
||||
case "{":
|
||||
terminator = "}"
|
||||
default:
|
||||
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||
}
|
||||
for {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == terminator {
|
||||
break
|
||||
}
|
||||
switch tok.value {
|
||||
case "key":
|
||||
if err := p.consumeToken(":"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(key, props.mkeyprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "value":
|
||||
if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(val, props.mvalprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
p.back()
|
||||
return p.errorf(`expected "key", "value", or %q, found %q`, terminator, tok.value)
|
||||
}
|
||||
}
|
||||
|
||||
dst.SetMapIndex(key, val)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that it's not already set if it's not a repeated field.
|
||||
if !props.Repeated && fieldSet[name] {
|
||||
return p.errorf("non-repeated field %q was repeated", name)
|
||||
}
|
||||
|
||||
if err := p.checkForColon(props, dst.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse into the field.
|
||||
fieldSet[name] = true
|
||||
if err := p.readAny(dst, props); err != nil {
|
||||
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||
return err
|
||||
}
|
||||
reqFieldErr = err
|
||||
}
|
||||
if props.Required {
|
||||
reqCount--
|
||||
}
|
||||
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if reqCount > 0 {
|
||||
return p.missingRequiredFieldError(sv)
|
||||
}
|
||||
return reqFieldErr
|
||||
}
|
||||
|
||||
// consumeExtName consumes extension name or expanded Any type URL and the
|
||||
// following ']'. It returns the name or URL consumed.
|
||||
func (p *textParser) consumeExtName() (string, error) {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return "", tok.err
|
||||
}
|
||||
|
||||
// If extension name or type url is quoted, it's a single token.
|
||||
if len(tok.value) > 2 && isQuote(tok.value[0]) && tok.value[len(tok.value)-1] == tok.value[0] {
|
||||
name, err := unquoteC(tok.value[1:len(tok.value)-1], rune(tok.value[0]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return name, p.consumeToken("]")
|
||||
}
|
||||
|
||||
// Consume everything up to "]"
|
||||
var parts []string
|
||||
for tok.value != "]" {
|
||||
parts = append(parts, tok.value)
|
||||
tok = p.next()
|
||||
if tok.err != nil {
|
||||
return "", p.errorf("unrecognized type_url or extension name: %s", tok.err)
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ""), nil
|
||||
}
|
||||
|
||||
// consumeOptionalSeparator consumes an optional semicolon or comma.
|
||||
// It is used in readStruct to provide backward compatibility.
|
||||
func (p *textParser) consumeOptionalSeparator() error {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value != ";" && tok.value != "," {
|
||||
p.back()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == "" {
|
||||
return p.errorf("unexpected EOF")
|
||||
}
|
||||
|
||||
switch fv := v; fv.Kind() {
|
||||
case reflect.Slice:
|
||||
at := v.Type()
|
||||
if at.Elem().Kind() == reflect.Uint8 {
|
||||
// Special case for []byte
|
||||
if tok.value[0] != '"' && tok.value[0] != '\'' {
|
||||
// Deliberately written out here, as the error after
|
||||
// this switch statement would write "invalid []byte: ...",
|
||||
// which is not as user-friendly.
|
||||
return p.errorf("invalid string: %v", tok.value)
|
||||
}
|
||||
bytes := []byte(tok.unquoted)
|
||||
fv.Set(reflect.ValueOf(bytes))
|
||||
return nil
|
||||
}
|
||||
// Repeated field.
|
||||
if tok.value == "[" {
|
||||
// Repeated field with list notation, like [1,2,3].
|
||||
for {
|
||||
fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem()))
|
||||
err := p.readAny(fv.Index(fv.Len()-1), props)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == "]" {
|
||||
break
|
||||
}
|
||||
if tok.value != "," {
|
||||
return p.errorf("Expected ']' or ',' found %q", tok.value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// One value of the repeated field.
|
||||
p.back()
|
||||
fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem()))
|
||||
return p.readAny(fv.Index(fv.Len()-1), props)
|
||||
case reflect.Bool:
|
||||
// Either "true", "false", 1 or 0.
|
||||
switch tok.value {
|
||||
case "true", "1":
|
||||
fv.SetBool(true)
|
||||
return nil
|
||||
case "false", "0":
|
||||
fv.SetBool(false)
|
||||
return nil
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
v := tok.value
|
||||
// Ignore 'f' for compatibility with output generated by C++, but don't
|
||||
// remove 'f' when the value is "-inf" or "inf".
|
||||
if strings.HasSuffix(v, "f") && tok.value != "-inf" && tok.value != "inf" {
|
||||
v = v[:len(v)-1]
|
||||
}
|
||||
if f, err := strconv.ParseFloat(v, fv.Type().Bits()); err == nil {
|
||||
fv.SetFloat(f)
|
||||
return nil
|
||||
}
|
||||
case reflect.Int32:
|
||||
if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil {
|
||||
fv.SetInt(x)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(props.Enum) == 0 {
|
||||
break
|
||||
}
|
||||
m, ok := enumValueMaps[props.Enum]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
x, ok := m[tok.value]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
fv.SetInt(int64(x))
|
||||
return nil
|
||||
case reflect.Int64:
|
||||
if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil {
|
||||
fv.SetInt(x)
|
||||
return nil
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// A basic field (indirected through pointer), or a repeated message/group
|
||||
p.back()
|
||||
fv.Set(reflect.New(fv.Type().Elem()))
|
||||
return p.readAny(fv.Elem(), props)
|
||||
case reflect.String:
|
||||
if tok.value[0] == '"' || tok.value[0] == '\'' {
|
||||
fv.SetString(tok.unquoted)
|
||||
return nil
|
||||
}
|
||||
case reflect.Struct:
|
||||
var terminator string
|
||||
switch tok.value {
|
||||
case "{":
|
||||
terminator = "}"
|
||||
case "<":
|
||||
terminator = ">"
|
||||
default:
|
||||
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||
}
|
||||
// TODO: Handle nested messages which implement encoding.TextUnmarshaler.
|
||||
return p.readStruct(fv, terminator)
|
||||
case reflect.Uint32:
|
||||
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||
fv.SetUint(uint64(x))
|
||||
return nil
|
||||
}
|
||||
case reflect.Uint64:
|
||||
if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil {
|
||||
fv.SetUint(x)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return p.errorf("invalid %v: %v", v.Type(), tok.value)
|
||||
}
|
||||
|
||||
// UnmarshalText reads a protocol buffer in Text format. UnmarshalText resets pb
|
||||
// before starting to unmarshal, so any existing data in pb is always removed.
|
||||
// If a required field is not set and no other error occurs,
|
||||
// UnmarshalText returns *RequiredNotSetError.
|
||||
func UnmarshalText(s string, pb Message) error {
|
||||
if um, ok := pb.(encoding.TextUnmarshaler); ok {
|
||||
err := um.UnmarshalText([]byte(s))
|
||||
return err
|
||||
}
|
||||
pb.Reset()
|
||||
v := reflect.ValueOf(pb)
|
||||
if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil {
|
||||
return pe
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
|
@ -0,0 +1,31 @@
|
|||
# Contributing to Go
|
||||
|
||||
Go is an open source project.
|
||||
|
||||
It is the work of hundreds of contributors. We appreciate your help!
|
||||
|
||||
|
||||
## Filing issues
|
||||
|
||||
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
|
||||
|
||||
1. What version of Go are you using (`go version`)?
|
||||
2. What operating system and processor architecture are you using?
|
||||
3. What did you do?
|
||||
4. What did you expect to see?
|
||||
5. What did you see instead?
|
||||
|
||||
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
|
||||
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
|
||||
|
||||
## Contributing code
|
||||
|
||||
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
|
||||
before sending patches.
|
||||
|
||||
**We do not accept GitHub pull requests**
|
||||
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
|
||||
|
||||
Unless otherwise noted, the Go source files are distributed under
|
||||
the BSD-style license found in the LICENSE file.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,22 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
|
@ -0,0 +1,3 @@
|
|||
This repository holds supplementary Go networking libraries.
|
||||
|
||||
To submit changes to this repository, see http://golang.org/doc/contribute.html.
|
|
@ -0,0 +1 @@
|
|||
issuerepo: golang/go
|
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package context defines the Context type, which carries deadlines,
|
||||
// cancelation signals, and other request-scoped values across API boundaries
|
||||
// and between processes.
|
||||
//
|
||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||
// servers should accept a Context. The chain of function calls between must
|
||||
// propagate the Context, optionally replacing it with a modified copy created
|
||||
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
||||
//
|
||||
// Programs that use Contexts should follow these rules to keep interfaces
|
||||
// consistent across packages and enable static analysis tools to check context
|
||||
// propagation:
|
||||
//
|
||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||
// explicitly to each function that needs it. The Context should be the first
|
||||
// parameter, typically named ctx:
|
||||
//
|
||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||
// // ... use ctx ...
|
||||
// }
|
||||
//
|
||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
||||
// if you are unsure about which Context to use.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
//
|
||||
// The same Context may be passed to functions running in different goroutines;
|
||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||
//
|
||||
// See http://blog.golang.org/context for example code for a server that uses
|
||||
// Contexts.
|
||||
package context // import "golang.org/x/net/context"
|
||||
|
||||
import "time"
|
||||
|
||||
// A Context carries a deadline, a cancelation signal, and other values across
|
||||
// API boundaries.
|
||||
//
|
||||
// Context's methods may be called by multiple goroutines simultaneously.
|
||||
type Context interface {
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
Deadline() (deadline time.Time, ok bool)
|
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
//
|
||||
// WithCancel arranges for Done to be closed when cancel is called;
|
||||
// WithDeadline arranges for Done to be closed when the deadline
|
||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||
// elapses.
|
||||
//
|
||||
// Done is provided for use in select statements:
|
||||
//
|
||||
// // Stream generates values with DoSomething and sends them to out
|
||||
// // until DoSomething returns an error or ctx.Done is closed.
|
||||
// func Stream(ctx context.Context, out chan<- Value) error {
|
||||
// for {
|
||||
// v, err := DoSomething(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return ctx.Err()
|
||||
// case out <- v:
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||
// a Done channel for cancelation.
|
||||
Done() <-chan struct{}
|
||||
|
||||
// Err returns a non-nil error value after Done is closed. Err returns
|
||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||
// context's deadline passed. No other values for Err are defined.
|
||||
// After Done is closed, successive calls to Err return the same value.
|
||||
Err() error
|
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
// if no value is associated with key. Successive calls to Value with
|
||||
// the same key returns the same result.
|
||||
//
|
||||
// Use context values only for request-scoped data that transits
|
||||
// processes and API boundaries, not for passing optional parameters to
|
||||
// functions.
|
||||
//
|
||||
// A key identifies a specific value in a Context. Functions that wish
|
||||
// to store values in Context typically allocate a key in a global
|
||||
// variable then use that key as the argument to context.WithValue and
|
||||
// Context.Value. A key can be any type that supports equality;
|
||||
// packages should define keys as an unexported type to avoid
|
||||
// collisions.
|
||||
//
|
||||
// Packages that define a Context key should provide type-safe accessors
|
||||
// for the values stores using that key:
|
||||
//
|
||||
// // Package user defines a User type that's stored in Contexts.
|
||||
// package user
|
||||
//
|
||||
// import "golang.org/x/net/context"
|
||||
//
|
||||
// // User is the type of value stored in the Contexts.
|
||||
// type User struct {...}
|
||||
//
|
||||
// // key is an unexported type for keys defined in this package.
|
||||
// // This prevents collisions with keys defined in other packages.
|
||||
// type key int
|
||||
//
|
||||
// // userKey is the key for user.User values in Contexts. It is
|
||||
// // unexported; clients use user.NewContext and user.FromContext
|
||||
// // instead of using this key directly.
|
||||
// var userKey key = 0
|
||||
//
|
||||
// // NewContext returns a new Context that carries value u.
|
||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||
// return context.WithValue(ctx, userKey, u)
|
||||
// }
|
||||
//
|
||||
// // FromContext returns the User value stored in ctx, if any.
|
||||
// func FromContext(ctx context.Context) (*User, bool) {
|
||||
// u, ok := ctx.Value(userKey).(*User)
|
||||
// return u, ok
|
||||
// }
|
||||
Value(key interface{}) interface{}
|
||||
}
|
||||
|
||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||
// values, and has no deadline. It is typically used by the main function,
|
||||
// initialization, and tests, and as the top-level Context for incoming
|
||||
// requests.
|
||||
func Background() Context {
|
||||
return background
|
||||
}
|
||||
|
||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||
// it's unclear which Context to use or it is not yet available (because the
|
||||
// surrounding function has not yet been extended to accept a Context
|
||||
// parameter). TODO is recognized by static analysis tools that determine
|
||||
// whether Contexts are propagated correctly in a program.
|
||||
func TODO() Context {
|
||||
return todo
|
||||
}
|
||||
|
||||
// A CancelFunc tells an operation to abandon its work.
|
||||
// A CancelFunc does not wait for the work to stop.
|
||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||
type CancelFunc func()
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"context" // standard library's context, as of Go 1.7
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
todo = context.TODO()
|
||||
background = context.Background()
|
||||
)
|
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = context.Canceled
|
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = context.DeadlineExceeded
|
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||
ctx, f := context.WithCancel(parent)
|
||||
return ctx, CancelFunc(f)
|
||||
}
|
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||
ctx, f := context.WithDeadline(parent, deadline)
|
||||
return ctx, CancelFunc(f)
|
||||
}
|
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||
return WithDeadline(parent, time.Now().Add(timeout))
|
||||
}
|
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||
return context.WithValue(parent, key, val)
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.7
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||
// struct{}, since vars of this type must have distinct addresses.
|
||||
type emptyCtx int
|
||||
|
||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (*emptyCtx) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Value(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *emptyCtx) String() string {
|
||||
switch e {
|
||||
case background:
|
||||
return "context.Background"
|
||||
case todo:
|
||||
return "context.TODO"
|
||||
}
|
||||
return "unknown empty Context"
|
||||
}
|
||||
|
||||
var (
|
||||
background = new(emptyCtx)
|
||||
todo = new(emptyCtx)
|
||||
)
|
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = errors.New("context canceled")
|
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = errors.New("context deadline exceeded")
|
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||
c := newCancelCtx(parent)
|
||||
propagateCancel(parent, c)
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
|
||||
// newCancelCtx returns an initialized cancelCtx.
|
||||
func newCancelCtx(parent Context) *cancelCtx {
|
||||
return &cancelCtx{
|
||||
Context: parent,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// propagateCancel arranges for child to be canceled when parent is.
|
||||
func propagateCancel(parent Context, child canceler) {
|
||||
if parent.Done() == nil {
|
||||
return // parent is never canceled
|
||||
}
|
||||
if p, ok := parentCancelCtx(parent); ok {
|
||||
p.mu.Lock()
|
||||
if p.err != nil {
|
||||
// parent has already been canceled
|
||||
child.cancel(false, p.err)
|
||||
} else {
|
||||
if p.children == nil {
|
||||
p.children = make(map[canceler]bool)
|
||||
}
|
||||
p.children[child] = true
|
||||
}
|
||||
p.mu.Unlock()
|
||||
} else {
|
||||
go func() {
|
||||
select {
|
||||
case <-parent.Done():
|
||||
child.cancel(false, parent.Err())
|
||||
case <-child.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// parentCancelCtx follows a chain of parent references until it finds a
|
||||
// *cancelCtx. This function understands how each of the concrete types in this
|
||||
// package represents its parent.
|
||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
||||
for {
|
||||
switch c := parent.(type) {
|
||||
case *cancelCtx:
|
||||
return c, true
|
||||
case *timerCtx:
|
||||
return c.cancelCtx, true
|
||||
case *valueCtx:
|
||||
parent = c.Context
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeChild removes a context from its parent.
|
||||
func removeChild(parent Context, child canceler) {
|
||||
p, ok := parentCancelCtx(parent)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
p.mu.Lock()
|
||||
if p.children != nil {
|
||||
delete(p.children, child)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
// A canceler is a context type that can be canceled directly. The
|
||||
// implementations are *cancelCtx and *timerCtx.
|
||||
type canceler interface {
|
||||
cancel(removeFromParent bool, err error)
|
||||
Done() <-chan struct{}
|
||||
}
|
||||
|
||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
||||
// that implement canceler.
|
||||
type cancelCtx struct {
|
||||
Context
|
||||
|
||||
done chan struct{} // closed by the first cancel call.
|
||||
|
||||
mu sync.Mutex
|
||||
children map[canceler]bool // set to nil by the first cancel call
|
||||
err error // set to non-nil by the first cancel call
|
||||
}
|
||||
|
||||
func (c *cancelCtx) Done() <-chan struct{} {
|
||||
return c.done
|
||||
}
|
||||
|
||||
func (c *cancelCtx) Err() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.err
|
||||
}
|
||||
|
||||
func (c *cancelCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithCancel", c.Context)
|
||||
}
|
||||
|
||||
// cancel closes c.done, cancels each of c's children, and, if
|
||||
// removeFromParent is true, removes c from its parent's children.
|
||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
||||
if err == nil {
|
||||
panic("context: internal error: missing cancel error")
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.err != nil {
|
||||
c.mu.Unlock()
|
||||
return // already canceled
|
||||
}
|
||||
c.err = err
|
||||
close(c.done)
|
||||
for child := range c.children {
|
||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||
child.cancel(false, err)
|
||||
}
|
||||
c.children = nil
|
||||
c.mu.Unlock()
|
||||
|
||||
if removeFromParent {
|
||||
removeChild(c.Context, c)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
||||
// The current deadline is already sooner than the new one.
|
||||
return WithCancel(parent)
|
||||
}
|
||||
c := &timerCtx{
|
||||
cancelCtx: newCancelCtx(parent),
|
||||
deadline: deadline,
|
||||
}
|
||||
propagateCancel(parent, c)
|
||||
d := deadline.Sub(time.Now())
|
||||
if d <= 0 {
|
||||
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.err == nil {
|
||||
c.timer = time.AfterFunc(d, func() {
|
||||
c.cancel(true, DeadlineExceeded)
|
||||
})
|
||||
}
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
|
||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
||||
// implement Done and Err. It implements cancel by stopping its timer then
|
||||
// delegating to cancelCtx.cancel.
|
||||
type timerCtx struct {
|
||||
*cancelCtx
|
||||
timer *time.Timer // Under cancelCtx.mu.
|
||||
|
||||
deadline time.Time
|
||||
}
|
||||
|
||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return c.deadline, true
|
||||
}
|
||||
|
||||
func (c *timerCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
||||
}
|
||||
|
||||
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
||||
c.cancelCtx.cancel(false, err)
|
||||
if removeFromParent {
|
||||
// Remove this timerCtx from its parent cancelCtx's children.
|
||||
removeChild(c.cancelCtx.Context, c)
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.timer != nil {
|
||||
c.timer.Stop()
|
||||
c.timer = nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||
return WithDeadline(parent, time.Now().Add(timeout))
|
||||
}
|
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||
return &valueCtx{parent, key, val}
|
||||
}
|
||||
|
||||
// A valueCtx carries a key-value pair. It implements Value for that key and
|
||||
// delegates all other calls to the embedded Context.
|
||||
type valueCtx struct {
|
||||
Context
|
||||
key, val interface{}
|
||||
}
|
||||
|
||||
func (c *valueCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
||||
}
|
||||
|
||||
func (c *valueCtx) Value(key interface{}) interface{} {
|
||||
if c.key == key {
|
||||
return c.val
|
||||
}
|
||||
return c.Context.Value(key)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
|
@ -0,0 +1,31 @@
|
|||
# Contributing to Go
|
||||
|
||||
Go is an open source project.
|
||||
|
||||
It is the work of hundreds of contributors. We appreciate your help!
|
||||
|
||||
|
||||
## Filing issues
|
||||
|
||||
When [filing an issue](https://github.com/golang/oauth2/issues), make sure to answer these five questions:
|
||||
|
||||
1. What version of Go are you using (`go version`)?
|
||||
2. What operating system and processor architecture are you using?
|
||||
3. What did you do?
|
||||
4. What did you expect to see?
|
||||
5. What did you see instead?
|
||||
|
||||
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
|
||||
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
|
||||
|
||||
## Contributing code
|
||||
|
||||
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
|
||||
before sending patches.
|
||||
|
||||
**We do not accept GitHub pull requests**
|
||||
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
|
||||
|
||||
Unless otherwise noted, the Go source files are distributed under
|
||||
the BSD-style license found in the LICENSE file.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009 The oauth2 Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,64 @@
|
|||
# OAuth2 for Go
|
||||
|
||||
[![Build Status](https://travis-ci.org/golang/oauth2.svg?branch=master)](https://travis-ci.org/golang/oauth2)
|
||||
|
||||
oauth2 package contains a client implementation for OAuth 2.0 spec.
|
||||
|
||||
## Installation
|
||||
|
||||
~~~~
|
||||
go get golang.org/x/oauth2
|
||||
~~~~
|
||||
|
||||
See godoc for further documentation and examples.
|
||||
|
||||
* [godoc.org/golang.org/x/oauth2](http://godoc.org/golang.org/x/oauth2)
|
||||
* [godoc.org/golang.org/x/oauth2/google](http://godoc.org/golang.org/x/oauth2/google)
|
||||
|
||||
|
||||
## App Engine
|
||||
|
||||
In change 96e89be (March 2015) we removed the `oauth2.Context2` type in favor
|
||||
of the [`context.Context`](https://golang.org/x/net/context#Context) type from
|
||||
the `golang.org/x/net/context` package
|
||||
|
||||
This means its no longer possible to use the "Classic App Engine"
|
||||
`appengine.Context` type with the `oauth2` package. (You're using
|
||||
Classic App Engine if you import the package `"appengine"`.)
|
||||
|
||||
To work around this, you may use the new `"google.golang.org/appengine"`
|
||||
package. This package has almost the same API as the `"appengine"` package,
|
||||
but it can be fetched with `go get` and used on "Managed VMs" and well as
|
||||
Classic App Engine.
|
||||
|
||||
See the [new `appengine` package's readme](https://github.com/golang/appengine#updating-a-go-app-engine-app)
|
||||
for information on updating your app.
|
||||
|
||||
If you don't want to update your entire app to use the new App Engine packages,
|
||||
you may use both sets of packages in parallel, using only the new packages
|
||||
with the `oauth2` package.
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
newappengine "google.golang.org/appengine"
|
||||
newurlfetch "google.golang.org/appengine/urlfetch"
|
||||
|
||||
"appengine"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
var c appengine.Context = appengine.NewContext(r)
|
||||
c.Infof("Logging a message with the old package")
|
||||
|
||||
var ctx context.Context = newappengine.NewContext(r)
|
||||
client := &http.Client{
|
||||
Transport: &oauth2.Transport{
|
||||
Source: google.AppEngineTokenSource(ctx, "scope"),
|
||||
Base: &newurlfetch.Transport{Context: ctx},
|
||||
},
|
||||
}
|
||||
client.Get("...")
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
// App Engine hooks.
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/internal"
|
||||
"google.golang.org/appengine/urlfetch"
|
||||
)
|
||||
|
||||
func init() {
|
||||
internal.RegisterContextClientFunc(contextClientAppEngine)
|
||||
}
|
||||
|
||||
func contextClientAppEngine(ctx context.Context) (*http.Client, error) {
|
||||
return urlfetch.Client(ctx), nil
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseKey converts the binary contents of a private key file
|
||||
// to an *rsa.PrivateKey. It detects whether the private key is in a
|
||||
// PEM container or not. If so, it extracts the the private key
|
||||
// from PEM container before conversion. It only supports PEM
|
||||
// containers with no passphrase.
|
||||
func ParseKey(key []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(key)
|
||||
if block != nil {
|
||||
key = block.Bytes
|
||||
}
|
||||
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v", err)
|
||||
}
|
||||
}
|
||||
parsed, ok := parsedKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("private key is invalid")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func ParseINI(ini io.Reader) (map[string]map[string]string, error) {
|
||||
result := map[string]map[string]string{
|
||||
"": map[string]string{}, // root section
|
||||
}
|
||||
scanner := bufio.NewScanner(ini)
|
||||
currentSection := ""
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, ";") {
|
||||
// comment.
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||
currentSection = strings.TrimSpace(line[1 : len(line)-1])
|
||||
result[currentSection] = map[string]string{}
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) == 2 && parts[0] != "" {
|
||||
result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error scanning ini: %v", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CondVal(v string) []string {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{v}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Token represents the crendentials used to authorize
|
||||
// the requests to access protected resources on the OAuth 2.0
|
||||
// provider's backend.
|
||||
//
|
||||
// This type is a mirror of oauth2.Token and exists to break
|
||||
// an otherwise-circular dependency. Other internal packages
|
||||
// should convert this Token into an oauth2.Token before use.
|
||||
type Token struct {
|
||||
// AccessToken is the token that authorizes and authenticates
|
||||
// the requests.
|
||||
AccessToken string
|
||||
|
||||
// TokenType is the type of token.
|
||||
// The Type method returns either this or "Bearer", the default.
|
||||
TokenType string
|
||||
|
||||
// RefreshToken is a token that's used by the application
|
||||
// (as opposed to the user) to refresh the access token
|
||||
// if it expires.
|
||||
RefreshToken string
|
||||
|
||||
// Expiry is the optional expiration time of the access token.
|
||||
//
|
||||
// If zero, TokenSource implementations will reuse the same
|
||||
// token forever and RefreshToken or equivalent
|
||||
// mechanisms for that TokenSource will not be used.
|
||||
Expiry time.Time
|
||||
|
||||
// Raw optionally contains extra metadata from the server
|
||||
// when updating a token.
|
||||
Raw interface{}
|
||||
}
|
||||
|
||||
// tokenJSON is the struct representing the HTTP response from OAuth2
|
||||
// providers returning a token in JSON form.
|
||||
type tokenJSON struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
|
||||
Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in
|
||||
}
|
||||
|
||||
func (e *tokenJSON) expiry() (t time.Time) {
|
||||
if v := e.ExpiresIn; v != 0 {
|
||||
return time.Now().Add(time.Duration(v) * time.Second)
|
||||
}
|
||||
if v := e.Expires; v != 0 {
|
||||
return time.Now().Add(time.Duration(v) * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type expirationTime int32
|
||||
|
||||
func (e *expirationTime) UnmarshalJSON(b []byte) error {
|
||||
var n json.Number
|
||||
err := json.Unmarshal(b, &n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i, err := n.Int64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*e = expirationTime(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
var brokenAuthHeaderProviders = []string{
|
||||
"https://accounts.google.com/",
|
||||
"https://api.dropbox.com/",
|
||||
"https://api.dropboxapi.com/",
|
||||
"https://api.instagram.com/",
|
||||
"https://api.netatmo.net/",
|
||||
"https://api.odnoklassniki.ru/",
|
||||
"https://api.pushbullet.com/",
|
||||
"https://api.soundcloud.com/",
|
||||
"https://api.twitch.tv/",
|
||||
"https://app.box.com/",
|
||||
"https://connect.stripe.com/",
|
||||
"https://login.microsoftonline.com/",
|
||||
"https://login.salesforce.com/",
|
||||
"https://oauth.sandbox.trainingpeaks.com/",
|
||||
"https://oauth.trainingpeaks.com/",
|
||||
"https://oauth.vk.com/",
|
||||
"https://openapi.baidu.com/",
|
||||
"https://slack.com/",
|
||||
"https://test-sandbox.auth.corp.google.com",
|
||||
"https://test.salesforce.com/",
|
||||
"https://user.gini.net/",
|
||||
"https://www.douban.com/",
|
||||
"https://www.googleapis.com/",
|
||||
"https://www.linkedin.com/",
|
||||
"https://www.strava.com/oauth/",
|
||||
"https://www.wunderlist.com/oauth/",
|
||||
"https://api.patreon.com/",
|
||||
}
|
||||
|
||||
func RegisterBrokenAuthHeaderProvider(tokenURL string) {
|
||||
brokenAuthHeaderProviders = append(brokenAuthHeaderProviders, tokenURL)
|
||||
}
|
||||
|
||||
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
|
||||
// implements the OAuth2 spec correctly
|
||||
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
||||
// In summary:
|
||||
// - Reddit only accepts client secret in the Authorization header
|
||||
// - Dropbox accepts either it in URL param or Auth header, but not both.
|
||||
// - Google only accepts URL param (not spec compliant?), not Auth header
|
||||
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
|
||||
func providerAuthHeaderWorks(tokenURL string) bool {
|
||||
for _, s := range brokenAuthHeaderProviders {
|
||||
if strings.HasPrefix(tokenURL, s) {
|
||||
// Some sites fail to implement the OAuth2 spec fully.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Assume the provider implements the spec properly
|
||||
// otherwise. We can add more exceptions as they're
|
||||
// discovered. We will _not_ be adding configurable hooks
|
||||
// to this package to let users select server bugs.
|
||||
return true
|
||||
}
|
||||
|
||||
func RetrieveToken(ctx context.Context, ClientID, ClientSecret, TokenURL string, v url.Values) (*Token, error) {
|
||||
hc, err := ContextClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Set("client_id", ClientID)
|
||||
bustedAuth := !providerAuthHeaderWorks(TokenURL)
|
||||
if bustedAuth && ClientSecret != "" {
|
||||
v.Set("client_secret", ClientSecret)
|
||||
}
|
||||
req, err := http.NewRequest("POST", TokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if !bustedAuth {
|
||||
req.SetBasicAuth(ClientID, ClientSecret)
|
||||
}
|
||||
r, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||
}
|
||||
if code := r.StatusCode; code < 200 || code > 299 {
|
||||
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
|
||||
}
|
||||
|
||||
var token *Token
|
||||
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
switch content {
|
||||
case "application/x-www-form-urlencoded", "text/plain":
|
||||
vals, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token = &Token{
|
||||
AccessToken: vals.Get("access_token"),
|
||||
TokenType: vals.Get("token_type"),
|
||||
RefreshToken: vals.Get("refresh_token"),
|
||||
Raw: vals,
|
||||
}
|
||||
e := vals.Get("expires_in")
|
||||
if e == "" {
|
||||
// TODO(jbd): Facebook's OAuth2 implementation is broken and
|
||||
// returns expires_in field in expires. Remove the fallback to expires,
|
||||
// when Facebook fixes their implementation.
|
||||
e = vals.Get("expires")
|
||||
}
|
||||
expires, _ := strconv.Atoi(e)
|
||||
if expires != 0 {
|
||||
token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
|
||||
}
|
||||
default:
|
||||
var tj tokenJSON
|
||||
if err = json.Unmarshal(body, &tj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token = &Token{
|
||||
AccessToken: tj.AccessToken,
|
||||
TokenType: tj.TokenType,
|
||||
RefreshToken: tj.RefreshToken,
|
||||
Expiry: tj.expiry(),
|
||||
Raw: make(map[string]interface{}),
|
||||
}
|
||||
json.Unmarshal(body, &token.Raw) // no error checks for optional fields
|
||||
}
|
||||
// Don't overwrite `RefreshToken` with an empty value
|
||||
// if this was a token refreshing request.
|
||||
if token.RefreshToken == "" {
|
||||
token.RefreshToken = v.Get("refresh_token")
|
||||
}
|
||||
return token, nil
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// HTTPClient is the context key to use with golang.org/x/net/context's
|
||||
// WithValue function to associate an *http.Client value with a context.
|
||||
var HTTPClient ContextKey
|
||||
|
||||
// ContextKey is just an empty struct. It exists so HTTPClient can be
|
||||
// an immutable public variable with a unique type. It's immutable
|
||||
// because nobody else can create a ContextKey, being unexported.
|
||||
type ContextKey struct{}
|
||||
|
||||
// ContextClientFunc is a func which tries to return an *http.Client
|
||||
// given a Context value. If it returns an error, the search stops
|
||||
// with that error. If it returns (nil, nil), the search continues
|
||||
// down the list of registered funcs.
|
||||
type ContextClientFunc func(context.Context) (*http.Client, error)
|
||||
|
||||
var contextClientFuncs []ContextClientFunc
|
||||
|
||||
func RegisterContextClientFunc(fn ContextClientFunc) {
|
||||
contextClientFuncs = append(contextClientFuncs, fn)
|
||||
}
|
||||
|
||||
func ContextClient(ctx context.Context) (*http.Client, error) {
|
||||
if ctx != nil {
|
||||
if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok {
|
||||
return hc, nil
|
||||
}
|
||||
}
|
||||
for _, fn := range contextClientFuncs {
|
||||
c, err := fn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c != nil {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
return http.DefaultClient, nil
|
||||
}
|
||||
|
||||
func ContextTransport(ctx context.Context) http.RoundTripper {
|
||||
hc, err := ContextClient(ctx)
|
||||
// This is a rare error case (somebody using nil on App Engine).
|
||||
if err != nil {
|
||||
return ErrorTransport{err}
|
||||
}
|
||||
return hc.Transport
|
||||
}
|
||||
|
||||
// ErrorTransport returns the specified error on RoundTrip.
|
||||
// This RoundTripper should be used in rare error cases where
|
||||
// error handling can be postponed to response handling time.
|
||||
type ErrorTransport struct{ Err error }
|
||||
|
||||
func (t ErrorTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
||||
return nil, t.Err
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package oauth2 provides support for making
|
||||
// OAuth2 authorized and authenticated HTTP requests.
|
||||
// It can additionally grant authorization with Bearer JWT.
|
||||
package oauth2 // import "golang.org/x/oauth2"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/internal"
|
||||
)
|
||||
|
||||
// NoContext is the default context you should supply if not using
|
||||
// your own context.Context (see https://golang.org/x/net/context).
|
||||
var NoContext = context.TODO()
|
||||
|
||||
// RegisterBrokenAuthHeaderProvider registers an OAuth2 server
|
||||
// identified by the tokenURL prefix as an OAuth2 implementation
|
||||
// which doesn't support the HTTP Basic authentication
|
||||
// scheme to authenticate with the authorization server.
|
||||
// Once a server is registered, credentials (client_id and client_secret)
|
||||
// will be passed as query parameters rather than being present
|
||||
// in the Authorization header.
|
||||
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
||||
func RegisterBrokenAuthHeaderProvider(tokenURL string) {
|
||||
internal.RegisterBrokenAuthHeaderProvider(tokenURL)
|
||||
}
|
||||
|
||||
// Config describes a typical 3-legged OAuth2 flow, with both the
|
||||
// client application information and the server's endpoint URLs.
|
||||
type Config struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string
|
||||
|
||||
// ClientSecret is the application's secret.
|
||||
ClientSecret string
|
||||
|
||||
// Endpoint contains the resource server's token endpoint
|
||||
// URLs. These are constants specific to each server and are
|
||||
// often available via site-specific packages, such as
|
||||
// google.Endpoint or github.Endpoint.
|
||||
Endpoint Endpoint
|
||||
|
||||
// RedirectURL is the URL to redirect users going through
|
||||
// the OAuth flow, after the resource owner's URLs.
|
||||
RedirectURL string
|
||||
|
||||
// Scope specifies optional requested permissions.
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
// A TokenSource is anything that can return a token.
|
||||
type TokenSource interface {
|
||||
// Token returns a token or an error.
|
||||
// Token must be safe for concurrent use by multiple goroutines.
|
||||
// The returned Token must not be modified.
|
||||
Token() (*Token, error)
|
||||
}
|
||||
|
||||
// Endpoint contains the OAuth 2.0 provider's authorization and token
|
||||
// endpoint URLs.
|
||||
type Endpoint struct {
|
||||
AuthURL string
|
||||
TokenURL string
|
||||
}
|
||||
|
||||
var (
|
||||
// AccessTypeOnline and AccessTypeOffline are options passed
|
||||
// to the Options.AuthCodeURL method. They modify the
|
||||
// "access_type" field that gets sent in the URL returned by
|
||||
// AuthCodeURL.
|
||||
//
|
||||
// Online is the default if neither is specified. If your
|
||||
// application needs to refresh access tokens when the user
|
||||
// is not present at the browser, then use offline. This will
|
||||
// result in your application obtaining a refresh token the
|
||||
// first time your application exchanges an authorization
|
||||
// code for a user.
|
||||
AccessTypeOnline AuthCodeOption = SetAuthURLParam("access_type", "online")
|
||||
AccessTypeOffline AuthCodeOption = SetAuthURLParam("access_type", "offline")
|
||||
|
||||
// ApprovalForce forces the users to view the consent dialog
|
||||
// and confirm the permissions request at the URL returned
|
||||
// from AuthCodeURL, even if they've already done so.
|
||||
ApprovalForce AuthCodeOption = SetAuthURLParam("approval_prompt", "force")
|
||||
)
|
||||
|
||||
// An AuthCodeOption is passed to Config.AuthCodeURL.
|
||||
type AuthCodeOption interface {
|
||||
setValue(url.Values)
|
||||
}
|
||||
|
||||
type setParam struct{ k, v string }
|
||||
|
||||
func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) }
|
||||
|
||||
// SetAuthURLParam builds an AuthCodeOption which passes key/value parameters
|
||||
// to a provider's authorization endpoint.
|
||||
func SetAuthURLParam(key, value string) AuthCodeOption {
|
||||
return setParam{key, value}
|
||||
}
|
||||
|
||||
// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
|
||||
// that asks for permissions for the required scopes explicitly.
|
||||
//
|
||||
// State is a token to protect the user from CSRF attacks. You must
|
||||
// always provide a non-zero string and validate that it matches the
|
||||
// the state query parameter on your redirect callback.
|
||||
// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info.
|
||||
//
|
||||
// Opts may include AccessTypeOnline or AccessTypeOffline, as well
|
||||
// as ApprovalForce.
|
||||
func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(c.Endpoint.AuthURL)
|
||||
v := url.Values{
|
||||
"response_type": {"code"},
|
||||
"client_id": {c.ClientID},
|
||||
"redirect_uri": internal.CondVal(c.RedirectURL),
|
||||
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||
"state": internal.CondVal(state),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.setValue(v)
|
||||
}
|
||||
if strings.Contains(c.Endpoint.AuthURL, "?") {
|
||||
buf.WriteByte('&')
|
||||
} else {
|
||||
buf.WriteByte('?')
|
||||
}
|
||||
buf.WriteString(v.Encode())
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PasswordCredentialsToken converts a resource owner username and password
|
||||
// pair into a token.
|
||||
//
|
||||
// Per the RFC, this grant type should only be used "when there is a high
|
||||
// degree of trust between the resource owner and the client (e.g., the client
|
||||
// is part of the device operating system or a highly privileged application),
|
||||
// and when other authorization grant types are not available."
|
||||
// See https://tools.ietf.org/html/rfc6749#section-4.3 for more info.
|
||||
//
|
||||
// The HTTP client to use is derived from the context.
|
||||
// If nil, http.DefaultClient is used.
|
||||
func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) {
|
||||
return retrieveToken(ctx, c, url.Values{
|
||||
"grant_type": {"password"},
|
||||
"username": {username},
|
||||
"password": {password},
|
||||
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||
})
|
||||
}
|
||||
|
||||
// Exchange converts an authorization code into a token.
|
||||
//
|
||||
// It is used after a resource provider redirects the user back
|
||||
// to the Redirect URI (the URL obtained from AuthCodeURL).
|
||||
//
|
||||
// The HTTP client to use is derived from the context.
|
||||
// If a client is not provided via the context, http.DefaultClient is used.
|
||||
//
|
||||
// The code will be in the *http.Request.FormValue("code"). Before
|
||||
// calling Exchange, be sure to validate FormValue("state").
|
||||
func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) {
|
||||
return retrieveToken(ctx, c, url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
"redirect_uri": internal.CondVal(c.RedirectURL),
|
||||
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||
})
|
||||
}
|
||||
|
||||
// Client returns an HTTP client using the provided token.
|
||||
// The token will auto-refresh as necessary. The underlying
|
||||
// HTTP transport will be obtained using the provided context.
|
||||
// The returned client and its Transport should not be modified.
|
||||
func (c *Config) Client(ctx context.Context, t *Token) *http.Client {
|
||||
return NewClient(ctx, c.TokenSource(ctx, t))
|
||||
}
|
||||
|
||||
// TokenSource returns a TokenSource that returns t until t expires,
|
||||
// automatically refreshing it as necessary using the provided context.
|
||||
//
|
||||
// Most users will use Config.Client instead.
|
||||
func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource {
|
||||
tkr := &tokenRefresher{
|
||||
ctx: ctx,
|
||||
conf: c,
|
||||
}
|
||||
if t != nil {
|
||||
tkr.refreshToken = t.RefreshToken
|
||||
}
|
||||
return &reuseTokenSource{
|
||||
t: t,
|
||||
new: tkr,
|
||||
}
|
||||
}
|
||||
|
||||
// tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token"
|
||||
// HTTP requests to renew a token using a RefreshToken.
|
||||
type tokenRefresher struct {
|
||||
ctx context.Context // used to get HTTP requests
|
||||
conf *Config
|
||||
refreshToken string
|
||||
}
|
||||
|
||||
// WARNING: Token is not safe for concurrent access, as it
|
||||
// updates the tokenRefresher's refreshToken field.
|
||||
// Within this package, it is used by reuseTokenSource which
|
||||
// synchronizes calls to this method with its own mutex.
|
||||
func (tf *tokenRefresher) Token() (*Token, error) {
|
||||
if tf.refreshToken == "" {
|
||||
return nil, errors.New("oauth2: token expired and refresh token is not set")
|
||||
}
|
||||
|
||||
tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {tf.refreshToken},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tf.refreshToken != tk.RefreshToken {
|
||||
tf.refreshToken = tk.RefreshToken
|
||||
}
|
||||
return tk, err
|
||||
}
|
||||
|
||||
// reuseTokenSource is a TokenSource that holds a single token in memory
|
||||
// and validates its expiry before each call to retrieve it with
|
||||
// Token. If it's expired, it will be auto-refreshed using the
|
||||
// new TokenSource.
|
||||
type reuseTokenSource struct {
|
||||
new TokenSource // called when t is expired.
|
||||
|
||||
mu sync.Mutex // guards t
|
||||
t *Token
|
||||
}
|
||||
|
||||
// Token returns the current token if it's still valid, else will
|
||||
// refresh the current token (using r.Context for HTTP client
|
||||
// information) and return the new one.
|
||||
func (s *reuseTokenSource) Token() (*Token, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.t.Valid() {
|
||||
return s.t, nil
|
||||
}
|
||||
t, err := s.new.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.t = t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// StaticTokenSource returns a TokenSource that always returns the same token.
|
||||
// Because the provided token t is never refreshed, StaticTokenSource is only
|
||||
// useful for tokens that never expire.
|
||||
func StaticTokenSource(t *Token) TokenSource {
|
||||
return staticTokenSource{t}
|
||||
}
|
||||
|
||||
// staticTokenSource is a TokenSource that always returns the same Token.
|
||||
type staticTokenSource struct {
|
||||
t *Token
|
||||
}
|
||||
|
||||
func (s staticTokenSource) Token() (*Token, error) {
|
||||
return s.t, nil
|
||||
}
|
||||
|
||||
// HTTPClient is the context key to use with golang.org/x/net/context's
|
||||
// WithValue function to associate an *http.Client value with a context.
|
||||
var HTTPClient internal.ContextKey
|
||||
|
||||
// NewClient creates an *http.Client from a Context and TokenSource.
|
||||
// The returned client is not valid beyond the lifetime of the context.
|
||||
//
|
||||
// As a special case, if src is nil, a non-OAuth2 client is returned
|
||||
// using the provided context. This exists to support related OAuth2
|
||||
// packages.
|
||||
func NewClient(ctx context.Context, src TokenSource) *http.Client {
|
||||
if src == nil {
|
||||
c, err := internal.ContextClient(ctx)
|
||||
if err != nil {
|
||||
return &http.Client{Transport: internal.ErrorTransport{err}}
|
||||
}
|
||||
return c
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: &Transport{
|
||||
Base: internal.ContextTransport(ctx),
|
||||
Source: ReuseTokenSource(nil, src),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ReuseTokenSource returns a TokenSource which repeatedly returns the
|
||||
// same token as long as it's valid, starting with t.
|
||||
// When its cached token is invalid, a new token is obtained from src.
|
||||
//
|
||||
// ReuseTokenSource is typically used to reuse tokens from a cache
|
||||
// (such as a file on disk) between runs of a program, rather than
|
||||
// obtaining new tokens unnecessarily.
|
||||
//
|
||||
// The initial token t may be nil, in which case the TokenSource is
|
||||
// wrapped in a caching version if it isn't one already. This also
|
||||
// means it's always safe to wrap ReuseTokenSource around any other
|
||||
// TokenSource without adverse effects.
|
||||
func ReuseTokenSource(t *Token, src TokenSource) TokenSource {
|
||||
// Don't wrap a reuseTokenSource in itself. That would work,
|
||||
// but cause an unnecessary number of mutex operations.
|
||||
// Just build the equivalent one.
|
||||
if rt, ok := src.(*reuseTokenSource); ok {
|
||||
if t == nil {
|
||||
// Just use it directly.
|
||||
return rt
|
||||
}
|
||||
src = rt.new
|
||||
}
|
||||
return &reuseTokenSource{
|
||||
t: t,
|
||||
new: src,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/internal"
|
||||
)
|
||||
|
||||
// expiryDelta determines how earlier a token should be considered
|
||||
// expired than its actual expiration time. It is used to avoid late
|
||||
// expirations due to client-server time mismatches.
|
||||
const expiryDelta = 10 * time.Second
|
||||
|
||||
// Token represents the crendentials used to authorize
|
||||
// the requests to access protected resources on the OAuth 2.0
|
||||
// provider's backend.
|
||||
//
|
||||
// Most users of this package should not access fields of Token
|
||||
// directly. They're exported mostly for use by related packages
|
||||
// implementing derivative OAuth2 flows.
|
||||
type Token struct {
|
||||
// AccessToken is the token that authorizes and authenticates
|
||||
// the requests.
|
||||
AccessToken string `json:"access_token"`
|
||||
|
||||
// TokenType is the type of token.
|
||||
// The Type method returns either this or "Bearer", the default.
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
|
||||
// RefreshToken is a token that's used by the application
|
||||
// (as opposed to the user) to refresh the access token
|
||||
// if it expires.
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
|
||||
// Expiry is the optional expiration time of the access token.
|
||||
//
|
||||
// If zero, TokenSource implementations will reuse the same
|
||||
// token forever and RefreshToken or equivalent
|
||||
// mechanisms for that TokenSource will not be used.
|
||||
Expiry time.Time `json:"expiry,omitempty"`
|
||||
|
||||
// raw optionally contains extra metadata from the server
|
||||
// when updating a token.
|
||||
raw interface{}
|
||||
}
|
||||
|
||||
// Type returns t.TokenType if non-empty, else "Bearer".
|
||||
func (t *Token) Type() string {
|
||||
if strings.EqualFold(t.TokenType, "bearer") {
|
||||
return "Bearer"
|
||||
}
|
||||
if strings.EqualFold(t.TokenType, "mac") {
|
||||
return "MAC"
|
||||
}
|
||||
if strings.EqualFold(t.TokenType, "basic") {
|
||||
return "Basic"
|
||||
}
|
||||
if t.TokenType != "" {
|
||||
return t.TokenType
|
||||
}
|
||||
return "Bearer"
|
||||
}
|
||||
|
||||
// SetAuthHeader sets the Authorization header to r using the access
|
||||
// token in t.
|
||||
//
|
||||
// This method is unnecessary when using Transport or an HTTP Client
|
||||
// returned by this package.
|
||||
func (t *Token) SetAuthHeader(r *http.Request) {
|
||||
r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
|
||||
}
|
||||
|
||||
// WithExtra returns a new Token that's a clone of t, but using the
|
||||
// provided raw extra map. This is only intended for use by packages
|
||||
// implementing derivative OAuth2 flows.
|
||||
func (t *Token) WithExtra(extra interface{}) *Token {
|
||||
t2 := new(Token)
|
||||
*t2 = *t
|
||||
t2.raw = extra
|
||||
return t2
|
||||
}
|
||||
|
||||
// Extra returns an extra field.
|
||||
// Extra fields are key-value pairs returned by the server as a
|
||||
// part of the token retrieval response.
|
||||
func (t *Token) Extra(key string) interface{} {
|
||||
if raw, ok := t.raw.(map[string]interface{}); ok {
|
||||
return raw[key]
|
||||
}
|
||||
|
||||
vals, ok := t.raw.(url.Values)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := vals.Get(key)
|
||||
switch s := strings.TrimSpace(v); strings.Count(s, ".") {
|
||||
case 0: // Contains no "."; try to parse as int
|
||||
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
return i
|
||||
}
|
||||
case 1: // Contains a single "."; try to parse as float
|
||||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// expired reports whether the token is expired.
|
||||
// t must be non-nil.
|
||||
func (t *Token) expired() bool {
|
||||
if t.Expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
return t.Expiry.Add(-expiryDelta).Before(time.Now())
|
||||
}
|
||||
|
||||
// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
|
||||
func (t *Token) Valid() bool {
|
||||
return t != nil && t.AccessToken != "" && !t.expired()
|
||||
}
|
||||
|
||||
// tokenFromInternal maps an *internal.Token struct into
|
||||
// a *Token struct.
|
||||
func tokenFromInternal(t *internal.Token) *Token {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return &Token{
|
||||
AccessToken: t.AccessToken,
|
||||
TokenType: t.TokenType,
|
||||
RefreshToken: t.RefreshToken,
|
||||
Expiry: t.Expiry,
|
||||
raw: t.Raw,
|
||||
}
|
||||
}
|
||||
|
||||
// retrieveToken takes a *Config and uses that to retrieve an *internal.Token.
|
||||
// This token is then mapped from *internal.Token into an *oauth2.Token which is returned along
|
||||
// with an error..
|
||||
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
|
||||
tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tokenFromInternal(tk), nil
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests,
|
||||
// wrapping a base RoundTripper and adding an Authorization header
|
||||
// with a token from the supplied Sources.
|
||||
//
|
||||
// Transport is a low-level mechanism. Most code will use the
|
||||
// higher-level Config.Client method instead.
|
||||
type Transport struct {
|
||||
// Source supplies the token to add to outgoing requests'
|
||||
// Authorization headers.
|
||||
Source TokenSource
|
||||
|
||||
// Base is the base RoundTripper used to make HTTP requests.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
Base http.RoundTripper
|
||||
|
||||
mu sync.Mutex // guards modReq
|
||||
modReq map[*http.Request]*http.Request // original -> modified
|
||||
}
|
||||
|
||||
// RoundTrip authorizes and authenticates the request with an
|
||||
// access token. If no token exists or token is expired,
|
||||
// tries to refresh/fetch a new token.
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if t.Source == nil {
|
||||
return nil, errors.New("oauth2: Transport's Source is nil")
|
||||
}
|
||||
token, err := t.Source.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req2 := cloneRequest(req) // per RoundTripper contract
|
||||
token.SetAuthHeader(req2)
|
||||
t.setModReq(req, req2)
|
||||
res, err := t.base().RoundTrip(req2)
|
||||
if err != nil {
|
||||
t.setModReq(req, nil)
|
||||
return nil, err
|
||||
}
|
||||
res.Body = &onEOFReader{
|
||||
rc: res.Body,
|
||||
fn: func() { t.setModReq(req, nil) },
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t *Transport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := t.base().(canceler); ok {
|
||||
t.mu.Lock()
|
||||
modReq := t.modReq[req]
|
||||
delete(t.modReq, req)
|
||||
t.mu.Unlock()
|
||||
cr.CancelRequest(modReq)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) base() http.RoundTripper {
|
||||
if t.Base != nil {
|
||||
return t.Base
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
func (t *Transport) setModReq(orig, mod *http.Request) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.modReq == nil {
|
||||
t.modReq = make(map[*http.Request]*http.Request)
|
||||
}
|
||||
if mod == nil {
|
||||
delete(t.modReq, orig)
|
||||
} else {
|
||||
t.modReq[orig] = mod
|
||||
}
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request.
|
||||
// The clone is a shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
// shallow copy of the struct
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
// deep copy of the Header
|
||||
r2.Header = make(http.Header, len(r.Header))
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = append([]string(nil), s...)
|
||||
}
|
||||
return r2
|
||||
}
|
||||
|
||||
type onEOFReader struct {
|
||||
rc io.ReadCloser
|
||||
fn func()
|
||||
}
|
||||
|
||||
func (r *onEOFReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.rc.Read(p)
|
||||
if err == io.EOF {
|
||||
r.runFunc()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *onEOFReader) Close() error {
|
||||
err := r.rc.Close()
|
||||
r.runFunc()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *onEOFReader) runFunc() {
|
||||
if fn := r.fn; fn != nil {
|
||||
fn()
|
||||
r.fn = nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,71 @@
|
|||
# Go App Engine packages
|
||||
|
||||
[![Build Status](https://travis-ci.org/golang/appengine.svg)](https://travis-ci.org/golang/appengine)
|
||||
|
||||
This repository supports the Go runtime on App Engine,
|
||||
including both the standard App Engine and the
|
||||
"App Engine flexible environment" (formerly known as "Managed VMs").
|
||||
It provides APIs for interacting with App Engine services.
|
||||
Its canonical import path is `google.golang.org/appengine`.
|
||||
|
||||
See https://cloud.google.com/appengine/docs/go/
|
||||
for more information.
|
||||
|
||||
File issue reports and feature requests on the [Google App Engine issue
|
||||
tracker](https://code.google.com/p/googleappengine/issues/entry?template=Go%20defect).
|
||||
|
||||
## Directory structure
|
||||
The top level directory of this repository is the `appengine` package. It
|
||||
contains the
|
||||
basic APIs (e.g. `appengine.NewContext`) that apply across APIs. Specific API
|
||||
packages are in subdirectories (e.g. `datastore`).
|
||||
|
||||
There is an `internal` subdirectory that contains service protocol buffers,
|
||||
plus packages required for connectivity to make API calls. App Engine apps
|
||||
should not directly import any package under `internal`.
|
||||
|
||||
## Updating a Go App Engine app
|
||||
|
||||
This section describes how to update an older Go App Engine app to use
|
||||
these packages. A provided tool, `aefix`, can help automate steps 2 and 3
|
||||
(run `go get google.golang.org/appengine/cmd/aefix` to install it), but
|
||||
read the details below since `aefix` can't perform all the changes.
|
||||
|
||||
### 1. Update YAML files (App Engine flexible environment / Managed VMs only)
|
||||
|
||||
The `app.yaml` file (and YAML files for modules) should have these new lines added:
|
||||
```
|
||||
vm: true
|
||||
```
|
||||
See https://cloud.google.com/appengine/docs/go/modules/#Go_Instance_scaling_and_class for details.
|
||||
|
||||
### 2. Update import paths
|
||||
|
||||
The import paths for App Engine packages are now fully qualified, based at `google.golang.org/appengine`.
|
||||
You will need to update your code to use import paths starting with that; for instance,
|
||||
code importing `appengine/datastore` will now need to import `google.golang.org/appengine/datastore`.
|
||||
|
||||
### 3. Update code using deprecated, removed or modified APIs
|
||||
|
||||
Most App Engine services are available with exactly the same API.
|
||||
A few APIs were cleaned up, and some are not available yet.
|
||||
This list summarises the differences:
|
||||
|
||||
* `appengine.Context` has been replaced with the `Context` type from `golang.org/x/net/context`.
|
||||
* Logging methods that were on `appengine.Context` are now functions in `google.golang.org/appengine/log`.
|
||||
* `appengine.Timeout` has been removed. Use `context.WithTimeout` instead.
|
||||
* `appengine.Datacenter` now takes a `context.Context` argument.
|
||||
* `datastore.PropertyLoadSaver` has been simplified to use slices in place of channels.
|
||||
* `delay.Call` now returns an error.
|
||||
* `search.FieldLoadSaver` now handles document metadata.
|
||||
* `urlfetch.Transport` no longer has a Deadline field; set a deadline on the
|
||||
`context.Context` instead.
|
||||
* `aetest` no longer declares its own Context type, and uses the standard one instead.
|
||||
* `taskqueue.QueueStats` no longer takes a maxTasks argument. That argument has been
|
||||
deprecated and unused for a long time.
|
||||
* `appengine.BackendHostname` and `appengine.BackendInstance` were for the deprecated backends feature.
|
||||
Use `appengine.ModuleHostname`and `appengine.ModuleName` instead.
|
||||
* Most of `appengine/file` and parts of `appengine/blobstore` are deprecated.
|
||||
Use [Google Cloud Storage](https://godoc.org/google.golang.org/cloud/storage) instead.
|
||||
* `appengine/socket` is not required on App Engine flexible environment / Managed VMs.
|
||||
Use the standard `net` package instead.
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package appengine provides basic functionality for Google App Engine.
|
||||
//
|
||||
// For more information on how to write Go apps for Google App Engine, see:
|
||||
// https://cloud.google.com/appengine/docs/go/
|
||||
package appengine // import "google.golang.org/appengine"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"google.golang.org/appengine/internal"
|
||||
)
|
||||
|
||||
// IsDevAppServer reports whether the App Engine app is running in the
|
||||
// development App Server.
|
||||
func IsDevAppServer() bool {
|
||||
return internal.IsDevAppServer()
|
||||
}
|
||||
|
||||
// NewContext returns a context for an in-flight HTTP request.
|
||||
// This function is cheap.
|
||||
func NewContext(req *http.Request) context.Context {
|
||||
return WithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
// WithContext returns a copy of the parent context
|
||||
// and associates it with an in-flight HTTP request.
|
||||
// This function is cheap.
|
||||
func WithContext(parent context.Context, req *http.Request) context.Context {
|
||||
return internal.WithContext(parent, req)
|
||||
}
|
||||
|
||||
// TODO(dsymonds): Add a Call function here? Otherwise other packages can't access internal.Call.
|
||||
|
||||
// BlobKey is a key for a blobstore blob.
|
||||
//
|
||||
// Conceptually, this type belongs in the blobstore package, but it lives in
|
||||
// the appengine package to avoid a circular dependency: blobstore depends on
|
||||
// datastore, and datastore needs to refer to the BlobKey type.
|
||||
type BlobKey string
|
||||
|
||||
// GeoPoint represents a location as latitude/longitude in degrees.
|
||||
type GeoPoint struct {
|
||||
Lat, Lng float64
|
||||
}
|
||||
|
||||
// Valid returns whether a GeoPoint is within [-90, 90] latitude and [-180, 180] longitude.
|
||||
func (g GeoPoint) Valid() bool {
|
||||
return -90 <= g.Lat && g.Lat <= 90 && -180 <= g.Lng && g.Lng <= 180
|
||||
}
|
||||
|
||||
// APICallFunc defines a function type for handling an API call.
|
||||
// See WithCallOverride.
|
||||
type APICallFunc func(ctx context.Context, service, method string, in, out proto.Message) error
|
||||
|
||||
// WithAPICallFunc returns a copy of the parent context
|
||||
// that will cause API calls to invoke f instead of their normal operation.
|
||||
//
|
||||
// This is intended for advanced users only.
|
||||
func WithAPICallFunc(ctx context.Context, f APICallFunc) context.Context {
|
||||
return internal.WithCallOverride(ctx, internal.CallOverrideFunc(f))
|
||||
}
|
||||
|
||||
// APICall performs an API call.
|
||||
//
|
||||
// This is not intended for general use; it is exported for use in conjunction
|
||||
// with WithAPICallFunc.
|
||||
func APICall(ctx context.Context, service, method string, in, out proto.Message) error {
|
||||
return internal.Call(ctx, service, method, in, out)
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package appengine
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"google.golang.org/appengine/internal"
|
||||
)
|
||||
|
||||
// The comment below must not be changed.
|
||||
// It is used by go-app-builder to recognise that this package has
|
||||
// the Main function to use in the synthetic main.
|
||||
// The gophers party all night; the rabbits provide the beats.
|
||||
|
||||
// Main is the principal entry point for an app running in App Engine "flexible environment".
|
||||
// It installs a trivial health checker if one isn't already registered,
|
||||
// and starts listening on port 8080 (overridden by the $PORT environment
|
||||
// variable).
|
||||
//
|
||||
// See https://cloud.google.com/appengine/docs/flexible/custom-runtimes#health_check_requests
|
||||
// for details on how to do your own health checking.
|
||||
//
|
||||
// Main never returns.
|
||||
//
|
||||
// Main is designed so that the app's main package looks like this:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "google.golang.org/appengine"
|
||||
//
|
||||
// _ "myapp/package0"
|
||||
// _ "myapp/package1"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// appengine.Main()
|
||||
// }
|
||||
//
|
||||
// The "myapp/packageX" packages are expected to register HTTP handlers
|
||||
// in their init functions.
|
||||
func Main() {
|
||||
internal.Main()
|
||||
}
|
||||
|
||||
// BackgroundContext returns a context not associated with a request.
|
||||
// This should only be used when not servicing a request.
|
||||
// This only works in App Engine "flexible environment".
|
||||
func BackgroundContext() context.Context {
|
||||
return internal.BackgroundContext()
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file provides error functions for common API failure modes.
|
||||
|
||||
package appengine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/appengine/internal"
|
||||
)
|
||||
|
||||
// IsOverQuota reports whether err represents an API call failure
|
||||
// due to insufficient available quota.
|
||||
func IsOverQuota(err error) bool {
|
||||
callErr, ok := err.(*internal.CallError)
|
||||
return ok && callErr.Code == 4
|
||||
}
|
||||
|
||||
// MultiError is returned by batch operations when there are errors with
|
||||
// particular elements. Errors will be in a one-to-one correspondence with
|
||||
// the input elements; successful elements will have a nil entry.
|
||||
type MultiError []error
|
||||
|
||||
func (m MultiError) Error() string {
|
||||
s, n := "", 0
|
||||
for _, e := range m {
|
||||
if e != nil {
|
||||
if n == 0 {
|
||||
s = e.Error()
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
switch n {
|
||||
case 0:
|
||||
return "(0 errors)"
|
||||
case 1:
|
||||
return s
|
||||
case 2:
|
||||
return s + " (and 1 other error)"
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appengine
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"google.golang.org/appengine/internal"
|
||||
pb "google.golang.org/appengine/internal/app_identity"
|
||||
modpb "google.golang.org/appengine/internal/modules"
|
||||
)
|
||||
|
||||
// AppID returns the application ID for the current application.
|
||||
// The string will be a plain application ID (e.g. "appid"), with a
|
||||
// domain prefix for custom domain deployments (e.g. "example.com:appid").
|
||||
func AppID(c context.Context) string { return internal.AppID(c) }
|
||||
|
||||
// DefaultVersionHostname returns the standard hostname of the default version
|
||||
// of the current application (e.g. "my-app.appspot.com"). This is suitable for
|
||||
// use in constructing URLs.
|
||||
func DefaultVersionHostname(c context.Context) string {
|
||||
return internal.DefaultVersionHostname(c)
|
||||
}
|
||||
|
||||
// ModuleName returns the module name of the current instance.
|
||||
func ModuleName(c context.Context) string {
|
||||
return internal.ModuleName(c)
|
||||
}
|
||||
|
||||
// ModuleHostname returns a hostname of a module instance.
|
||||
// If module is the empty string, it refers to the module of the current instance.
|
||||
// If version is empty, it refers to the version of the current instance if valid,
|
||||
// or the default version of the module of the current instance.
|
||||
// If instance is empty, ModuleHostname returns the load-balancing hostname.
|
||||
func ModuleHostname(c context.Context, module, version, instance string) (string, error) {
|
||||
req := &modpb.GetHostnameRequest{}
|
||||
if module != "" {
|
||||
req.Module = &module
|
||||
}
|
||||
if version != "" {
|
||||
req.Version = &version
|
||||
}
|
||||
if instance != "" {
|
||||
req.Instance = &instance
|
||||
}
|
||||
res := &modpb.GetHostnameResponse{}
|
||||
if err := internal.Call(c, "modules", "GetHostname", req, res); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return *res.Hostname, nil
|
||||
}
|
||||
|
||||
// VersionID returns the version ID for the current application.
|
||||
// It will be of the form "X.Y", where X is specified in app.yaml,
|
||||
// and Y is a number generated when each version of the app is uploaded.
|
||||
// It does not include a module name.
|
||||
func VersionID(c context.Context) string { return internal.VersionID(c) }
|
||||
|
||||
// InstanceID returns a mostly-unique identifier for this instance.
|
||||
func InstanceID() string { return internal.InstanceID() }
|
||||
|
||||
// Datacenter returns an identifier for the datacenter that the instance is running in.
|
||||
func Datacenter(c context.Context) string { return internal.Datacenter(c) }
|
||||
|
||||
// ServerSoftware returns the App Engine release version.
|
||||
// In production, it looks like "Google App Engine/X.Y.Z".
|
||||
// In the development appserver, it looks like "Development/X.Y".
|
||||
func ServerSoftware() string { return internal.ServerSoftware() }
|
||||
|
||||
// RequestID returns a string that uniquely identifies the request.
|
||||
func RequestID(c context.Context) string { return internal.RequestID(c) }
|
||||
|
||||
// AccessToken generates an OAuth2 access token for the specified scopes on
|
||||
// behalf of service account of this application. This token will expire after
|
||||
// the returned time.
|
||||
func AccessToken(c context.Context, scopes ...string) (token string, expiry time.Time, err error) {
|
||||
req := &pb.GetAccessTokenRequest{Scope: scopes}
|
||||
res := &pb.GetAccessTokenResponse{}
|
||||
|
||||
err = internal.Call(c, "app_identity_service", "GetAccessToken", req, res)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
return res.GetAccessToken(), time.Unix(res.GetExpirationTime(), 0), nil
|
||||
}
|
||||
|
||||
// Certificate represents a public certificate for the app.
|
||||
type Certificate struct {
|
||||
KeyName string
|
||||
Data []byte // PEM-encoded X.509 certificate
|
||||
}
|
||||
|
||||
// PublicCertificates retrieves the public certificates for the app.
|
||||
// They can be used to verify a signature returned by SignBytes.
|
||||
func PublicCertificates(c context.Context) ([]Certificate, error) {
|
||||
req := &pb.GetPublicCertificateForAppRequest{}
|
||||
res := &pb.GetPublicCertificateForAppResponse{}
|
||||
if err := internal.Call(c, "app_identity_service", "GetPublicCertificatesForApp", req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cs []Certificate
|
||||
for _, pc := range res.PublicCertificateList {
|
||||
cs = append(cs, Certificate{
|
||||
KeyName: pc.GetKeyName(),
|
||||
Data: []byte(pc.GetX509CertificatePem()),
|
||||
})
|
||||
}
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// ServiceAccount returns a string representing the service account name, in
|
||||
// the form of an email address (typically app_id@appspot.gserviceaccount.com).
|
||||
func ServiceAccount(c context.Context) (string, error) {
|
||||
req := &pb.GetServiceAccountNameRequest{}
|
||||
res := &pb.GetServiceAccountNameResponse{}
|
||||
|
||||
err := internal.Call(c, "app_identity_service", "GetServiceAccountName", req, res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.GetServiceAccountName(), err
|
||||
}
|
||||
|
||||
// SignBytes signs bytes using a private key unique to your application.
|
||||
func SignBytes(c context.Context, bytes []byte) (keyName string, signature []byte, err error) {
|
||||
req := &pb.SignForAppRequest{BytesToSign: bytes}
|
||||
res := &pb.SignForAppResponse{}
|
||||
|
||||
if err := internal.Call(c, "app_identity_service", "SignForApp", req, res); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return res.GetKeyName(), res.GetSignatureBytes(), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
internal.RegisterErrorCodeMap("app_identity_service", pb.AppIdentityServiceError_ErrorCode_name)
|
||||
internal.RegisterErrorCodeMap("modules", modpb.ModulesServiceError_ErrorCode_name)
|
||||
}
|
|
@ -0,0 +1,509 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
netcontext "golang.org/x/net/context"
|
||||
|
||||
remotepb "google.golang.org/appengine/internal/remote_api"
|
||||
)
|
||||
|
||||
const (
|
||||
apiPath = "/rpc_http"
|
||||
)
|
||||
|
||||
var (
|
||||
// Incoming headers.
|
||||
ticketHeader = http.CanonicalHeaderKey("X-AppEngine-API-Ticket")
|
||||
dapperHeader = http.CanonicalHeaderKey("X-Google-DapperTraceInfo")
|
||||
traceHeader = http.CanonicalHeaderKey("X-Cloud-Trace-Context")
|
||||
curNamespaceHeader = http.CanonicalHeaderKey("X-AppEngine-Current-Namespace")
|
||||
userIPHeader = http.CanonicalHeaderKey("X-AppEngine-User-IP")
|
||||
remoteAddrHeader = http.CanonicalHeaderKey("X-AppEngine-Remote-Addr")
|
||||
|
||||
// Outgoing headers.
|
||||
apiEndpointHeader = http.CanonicalHeaderKey("X-Google-RPC-Service-Endpoint")
|
||||
apiEndpointHeaderValue = []string{"app-engine-apis"}
|
||||
apiMethodHeader = http.CanonicalHeaderKey("X-Google-RPC-Service-Method")
|
||||
apiMethodHeaderValue = []string{"/VMRemoteAPI.CallRemoteAPI"}
|
||||
apiDeadlineHeader = http.CanonicalHeaderKey("X-Google-RPC-Service-Deadline")
|
||||
apiContentType = http.CanonicalHeaderKey("Content-Type")
|
||||
apiContentTypeValue = []string{"application/octet-stream"}
|
||||
|
||||
apiHTTPClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: limitDial,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func apiURL() *url.URL {
|
||||
host, port := "appengine.googleapis.internal", "10001"
|
||||
if h := os.Getenv("API_HOST"); h != "" {
|
||||
host = h
|
||||
}
|
||||
if p := os.Getenv("API_PORT"); p != "" {
|
||||
port = p
|
||||
}
|
||||
return &url.URL{
|
||||
Scheme: "http",
|
||||
Host: host + ":" + port,
|
||||
Path: apiPath,
|
||||
}
|
||||
}
|
||||
|
||||
func handleHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
c := &context{
|
||||
req: r,
|
||||
outHeader: w.Header(),
|
||||
apiURL: apiURL(),
|
||||
logger: globalLogger(),
|
||||
}
|
||||
|
||||
ctxs.Lock()
|
||||
ctxs.m[r] = c
|
||||
ctxs.Unlock()
|
||||
defer func() {
|
||||
ctxs.Lock()
|
||||
delete(ctxs.m, r)
|
||||
ctxs.Unlock()
|
||||
}()
|
||||
|
||||
// Patch up RemoteAddr so it looks reasonable.
|
||||
if addr := r.Header.Get(userIPHeader); addr != "" {
|
||||
r.RemoteAddr = addr
|
||||
} else if addr = r.Header.Get(remoteAddrHeader); addr != "" {
|
||||
r.RemoteAddr = addr
|
||||
} else {
|
||||
// Should not normally reach here, but pick a sensible default anyway.
|
||||
r.RemoteAddr = "127.0.0.1"
|
||||
}
|
||||
// The address in the headers will most likely be of these forms:
|
||||
// 123.123.123.123
|
||||
// 2001:db8::1
|
||||
// net/http.Request.RemoteAddr is specified to be in "IP:port" form.
|
||||
if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
|
||||
// Assume the remote address is only a host; add a default port.
|
||||
r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80")
|
||||
}
|
||||
|
||||
executeRequestSafely(c, r)
|
||||
c.outHeader = nil // make sure header changes aren't respected any more
|
||||
|
||||
// Avoid nil Write call if c.Write is never called.
|
||||
if c.outCode != 0 {
|
||||
w.WriteHeader(c.outCode)
|
||||
}
|
||||
if c.outBody != nil {
|
||||
w.Write(c.outBody)
|
||||
}
|
||||
}
|
||||
|
||||
func executeRequestSafely(c *context, r *http.Request) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
logf(c, 4, "%s", renderPanic(x)) // 4 == critical
|
||||
c.outCode = 500
|
||||
}
|
||||
}()
|
||||
|
||||
http.DefaultServeMux.ServeHTTP(c, r)
|
||||
}
|
||||
|
||||
func renderPanic(x interface{}) string {
|
||||
buf := make([]byte, 16<<10) // 16 KB should be plenty
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
|
||||
// Remove the first few stack frames:
|
||||
// this func
|
||||
// the recover closure in the caller
|
||||
// That will root the stack trace at the site of the panic.
|
||||
const (
|
||||
skipStart = "internal.renderPanic"
|
||||
skipFrames = 2
|
||||
)
|
||||
start := bytes.Index(buf, []byte(skipStart))
|
||||
p := start
|
||||
for i := 0; i < skipFrames*2 && p+1 < len(buf); i++ {
|
||||
p = bytes.IndexByte(buf[p+1:], '\n') + p + 1
|
||||
if p < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p >= 0 {
|
||||
// buf[start:p+1] is the block to remove.
|
||||
// Copy buf[p+1:] over buf[start:] and shrink buf.
|
||||
copy(buf[start:], buf[p+1:])
|
||||
buf = buf[:len(buf)-(p+1-start)]
|
||||
}
|
||||
|
||||
// Add panic heading.
|
||||
head := fmt.Sprintf("panic: %v\n\n", x)
|
||||
if len(head) > len(buf) {
|
||||
// Extremely unlikely to happen.
|
||||
return head
|
||||
}
|
||||
copy(buf[len(head):], buf)
|
||||
copy(buf, head)
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
var ctxs = struct {
|
||||
sync.Mutex
|
||||
m map[*http.Request]*context
|
||||
bg *context // background context, lazily initialized
|
||||
// dec is used by tests to decorate the netcontext.Context returned
|
||||
// for a given request. This allows tests to add overrides (such as
|
||||
// WithAppIDOverride) to the context. The map is nil outside tests.
|
||||
dec map[*http.Request]func(netcontext.Context) netcontext.Context
|
||||
}{
|
||||
m: make(map[*http.Request]*context),
|
||||
}
|
||||
|
||||
// context represents the context of an in-flight HTTP request.
|
||||
// It implements the appengine.Context and http.ResponseWriter interfaces.
|
||||
type context struct {
|
||||
req *http.Request
|
||||
logger *jsonLogger
|
||||
|
||||
outCode int
|
||||
outHeader http.Header
|
||||
outBody []byte
|
||||
|
||||
apiURL *url.URL
|
||||
}
|
||||
|
||||
var contextKey = "holds a *context"
|
||||
|
||||
func fromContext(ctx netcontext.Context) *context {
|
||||
c, _ := ctx.Value(&contextKey).(*context)
|
||||
return c
|
||||
}
|
||||
|
||||
func withContext(parent netcontext.Context, c *context) netcontext.Context {
|
||||
ctx := netcontext.WithValue(parent, &contextKey, c)
|
||||
if ns := c.req.Header.Get(curNamespaceHeader); ns != "" {
|
||||
ctx = withNamespace(ctx, ns)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func toContext(c *context) netcontext.Context {
|
||||
return withContext(netcontext.Background(), c)
|
||||
}
|
||||
|
||||
func IncomingHeaders(ctx netcontext.Context) http.Header {
|
||||
if c := fromContext(ctx); c != nil {
|
||||
return c.req.Header
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context {
|
||||
ctxs.Lock()
|
||||
c := ctxs.m[req]
|
||||
d := ctxs.dec[req]
|
||||
ctxs.Unlock()
|
||||
|
||||
if d != nil {
|
||||
parent = d(parent)
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
// Someone passed in an http.Request that is not in-flight.
|
||||
// We panic here rather than panicking at a later point
|
||||
// so that stack traces will be more sensible.
|
||||
log.Panic("appengine: NewContext passed an unknown http.Request")
|
||||
}
|
||||
return withContext(parent, c)
|
||||
}
|
||||
|
||||
func BackgroundContext() netcontext.Context {
|
||||
ctxs.Lock()
|
||||
defer ctxs.Unlock()
|
||||
|
||||
if ctxs.bg != nil {
|
||||
return toContext(ctxs.bg)
|
||||
}
|
||||
|
||||
// Compute background security ticket.
|
||||
appID := partitionlessAppID()
|
||||
escAppID := strings.Replace(strings.Replace(appID, ":", "_", -1), ".", "_", -1)
|
||||
majVersion := VersionID(nil)
|
||||
if i := strings.Index(majVersion, "."); i > 0 {
|
||||
majVersion = majVersion[:i]
|
||||
}
|
||||
ticket := fmt.Sprintf("%s/%s.%s.%s", escAppID, ModuleName(nil), majVersion, InstanceID())
|
||||
|
||||
ctxs.bg = &context{
|
||||
req: &http.Request{
|
||||
Header: http.Header{
|
||||
ticketHeader: []string{ticket},
|
||||
},
|
||||
},
|
||||
apiURL: apiURL(),
|
||||
logger: globalLogger(),
|
||||
}
|
||||
|
||||
return toContext(ctxs.bg)
|
||||
}
|
||||
|
||||
// RegisterTestRequest registers the HTTP request req for testing, such that
|
||||
// any API calls are sent to the provided URL. It returns a closure to delete
|
||||
// the registration.
|
||||
// It should only be used by aetest package.
|
||||
func RegisterTestRequest(req *http.Request, apiURL *url.URL, decorate func(netcontext.Context) netcontext.Context) func() {
|
||||
c := &context{
|
||||
req: req,
|
||||
apiURL: apiURL,
|
||||
logger: globalLogger(),
|
||||
}
|
||||
ctxs.Lock()
|
||||
defer ctxs.Unlock()
|
||||
if _, ok := ctxs.m[req]; ok {
|
||||
log.Panic("req already associated with context")
|
||||
}
|
||||
if _, ok := ctxs.dec[req]; ok {
|
||||
log.Panic("req already associated with context")
|
||||
}
|
||||
if ctxs.dec == nil {
|
||||
ctxs.dec = make(map[*http.Request]func(netcontext.Context) netcontext.Context)
|
||||
}
|
||||
ctxs.m[req] = c
|
||||
ctxs.dec[req] = decorate
|
||||
|
||||
return func() {
|
||||
ctxs.Lock()
|
||||
delete(ctxs.m, req)
|
||||
delete(ctxs.dec, req)
|
||||
ctxs.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var errTimeout = &CallError{
|
||||
Detail: "Deadline exceeded",
|
||||
Code: int32(remotepb.RpcError_CANCELLED),
|
||||
Timeout: true,
|
||||
}
|
||||
|
||||
func (c *context) Header() http.Header { return c.outHeader }
|
||||
|
||||
// Copied from $GOROOT/src/pkg/net/http/transfer.go. Some response status
|
||||
// codes do not permit a response body (nor response entity headers such as
|
||||
// Content-Length, Content-Type, etc).
|
||||
func bodyAllowedForStatus(status int) bool {
|
||||
switch {
|
||||
case status >= 100 && status <= 199:
|
||||
return false
|
||||
case status == 204:
|
||||
return false
|
||||
case status == 304:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *context) Write(b []byte) (int, error) {
|
||||
if c.outCode == 0 {
|
||||
c.WriteHeader(http.StatusOK)
|
||||
}
|
||||
if len(b) > 0 && !bodyAllowedForStatus(c.outCode) {
|
||||
return 0, http.ErrBodyNotAllowed
|
||||
}
|
||||
c.outBody = append(c.outBody, b...)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (c *context) WriteHeader(code int) {
|
||||
if c.outCode != 0 {
|
||||
logf(c, 3, "WriteHeader called multiple times on request.") // error level
|
||||
return
|
||||
}
|
||||
c.outCode = code
|
||||
}
|
||||
|
||||
func (c *context) post(body []byte, timeout time.Duration) (b []byte, err error) {
|
||||
hreq := &http.Request{
|
||||
Method: "POST",
|
||||
URL: c.apiURL,
|
||||
Header: http.Header{
|
||||
apiEndpointHeader: apiEndpointHeaderValue,
|
||||
apiMethodHeader: apiMethodHeaderValue,
|
||||
apiContentType: apiContentTypeValue,
|
||||
apiDeadlineHeader: []string{strconv.FormatFloat(timeout.Seconds(), 'f', -1, 64)},
|
||||
},
|
||||
Body: ioutil.NopCloser(bytes.NewReader(body)),
|
||||
ContentLength: int64(len(body)),
|
||||
Host: c.apiURL.Host,
|
||||
}
|
||||
if info := c.req.Header.Get(dapperHeader); info != "" {
|
||||
hreq.Header.Set(dapperHeader, info)
|
||||
}
|
||||
if info := c.req.Header.Get(traceHeader); info != "" {
|
||||
hreq.Header.Set(traceHeader, info)
|
||||
}
|
||||
|
||||
tr := apiHTTPClient.Transport.(*http.Transport)
|
||||
|
||||
var timedOut int32 // atomic; set to 1 if timed out
|
||||
t := time.AfterFunc(timeout, func() {
|
||||
atomic.StoreInt32(&timedOut, 1)
|
||||
tr.CancelRequest(hreq)
|
||||
})
|
||||
defer t.Stop()
|
||||
defer func() {
|
||||
// Check if timeout was exceeded.
|
||||
if atomic.LoadInt32(&timedOut) != 0 {
|
||||
err = errTimeout
|
||||
}
|
||||
}()
|
||||
|
||||
hresp, err := apiHTTPClient.Do(hreq)
|
||||
if err != nil {
|
||||
return nil, &CallError{
|
||||
Detail: fmt.Sprintf("service bridge HTTP failed: %v", err),
|
||||
Code: int32(remotepb.RpcError_UNKNOWN),
|
||||
}
|
||||
}
|
||||
defer hresp.Body.Close()
|
||||
hrespBody, err := ioutil.ReadAll(hresp.Body)
|
||||
if hresp.StatusCode != 200 {
|
||||
return nil, &CallError{
|
||||
Detail: fmt.Sprintf("service bridge returned HTTP %d (%q)", hresp.StatusCode, hrespBody),
|
||||
Code: int32(remotepb.RpcError_UNKNOWN),
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, &CallError{
|
||||
Detail: fmt.Sprintf("service bridge response bad: %v", err),
|
||||
Code: int32(remotepb.RpcError_UNKNOWN),
|
||||
}
|
||||
}
|
||||
return hrespBody, nil
|
||||
}
|
||||
|
||||
func Call(ctx netcontext.Context, service, method string, in, out proto.Message) error {
|
||||
if ns := NamespaceFromContext(ctx); ns != "" {
|
||||
if fn, ok := NamespaceMods[service]; ok {
|
||||
fn(in, ns)
|
||||
}
|
||||
}
|
||||
|
||||
if f, ctx, ok := callOverrideFromContext(ctx); ok {
|
||||
return f(ctx, service, method, in, out)
|
||||
}
|
||||
|
||||
// Handle already-done contexts quickly.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
c := fromContext(ctx)
|
||||
if c == nil {
|
||||
// Give a good error message rather than a panic lower down.
|
||||
return errors.New("not an App Engine context")
|
||||
}
|
||||
|
||||
// Apply transaction modifications if we're in a transaction.
|
||||
if t := transactionFromContext(ctx); t != nil {
|
||||
if t.finished {
|
||||
return errors.New("transaction context has expired")
|
||||
}
|
||||
applyTransaction(in, &t.transaction)
|
||||
}
|
||||
|
||||
// Default RPC timeout is 60s.
|
||||
timeout := 60 * time.Second
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
timeout = deadline.Sub(time.Now())
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ticket := c.req.Header.Get(ticketHeader)
|
||||
req := &remotepb.Request{
|
||||
ServiceName: &service,
|
||||
Method: &method,
|
||||
Request: data,
|
||||
RequestId: &ticket,
|
||||
}
|
||||
hreqBody, err := proto.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hrespBody, err := c.post(hreqBody, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res := &remotepb.Response{}
|
||||
if err := proto.Unmarshal(hrespBody, res); err != nil {
|
||||
return err
|
||||
}
|
||||
if res.RpcError != nil {
|
||||
ce := &CallError{
|
||||
Detail: res.RpcError.GetDetail(),
|
||||
Code: *res.RpcError.Code,
|
||||
}
|
||||
switch remotepb.RpcError_ErrorCode(ce.Code) {
|
||||
case remotepb.RpcError_CANCELLED, remotepb.RpcError_DEADLINE_EXCEEDED:
|
||||
ce.Timeout = true
|
||||
}
|
||||
return ce
|
||||
}
|
||||
if res.ApplicationError != nil {
|
||||
return &APIError{
|
||||
Service: *req.ServiceName,
|
||||
Detail: res.ApplicationError.GetDetail(),
|
||||
Code: *res.ApplicationError.Code,
|
||||
}
|
||||
}
|
||||
if res.Exception != nil || res.JavaException != nil {
|
||||
// This shouldn't happen, but let's be defensive.
|
||||
return &CallError{
|
||||
Detail: "service bridge returned exception",
|
||||
Code: int32(remotepb.RpcError_UNKNOWN),
|
||||
}
|
||||
}
|
||||
return proto.Unmarshal(res.Response, out)
|
||||
}
|
||||
|
||||
func (c *context) Request() *http.Request {
|
||||
return c.req
|
||||
}
|
||||
|
||||
func ContextForTesting(req *http.Request) netcontext.Context {
|
||||
return toContext(&context{
|
||||
req: req,
|
||||
logger: testLogger,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"appengine"
|
||||
"appengine_internal"
|
||||
basepb "appengine_internal/base"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
netcontext "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var contextKey = "holds an appengine.Context"
|
||||
|
||||
func fromContext(ctx netcontext.Context) appengine.Context {
|
||||
c, _ := ctx.Value(&contextKey).(appengine.Context)
|
||||
return c
|
||||
}
|
||||
|
||||
// This is only for classic App Engine adapters.
|
||||
func ClassicContextFromContext(ctx netcontext.Context) appengine.Context {
|
||||
return fromContext(ctx)
|
||||
}
|
||||
|
||||
func withContext(parent netcontext.Context, c appengine.Context) netcontext.Context {
|
||||
ctx := netcontext.WithValue(parent, &contextKey, c)
|
||||
|
||||
s := &basepb.StringProto{}
|
||||
c.Call("__go__", "GetNamespace", &basepb.VoidProto{}, s, nil)
|
||||
if ns := s.GetValue(); ns != "" {
|
||||
ctx = NamespacedContext(ctx, ns)
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func IncomingHeaders(ctx netcontext.Context) http.Header {
|
||||
if c := fromContext(ctx); c != nil {
|
||||
if req, ok := c.Request().(*http.Request); ok {
|
||||
return req.Header
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context {
|
||||
c := appengine.NewContext(req)
|
||||
return withContext(parent, c)
|
||||
}
|
||||
|
||||
type testingContext struct {
|
||||
appengine.Context
|
||||
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
func (t *testingContext) FullyQualifiedAppID() string { return "dev~testcontext" }
|
||||
func (t *testingContext) Call(service, method string, _, _ appengine_internal.ProtoMessage, _ *appengine_internal.CallOptions) error {
|
||||
if service == "__go__" && method == "GetNamespace" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("testingContext: unsupported Call")
|
||||
}
|
||||
func (t *testingContext) Request() interface{} { return t.req }
|
||||
|
||||
func ContextForTesting(req *http.Request) netcontext.Context {
|
||||
return withContext(netcontext.Background(), &testingContext{req: req})
|
||||
}
|
||||
|
||||
func Call(ctx netcontext.Context, service, method string, in, out proto.Message) error {
|
||||
if ns := NamespaceFromContext(ctx); ns != "" {
|
||||
if fn, ok := NamespaceMods[service]; ok {
|
||||
fn(in, ns)
|
||||
}
|
||||
}
|
||||
|
||||
if f, ctx, ok := callOverrideFromContext(ctx); ok {
|
||||
return f(ctx, service, method, in, out)
|
||||
}
|
||||
|
||||
// Handle already-done contexts quickly.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
c := fromContext(ctx)
|
||||
if c == nil {
|
||||
// Give a good error message rather than a panic lower down.
|
||||
return errors.New("not an App Engine context")
|
||||
}
|
||||
|
||||
// Apply transaction modifications if we're in a transaction.
|
||||
if t := transactionFromContext(ctx); t != nil {
|
||||
if t.finished {
|
||||
return errors.New("transaction context has expired")
|
||||
}
|
||||
applyTransaction(in, &t.transaction)
|
||||
}
|
||||
|
||||
var opts *appengine_internal.CallOptions
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
opts = &appengine_internal.CallOptions{
|
||||
Timeout: d.Sub(time.Now()),
|
||||
}
|
||||
}
|
||||
|
||||
err := c.Call(service, method, in, out, opts)
|
||||
switch v := err.(type) {
|
||||
case *appengine_internal.APIError:
|
||||
return &APIError{
|
||||
Service: v.Service,
|
||||
Detail: v.Detail,
|
||||
Code: v.Code,
|
||||
}
|
||||
case *appengine_internal.CallError:
|
||||
return &CallError{
|
||||
Detail: v.Detail,
|
||||
Code: v.Code,
|
||||
Timeout: v.Timeout,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func handleHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
panic("handleHTTP called; this should be impossible")
|
||||
}
|
||||
|
||||
func logf(c appengine.Context, level int64, format string, args ...interface{}) {
|
||||
var fn func(format string, args ...interface{})
|
||||
switch level {
|
||||
case 0:
|
||||
fn = c.Debugf
|
||||
case 1:
|
||||
fn = c.Infof
|
||||
case 2:
|
||||
fn = c.Warningf
|
||||
case 3:
|
||||
fn = c.Errorf
|
||||
case 4:
|
||||
fn = c.Criticalf
|
||||
default:
|
||||
// This shouldn't happen.
|
||||
fn = c.Criticalf
|
||||
}
|
||||
fn(format, args...)
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
netcontext "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type CallOverrideFunc func(ctx netcontext.Context, service, method string, in, out proto.Message) error
|
||||
|
||||
var callOverrideKey = "holds []CallOverrideFunc"
|
||||
|
||||
func WithCallOverride(ctx netcontext.Context, f CallOverrideFunc) netcontext.Context {
|
||||
// We avoid appending to any existing call override
|
||||
// so we don't risk overwriting a popped stack below.
|
||||
var cofs []CallOverrideFunc
|
||||
if uf, ok := ctx.Value(&callOverrideKey).([]CallOverrideFunc); ok {
|
||||
cofs = append(cofs, uf...)
|
||||
}
|
||||
cofs = append(cofs, f)
|
||||
return netcontext.WithValue(ctx, &callOverrideKey, cofs)
|
||||
}
|
||||
|
||||
func callOverrideFromContext(ctx netcontext.Context) (CallOverrideFunc, netcontext.Context, bool) {
|
||||
cofs, _ := ctx.Value(&callOverrideKey).([]CallOverrideFunc)
|
||||
if len(cofs) == 0 {
|
||||
return nil, nil, false
|
||||
}
|
||||
// We found a list of overrides; grab the last, and reconstitute a
|
||||
// context that will hide it.
|
||||
f := cofs[len(cofs)-1]
|
||||
ctx = netcontext.WithValue(ctx, &callOverrideKey, cofs[:len(cofs)-1])
|
||||
return f, ctx, true
|
||||
}
|
||||
|
||||
type logOverrideFunc func(level int64, format string, args ...interface{})
|
||||
|
||||
var logOverrideKey = "holds a logOverrideFunc"
|
||||
|
||||
func WithLogOverride(ctx netcontext.Context, f logOverrideFunc) netcontext.Context {
|
||||
return netcontext.WithValue(ctx, &logOverrideKey, f)
|
||||
}
|
||||
|
||||
var appIDOverrideKey = "holds a string, being the full app ID"
|
||||
|
||||
func WithAppIDOverride(ctx netcontext.Context, appID string) netcontext.Context {
|
||||
return netcontext.WithValue(ctx, &appIDOverrideKey, appID)
|
||||
}
|
||||
|
||||
var namespaceKey = "holds the namespace string"
|
||||
|
||||
func withNamespace(ctx netcontext.Context, ns string) netcontext.Context {
|
||||
return netcontext.WithValue(ctx, &namespaceKey, ns)
|
||||
}
|
||||
|
||||
func NamespaceFromContext(ctx netcontext.Context) string {
|
||||
// If there's no namespace, return the empty string.
|
||||
ns, _ := ctx.Value(&namespaceKey).(string)
|
||||
return ns
|
||||
}
|
||||
|
||||
// FullyQualifiedAppID returns the fully-qualified application ID.
|
||||
// This may contain a partition prefix (e.g. "s~" for High Replication apps),
|
||||
// or a domain prefix (e.g. "example.com:").
|
||||
func FullyQualifiedAppID(ctx netcontext.Context) string {
|
||||
if id, ok := ctx.Value(&appIDOverrideKey).(string); ok {
|
||||
return id
|
||||
}
|
||||
return fullyQualifiedAppID(ctx)
|
||||
}
|
||||
|
||||
func Logf(ctx netcontext.Context, level int64, format string, args ...interface{}) {
|
||||
if f, ok := ctx.Value(&logOverrideKey).(logOverrideFunc); ok {
|
||||
f(level, format, args...)
|
||||
return
|
||||
}
|
||||
logf(fromContext(ctx), level, format, args...)
|
||||
}
|
||||
|
||||
// NamespacedContext wraps a Context to support namespaces.
|
||||
func NamespacedContext(ctx netcontext.Context, namespace string) netcontext.Context {
|
||||
return withNamespace(ctx, namespace)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseFullAppID(appid string) (partition, domain, displayID string) {
|
||||
if i := strings.Index(appid, "~"); i != -1 {
|
||||
partition, appid = appid[:i], appid[i+1:]
|
||||
}
|
||||
if i := strings.Index(appid, ":"); i != -1 {
|
||||
domain, appid = appid[:i], appid[i+1:]
|
||||
}
|
||||
return partition, domain, appid
|
||||
}
|
||||
|
||||
// appID returns "appid" or "domain.com:appid".
|
||||
func appID(fullAppID string) string {
|
||||
_, dom, dis := parseFullAppID(fullAppID)
|
||||
if dom != "" {
|
||||
return dom + ":" + dis
|
||||
}
|
||||
return dis
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
// Code generated by protoc-gen-go.
|
||||
// source: google.golang.org/appengine/internal/base/api_base.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package base is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
google.golang.org/appengine/internal/base/api_base.proto
|
||||
|
||||
It has these top-level messages:
|
||||
StringProto
|
||||
Integer32Proto
|
||||
Integer64Proto
|
||||
BoolProto
|
||||
DoubleProto
|
||||
BytesProto
|
||||
VoidProto
|
||||
*/
|
||||
package base
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
type StringProto struct {
|
||||
Value *string `protobuf:"bytes,1,req,name=value" json:"value,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *StringProto) Reset() { *m = StringProto{} }
|
||||
func (m *StringProto) String() string { return proto.CompactTextString(m) }
|
||||
func (*StringProto) ProtoMessage() {}
|
||||
|
||||
func (m *StringProto) GetValue() string {
|
||||
if m != nil && m.Value != nil {
|
||||
return *m.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Integer32Proto struct {
|
||||
Value *int32 `protobuf:"varint,1,req,name=value" json:"value,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Integer32Proto) Reset() { *m = Integer32Proto{} }
|
||||
func (m *Integer32Proto) String() string { return proto.CompactTextString(m) }
|
||||
func (*Integer32Proto) ProtoMessage() {}
|
||||
|
||||
func (m *Integer32Proto) GetValue() int32 {
|
||||
if m != nil && m.Value != nil {
|
||||
return *m.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Integer64Proto struct {
|
||||
Value *int64 `protobuf:"varint,1,req,name=value" json:"value,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Integer64Proto) Reset() { *m = Integer64Proto{} }
|
||||
func (m *Integer64Proto) String() string { return proto.CompactTextString(m) }
|
||||
func (*Integer64Proto) ProtoMessage() {}
|
||||
|
||||
func (m *Integer64Proto) GetValue() int64 {
|
||||
if m != nil && m.Value != nil {
|
||||
return *m.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type BoolProto struct {
|
||||
Value *bool `protobuf:"varint,1,req,name=value" json:"value,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *BoolProto) Reset() { *m = BoolProto{} }
|
||||
func (m *BoolProto) String() string { return proto.CompactTextString(m) }
|
||||
func (*BoolProto) ProtoMessage() {}
|
||||
|
||||
func (m *BoolProto) GetValue() bool {
|
||||
if m != nil && m.Value != nil {
|
||||
return *m.Value
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type DoubleProto struct {
|
||||
Value *float64 `protobuf:"fixed64,1,req,name=value" json:"value,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DoubleProto) Reset() { *m = DoubleProto{} }
|
||||
func (m *DoubleProto) String() string { return proto.CompactTextString(m) }
|
||||
func (*DoubleProto) ProtoMessage() {}
|
||||
|
||||
func (m *DoubleProto) GetValue() float64 {
|
||||
if m != nil && m.Value != nil {
|
||||
return *m.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type BytesProto struct {
|
||||
Value []byte `protobuf:"bytes,1,req,name=value" json:"value,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *BytesProto) Reset() { *m = BytesProto{} }
|
||||
func (m *BytesProto) String() string { return proto.CompactTextString(m) }
|
||||
func (*BytesProto) ProtoMessage() {}
|
||||
|
||||
func (m *BytesProto) GetValue() []byte {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type VoidProto struct {
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *VoidProto) Reset() { *m = VoidProto{} }
|
||||
func (m *VoidProto) String() string { return proto.CompactTextString(m) }
|
||||
func (*VoidProto) ProtoMessage() {}
|
|
@ -0,0 +1,33 @@
|
|||
// Built-in base types for API calls. Primarily useful as return types.
|
||||
|
||||
syntax = "proto2";
|
||||
option go_package = "base";
|
||||
|
||||
package appengine.base;
|
||||
|
||||
message StringProto {
|
||||
required string value = 1;
|
||||
}
|
||||
|
||||
message Integer32Proto {
|
||||
required int32 value = 1;
|
||||
}
|
||||
|
||||
message Integer64Proto {
|
||||
required int64 value = 1;
|
||||
}
|
||||
|
||||
message BoolProto {
|
||||
required bool value = 1;
|
||||
}
|
||||
|
||||
message DoubleProto {
|
||||
required double value = 1;
|
||||
}
|
||||
|
||||
message BytesProto {
|
||||
required bytes value = 1 [ctype=CORD];
|
||||
}
|
||||
|
||||
message VoidProto {
|
||||
}
|
2778
vendor/google.golang.org/appengine/internal/datastore/datastore_v3.pb.go
generated
vendored
Normal file
2778
vendor/google.golang.org/appengine/internal/datastore/datastore_v3.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
541
vendor/google.golang.org/appengine/internal/datastore/datastore_v3.proto
generated
vendored
Executable file
541
vendor/google.golang.org/appengine/internal/datastore/datastore_v3.proto
generated
vendored
Executable file
|
@ -0,0 +1,541 @@
|
|||
syntax = "proto2";
|
||||
option go_package = "datastore";
|
||||
|
||||
package appengine;
|
||||
|
||||
message Action{}
|
||||
|
||||
message PropertyValue {
|
||||
optional int64 int64Value = 1;
|
||||
optional bool booleanValue = 2;
|
||||
optional string stringValue = 3;
|
||||
optional double doubleValue = 4;
|
||||
|
||||
optional group PointValue = 5 {
|
||||
required double x = 6;
|
||||
required double y = 7;
|
||||
}
|
||||
|
||||
optional group UserValue = 8 {
|
||||
required string email = 9;
|
||||
required string auth_domain = 10;
|
||||
optional string nickname = 11;
|
||||
optional string federated_identity = 21;
|
||||
optional string federated_provider = 22;
|
||||
}
|
||||
|
||||
optional group ReferenceValue = 12 {
|
||||
required string app = 13;
|
||||
optional string name_space = 20;
|
||||
repeated group PathElement = 14 {
|
||||
required string type = 15;
|
||||
optional int64 id = 16;
|
||||
optional string name = 17;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message Property {
|
||||
enum Meaning {
|
||||
NO_MEANING = 0;
|
||||
BLOB = 14;
|
||||
TEXT = 15;
|
||||
BYTESTRING = 16;
|
||||
|
||||
ATOM_CATEGORY = 1;
|
||||
ATOM_LINK = 2;
|
||||
ATOM_TITLE = 3;
|
||||
ATOM_CONTENT = 4;
|
||||
ATOM_SUMMARY = 5;
|
||||
ATOM_AUTHOR = 6;
|
||||
|
||||
GD_WHEN = 7;
|
||||
GD_EMAIL = 8;
|
||||
GEORSS_POINT = 9;
|
||||
GD_IM = 10;
|
||||
|
||||
GD_PHONENUMBER = 11;
|
||||
GD_POSTALADDRESS = 12;
|
||||
|
||||
GD_RATING = 13;
|
||||
|
||||
BLOBKEY = 17;
|
||||
ENTITY_PROTO = 19;
|
||||
|
||||
INDEX_VALUE = 18;
|
||||
};
|
||||
|
||||
optional Meaning meaning = 1 [default = NO_MEANING];
|
||||
optional string meaning_uri = 2;
|
||||
|
||||
required string name = 3;
|
||||
|
||||
required PropertyValue value = 5;
|
||||
|
||||
required bool multiple = 4;
|
||||
|
||||
optional bool searchable = 6 [default=false];
|
||||
|
||||
enum FtsTokenizationOption {
|
||||
HTML = 1;
|
||||
ATOM = 2;
|
||||
}
|
||||
|
||||
optional FtsTokenizationOption fts_tokenization_option = 8;
|
||||
|
||||
optional string locale = 9 [default = "en"];
|
||||
}
|
||||
|
||||
message Path {
|
||||
repeated group Element = 1 {
|
||||
required string type = 2;
|
||||
optional int64 id = 3;
|
||||
optional string name = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message Reference {
|
||||
required string app = 13;
|
||||
optional string name_space = 20;
|
||||
required Path path = 14;
|
||||
}
|
||||
|
||||
message User {
|
||||
required string email = 1;
|
||||
required string auth_domain = 2;
|
||||
optional string nickname = 3;
|
||||
optional string federated_identity = 6;
|
||||
optional string federated_provider = 7;
|
||||
}
|
||||
|
||||
message EntityProto {
|
||||
required Reference key = 13;
|
||||
required Path entity_group = 16;
|
||||
optional User owner = 17;
|
||||
|
||||
enum Kind {
|
||||
GD_CONTACT = 1;
|
||||
GD_EVENT = 2;
|
||||
GD_MESSAGE = 3;
|
||||
}
|
||||
optional Kind kind = 4;
|
||||
optional string kind_uri = 5;
|
||||
|
||||
repeated Property property = 14;
|
||||
repeated Property raw_property = 15;
|
||||
|
||||
optional int32 rank = 18;
|
||||
}
|
||||
|
||||
message CompositeProperty {
|
||||
required int64 index_id = 1;
|
||||
repeated string value = 2;
|
||||
}
|
||||
|
||||
message Index {
|
||||
required string entity_type = 1;
|
||||
required bool ancestor = 5;
|
||||
repeated group Property = 2 {
|
||||
required string name = 3;
|
||||
enum Direction {
|
||||
ASCENDING = 1;
|
||||
DESCENDING = 2;
|
||||
}
|
||||
optional Direction direction = 4 [default = ASCENDING];
|
||||
}
|
||||
}
|
||||
|
||||
message CompositeIndex {
|
||||
required string app_id = 1;
|
||||
required int64 id = 2;
|
||||
required Index definition = 3;
|
||||
|
||||
enum State {
|
||||
WRITE_ONLY = 1;
|
||||
READ_WRITE = 2;
|
||||
DELETED = 3;
|
||||
ERROR = 4;
|
||||
}
|
||||
required State state = 4;
|
||||
|
||||
optional bool only_use_if_required = 6 [default = false];
|
||||
}
|
||||
|
||||
message IndexPostfix {
|
||||
message IndexValue {
|
||||
required string property_name = 1;
|
||||
required PropertyValue value = 2;
|
||||
}
|
||||
|
||||
repeated IndexValue index_value = 1;
|
||||
|
||||
optional Reference key = 2;
|
||||
|
||||
optional bool before = 3 [default=true];
|
||||
}
|
||||
|
||||
message IndexPosition {
|
||||
optional string key = 1;
|
||||
|
||||
optional bool before = 2 [default=true];
|
||||
}
|
||||
|
||||
message Snapshot {
|
||||
enum Status {
|
||||
INACTIVE = 0;
|
||||
ACTIVE = 1;
|
||||
}
|
||||
|
||||
required int64 ts = 1;
|
||||
}
|
||||
|
||||
message InternalHeader {
|
||||
optional string qos = 1;
|
||||
}
|
||||
|
||||
message Transaction {
|
||||
optional InternalHeader header = 4;
|
||||
required fixed64 handle = 1;
|
||||
required string app = 2;
|
||||
optional bool mark_changes = 3 [default = false];
|
||||
}
|
||||
|
||||
message Query {
|
||||
optional InternalHeader header = 39;
|
||||
|
||||
required string app = 1;
|
||||
optional string name_space = 29;
|
||||
|
||||
optional string kind = 3;
|
||||
optional Reference ancestor = 17;
|
||||
|
||||
repeated group Filter = 4 {
|
||||
enum Operator {
|
||||
LESS_THAN = 1;
|
||||
LESS_THAN_OR_EQUAL = 2;
|
||||
GREATER_THAN = 3;
|
||||
GREATER_THAN_OR_EQUAL = 4;
|
||||
EQUAL = 5;
|
||||
IN = 6;
|
||||
EXISTS = 7;
|
||||
}
|
||||
|
||||
required Operator op = 6;
|
||||
repeated Property property = 14;
|
||||
}
|
||||
|
||||
optional string search_query = 8;
|
||||
|
||||
repeated group Order = 9 {
|
||||
enum Direction {
|
||||
ASCENDING = 1;
|
||||
DESCENDING = 2;
|
||||
}
|
||||
|
||||
required string property = 10;
|
||||
optional Direction direction = 11 [default = ASCENDING];
|
||||
}
|
||||
|
||||
enum Hint {
|
||||
ORDER_FIRST = 1;
|
||||
ANCESTOR_FIRST = 2;
|
||||
FILTER_FIRST = 3;
|
||||
}
|
||||
optional Hint hint = 18;
|
||||
|
||||
optional int32 count = 23;
|
||||
|
||||
optional int32 offset = 12 [default = 0];
|
||||
|
||||
optional int32 limit = 16;
|
||||
|
||||
optional CompiledCursor compiled_cursor = 30;
|
||||
optional CompiledCursor end_compiled_cursor = 31;
|
||||
|
||||
repeated CompositeIndex composite_index = 19;
|
||||
|
||||
optional bool require_perfect_plan = 20 [default = false];
|
||||
|
||||
optional bool keys_only = 21 [default = false];
|
||||
|
||||
optional Transaction transaction = 22;
|
||||
|
||||
optional bool compile = 25 [default = false];
|
||||
|
||||
optional int64 failover_ms = 26;
|
||||
|
||||
optional bool strong = 32;
|
||||
|
||||
repeated string property_name = 33;
|
||||
|
||||
repeated string group_by_property_name = 34;
|
||||
|
||||
optional bool distinct = 24;
|
||||
|
||||
optional int64 min_safe_time_seconds = 35;
|
||||
|
||||
repeated string safe_replica_name = 36;
|
||||
|
||||
optional bool persist_offset = 37 [default=false];
|
||||
}
|
||||
|
||||
message CompiledQuery {
|
||||
required group PrimaryScan = 1 {
|
||||
optional string index_name = 2;
|
||||
|
||||
optional string start_key = 3;
|
||||
optional bool start_inclusive = 4;
|
||||
optional string end_key = 5;
|
||||
optional bool end_inclusive = 6;
|
||||
|
||||
repeated string start_postfix_value = 22;
|
||||
repeated string end_postfix_value = 23;
|
||||
|
||||
optional int64 end_unapplied_log_timestamp_us = 19;
|
||||
}
|
||||
|
||||
repeated group MergeJoinScan = 7 {
|
||||
required string index_name = 8;
|
||||
|
||||
repeated string prefix_value = 9;
|
||||
|
||||
optional bool value_prefix = 20 [default=false];
|
||||
}
|
||||
|
||||
optional Index index_def = 21;
|
||||
|
||||
optional int32 offset = 10 [default = 0];
|
||||
|
||||
optional int32 limit = 11;
|
||||
|
||||
required bool keys_only = 12;
|
||||
|
||||
repeated string property_name = 24;
|
||||
|
||||
optional int32 distinct_infix_size = 25;
|
||||
|
||||
optional group EntityFilter = 13 {
|
||||
optional bool distinct = 14 [default=false];
|
||||
|
||||
optional string kind = 17;
|
||||
optional Reference ancestor = 18;
|
||||
}
|
||||
}
|
||||
|
||||
message CompiledCursor {
|
||||
optional group Position = 2 {
|
||||
optional string start_key = 27;
|
||||
|
||||
repeated group IndexValue = 29 {
|
||||
optional string property = 30;
|
||||
required PropertyValue value = 31;
|
||||
}
|
||||
|
||||
optional Reference key = 32;
|
||||
|
||||
optional bool start_inclusive = 28 [default=true];
|
||||
}
|
||||
}
|
||||
|
||||
message Cursor {
|
||||
required fixed64 cursor = 1;
|
||||
|
||||
optional string app = 2;
|
||||
}
|
||||
|
||||
message Error {
|
||||
enum ErrorCode {
|
||||
BAD_REQUEST = 1;
|
||||
CONCURRENT_TRANSACTION = 2;
|
||||
INTERNAL_ERROR = 3;
|
||||
NEED_INDEX = 4;
|
||||
TIMEOUT = 5;
|
||||
PERMISSION_DENIED = 6;
|
||||
BIGTABLE_ERROR = 7;
|
||||
COMMITTED_BUT_STILL_APPLYING = 8;
|
||||
CAPABILITY_DISABLED = 9;
|
||||
TRY_ALTERNATE_BACKEND = 10;
|
||||
SAFE_TIME_TOO_OLD = 11;
|
||||
}
|
||||
}
|
||||
|
||||
message Cost {
|
||||
optional int32 index_writes = 1;
|
||||
optional int32 index_write_bytes = 2;
|
||||
optional int32 entity_writes = 3;
|
||||
optional int32 entity_write_bytes = 4;
|
||||
optional group CommitCost = 5 {
|
||||
optional int32 requested_entity_puts = 6;
|
||||
optional int32 requested_entity_deletes = 7;
|
||||
};
|
||||
optional int32 approximate_storage_delta = 8;
|
||||
optional int32 id_sequence_updates = 9;
|
||||
}
|
||||
|
||||
message GetRequest {
|
||||
optional InternalHeader header = 6;
|
||||
|
||||
repeated Reference key = 1;
|
||||
optional Transaction transaction = 2;
|
||||
|
||||
optional int64 failover_ms = 3;
|
||||
|
||||
optional bool strong = 4;
|
||||
|
||||
optional bool allow_deferred = 5 [default=false];
|
||||
}
|
||||
|
||||
message GetResponse {
|
||||
repeated group Entity = 1 {
|
||||
optional EntityProto entity = 2;
|
||||
optional Reference key = 4;
|
||||
|
||||
optional int64 version = 3;
|
||||
}
|
||||
|
||||
repeated Reference deferred = 5;
|
||||
|
||||
optional bool in_order = 6 [default=true];
|
||||
}
|
||||
|
||||
message PutRequest {
|
||||
optional InternalHeader header = 11;
|
||||
|
||||
repeated EntityProto entity = 1;
|
||||
optional Transaction transaction = 2;
|
||||
repeated CompositeIndex composite_index = 3;
|
||||
|
||||
optional bool trusted = 4 [default = false];
|
||||
|
||||
optional bool force = 7 [default = false];
|
||||
|
||||
optional bool mark_changes = 8 [default = false];
|
||||
repeated Snapshot snapshot = 9;
|
||||
|
||||
enum AutoIdPolicy {
|
||||
CURRENT = 0;
|
||||
SEQUENTIAL = 1;
|
||||
}
|
||||
optional AutoIdPolicy auto_id_policy = 10 [default = CURRENT];
|
||||
}
|
||||
|
||||
message PutResponse {
|
||||
repeated Reference key = 1;
|
||||
optional Cost cost = 2;
|
||||
repeated int64 version = 3;
|
||||
}
|
||||
|
||||
message TouchRequest {
|
||||
optional InternalHeader header = 10;
|
||||
|
||||
repeated Reference key = 1;
|
||||
repeated CompositeIndex composite_index = 2;
|
||||
optional bool force = 3 [default = false];
|
||||
repeated Snapshot snapshot = 9;
|
||||
}
|
||||
|
||||
message TouchResponse {
|
||||
optional Cost cost = 1;
|
||||
}
|
||||
|
||||
message DeleteRequest {
|
||||
optional InternalHeader header = 10;
|
||||
|
||||
repeated Reference key = 6;
|
||||
optional Transaction transaction = 5;
|
||||
|
||||
optional bool trusted = 4 [default = false];
|
||||
|
||||
optional bool force = 7 [default = false];
|
||||
|
||||
optional bool mark_changes = 8 [default = false];
|
||||
repeated Snapshot snapshot = 9;
|
||||
}
|
||||
|
||||
message DeleteResponse {
|
||||
optional Cost cost = 1;
|
||||
repeated int64 version = 3;
|
||||
}
|
||||
|
||||
message NextRequest {
|
||||
optional InternalHeader header = 5;
|
||||
|
||||
required Cursor cursor = 1;
|
||||
optional int32 count = 2;
|
||||
|
||||
optional int32 offset = 4 [default = 0];
|
||||
|
||||
optional bool compile = 3 [default = false];
|
||||
}
|
||||
|
||||
message QueryResult {
|
||||
optional Cursor cursor = 1;
|
||||
|
||||
repeated EntityProto result = 2;
|
||||
|
||||
optional int32 skipped_results = 7;
|
||||
|
||||
required bool more_results = 3;
|
||||
|
||||
optional bool keys_only = 4;
|
||||
|
||||
optional bool index_only = 9;
|
||||
|
||||
optional bool small_ops = 10;
|
||||
|
||||
optional CompiledQuery compiled_query = 5;
|
||||
|
||||
optional CompiledCursor compiled_cursor = 6;
|
||||
|
||||
repeated CompositeIndex index = 8;
|
||||
|
||||
repeated int64 version = 11;
|
||||
}
|
||||
|
||||
message AllocateIdsRequest {
|
||||
optional InternalHeader header = 4;
|
||||
|
||||
optional Reference model_key = 1;
|
||||
|
||||
optional int64 size = 2;
|
||||
|
||||
optional int64 max = 3;
|
||||
|
||||
repeated Reference reserve = 5;
|
||||
}
|
||||
|
||||
message AllocateIdsResponse {
|
||||
required int64 start = 1;
|
||||
required int64 end = 2;
|
||||
optional Cost cost = 3;
|
||||
}
|
||||
|
||||
message CompositeIndices {
|
||||
repeated CompositeIndex index = 1;
|
||||
}
|
||||
|
||||
message AddActionsRequest {
|
||||
optional InternalHeader header = 3;
|
||||
|
||||
required Transaction transaction = 1;
|
||||
repeated Action action = 2;
|
||||
}
|
||||
|
||||
message AddActionsResponse {
|
||||
}
|
||||
|
||||
message BeginTransactionRequest {
|
||||
optional InternalHeader header = 3;
|
||||
|
||||
required string app = 1;
|
||||
optional bool allow_multiple_eg = 2 [default = false];
|
||||
}
|
||||
|
||||
message CommitResponse {
|
||||
optional Cost cost = 1;
|
||||
|
||||
repeated group Version = 3 {
|
||||
required Reference root_entity_key = 4;
|
||||
required int64 version = 5;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
import netcontext "golang.org/x/net/context"
|
||||
|
||||
// These functions are implementations of the wrapper functions
|
||||
// in ../appengine/identity.go. See that file for commentary.
|
||||
|
||||
func AppID(c netcontext.Context) string {
|
||||
return appID(FullyQualifiedAppID(c))
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"appengine"
|
||||
|
||||
netcontext "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func DefaultVersionHostname(ctx netcontext.Context) string {
|
||||
return appengine.DefaultVersionHostname(fromContext(ctx))
|
||||
}
|
||||
|
||||
func RequestID(ctx netcontext.Context) string { return appengine.RequestID(fromContext(ctx)) }
|
||||
func Datacenter(_ netcontext.Context) string { return appengine.Datacenter() }
|
||||
func ServerSoftware() string { return appengine.ServerSoftware() }
|
||||
func ModuleName(ctx netcontext.Context) string { return appengine.ModuleName(fromContext(ctx)) }
|
||||
func VersionID(ctx netcontext.Context) string { return appengine.VersionID(fromContext(ctx)) }
|
||||
func InstanceID() string { return appengine.InstanceID() }
|
||||
func IsDevAppServer() bool { return appengine.IsDevAppServer() }
|
||||
|
||||
func fullyQualifiedAppID(ctx netcontext.Context) string { return fromContext(ctx).FullyQualifiedAppID() }
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
netcontext "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// These functions are implementations of the wrapper functions
|
||||
// in ../appengine/identity.go. See that file for commentary.
|
||||
|
||||
const (
|
||||
hDefaultVersionHostname = "X-AppEngine-Default-Version-Hostname"
|
||||
hRequestLogId = "X-AppEngine-Request-Log-Id"
|
||||
hDatacenter = "X-AppEngine-Datacenter"
|
||||
)
|
||||
|
||||
func ctxHeaders(ctx netcontext.Context) http.Header {
|
||||
return fromContext(ctx).Request().Header
|
||||
}
|
||||
|
||||
func DefaultVersionHostname(ctx netcontext.Context) string {
|
||||
return ctxHeaders(ctx).Get(hDefaultVersionHostname)
|
||||
}
|
||||
|
||||
func RequestID(ctx netcontext.Context) string {
|
||||
return ctxHeaders(ctx).Get(hRequestLogId)
|
||||
}
|
||||
|
||||
func Datacenter(ctx netcontext.Context) string {
|
||||
return ctxHeaders(ctx).Get(hDatacenter)
|
||||
}
|
||||
|
||||
func ServerSoftware() string {
|
||||
// TODO(dsymonds): Remove fallback when we've verified this.
|
||||
if s := os.Getenv("SERVER_SOFTWARE"); s != "" {
|
||||
return s
|
||||
}
|
||||
return "Google App Engine/1.x.x"
|
||||
}
|
||||
|
||||
// TODO(dsymonds): Remove the metadata fetches.
|
||||
|
||||
func ModuleName(_ netcontext.Context) string {
|
||||
if s := os.Getenv("GAE_MODULE_NAME"); s != "" {
|
||||
return s
|
||||
}
|
||||
return string(mustGetMetadata("instance/attributes/gae_backend_name"))
|
||||
}
|
||||
|
||||
func VersionID(_ netcontext.Context) string {
|
||||
if s1, s2 := os.Getenv("GAE_MODULE_VERSION"), os.Getenv("GAE_MINOR_VERSION"); s1 != "" && s2 != "" {
|
||||
return s1 + "." + s2
|
||||
}
|
||||
return string(mustGetMetadata("instance/attributes/gae_backend_version")) + "." + string(mustGetMetadata("instance/attributes/gae_backend_minor_version"))
|
||||
}
|
||||
|
||||
func InstanceID() string {
|
||||
if s := os.Getenv("GAE_MODULE_INSTANCE"); s != "" {
|
||||
return s
|
||||
}
|
||||
return string(mustGetMetadata("instance/attributes/gae_backend_instance"))
|
||||
}
|
||||
|
||||
func partitionlessAppID() string {
|
||||
// gae_project has everything except the partition prefix.
|
||||
appID := os.Getenv("GAE_LONG_APP_ID")
|
||||
if appID == "" {
|
||||
appID = string(mustGetMetadata("instance/attributes/gae_project"))
|
||||
}
|
||||
return appID
|
||||
}
|
||||
|
||||
func fullyQualifiedAppID(_ netcontext.Context) string {
|
||||
appID := partitionlessAppID()
|
||||
|
||||
part := os.Getenv("GAE_PARTITION")
|
||||
if part == "" {
|
||||
part = string(mustGetMetadata("instance/attributes/gae_partition"))
|
||||
}
|
||||
|
||||
if part != "" {
|
||||
appID = part + "~" + appID
|
||||
}
|
||||
return appID
|
||||
}
|
||||
|
||||
func IsDevAppServer() bool {
|
||||
return os.Getenv("RUN_WITH_DEVAPPSERVER") != ""
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal provides support for package appengine.
|
||||
//
|
||||
// Programs should not use this package directly. Its API is not stable.
|
||||
// Use packages appengine and appengine/* instead.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
remotepb "google.golang.org/appengine/internal/remote_api"
|
||||
)
|
||||
|
||||
// errorCodeMaps is a map of service name to the error code map for the service.
|
||||
var errorCodeMaps = make(map[string]map[int32]string)
|
||||
|
||||
// RegisterErrorCodeMap is called from API implementations to register their
|
||||
// error code map. This should only be called from init functions.
|
||||
func RegisterErrorCodeMap(service string, m map[int32]string) {
|
||||
errorCodeMaps[service] = m
|
||||
}
|
||||
|
||||
type timeoutCodeKey struct {
|
||||
service string
|
||||
code int32
|
||||
}
|
||||
|
||||
// timeoutCodes is the set of service+code pairs that represent timeouts.
|
||||
var timeoutCodes = make(map[timeoutCodeKey]bool)
|
||||
|
||||
func RegisterTimeoutErrorCode(service string, code int32) {
|
||||
timeoutCodes[timeoutCodeKey{service, code}] = true
|
||||
}
|
||||
|
||||
// APIError is the type returned by appengine.Context's Call method
|
||||
// when an API call fails in an API-specific way. This may be, for instance,
|
||||
// a taskqueue API call failing with TaskQueueServiceError::UNKNOWN_QUEUE.
|
||||
type APIError struct {
|
||||
Service string
|
||||
Detail string
|
||||
Code int32 // API-specific error code
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
if e.Code == 0 {
|
||||
if e.Detail == "" {
|
||||
return "APIError <empty>"
|
||||
}
|
||||
return e.Detail
|
||||
}
|
||||
s := fmt.Sprintf("API error %d", e.Code)
|
||||
if m, ok := errorCodeMaps[e.Service]; ok {
|
||||
s += " (" + e.Service + ": " + m[e.Code] + ")"
|
||||
} else {
|
||||
// Shouldn't happen, but provide a bit more detail if it does.
|
||||
s = e.Service + " " + s
|
||||
}
|
||||
if e.Detail != "" {
|
||||
s += ": " + e.Detail
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *APIError) IsTimeout() bool {
|
||||
return timeoutCodes[timeoutCodeKey{e.Service, e.Code}]
|
||||
}
|
||||
|
||||
// CallError is the type returned by appengine.Context's Call method when an
|
||||
// API call fails in a generic way, such as RpcError::CAPABILITY_DISABLED.
|
||||
type CallError struct {
|
||||
Detail string
|
||||
Code int32
|
||||
// TODO: Remove this if we get a distinguishable error code.
|
||||
Timeout bool
|
||||
}
|
||||
|
||||
func (e *CallError) Error() string {
|
||||
var msg string
|
||||
switch remotepb.RpcError_ErrorCode(e.Code) {
|
||||
case remotepb.RpcError_UNKNOWN:
|
||||
return e.Detail
|
||||
case remotepb.RpcError_OVER_QUOTA:
|
||||
msg = "Over quota"
|
||||
case remotepb.RpcError_CAPABILITY_DISABLED:
|
||||
msg = "Capability disabled"
|
||||
case remotepb.RpcError_CANCELLED:
|
||||
msg = "Canceled"
|
||||
default:
|
||||
msg = fmt.Sprintf("Call error %d", e.Code)
|
||||
}
|
||||
s := msg + ": " + e.Detail
|
||||
if e.Timeout {
|
||||
s += " (timeout)"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *CallError) IsTimeout() bool {
|
||||
return e.Timeout
|
||||
}
|
||||
|
||||
func Main() {
|
||||
installHealthChecker(http.DefaultServeMux)
|
||||
|
||||
port := "8080"
|
||||
if s := os.Getenv("PORT"); s != "" {
|
||||
port = s
|
||||
}
|
||||
|
||||
if err := http.ListenAndServe(":"+port, http.HandlerFunc(handleHTTP)); err != nil {
|
||||
log.Fatalf("http.ListenAndServe: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func installHealthChecker(mux *http.ServeMux) {
|
||||
// If no health check handler has been installed by this point, add a trivial one.
|
||||
const healthPath = "/_ah/health"
|
||||
hreq := &http.Request{
|
||||
Method: "GET",
|
||||
URL: &url.URL{
|
||||
Path: healthPath,
|
||||
},
|
||||
}
|
||||
if _, pat := mux.Handler(hreq); pat != healthPath {
|
||||
mux.HandleFunc(healthPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "ok")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// NamespaceMods is a map from API service to a function that will mutate an RPC request to attach a namespace.
|
||||
// The function should be prepared to be called on the same message more than once; it should only modify the
|
||||
// RPC request the first time.
|
||||
var NamespaceMods = make(map[string]func(m proto.Message, namespace string))
|
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// jsonLogger writes logs in the JSON format required for Flex Logging. It can
|
||||
// be used concurrently.
|
||||
type jsonLogger struct {
|
||||
mu sync.Mutex
|
||||
enc *json.Encoder
|
||||
}
|
||||
|
||||
type logLine struct {
|
||||
Message string `json:"message"`
|
||||
Timestamp logTimestamp `json:"timestamp"`
|
||||
Severity string `json:"severity"`
|
||||
TraceID string `json:"traceId,omitempty"`
|
||||
}
|
||||
|
||||
type logTimestamp struct {
|
||||
Seconds int64 `json:"seconds"`
|
||||
Nanos int `json:"nanos"`
|
||||
}
|
||||
|
||||
var (
|
||||
logger *jsonLogger
|
||||
loggerOnce sync.Once
|
||||
|
||||
logPath = "/var/log/app_engine/app.json"
|
||||
stderrLogger = newJSONLogger(os.Stderr)
|
||||
testLogger = newJSONLogger(ioutil.Discard)
|
||||
|
||||
levels = map[int64]string{
|
||||
0: "DEBUG",
|
||||
1: "INFO",
|
||||
2: "WARNING",
|
||||
3: "ERROR",
|
||||
4: "CRITICAL",
|
||||
}
|
||||
)
|
||||
|
||||
func globalLogger() *jsonLogger {
|
||||
loggerOnce.Do(func() {
|
||||
f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Printf("failed to open/create log file, logging to stderr: %v", err)
|
||||
logger = stderrLogger
|
||||
return
|
||||
}
|
||||
|
||||
logger = newJSONLogger(f)
|
||||
})
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
func logf(ctx *context, level int64, format string, args ...interface{}) {
|
||||
s := strings.TrimSpace(fmt.Sprintf(format, args...))
|
||||
now := time.Now()
|
||||
|
||||
trace := ctx.req.Header.Get(traceHeader)
|
||||
if i := strings.Index(trace, "/"); i > -1 {
|
||||
trace = trace[:i]
|
||||
}
|
||||
|
||||
line := &logLine{
|
||||
Message: s,
|
||||
Timestamp: logTimestamp{
|
||||
Seconds: now.Unix(),
|
||||
Nanos: now.Nanosecond(),
|
||||
},
|
||||
Severity: levels[level],
|
||||
TraceID: trace,
|
||||
}
|
||||
|
||||
if err := ctx.logger.emit(line); err != nil {
|
||||
log.Printf("failed to write log line to file: %v", err)
|
||||
}
|
||||
|
||||
log.Print(levels[level] + ": " + s)
|
||||
}
|
||||
|
||||
func newJSONLogger(w io.Writer) *jsonLogger {
|
||||
return &jsonLogger{
|
||||
enc: json.NewEncoder(w),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *jsonLogger) emit(line *logLine) error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
return l.enc.Encode(line)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
// This file has code for accessing metadata.
|
||||
//
|
||||
// References:
|
||||
// https://cloud.google.com/compute/docs/metadata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataHost = "metadata"
|
||||
metadataPath = "/computeMetadata/v1/"
|
||||
)
|
||||
|
||||
var (
|
||||
metadataRequestHeaders = http.Header{
|
||||
"Metadata-Flavor": []string{"Google"},
|
||||
}
|
||||
)
|
||||
|
||||
// TODO(dsymonds): Do we need to support default values, like Python?
|
||||
func mustGetMetadata(key string) []byte {
|
||||
b, err := getMetadata(key)
|
||||
if err != nil {
|
||||
log.Fatalf("Metadata fetch failed: %v", err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func getMetadata(key string) ([]byte, error) {
|
||||
// TODO(dsymonds): May need to use url.Parse to support keys with query args.
|
||||
req := &http.Request{
|
||||
Method: "GET",
|
||||
URL: &url.URL{
|
||||
Scheme: "http",
|
||||
Host: metadataHost,
|
||||
Path: metadataPath + key,
|
||||
},
|
||||
Header: metadataRequestHeaders,
|
||||
Host: metadataHost,
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("metadata server returned HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
// This file implements a network dialer that limits the number of concurrent connections.
|
||||
// It is only used for API calls.
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var limitSem = make(chan int, 100) // TODO(dsymonds): Use environment variable.
|
||||
|
||||
func limitRelease() {
|
||||
// non-blocking
|
||||
select {
|
||||
case <-limitSem:
|
||||
default:
|
||||
// This should not normally happen.
|
||||
log.Print("appengine: unbalanced limitSem release!")
|
||||
}
|
||||
}
|
||||
|
||||
func limitDial(network, addr string) (net.Conn, error) {
|
||||
limitSem <- 1
|
||||
|
||||
// Dial with a timeout in case the API host is MIA.
|
||||
// The connection should normally be very fast.
|
||||
conn, err := net.DialTimeout(network, addr, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
limitRelease()
|
||||
return nil, err
|
||||
}
|
||||
lc := &limitConn{Conn: conn}
|
||||
runtime.SetFinalizer(lc, (*limitConn).Close) // shouldn't usually be required
|
||||
return lc, nil
|
||||
}
|
||||
|
||||
type limitConn struct {
|
||||
close sync.Once
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (lc *limitConn) Close() error {
|
||||
defer lc.close.Do(func() {
|
||||
limitRelease()
|
||||
runtime.SetFinalizer(lc, nil)
|
||||
})
|
||||
return lc.Conn.Close()
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash -e
|
||||
#
|
||||
# This script rebuilds the generated code for the protocol buffers.
|
||||
# To run this you will need protoc and goprotobuf installed;
|
||||
# see https://github.com/golang/protobuf for instructions.
|
||||
|
||||
PKG=google.golang.org/appengine
|
||||
|
||||
function die() {
|
||||
echo 1>&2 $*
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Sanity check that the right tools are accessible.
|
||||
for tool in go protoc protoc-gen-go; do
|
||||
q=$(which $tool) || die "didn't find $tool"
|
||||
echo 1>&2 "$tool: $q"
|
||||
done
|
||||
|
||||
echo -n 1>&2 "finding package dir... "
|
||||
pkgdir=$(go list -f '{{.Dir}}' $PKG)
|
||||
echo 1>&2 $pkgdir
|
||||
base=$(echo $pkgdir | sed "s,/$PKG\$,,")
|
||||
echo 1>&2 "base: $base"
|
||||
cd $base
|
||||
|
||||
# Run protoc once per package.
|
||||
for dir in $(find $PKG/internal -name '*.proto' | xargs dirname | sort | uniq); do
|
||||
echo 1>&2 "* $dir"
|
||||
protoc --go_out=. $dir/*.proto
|
||||
done
|
||||
|
||||
for f in $(find $PKG/internal -name '*.pb.go'); do
|
||||
# Remove proto.RegisterEnum calls.
|
||||
# These cause duplicate registration panics when these packages
|
||||
# are used on classic App Engine. proto.RegisterEnum only affects
|
||||
# parsing the text format; we don't care about that.
|
||||
# https://code.google.com/p/googleappengine/issues/detail?id=11670#c17
|
||||
sed -i '/proto.RegisterEnum/d' $f
|
||||
done
|
231
vendor/google.golang.org/appengine/internal/remote_api/remote_api.pb.go
generated
vendored
Normal file
231
vendor/google.golang.org/appengine/internal/remote_api/remote_api.pb.go
generated
vendored
Normal file
|
@ -0,0 +1,231 @@
|
|||
// Code generated by protoc-gen-go.
|
||||
// source: google.golang.org/appengine/internal/remote_api/remote_api.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package remote_api is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
google.golang.org/appengine/internal/remote_api/remote_api.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Request
|
||||
ApplicationError
|
||||
RpcError
|
||||
Response
|
||||
*/
|
||||
package remote_api
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
type RpcError_ErrorCode int32
|
||||
|
||||
const (
|
||||
RpcError_UNKNOWN RpcError_ErrorCode = 0
|
||||
RpcError_CALL_NOT_FOUND RpcError_ErrorCode = 1
|
||||
RpcError_PARSE_ERROR RpcError_ErrorCode = 2
|
||||
RpcError_SECURITY_VIOLATION RpcError_ErrorCode = 3
|
||||
RpcError_OVER_QUOTA RpcError_ErrorCode = 4
|
||||
RpcError_REQUEST_TOO_LARGE RpcError_ErrorCode = 5
|
||||
RpcError_CAPABILITY_DISABLED RpcError_ErrorCode = 6
|
||||
RpcError_FEATURE_DISABLED RpcError_ErrorCode = 7
|
||||
RpcError_BAD_REQUEST RpcError_ErrorCode = 8
|
||||
RpcError_RESPONSE_TOO_LARGE RpcError_ErrorCode = 9
|
||||
RpcError_CANCELLED RpcError_ErrorCode = 10
|
||||
RpcError_REPLAY_ERROR RpcError_ErrorCode = 11
|
||||
RpcError_DEADLINE_EXCEEDED RpcError_ErrorCode = 12
|
||||
)
|
||||
|
||||
var RpcError_ErrorCode_name = map[int32]string{
|
||||
0: "UNKNOWN",
|
||||
1: "CALL_NOT_FOUND",
|
||||
2: "PARSE_ERROR",
|
||||
3: "SECURITY_VIOLATION",
|
||||
4: "OVER_QUOTA",
|
||||
5: "REQUEST_TOO_LARGE",
|
||||
6: "CAPABILITY_DISABLED",
|
||||
7: "FEATURE_DISABLED",
|
||||
8: "BAD_REQUEST",
|
||||
9: "RESPONSE_TOO_LARGE",
|
||||
10: "CANCELLED",
|
||||
11: "REPLAY_ERROR",
|
||||
12: "DEADLINE_EXCEEDED",
|
||||
}
|
||||
var RpcError_ErrorCode_value = map[string]int32{
|
||||
"UNKNOWN": 0,
|
||||
"CALL_NOT_FOUND": 1,
|
||||
"PARSE_ERROR": 2,
|
||||
"SECURITY_VIOLATION": 3,
|
||||
"OVER_QUOTA": 4,
|
||||
"REQUEST_TOO_LARGE": 5,
|
||||
"CAPABILITY_DISABLED": 6,
|
||||
"FEATURE_DISABLED": 7,
|
||||
"BAD_REQUEST": 8,
|
||||
"RESPONSE_TOO_LARGE": 9,
|
||||
"CANCELLED": 10,
|
||||
"REPLAY_ERROR": 11,
|
||||
"DEADLINE_EXCEEDED": 12,
|
||||
}
|
||||
|
||||
func (x RpcError_ErrorCode) Enum() *RpcError_ErrorCode {
|
||||
p := new(RpcError_ErrorCode)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
func (x RpcError_ErrorCode) String() string {
|
||||
return proto.EnumName(RpcError_ErrorCode_name, int32(x))
|
||||
}
|
||||
func (x *RpcError_ErrorCode) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(RpcError_ErrorCode_value, data, "RpcError_ErrorCode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = RpcError_ErrorCode(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
ServiceName *string `protobuf:"bytes,2,req,name=service_name" json:"service_name,omitempty"`
|
||||
Method *string `protobuf:"bytes,3,req,name=method" json:"method,omitempty"`
|
||||
Request []byte `protobuf:"bytes,4,req,name=request" json:"request,omitempty"`
|
||||
RequestId *string `protobuf:"bytes,5,opt,name=request_id" json:"request_id,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
|
||||
func (m *Request) GetServiceName() string {
|
||||
if m != nil && m.ServiceName != nil {
|
||||
return *m.ServiceName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetMethod() string {
|
||||
if m != nil && m.Method != nil {
|
||||
return *m.Method
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetRequest() []byte {
|
||||
if m != nil {
|
||||
return m.Request
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Request) GetRequestId() string {
|
||||
if m != nil && m.RequestId != nil {
|
||||
return *m.RequestId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ApplicationError struct {
|
||||
Code *int32 `protobuf:"varint,1,req,name=code" json:"code,omitempty"`
|
||||
Detail *string `protobuf:"bytes,2,req,name=detail" json:"detail,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ApplicationError) Reset() { *m = ApplicationError{} }
|
||||
func (m *ApplicationError) String() string { return proto.CompactTextString(m) }
|
||||
func (*ApplicationError) ProtoMessage() {}
|
||||
|
||||
func (m *ApplicationError) GetCode() int32 {
|
||||
if m != nil && m.Code != nil {
|
||||
return *m.Code
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ApplicationError) GetDetail() string {
|
||||
if m != nil && m.Detail != nil {
|
||||
return *m.Detail
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type RpcError struct {
|
||||
Code *int32 `protobuf:"varint,1,req,name=code" json:"code,omitempty"`
|
||||
Detail *string `protobuf:"bytes,2,opt,name=detail" json:"detail,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *RpcError) Reset() { *m = RpcError{} }
|
||||
func (m *RpcError) String() string { return proto.CompactTextString(m) }
|
||||
func (*RpcError) ProtoMessage() {}
|
||||
|
||||
func (m *RpcError) GetCode() int32 {
|
||||
if m != nil && m.Code != nil {
|
||||
return *m.Code
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *RpcError) GetDetail() string {
|
||||
if m != nil && m.Detail != nil {
|
||||
return *m.Detail
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Response []byte `protobuf:"bytes,1,opt,name=response" json:"response,omitempty"`
|
||||
Exception []byte `protobuf:"bytes,2,opt,name=exception" json:"exception,omitempty"`
|
||||
ApplicationError *ApplicationError `protobuf:"bytes,3,opt,name=application_error" json:"application_error,omitempty"`
|
||||
JavaException []byte `protobuf:"bytes,4,opt,name=java_exception" json:"java_exception,omitempty"`
|
||||
RpcError *RpcError `protobuf:"bytes,5,opt,name=rpc_error" json:"rpc_error,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
|
||||
func (m *Response) GetResponse() []byte {
|
||||
if m != nil {
|
||||
return m.Response
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Response) GetException() []byte {
|
||||
if m != nil {
|
||||
return m.Exception
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Response) GetApplicationError() *ApplicationError {
|
||||
if m != nil {
|
||||
return m.ApplicationError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Response) GetJavaException() []byte {
|
||||
if m != nil {
|
||||
return m.JavaException
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Response) GetRpcError() *RpcError {
|
||||
if m != nil {
|
||||
return m.RpcError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
}
|
44
vendor/google.golang.org/appengine/internal/remote_api/remote_api.proto
generated
vendored
Normal file
44
vendor/google.golang.org/appengine/internal/remote_api/remote_api.proto
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
syntax = "proto2";
|
||||
option go_package = "remote_api";
|
||||
|
||||
package remote_api;
|
||||
|
||||
message Request {
|
||||
required string service_name = 2;
|
||||
required string method = 3;
|
||||
required bytes request = 4;
|
||||
optional string request_id = 5;
|
||||
}
|
||||
|
||||
message ApplicationError {
|
||||
required int32 code = 1;
|
||||
required string detail = 2;
|
||||
}
|
||||
|
||||
message RpcError {
|
||||
enum ErrorCode {
|
||||
UNKNOWN = 0;
|
||||
CALL_NOT_FOUND = 1;
|
||||
PARSE_ERROR = 2;
|
||||
SECURITY_VIOLATION = 3;
|
||||
OVER_QUOTA = 4;
|
||||
REQUEST_TOO_LARGE = 5;
|
||||
CAPABILITY_DISABLED = 6;
|
||||
FEATURE_DISABLED = 7;
|
||||
BAD_REQUEST = 8;
|
||||
RESPONSE_TOO_LARGE = 9;
|
||||
CANCELLED = 10;
|
||||
REPLAY_ERROR = 11;
|
||||
DEADLINE_EXCEEDED = 12;
|
||||
}
|
||||
required int32 code = 1;
|
||||
optional string detail = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
optional bytes response = 1;
|
||||
optional bytes exception = 2;
|
||||
optional ApplicationError application_error = 3;
|
||||
optional bytes java_exception = 4;
|
||||
optional RpcError rpc_error = 5;
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
// This file implements hooks for applying datastore transactions.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
netcontext "golang.org/x/net/context"
|
||||
|
||||
basepb "google.golang.org/appengine/internal/base"
|
||||
pb "google.golang.org/appengine/internal/datastore"
|
||||
)
|
||||
|
||||
var transactionSetters = make(map[reflect.Type]reflect.Value)
|
||||
|
||||
// RegisterTransactionSetter registers a function that sets transaction information
|
||||
// in a protocol buffer message. f should be a function with two arguments,
|
||||
// the first being a protocol buffer type, and the second being *datastore.Transaction.
|
||||
func RegisterTransactionSetter(f interface{}) {
|
||||
v := reflect.ValueOf(f)
|
||||
transactionSetters[v.Type().In(0)] = v
|
||||
}
|
||||
|
||||
// applyTransaction applies the transaction t to message pb
|
||||
// by using the relevant setter passed to RegisterTransactionSetter.
|
||||
func applyTransaction(pb proto.Message, t *pb.Transaction) {
|
||||
v := reflect.ValueOf(pb)
|
||||
if f, ok := transactionSetters[v.Type()]; ok {
|
||||
f.Call([]reflect.Value{v, reflect.ValueOf(t)})
|
||||
}
|
||||
}
|
||||
|
||||
var transactionKey = "used for *Transaction"
|
||||
|
||||
func transactionFromContext(ctx netcontext.Context) *transaction {
|
||||
t, _ := ctx.Value(&transactionKey).(*transaction)
|
||||
return t
|
||||
}
|
||||
|
||||
func withTransaction(ctx netcontext.Context, t *transaction) netcontext.Context {
|
||||
return netcontext.WithValue(ctx, &transactionKey, t)
|
||||
}
|
||||
|
||||
type transaction struct {
|
||||
transaction pb.Transaction
|
||||
finished bool
|
||||
}
|
||||
|
||||
var ErrConcurrentTransaction = errors.New("internal: concurrent transaction")
|
||||
|
||||
func RunTransactionOnce(c netcontext.Context, f func(netcontext.Context) error, xg bool) error {
|
||||
if transactionFromContext(c) != nil {
|
||||
return errors.New("nested transactions are not supported")
|
||||
}
|
||||
|
||||
// Begin the transaction.
|
||||
t := &transaction{}
|
||||
req := &pb.BeginTransactionRequest{
|
||||
App: proto.String(FullyQualifiedAppID(c)),
|
||||
}
|
||||
if xg {
|
||||
req.AllowMultipleEg = proto.Bool(true)
|
||||
}
|
||||
if err := Call(c, "datastore_v3", "BeginTransaction", req, &t.transaction); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call f, rolling back the transaction if f returns a non-nil error, or panics.
|
||||
// The panic is not recovered.
|
||||
defer func() {
|
||||
if t.finished {
|
||||
return
|
||||
}
|
||||
t.finished = true
|
||||
// Ignore the error return value, since we are already returning a non-nil
|
||||
// error (or we're panicking).
|
||||
Call(c, "datastore_v3", "Rollback", &t.transaction, &basepb.VoidProto{})
|
||||
}()
|
||||
if err := f(withTransaction(c, t)); err != nil {
|
||||
return err
|
||||
}
|
||||
t.finished = true
|
||||
|
||||
// Commit the transaction.
|
||||
res := &pb.CommitResponse{}
|
||||
err := Call(c, "datastore_v3", "Commit", &t.transaction, res)
|
||||
if ae, ok := err.(*APIError); ok {
|
||||
/* TODO: restore this conditional
|
||||
if appengine.IsDevAppServer() {
|
||||
*/
|
||||
// The Python Dev AppServer raises an ApplicationError with error code 2 (which is
|
||||
// Error.CONCURRENT_TRANSACTION) and message "Concurrency exception.".
|
||||
if ae.Code == int32(pb.Error_BAD_REQUEST) && ae.Detail == "ApplicationError: 2 Concurrency exception." {
|
||||
return ErrConcurrentTransaction
|
||||
}
|
||||
if ae.Code == int32(pb.Error_CONCURRENT_TRANSACTION) {
|
||||
return ErrConcurrentTransaction
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
355
vendor/google.golang.org/appengine/internal/urlfetch/urlfetch_service.pb.go
generated
vendored
Normal file
355
vendor/google.golang.org/appengine/internal/urlfetch/urlfetch_service.pb.go
generated
vendored
Normal file
|
@ -0,0 +1,355 @@
|
|||
// Code generated by protoc-gen-go.
|
||||
// source: google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package urlfetch is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto
|
||||
|
||||
It has these top-level messages:
|
||||
URLFetchServiceError
|
||||
URLFetchRequest
|
||||
URLFetchResponse
|
||||
*/
|
||||
package urlfetch
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
type URLFetchServiceError_ErrorCode int32
|
||||
|
||||
const (
|
||||
URLFetchServiceError_OK URLFetchServiceError_ErrorCode = 0
|
||||
URLFetchServiceError_INVALID_URL URLFetchServiceError_ErrorCode = 1
|
||||
URLFetchServiceError_FETCH_ERROR URLFetchServiceError_ErrorCode = 2
|
||||
URLFetchServiceError_UNSPECIFIED_ERROR URLFetchServiceError_ErrorCode = 3
|
||||
URLFetchServiceError_RESPONSE_TOO_LARGE URLFetchServiceError_ErrorCode = 4
|
||||
URLFetchServiceError_DEADLINE_EXCEEDED URLFetchServiceError_ErrorCode = 5
|
||||
URLFetchServiceError_SSL_CERTIFICATE_ERROR URLFetchServiceError_ErrorCode = 6
|
||||
URLFetchServiceError_DNS_ERROR URLFetchServiceError_ErrorCode = 7
|
||||
URLFetchServiceError_CLOSED URLFetchServiceError_ErrorCode = 8
|
||||
URLFetchServiceError_INTERNAL_TRANSIENT_ERROR URLFetchServiceError_ErrorCode = 9
|
||||
URLFetchServiceError_TOO_MANY_REDIRECTS URLFetchServiceError_ErrorCode = 10
|
||||
URLFetchServiceError_MALFORMED_REPLY URLFetchServiceError_ErrorCode = 11
|
||||
URLFetchServiceError_CONNECTION_ERROR URLFetchServiceError_ErrorCode = 12
|
||||
)
|
||||
|
||||
var URLFetchServiceError_ErrorCode_name = map[int32]string{
|
||||
0: "OK",
|
||||
1: "INVALID_URL",
|
||||
2: "FETCH_ERROR",
|
||||
3: "UNSPECIFIED_ERROR",
|
||||
4: "RESPONSE_TOO_LARGE",
|
||||
5: "DEADLINE_EXCEEDED",
|
||||
6: "SSL_CERTIFICATE_ERROR",
|
||||
7: "DNS_ERROR",
|
||||
8: "CLOSED",
|
||||
9: "INTERNAL_TRANSIENT_ERROR",
|
||||
10: "TOO_MANY_REDIRECTS",
|
||||
11: "MALFORMED_REPLY",
|
||||
12: "CONNECTION_ERROR",
|
||||
}
|
||||
var URLFetchServiceError_ErrorCode_value = map[string]int32{
|
||||
"OK": 0,
|
||||
"INVALID_URL": 1,
|
||||
"FETCH_ERROR": 2,
|
||||
"UNSPECIFIED_ERROR": 3,
|
||||
"RESPONSE_TOO_LARGE": 4,
|
||||
"DEADLINE_EXCEEDED": 5,
|
||||
"SSL_CERTIFICATE_ERROR": 6,
|
||||
"DNS_ERROR": 7,
|
||||
"CLOSED": 8,
|
||||
"INTERNAL_TRANSIENT_ERROR": 9,
|
||||
"TOO_MANY_REDIRECTS": 10,
|
||||
"MALFORMED_REPLY": 11,
|
||||
"CONNECTION_ERROR": 12,
|
||||
}
|
||||
|
||||
func (x URLFetchServiceError_ErrorCode) Enum() *URLFetchServiceError_ErrorCode {
|
||||
p := new(URLFetchServiceError_ErrorCode)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
func (x URLFetchServiceError_ErrorCode) String() string {
|
||||
return proto.EnumName(URLFetchServiceError_ErrorCode_name, int32(x))
|
||||
}
|
||||
func (x *URLFetchServiceError_ErrorCode) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(URLFetchServiceError_ErrorCode_value, data, "URLFetchServiceError_ErrorCode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = URLFetchServiceError_ErrorCode(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type URLFetchRequest_RequestMethod int32
|
||||
|
||||
const (
|
||||
URLFetchRequest_GET URLFetchRequest_RequestMethod = 1
|
||||
URLFetchRequest_POST URLFetchRequest_RequestMethod = 2
|
||||
URLFetchRequest_HEAD URLFetchRequest_RequestMethod = 3
|
||||
URLFetchRequest_PUT URLFetchRequest_RequestMethod = 4
|
||||
URLFetchRequest_DELETE URLFetchRequest_RequestMethod = 5
|
||||
URLFetchRequest_PATCH URLFetchRequest_RequestMethod = 6
|
||||
)
|
||||
|
||||
var URLFetchRequest_RequestMethod_name = map[int32]string{
|
||||
1: "GET",
|
||||
2: "POST",
|
||||
3: "HEAD",
|
||||
4: "PUT",
|
||||
5: "DELETE",
|
||||
6: "PATCH",
|
||||
}
|
||||
var URLFetchRequest_RequestMethod_value = map[string]int32{
|
||||
"GET": 1,
|
||||
"POST": 2,
|
||||
"HEAD": 3,
|
||||
"PUT": 4,
|
||||
"DELETE": 5,
|
||||
"PATCH": 6,
|
||||
}
|
||||
|
||||
func (x URLFetchRequest_RequestMethod) Enum() *URLFetchRequest_RequestMethod {
|
||||
p := new(URLFetchRequest_RequestMethod)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
func (x URLFetchRequest_RequestMethod) String() string {
|
||||
return proto.EnumName(URLFetchRequest_RequestMethod_name, int32(x))
|
||||
}
|
||||
func (x *URLFetchRequest_RequestMethod) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(URLFetchRequest_RequestMethod_value, data, "URLFetchRequest_RequestMethod")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = URLFetchRequest_RequestMethod(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type URLFetchServiceError struct {
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *URLFetchServiceError) Reset() { *m = URLFetchServiceError{} }
|
||||
func (m *URLFetchServiceError) String() string { return proto.CompactTextString(m) }
|
||||
func (*URLFetchServiceError) ProtoMessage() {}
|
||||
|
||||
type URLFetchRequest struct {
|
||||
Method *URLFetchRequest_RequestMethod `protobuf:"varint,1,req,name=Method,enum=appengine.URLFetchRequest_RequestMethod" json:"Method,omitempty"`
|
||||
Url *string `protobuf:"bytes,2,req,name=Url" json:"Url,omitempty"`
|
||||
Header []*URLFetchRequest_Header `protobuf:"group,3,rep,name=Header" json:"header,omitempty"`
|
||||
Payload []byte `protobuf:"bytes,6,opt,name=Payload" json:"Payload,omitempty"`
|
||||
FollowRedirects *bool `protobuf:"varint,7,opt,name=FollowRedirects,def=1" json:"FollowRedirects,omitempty"`
|
||||
Deadline *float64 `protobuf:"fixed64,8,opt,name=Deadline" json:"Deadline,omitempty"`
|
||||
MustValidateServerCertificate *bool `protobuf:"varint,9,opt,name=MustValidateServerCertificate,def=1" json:"MustValidateServerCertificate,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *URLFetchRequest) Reset() { *m = URLFetchRequest{} }
|
||||
func (m *URLFetchRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*URLFetchRequest) ProtoMessage() {}
|
||||
|
||||
const Default_URLFetchRequest_FollowRedirects bool = true
|
||||
const Default_URLFetchRequest_MustValidateServerCertificate bool = true
|
||||
|
||||
func (m *URLFetchRequest) GetMethod() URLFetchRequest_RequestMethod {
|
||||
if m != nil && m.Method != nil {
|
||||
return *m.Method
|
||||
}
|
||||
return URLFetchRequest_GET
|
||||
}
|
||||
|
||||
func (m *URLFetchRequest) GetUrl() string {
|
||||
if m != nil && m.Url != nil {
|
||||
return *m.Url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *URLFetchRequest) GetHeader() []*URLFetchRequest_Header {
|
||||
if m != nil {
|
||||
return m.Header
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *URLFetchRequest) GetPayload() []byte {
|
||||
if m != nil {
|
||||
return m.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *URLFetchRequest) GetFollowRedirects() bool {
|
||||
if m != nil && m.FollowRedirects != nil {
|
||||
return *m.FollowRedirects
|
||||
}
|
||||
return Default_URLFetchRequest_FollowRedirects
|
||||
}
|
||||
|
||||
func (m *URLFetchRequest) GetDeadline() float64 {
|
||||
if m != nil && m.Deadline != nil {
|
||||
return *m.Deadline
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *URLFetchRequest) GetMustValidateServerCertificate() bool {
|
||||
if m != nil && m.MustValidateServerCertificate != nil {
|
||||
return *m.MustValidateServerCertificate
|
||||
}
|
||||
return Default_URLFetchRequest_MustValidateServerCertificate
|
||||
}
|
||||
|
||||
type URLFetchRequest_Header struct {
|
||||
Key *string `protobuf:"bytes,4,req,name=Key" json:"Key,omitempty"`
|
||||
Value *string `protobuf:"bytes,5,req,name=Value" json:"Value,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *URLFetchRequest_Header) Reset() { *m = URLFetchRequest_Header{} }
|
||||
func (m *URLFetchRequest_Header) String() string { return proto.CompactTextString(m) }
|
||||
func (*URLFetchRequest_Header) ProtoMessage() {}
|
||||
|
||||
func (m *URLFetchRequest_Header) GetKey() string {
|
||||
if m != nil && m.Key != nil {
|
||||
return *m.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *URLFetchRequest_Header) GetValue() string {
|
||||
if m != nil && m.Value != nil {
|
||||
return *m.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type URLFetchResponse struct {
|
||||
Content []byte `protobuf:"bytes,1,opt,name=Content" json:"Content,omitempty"`
|
||||
StatusCode *int32 `protobuf:"varint,2,req,name=StatusCode" json:"StatusCode,omitempty"`
|
||||
Header []*URLFetchResponse_Header `protobuf:"group,3,rep,name=Header" json:"header,omitempty"`
|
||||
ContentWasTruncated *bool `protobuf:"varint,6,opt,name=ContentWasTruncated,def=0" json:"ContentWasTruncated,omitempty"`
|
||||
ExternalBytesSent *int64 `protobuf:"varint,7,opt,name=ExternalBytesSent" json:"ExternalBytesSent,omitempty"`
|
||||
ExternalBytesReceived *int64 `protobuf:"varint,8,opt,name=ExternalBytesReceived" json:"ExternalBytesReceived,omitempty"`
|
||||
FinalUrl *string `protobuf:"bytes,9,opt,name=FinalUrl" json:"FinalUrl,omitempty"`
|
||||
ApiCpuMilliseconds *int64 `protobuf:"varint,10,opt,name=ApiCpuMilliseconds,def=0" json:"ApiCpuMilliseconds,omitempty"`
|
||||
ApiBytesSent *int64 `protobuf:"varint,11,opt,name=ApiBytesSent,def=0" json:"ApiBytesSent,omitempty"`
|
||||
ApiBytesReceived *int64 `protobuf:"varint,12,opt,name=ApiBytesReceived,def=0" json:"ApiBytesReceived,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) Reset() { *m = URLFetchResponse{} }
|
||||
func (m *URLFetchResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*URLFetchResponse) ProtoMessage() {}
|
||||
|
||||
const Default_URLFetchResponse_ContentWasTruncated bool = false
|
||||
const Default_URLFetchResponse_ApiCpuMilliseconds int64 = 0
|
||||
const Default_URLFetchResponse_ApiBytesSent int64 = 0
|
||||
const Default_URLFetchResponse_ApiBytesReceived int64 = 0
|
||||
|
||||
func (m *URLFetchResponse) GetContent() []byte {
|
||||
if m != nil {
|
||||
return m.Content
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) GetStatusCode() int32 {
|
||||
if m != nil && m.StatusCode != nil {
|
||||
return *m.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) GetHeader() []*URLFetchResponse_Header {
|
||||
if m != nil {
|
||||
return m.Header
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) GetContentWasTruncated() bool {
|
||||
if m != nil && m.ContentWasTruncated != nil {
|
||||
return *m.ContentWasTruncated
|
||||
}
|
||||
return Default_URLFetchResponse_ContentWasTruncated
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) GetExternalBytesSent() int64 {
|
||||
if m != nil && m.ExternalBytesSent != nil {
|
||||
return *m.ExternalBytesSent
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) GetExternalBytesReceived() int64 {
|
||||
if m != nil && m.ExternalBytesReceived != nil {
|
||||
return *m.ExternalBytesReceived
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) GetFinalUrl() string {
|
||||
if m != nil && m.FinalUrl != nil {
|
||||
return *m.FinalUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) GetApiCpuMilliseconds() int64 {
|
||||
if m != nil && m.ApiCpuMilliseconds != nil {
|
||||
return *m.ApiCpuMilliseconds
|
||||
}
|
||||
return Default_URLFetchResponse_ApiCpuMilliseconds
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) GetApiBytesSent() int64 {
|
||||
if m != nil && m.ApiBytesSent != nil {
|
||||
return *m.ApiBytesSent
|
||||
}
|
||||
return Default_URLFetchResponse_ApiBytesSent
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse) GetApiBytesReceived() int64 {
|
||||
if m != nil && m.ApiBytesReceived != nil {
|
||||
return *m.ApiBytesReceived
|
||||
}
|
||||
return Default_URLFetchResponse_ApiBytesReceived
|
||||
}
|
||||
|
||||
type URLFetchResponse_Header struct {
|
||||
Key *string `protobuf:"bytes,4,req,name=Key" json:"Key,omitempty"`
|
||||
Value *string `protobuf:"bytes,5,req,name=Value" json:"Value,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse_Header) Reset() { *m = URLFetchResponse_Header{} }
|
||||
func (m *URLFetchResponse_Header) String() string { return proto.CompactTextString(m) }
|
||||
func (*URLFetchResponse_Header) ProtoMessage() {}
|
||||
|
||||
func (m *URLFetchResponse_Header) GetKey() string {
|
||||
if m != nil && m.Key != nil {
|
||||
return *m.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *URLFetchResponse_Header) GetValue() string {
|
||||
if m != nil && m.Value != nil {
|
||||
return *m.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue