Compare commits

...

72 Commits

Author SHA1 Message Date
kingxt
43e712d86a fix type convert error (#395) 2021-01-16 18:24:11 +08:00
kingxt
4db20677f7 optimized (#392) 2021-01-15 11:36:37 +08:00
Kevin Wan
6887fb22de add more tests for codec (#391) 2021-01-14 23:39:44 +08:00
Kevin Wan
50fbdbcfd7 update readme (#390) 2021-01-14 22:26:31 +08:00
ALMAS
c77b8489d7 Update periodicalexecutor.go (#389) 2021-01-14 22:20:09 +08:00
Kevin Wan
eca4ed2cc0 format code (#386) 2021-01-14 13:24:24 +08:00
Kevin Wan
744c18b7cb simplify cgroup controller separation (#384) 2021-01-13 20:58:33 +08:00
miaogaolin
8d6f6f933e fix cgroup bug (#380) 2021-01-13 20:39:57 +08:00
Kevin Wan
37c3b9f5c1 make sure unlock safe even if listeners panic (#383)
* make sure unlock safe even if listeners panic

* fix #378

* fix #378
2021-01-13 18:43:42 +08:00
卢永杰
1f1dcd16e6 fix server.start return nil points (#379)
Co-authored-by: luyongjie <luyongjie@37.com>
2021-01-13 18:40:39 +08:00
文杰
3285436f75 f-fix spell (#381)
Co-authored-by: chenwenjie <chenwenjie@zzstc.cn>
2021-01-13 18:07:31 +08:00
kingxt
7f49bd8a31 code optimized (#382) 2021-01-13 16:37:33 +08:00
kingxt
9cd2015661 fix inner type generate error (#377)
* fix point type bug

* optimized

* fix inner type error
2021-01-13 11:54:53 +08:00
kingxt
cf3a1020b0 Java optimized (#376)
* optiimzed java gen

* optiimzed java gen

* fix
2021-01-12 14:14:49 +08:00
kingxt
ee19fb736b feature: refactor api parse to g4 (#365)
* feature: refactor api parse to g4

* new g4 parser

* add CHANGE_LOG.MD

* refactor

* fix byte bug

* refactor

* optimized

* optimized

* revert

* update readme.md

* update readme.md

* update readme.md

* update readme.md

* remove no need

* fix java gen

* add upgrade

* resolve confilits

Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2021-01-11 15:10:51 +08:00
Kevin Wan
b0ccfb8eb4 add more tests for conf (#371) 2021-01-10 21:53:16 +08:00
Kevin Wan
444e5a711f update doc to use table to render plugins (#370) 2021-01-09 19:54:34 +08:00
Kevin Wan
8774d72ddb remove duplicated code in goctl (#369) 2021-01-09 00:17:23 +08:00
HarryWang29
e3fcdbf040 fix return in for (#367)
Co-authored-by: HarryWang29 <wrz890829@gmail.com>
2021-01-08 22:47:27 +08:00
Kevin Wan
2854ca03b4 update goctl version to 1.1.3 (#364) 2021-01-08 14:02:59 +08:00
anqiansong
6c624a6ed0 Feature model fix (#362)
* fix sql builderx adding raw string quotation marks incompatibility bug

* add unit test

* remove comments

* fix sql builderx adding raw string quotation marks incompatibility bug
2021-01-08 12:01:21 +08:00
Kevin Wan
57b73d8b49 make sure offset less than size even it's checked inside (#354) 2021-01-05 16:06:36 +08:00
Kevin Wan
a79cee12ee add godoc for RollingWindow (#351) 2021-01-04 22:43:55 +08:00
zjbztianya
7a921f66e6 simple rolling windows code (#346) 2021-01-04 22:11:18 +08:00
kingxt
12e235efb0 optimized goctl format (#336)
* fix format

* refactor

* refactor

* optimized

* refactor

* refactor

* refactor

* add js path prefix
2021-01-04 18:59:48 +08:00
Kevin Wan
01060cf16d close issue of #337 (#347) 2021-01-04 16:36:27 +08:00
Kevin Wan
0786862a35 align bucket boundary to interval in rolling window (#345) 2021-01-04 11:17:59 +08:00
Kevin Wan
efa43483b2 fix potential data race in PeriodicalExecutor (#344)
* fix potential data race in PeriodicalExecutor

* add comment
2021-01-03 20:56:17 +08:00
Kevin Wan
771371e051 simplify rolling window code, and make tests run faster (#343) 2021-01-03 20:47:29 +08:00
zjbztianya
2ee95f8981 fix rolling window bug (#340) 2021-01-03 20:27:47 +08:00
Kevin Wan
5bc01e4bfd set guarded to false only on quitting background flush (#342)
* set guarded to false only on quitting background flush

* set guarded to false only on quitting background flush, cont.
2021-01-03 19:54:11 +08:00
Kevin Wan
510e966982 simplify periodical executor background routine (#339) 2021-01-03 14:02:51 +08:00
Kevin Wan
10e3b8ac80 optimize code that fixes issue #317 (#338) 2021-01-02 19:01:37 +08:00
Kevin Wan
04059bbf5a add discord chat group in readme 2021-01-02 18:35:33 +08:00
weibobo
d643007c79 fix bug #317 (#335)
* fix bug #317.
* add counter for current task. If it's bigger then zero, do not quit background thread

* Revert "fix issue #317 (#331)"

This reverts commit fc43876cc5.
2021-01-02 18:04:04 +08:00
Kevin Wan
fc43876cc5 fix issue #317 (#331) 2021-01-01 13:24:28 +08:00
FengZhang
a926cb514f modify the goctl gensvc template (#323) 2020-12-30 10:05:26 +08:00
kingxt
25cab2f273 Java (#327)
* add g4 file

* new define api by g4

* reactor parser to g4gen

* add syntax parser & test

* add syntax parser & test

* add syntax parser & test

* update g4 file

* add import parse & test

* ractor AT lexer

* panic with error

* revert AT

* update g4 file

* update g4 file

* update g4 file

* optimize parser

* update g4 file

* parse info

* optimized java generator

* revert

* optimize java generator

* update java generator

* update java generator

* update java generator

* update java generator

Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2020-12-29 17:50:41 +08:00
Kevin Wan
8d2e2753a2 simplify http.Flusher implementation (#326)
* simplify code with http.Flusher type conversion

* simplify code with http.Flusher type conversion, better version
2020-12-29 15:02:36 +08:00
Kevin Wan
cc4c50e3eb fix broken link. 2020-12-29 11:54:32 +08:00
Kevin Wan
751072bdb0 fix broken doc link 2020-12-29 11:52:55 +08:00
Kevin Wan
e97e1f10db simplify code with http.Flusher type conversion (#325)
* simplify code with http.Flusher type conversion

* simplify code with http.Flusher type conversion, better version
2020-12-29 10:25:55 +08:00
jichangyun
0bd2a0656c The ResponseWriters defined in rest.handler add Flush interface. (#318) 2020-12-28 21:30:24 +08:00
Kevin Wan
71a2b20301 add more tests for prof (#322) 2020-12-27 14:45:14 +08:00
Kevin Wan
8df7de94e3 add more tests for zrpc (#321) 2020-12-27 14:08:24 +08:00
Kevin Wan
bf21203297 add more tests (#320) 2020-12-27 12:26:31 +08:00
Kevin Wan
ae98375194 add more tests (#319) 2020-12-26 20:30:02 +08:00
Kevin Wan
82d1ccf376 fixes #286 (#315) 2020-12-25 19:47:27 +08:00
Kevin Wan
bb6d49c17e add go report card back (#313)
* add go report card back

* avoid test failure, run tests sequentially
2020-12-25 12:09:59 +08:00
Kevin Wan
ed735ec47c Update codeql-analysis.yml
disable python code analysis, python code is in examples.
2020-12-25 12:09:43 +08:00
Kevin Wan
ba4bac3a03 format code (#312) 2020-12-25 11:53:37 +08:00
FengZhang
08433d7e04 add config load support env var (#309) 2020-12-25 11:42:19 +08:00
anqiansong
a3b525b50d feature model fix (#296)
* add raw stirng quote for sql field

* remove unused code
2020-12-21 09:43:32 +08:00
Kevin Wan
097f6886f2 Update readme.md 2020-12-15 23:47:41 +08:00
Kevin Wan
07a1549634 add wechat micro practice qrcode image (#289) 2020-12-14 17:49:58 +08:00
Kevin Wan
befca26c58 Update readme.md
add goproxy.cn download badge
2020-12-13 00:02:32 +08:00
Kevin Wan
3556a2eef4 Update readme-en.md
goreportcard is not working, submitted an issue to them.
2020-12-12 23:40:26 +08:00
Kevin Wan
807765f77e Update readme.md
goreportcard is not working, submitted a issue to them.
2020-12-12 23:39:28 +08:00
Kevin Wan
e44584e549 Create codeql-analysis.yml 2020-12-12 23:01:15 +08:00
Kevin Wan
acd48f0abb optimize dockerfile generation (#284) 2020-12-12 16:53:06 +08:00
kingxt
f919bc6713 refactor (#283) 2020-12-12 11:18:22 +08:00
Kevin Wan
a0030b8f45 format dockerfile on non-chinese mode (#282) 2020-12-12 10:13:33 +08:00
Kevin Wan
a5f0cce1b1 Update readme-en.md 2020-12-12 09:06:09 +08:00
Kevin Wan
4d13dda605 add EXPOSE in dockerfile generation (#281) 2020-12-12 08:18:01 +08:00
songmeizi
b56cc8e459 optimize test case of TestRpcGenerate (#279)
Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2020-12-11 21:57:04 +08:00
Kevin Wan
c435811479 fix gocyclo warnings (#278) 2020-12-11 20:57:48 +08:00
Kevin Wan
c686c93fb5 fix dockerfile generation bug (#277) 2020-12-11 20:31:31 +08:00
Kevin Wan
da8f76e6bd add category docker & kube (#276) 2020-12-11 18:53:40 +08:00
Kevin Wan
99596a4149 fix issue #266 (#275)
* optimize dockerfile

* fix issue #266
2020-12-11 16:12:33 +08:00
wayne
ec2a9f2c57 fix tracelogger_test TestTraceLog (#271) 2020-12-10 17:04:57 +08:00
Kevin Wan
fd73ced6dc optimize dockerfile (#272) 2020-12-10 16:21:06 +08:00
Kevin Wan
5071736ab4 fmt code (#270) 2020-12-10 15:16:13 +08:00
174 changed files with 16563 additions and 3246 deletions

67
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '18 19 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

2
.gitignore vendored
View File

@@ -4,6 +4,7 @@
# Unignore all with extensions
!*.*
!**/Dockerfile
!**/Makefile
# Unignore all dirs
!*/
@@ -12,7 +13,6 @@
.idea
**/.DS_Store
**/logs
!Makefile
# gitlab ci
.cache

View File

@@ -146,15 +146,15 @@ func EcbEncryptBase64(key, src string) (string, error) {
}
func getKeyBytes(key string) ([]byte, error) {
if len(key) > 32 {
if keyBytes, err := base64.StdEncoding.DecodeString(key); err != nil {
return nil, err
} else {
return keyBytes, nil
}
if len(key) <= 32 {
return []byte(key), nil
}
return []byte(key), nil
if keyBytes, err := base64.StdEncoding.DecodeString(key); err != nil {
return nil, err
} else {
return keyBytes, nil
}
}
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {

64
core/codec/aesecb_test.go Normal file
View File

@@ -0,0 +1,64 @@
package codec
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAesEcb(t *testing.T) {
var (
key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
val = []byte("hello")
badKey1 = []byte("aaaaaaaaa")
// more than 32 chars
badKey2 = []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
)
_, err := EcbEncrypt(badKey1, val)
assert.NotNil(t, err)
_, err = EcbEncrypt(badKey2, val)
assert.NotNil(t, err)
dst, err := EcbEncrypt(key, val)
assert.Nil(t, err)
_, err = EcbDecrypt(badKey1, dst)
assert.NotNil(t, err)
_, err = EcbDecrypt(badKey2, dst)
assert.NotNil(t, err)
_, err = EcbDecrypt(key, val)
// not enough block, just nil
assert.Nil(t, err)
src, err := EcbDecrypt(key, dst)
assert.Nil(t, err)
assert.Equal(t, val, src)
}
func TestAesEcbBase64(t *testing.T) {
const (
val = "hello"
badKey1 = "aaaaaaaaa"
// more than 32 chars
badKey2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
)
var key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
b64Key := base64.StdEncoding.EncodeToString(key)
b64Val := base64.StdEncoding.EncodeToString([]byte(val))
_, err := EcbEncryptBase64(badKey1, val)
assert.NotNil(t, err)
_, err = EcbEncryptBase64(badKey2, val)
assert.NotNil(t, err)
_, err = EcbEncryptBase64(b64Key, val)
assert.NotNil(t, err)
dst, err := EcbEncryptBase64(b64Key, b64Val)
assert.Nil(t, err)
_, err = EcbDecryptBase64(badKey1, dst)
assert.NotNil(t, err)
_, err = EcbDecryptBase64(badKey2, dst)
assert.NotNil(t, err)
_, err = EcbDecryptBase64(b64Key, val)
assert.NotNil(t, err)
src, err := EcbDecryptBase64(b64Key, dst)
b, err := base64.StdEncoding.DecodeString(src)
assert.Nil(t, err)
assert.Equal(t, val, string(b))
}

View File

@@ -8,8 +8,10 @@ import (
)
type (
// RollingWindowOption let callers customize the RollingWindow.
RollingWindowOption func(rollingWindow *RollingWindow)
// RollingWindow defines a rolling window to calculate the events in buckets with time interval.
RollingWindow struct {
lock sync.RWMutex
size int
@@ -17,10 +19,12 @@ type (
interval time.Duration
offset int
ignoreCurrent bool
lastTime time.Duration
lastTime time.Duration // start time of the last bucket
}
)
// NewRollingWindow returns a RollingWindow that with size buckets and time interval,
// use opts to customize the RollingWindow.
func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOption) *RollingWindow {
if size < 1 {
panic("size must be greater than 0")
@@ -38,6 +42,7 @@ func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOpt
return w
}
// Add adds value to current bucket.
func (rw *RollingWindow) Add(v float64) {
rw.lock.Lock()
defer rw.lock.Unlock()
@@ -45,6 +50,7 @@ func (rw *RollingWindow) Add(v float64) {
rw.win.add(rw.offset, v)
}
// Reduce runs fn on all buckets, ignore current bucket if ignoreCurrent was set.
func (rw *RollingWindow) Reduce(fn func(b *Bucket)) {
rw.lock.RLock()
defer rw.lock.RUnlock()
@@ -79,26 +85,18 @@ func (rw *RollingWindow) updateOffset() {
}
offset := rw.offset
start := offset + 1
steps := start + span
var remainder int
if steps > rw.size {
remainder = steps - rw.size
steps = rw.size
}
// reset expired buckets
for i := start; i < steps; i++ {
rw.win.resetBucket(i)
}
for i := 0; i < remainder; i++ {
rw.win.resetBucket(i)
for i := 0; i < span; i++ {
rw.win.resetBucket((offset + i + 1) % rw.size)
}
rw.offset = (offset + span) % rw.size
rw.lastTime = timex.Now()
now := timex.Now()
// align to interval time boundary
rw.lastTime = now - (now-rw.lastTime)%rw.interval
}
// Bucket defines the bucket that holds sum and num of additions.
type Bucket struct {
Sum float64
Count int64
@@ -144,6 +142,7 @@ func (w *window) resetBucket(offset int) {
w.buckets[offset%w.size].reset()
}
// IgnoreCurrentBucket lets the Reduce call ignore current bucket.
func IgnoreCurrentBucket() RollingWindowOption {
return func(w *RollingWindow) {
w.ignoreCurrent = true

View File

@@ -105,6 +105,37 @@ func TestRollingWindowReduce(t *testing.T) {
}
}
func TestRollingWindowBucketTimeBoundary(t *testing.T) {
const size = 3
interval := time.Millisecond * 30
r := NewRollingWindow(size, interval)
listBuckets := func() []float64 {
var buckets []float64
r.Reduce(func(b *Bucket) {
buckets = append(buckets, b.Sum)
})
return buckets
}
assert.Equal(t, []float64{0, 0, 0}, listBuckets())
r.Add(1)
assert.Equal(t, []float64{0, 0, 1}, listBuckets())
time.Sleep(time.Millisecond * 45)
r.Add(2)
r.Add(3)
assert.Equal(t, []float64{0, 1, 5}, listBuckets())
// sleep time should be less than interval, and make the bucket change happen
time.Sleep(time.Millisecond * 20)
r.Add(4)
r.Add(5)
r.Add(6)
assert.Equal(t, []float64{1, 5, 15}, listBuckets())
time.Sleep(time.Millisecond * 100)
r.Add(7)
r.Add(8)
r.Add(9)
assert.Equal(t, []float64{0, 0, 24}, listBuckets())
}
func TestRollingWindowDataRace(t *testing.T) {
const size = 3
r := NewRollingWindow(size, duration)

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"github.com/tal-tech/go-zero/core/mapping"
@@ -19,7 +20,7 @@ func LoadConfig(file string, v interface{}) error {
if content, err := ioutil.ReadFile(file); err != nil {
return err
} else if loader, ok := loaders[path.Ext(file)]; ok {
return loader(content, v)
return loader([]byte(os.ExpandEnv(string(content))), v)
} else {
return fmt.Errorf("unrecoginized file type: %s", file)
}

View File

@@ -6,9 +6,21 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs"
"github.com/tal-tech/go-zero/core/hash"
)
func TestLoadConfig_notExists(t *testing.T) {
assert.NotNil(t, LoadConfig("not_a_file", nil))
}
func TestLoadConfig_notRecogFile(t *testing.T) {
filename, err := fs.TempFilenameWithText("hello")
assert.Nil(t, err)
defer os.Remove(filename)
assert.NotNil(t, LoadConfig(filename, nil))
}
func TestConfigJson(t *testing.T) {
tests := []string{
".json",
@@ -17,13 +29,14 @@ func TestConfigJson(t *testing.T) {
}
text := `{
"a": "foo",
"b": 1
"b": 1,
"c": "${FOO}"
}`
for _, test := range tests {
test := test
t.Run(test, func(t *testing.T) {
t.Parallel()
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(test, text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
@@ -31,10 +44,12 @@ func TestConfigJson(t *testing.T) {
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
}
MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "2", val.C)
})
}
}

View File

@@ -24,6 +24,20 @@ func TestProperties(t *testing.T) {
assert.Equal(t, "test", props.GetString("app.name"))
assert.Equal(t, "app", props.GetString("app.program"))
assert.Equal(t, 5, props.GetInt("app.threads"))
val := props.ToString()
assert.Contains(t, val, "app.name")
assert.Contains(t, val, "app.program")
assert.Contains(t, val, "app.threads")
}
func TestLoadProperties_badContent(t *testing.T) {
filename, err := fs.TempFilenameWithText("hello")
assert.Nil(t, err)
defer os.Remove(filename)
_, err = LoadProperties(filename)
assert.NotNil(t, err)
assert.True(t, len(err.Error()) > 0)
}
func TestSetString(t *testing.T) {

View File

@@ -18,7 +18,8 @@ type (
disconnected bool
currentState connectivity.State
listeners []func()
lock sync.Mutex
// lock only guards listeners, because only listens can be accessed by other goroutines.
lock sync.Mutex
}
)
@@ -32,27 +33,33 @@ func (sw *stateWatcher) addListener(l func()) {
sw.lock.Unlock()
}
func (sw *stateWatcher) notifyListeners() {
sw.lock.Lock()
defer sw.lock.Unlock()
for _, l := range sw.listeners {
l()
}
}
func (sw *stateWatcher) updateState(conn etcdConn) {
sw.currentState = conn.GetState()
switch sw.currentState {
case connectivity.TransientFailure, connectivity.Shutdown:
sw.disconnected = true
case connectivity.Ready:
if sw.disconnected {
sw.disconnected = false
sw.notifyListeners()
}
}
}
func (sw *stateWatcher) watch(conn etcdConn) {
sw.currentState = conn.GetState()
for {
if conn.WaitForStateChange(context.Background(), sw.currentState) {
newState := conn.GetState()
sw.lock.Lock()
sw.currentState = newState
switch newState {
case connectivity.TransientFailure, connectivity.Shutdown:
sw.disconnected = true
case connectivity.Ready:
if sw.disconnected {
sw.disconnected = false
for _, l := range sw.listeners {
l()
}
}
}
sw.lock.Unlock()
sw.updateState(conn)
}
}
}

View File

@@ -3,6 +3,7 @@ package executors
import (
"reflect"
"sync"
"sync/atomic"
"time"
"github.com/tal-tech/go-zero/core/lang"
@@ -35,6 +36,7 @@ type (
// avoid race condition on waitGroup when calling wg.Add/Done/Wait(...)
wgBarrier syncx.Barrier
confirmChan chan lang.PlaceholderType
inflight int32
guarded bool
newTicker func(duration time.Duration) timex.Ticker
lock sync.Mutex
@@ -49,7 +51,7 @@ func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *Per
container: container,
confirmChan: make(chan lang.PlaceholderType),
newTicker: func(d time.Duration) timex.Ticker {
return timex.NewTicker(interval)
return timex.NewTicker(d)
},
}
proc.AddShutdownListener(func() {
@@ -91,18 +93,16 @@ func (pe *PeriodicalExecutor) Wait() {
func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool) {
pe.lock.Lock()
defer func() {
var start bool
if !pe.guarded {
pe.guarded = true
start = true
// defer to unlock quickly
defer pe.backgroundFlush()
}
pe.lock.Unlock()
if start {
pe.backgroundFlush()
}
}()
if pe.container.AddTask(task) {
atomic.AddInt32(&pe.inflight, 1)
return pe.container.RemoveAll(), true
}
@@ -111,6 +111,9 @@ func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool)
func (pe *PeriodicalExecutor) backgroundFlush() {
threading.GoSafe(func() {
// flush before quit goroutine to avoid missing tasks
defer pe.Flush()
ticker := pe.newTicker(pe.interval)
defer ticker.Stop()
@@ -120,6 +123,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
select {
case vals := <-pe.commander:
commanded = true
atomic.AddInt32(&pe.inflight, -1)
pe.enterExecution()
pe.confirmChan <- lang.Placeholder
pe.executeTasks(vals)
@@ -129,13 +133,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
commanded = false
} else if pe.Flush() {
last = timex.Now()
} else if timex.Since(last) > pe.interval*idleRound {
pe.lock.Lock()
pe.guarded = false
pe.lock.Unlock()
// flush again to avoid missing tasks
pe.Flush()
} else if pe.shallQuit(last) {
return
}
}
@@ -178,3 +176,19 @@ func (pe *PeriodicalExecutor) hasTasks(tasks interface{}) bool {
return true
}
}
func (pe *PeriodicalExecutor) shallQuit(last time.Duration) (stop bool) {
if timex.Since(last) <= pe.interval*idleRound {
return
}
// checking pe.inflight and setting pe.guarded should be locked together
pe.lock.Lock()
if atomic.LoadInt32(&pe.inflight) == 0 {
pe.guarded = false
stop = true
}
pe.lock.Unlock()
return
}

View File

@@ -140,6 +140,26 @@ func TestPeriodicalExecutor_WaitFast(t *testing.T) {
assert.Equal(t, total, cnt)
}
func TestPeriodicalExecutor_Deadlock(t *testing.T) {
executor := NewBulkExecutor(func(tasks []interface{}) {
}, WithBulkTasks(1), WithBulkInterval(time.Millisecond))
for i := 0; i < 1e5; i++ {
executor.Add(1)
}
}
func TestPeriodicalExecutor_hasTasks(t *testing.T) {
ticker := timex.NewFakeTicker()
defer ticker.Stop()
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, nil))
exec.newTicker = func(d time.Duration) timex.Ticker {
return ticker
}
assert.False(t, exec.hasTasks(nil))
assert.True(t, exec.hasTasks(1))
}
// go test -benchtime 10s -bench .
func BenchmarkExecutor(b *testing.B) {
b.ReportAllocs()

View File

@@ -21,6 +21,7 @@ var mock tracespec.Trace = new(mockTrace)
func TestTraceLog(t *testing.T) {
var buf mockWriter
atomic.StoreUint32(&initialized, 1)
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))

View File

@@ -153,58 +153,57 @@ func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fie
key := strings.TrimSpace(segments[0])
options := segments[1:]
if len(options) > 0 {
var fieldOpts fieldOptions
for _, segment := range options {
option := strings.TrimSpace(segment)
switch {
case option == stringOption:
fieldOpts.FromString = true
case strings.HasPrefix(option, optionalOption):
segs := strings.Split(option, equalToken)
switch len(segs) {
case 1:
fieldOpts.Optional = true
case 2:
fieldOpts.Optional = true
fieldOpts.OptionalDep = segs[1]
default:
return "", nil, fmt.Errorf("field %s has wrong optional", field.Name)
}
case option == optionalOption:
fieldOpts.Optional = true
case strings.HasPrefix(option, optionsOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong options", field.Name)
} else {
fieldOpts.Options = strings.Split(segs[1], optionSeparator)
}
case strings.HasPrefix(option, defaultOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong default option", field.Name)
} else {
fieldOpts.Default = strings.TrimSpace(segs[1])
}
case strings.HasPrefix(option, rangeOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong range", field.Name)
}
if nr, err := parseNumberRange(segs[1]); err != nil {
return "", nil, err
} else {
fieldOpts.Range = nr
}
}
}
return key, &fieldOpts, nil
if len(options) == 0 {
return key, nil, nil
}
return key, nil, nil
var fieldOpts fieldOptions
for _, segment := range options {
option := strings.TrimSpace(segment)
switch {
case option == stringOption:
fieldOpts.FromString = true
case strings.HasPrefix(option, optionalOption):
segs := strings.Split(option, equalToken)
switch len(segs) {
case 1:
fieldOpts.Optional = true
case 2:
fieldOpts.Optional = true
fieldOpts.OptionalDep = segs[1]
default:
return "", nil, fmt.Errorf("field %s has wrong optional", field.Name)
}
case option == optionalOption:
fieldOpts.Optional = true
case strings.HasPrefix(option, optionsOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong options", field.Name)
} else {
fieldOpts.Options = strings.Split(segs[1], optionSeparator)
}
case strings.HasPrefix(option, defaultOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong default option", field.Name)
} else {
fieldOpts.Default = strings.TrimSpace(segs[1])
}
case strings.HasPrefix(option, rangeOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong range", field.Name)
}
if nr, err := parseNumberRange(segs[1]); err != nil {
return "", nil, err
} else {
fieldOpts.Range = nr
}
}
}
return key, &fieldOpts, nil
}
func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {

View File

@@ -4,12 +4,14 @@ package proc
import "time"
// AddShutdownListener returns fn itself on windows, lets callers call fn on their own.
func AddShutdownListener(fn func()) func() {
return nil
return fn
}
// AddWrapUpListener returns fn itself on windows, lets callers call fn on their own.
func AddWrapUpListener(fn func()) func() {
return nil
return fn
}
func SetTimeoutToForceQuit(duration time.Duration) {

View File

@@ -0,0 +1,16 @@
package prof
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestReport(t *testing.T) {
once.Do(func() {})
assert.NotContains(t, generateReport(), "foo")
report("foo", time.Second)
assert.Contains(t, generateReport(), "foo")
report("foo", time.Second)
}

View File

@@ -0,0 +1,23 @@
package prof
import (
"testing"
"github.com/tal-tech/go-zero/core/utils"
)
func TestProfiler(t *testing.T) {
EnableProfiling()
Start()
Report("foo", ProfilePoint{
ElapsedTimer: utils.NewElapsedTimer(),
})
}
func TestNullProfiler(t *testing.T) {
p := newNullProfiler()
p.Start()
p.Report("foo", ProfilePoint{
ElapsedTimer: utils.NewElapsedTimer(),
})
}

View File

@@ -92,11 +92,11 @@ func currentCgroup() (*cgroup, error) {
continue
}
cgroups[subsys] = path.Join(cgroupDir, subsys)
if strings.Contains(subsys, ",") {
for _, k := range strings.Split(subsys, ",") {
cgroups[k] = path.Join(cgroupDir, k)
}
// https://man7.org/linux/man-pages/man7/cgroups.7.html
// comma-separated list of controllers for cgroup version 1
fields := strings.Split(subsys, ",")
for _, val := range fields {
cgroups[val] = path.Join(cgroupDir, val)
}
}

View File

@@ -70,8 +70,6 @@ func (g *sharedGroup) createCall(key string) (c *call, done bool) {
func (g *sharedGroup) makeCall(c *call, key string, fn func() (interface{}, error)) {
defer func() {
// delete key first, done later. can't reverse the order, because if reverse,
// another Do call might wg.Wait() without get notified with wg.Done()
g.lock.Lock()
delete(g.calls, key)
g.lock.Unlock()

1
go.mod
View File

@@ -6,6 +6,7 @@ require (
github.com/ClickHouse/clickhouse-go v1.4.3
github.com/DATA-DOG/go-sqlmock v1.4.1
github.com/alicebob/miniredis/v2 v2.14.1
github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dchest/siphash v1.2.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible

2
go.sum
View File

@@ -16,6 +16,8 @@ github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGn
github.com/alicebob/miniredis/v2 v2.14.1 h1:GjlbSeoJ24bzdLRs13HoMEeaRZx9kg5nHoRW7QV/nCs=
github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2 h1:rL2miklL5rhxUaZO7hntBcy/VHaiyuPQ4EJoy/NMwaM=
github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

View File

@@ -129,7 +129,7 @@ go get -u github.com/tal-tech/go-zero
the .api files also can be generate by goctl, like below:
```shell
goctl api -o greet.api
goctl api -o greet.api
```
3. generate the go server side code
@@ -208,3 +208,7 @@ goctl api -o greet.api
* [Rapid development of microservice systems](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl-en.md)
* [Rapid development of microservice systems - multiple RPCs](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore-en.md)
## 9. Chat group
Join the chat via https://discord.gg/4JQvC5A4Fe

View File

@@ -5,8 +5,9 @@
[English](readme-en.md) | 简体中文
[![Go](https://github.com/tal-tech/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/tal-tech/go-zero/actions)
[![codecov](https://codecov.io/gh/tal-tech/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/tal-tech/go-zero)
[![Go Report Card](https://goreportcard.com/badge/github.com/tal-tech/go-zero)](https://goreportcard.com/report/github.com/tal-tech/go-zero)
[![goproxy](https://goproxy.cn/stats/github.com/tal-tech/go-zero/badges/download-count.svg)](https://goproxy.cn/stats/github.com/tal-tech/go-zero/badges/download-count.svg)
[![codecov](https://codecov.io/gh/tal-tech/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/tal-tech/go-zero)
[![Release](https://img.shields.io/github/v/release/tal-tech/go-zero.svg?style=flat-square)](https://github.com/tal-tech/go-zero)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -95,7 +96,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
[快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
[快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/docs/frame/bookstore.md)
[快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/docs/zero/bookstore.md)
1. 安装 goctl 工具
@@ -156,23 +157,30 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
## 7. 文档
* API 文档 (逐步完善中)
* API 文档
[https://www.yuque.com/tal-tech/go-zero](https://www.yuque.com/tal-tech/go-zero)
* awesome 系列
* awesome 系列(更多文章见『微服务实践』公众号)
* [快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
* [快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/docs/frame/bookstore.md)
* [快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/docs/zero/bookstore.md)
* [goctl 使用帮助](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
* [通过 MapReduce 降低服务响应时间](https://github.com/tal-tech/zero-doc/blob/main/doc/mapreduce.md)
* [关键字替换和敏感词过滤工具](https://github.com/tal-tech/zero-doc/blob/main/doc/keywords.md)
* [进程内缓存使用方法](https://github.com/tal-tech/zero-doc/blob/main/doc/collection.md)
* [防止缓存击穿之进程内共享调用](https://github.com/tal-tech/zero-doc/blob/main/doc/sharedcalls.md)
* [基于 prometheus 的微服务指标监控](https://github.com/tal-tech/zero-doc/blob/main/doc/metric.md)
* [文本序列化和反序列化](https://github.com/tal-tech/zero-doc/blob/main/doc/mapping.md)
* [快速构建 jwt 鉴权认证](https://github.com/tal-tech/zero-doc/blob/main/doc/jwt.md)
* 精选 `goctl` 插件
## 8. 微信交流群
| 插件 | 用途 |
| ------------- |:-------------|
| [goctl-swagger](https://github.com/zeromicro/goctl-swagger) | 一键生成 `api` 的 `swagger` 文档 |
| [goctl-android](https://github.com/zeromicro/goctl-android) | 生成 `java (android)` 端 `http client` 请求代码 |
| [goctl-go-compact](https://github.com/zeromicro/goctl-go-compact) | 合并 `api` 里同一个 `group` 里的 `handler` 到一个 `go` 文件 |
## 8. 微信公众号
`go-zero` 相关文章都会在 `微服务实践` 公众号整理呈现,欢迎扫码关注,也可以通过公众号私信我 👏
<img src="https://gitee.com/kevwan/static/raw/master/images/wechat-micro.jpg" alt="wechat" width="300" />
## 9. 微信交流群
如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。

171
rest/engine_test.go Normal file
View File

@@ -0,0 +1,171 @@
package rest
import (
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/conf"
)
func TestNewEngine(t *testing.T) {
yamls := []string{
`Name: foo
Port: 54321
`,
`Name: foo
Port: 54321
CpuThreshold: 500
`,
`Name: foo
Port: 54321
CpuThreshold: 500
Verbose: true
`,
}
routes := []featuredRoutes{
{
jwt: jwtSetting{},
signature: signatureSetting{},
routes: []Route{{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {},
}},
},
{
priority: true,
jwt: jwtSetting{},
signature: signatureSetting{},
routes: []Route{{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {},
}},
},
{
priority: true,
jwt: jwtSetting{
enabled: true,
},
signature: signatureSetting{},
routes: []Route{{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {},
}},
},
{
priority: true,
jwt: jwtSetting{
enabled: true,
prevSecret: "thesecret",
},
signature: signatureSetting{},
routes: []Route{{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {},
}},
},
{
priority: true,
jwt: jwtSetting{
enabled: true,
},
signature: signatureSetting{},
routes: []Route{{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {},
}},
},
{
priority: true,
jwt: jwtSetting{
enabled: true,
},
signature: signatureSetting{
enabled: true,
},
routes: []Route{{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {},
}},
},
{
priority: true,
jwt: jwtSetting{
enabled: true,
},
signature: signatureSetting{
enabled: true,
SignatureConf: SignatureConf{
Strict: true,
},
},
routes: []Route{{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {},
}},
},
{
priority: true,
jwt: jwtSetting{
enabled: true,
},
signature: signatureSetting{
enabled: true,
SignatureConf: SignatureConf{
Strict: true,
PrivateKeys: []PrivateKeyConf{
{
Fingerprint: "a",
KeyFile: "b",
},
},
},
},
routes: []Route{{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {},
}},
},
}
for _, yaml := range yamls {
for _, route := range routes {
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(yaml), &cnf))
ng := newEngine(cnf)
ng.AddRoutes(route)
ng.use(func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
}
})
assert.NotNil(t, ng.StartWithRouter(mockedRouter{}))
}
}
}
type mockedRouter struct {
}
func (m mockedRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
}
func (m mockedRouter) Handle(method string, path string, handler http.Handler) error {
return errors.New("foo")
}
func (m mockedRouter) SetNotFoundHandler(handler http.Handler) {
}
func (m mockedRouter) SetNotAllowedHandler(handler http.Handler) {
}

View File

@@ -46,18 +46,18 @@ func Authorize(secret string, opts ...AuthorizeOption) func(http.Handler) http.H
parser := token.NewTokenParser()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, err := parser.ParseToken(r, secret, authOpts.PrevSecret)
tok, err := parser.ParseToken(r, secret, authOpts.PrevSecret)
if err != nil {
unauthorized(w, r, err, authOpts.Callback)
return
}
if !token.Valid {
if !tok.Valid {
unauthorized(w, r, errInvalidToken, authOpts.Callback)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
claims, ok := tok.Claims.(jwt.MapClaims)
if !ok {
unauthorized(w, r, errNoClaims, authOpts.Callback)
return
@@ -122,6 +122,12 @@ func newGuardedResponseWriter(w http.ResponseWriter) *guardedResponseWriter {
}
}
func (grw *guardedResponseWriter) Flush() {
if flusher, ok := grw.writer.(http.Flusher); ok {
flusher.Flush()
}
}
func (grw *guardedResponseWriter) Header() http.Header {
return grw.writer.Header()
}

View File

@@ -41,6 +41,10 @@ func TestAuthHandler(t *testing.T) {
w.Header().Set("X-Test", "test")
_, err := w.Write([]byte("content"))
assert.Nil(t, err)
flusher, ok := w.(http.Flusher)
assert.True(t, ok)
flusher.Flush()
}))
resp := httptest.NewRecorder()

View File

@@ -83,6 +83,12 @@ func newCryptionResponseWriter(w http.ResponseWriter) *cryptionResponseWriter {
}
}
func (w *cryptionResponseWriter) Flush() {
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
}
func (w *cryptionResponseWriter) Header() http.Header {
return w.ResponseWriter.Header()
}

View File

@@ -87,3 +87,19 @@ func TestCryptionHandlerWriteHeader(t *testing.T) {
handler.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusServiceUnavailable, recorder.Code)
}
func TestCryptionHandlerFlush(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/any", nil)
handler := CryptionHandler(aesKey)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(respText))
flusher, ok := w.(http.Flusher)
assert.True(t, ok)
flusher.Flush()
}))
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
expect, err := codec.EcbEncrypt(aesKey, []byte(respText))
assert.Nil(t, err)
assert.Equal(t, base64.StdEncoding.EncodeToString(expect), recorder.Body.String())
}

View File

@@ -38,6 +38,12 @@ func (w *LoggedResponseWriter) WriteHeader(code int) {
w.code = code
}
func (w *LoggedResponseWriter) Flush() {
if flusher, ok := w.w.(http.Flusher); ok {
flusher.Flush()
}
}
func LogHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
timer := utils.NewElapsedTimer()
@@ -68,6 +74,10 @@ func newDetailLoggedResponseWriter(writer *LoggedResponseWriter, buf *bytes.Buff
}
}
func (w *DetailLoggedResponseWriter) Flush() {
w.writer.Flush()
}
func (w *DetailLoggedResponseWriter) Header() http.Header {
return w.writer.Header()
}

View File

@@ -30,6 +30,10 @@ func TestLogHandler(t *testing.T) {
w.WriteHeader(http.StatusServiceUnavailable)
_, err := w.Write([]byte("content"))
assert.Nil(t, err)
flusher, ok := w.(http.Flusher)
assert.True(t, ok)
flusher.Flush()
}))
resp := httptest.NewRecorder()

View File

@@ -7,6 +7,12 @@ type WithCodeResponseWriter struct {
Code int
}
func (w *WithCodeResponseWriter) Flush() {
if flusher, ok := w.Writer.(http.Flusher); ok {
flusher.Flush()
}
}
func (w *WithCodeResponseWriter) Header() http.Header {
return w.Writer.Header()
}

View File

@@ -0,0 +1,33 @@
package security
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithCodeResponseWriter(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cw := &WithCodeResponseWriter{Writer: w}
cw.Header().Set("X-Test", "test")
cw.WriteHeader(http.StatusServiceUnavailable)
assert.Equal(t, cw.Code, http.StatusServiceUnavailable)
_, err := cw.Write([]byte("content"))
assert.Nil(t, err)
flusher, ok := http.ResponseWriter(cw).(http.Flusher)
assert.True(t, ok)
flusher.Flush()
})
resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req)
assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
assert.Equal(t, "test", resp.Header().Get("X-Test"))
assert.Equal(t, "content", resp.Body.String())
}

View File

@@ -64,7 +64,7 @@ func (pr *patRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
allow, ok := pr.methodNotAllowed(r.Method, reqPath)
allows, ok := pr.methodsAllowed(r.Method, reqPath)
if !ok {
pr.handleNotFound(w, r)
return
@@ -73,7 +73,7 @@ func (pr *patRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if pr.notAllowed != nil {
pr.notAllowed.ServeHTTP(w, r)
} else {
w.Header().Set(allowHeader, allow)
w.Header().Set(allowHeader, allows)
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
@@ -94,7 +94,7 @@ func (pr *patRouter) handleNotFound(w http.ResponseWriter, r *http.Request) {
}
}
func (pr *patRouter) methodNotAllowed(method, path string) (string, bool) {
func (pr *patRouter) methodsAllowed(method, path string) (string, bool) {
var allows []string
for treeMethod, tree := range pr.trees {

View File

@@ -1,7 +1,6 @@
package rest
import (
"errors"
"log"
"net/http"
@@ -24,6 +23,9 @@ type (
}
)
// MustNewServer returns a server with given config of c and options defined in opts.
// Be aware that later RunOption might overwrite previous one that write the same option.
// The process will exit if error occurs.
func MustNewServer(c RestConf, opts ...RunOption) *Server {
engine, err := NewServer(c, opts...)
if err != nil {
@@ -33,11 +35,9 @@ func MustNewServer(c RestConf, opts ...RunOption) *Server {
return engine
}
// NewServer returns a server with given config of c and options defined in opts.
// Be aware that later RunOption might overwrite previous one that write the same option.
func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
if len(opts) > 1 {
return nil, errors.New("only one RunOption is allowed")
}
if err := c.SetUp(); err != nil {
return nil, err
}

View File

@@ -8,18 +8,84 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rest/httpx"
"github.com/tal-tech/go-zero/rest/router"
)
func TestNewServer(t *testing.T) {
_, err := NewServer(RestConf{}, WithNotFoundHandler(nil), WithNotAllowedHandler(nil))
assert.NotNil(t, err)
const configYaml = `
Name: foo
Port: 54321
`
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
failStart := func(server *Server) {
server.opts.start = func(e *engine) error {
return http.ErrServerClosed
}
}
tests := []struct {
c RestConf
opts []RunOption
fail bool
}{
{
c: RestConf{},
opts: []RunOption{failStart},
fail: true,
},
{
c: cnf,
opts: []RunOption{failStart},
},
{
c: cnf,
opts: []RunOption{WithNotAllowedHandler(nil), failStart},
},
{
c: cnf,
opts: []RunOption{WithNotFoundHandler(nil), failStart},
},
{
c: cnf,
opts: []RunOption{WithUnauthorizedCallback(nil), failStart},
},
{
c: cnf,
opts: []RunOption{WithUnsignedCallback(nil), failStart},
},
}
for _, test := range tests {
srv, err := NewServer(test.c, test.opts...)
if test.fail {
assert.NotNil(t, err)
}
if err != nil {
continue
}
srv.Use(ToMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}))
srv.AddRoute(Route{
Method: http.MethodGet,
Path: "/",
Handler: nil,
}, WithJwt("thesecret"), WithSignature(SignatureConf{}),
WithJwtTransition("preivous", "thenewone"))
srv.Start()
srv.Stop()
}
}
func TestWithMiddleware(t *testing.T) {
m := make(map[string]string)
router := router.NewRouter()
rt := router.NewRouter()
handler := func(w http.ResponseWriter, r *http.Request) {
var v struct {
Nickname string `form:"nickname"`
@@ -56,14 +122,14 @@ func TestWithMiddleware(t *testing.T) {
"http://hello.com/second/wan/2020?nickname=whatever&zipcode=200000",
}
for _, route := range rs {
assert.Nil(t, router.Handle(route.Method, route.Path, route.Handler))
assert.Nil(t, rt.Handle(route.Method, route.Path, route.Handler))
}
for _, url := range urls {
r, err := http.NewRequest(http.MethodGet, url, nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, r)
rt.ServeHTTP(rr, r)
assert.Equal(t, "whatever:200000", rr.Body.String())
}
@@ -76,7 +142,7 @@ func TestWithMiddleware(t *testing.T) {
func TestMultiMiddlewares(t *testing.T) {
m := make(map[string]string)
router := router.NewRouter()
rt := router.NewRouter()
handler := func(w http.ResponseWriter, r *http.Request) {
var v struct {
Nickname string `form:"nickname"`
@@ -127,14 +193,14 @@ func TestMultiMiddlewares(t *testing.T) {
"http://hello.com/second/wan/2020?nickname=whatever&zipcode=200000",
}
for _, route := range rs {
assert.Nil(t, router.Handle(route.Method, route.Path, route.Handler))
assert.Nil(t, rt.Handle(route.Method, route.Path, route.Handler))
}
for _, url := range urls {
r, err := http.NewRequest(http.MethodGet, url, nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, r)
rt.ServeHTTP(rr, r)
assert.Equal(t, "whatever:200000200000", rr.Body.String())
}

15
tools/goctl/CHANGE_LOG.MD Normal file
View File

@@ -0,0 +1,15 @@
# 2020-01-08
## features ![](https://img.shields.io/static/v1?label=&message=new&color=red)
* reactor api parse by g4
* add syntax lexer for api
* support java-style documentation comments
* support parsing doc and comment
* support import group
> original: [api grammar document](./api/parser/readme.md)
# 2020-01-08
* add change log

View File

@@ -12,18 +12,21 @@ import (
"github.com/urfave/cli"
)
const apiTemplate = `info(
const apiTemplate = `
syntax = "v1"
info(
title: // TODO: add title
desc: // TODO: add description
author: "{{.gitUser}}"
email: "{{.gitEmail}}"
)
type request struct {
type request {
// TODO: add members here and delete this comment
}
type response struct {
type response {
// TODO: add members here and delete this comment
}

View File

@@ -19,11 +19,7 @@ func DartCommand(c *cli.Context) error {
return errors.New("missing -dir")
}
p, err := parser.NewParser(apiFile)
if err != nil {
return err
}
api, err := p.Parse()
api, err := parser.Parse(apiFile)
if err != nil {
return err
}

View File

@@ -59,7 +59,6 @@ func genData(dir string, api *spec.ApiSpec) error {
return e
}
convertMemberType(api)
return t.Execute(file, api)
}

View File

@@ -1,12 +1,10 @@
package dartgen
import (
"log"
"os"
"reflect"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
)
@@ -39,47 +37,6 @@ func tagGet(tag, k string) (reflect.Value, error) {
return reflect.ValueOf(out), nil
}
func convertMemberType(api *spec.ApiSpec) {
for i, t := range api.Types {
for j, mem := range t.Members {
api.Types[i].Members[j].Type = goTypeToDart(mem.Type)
}
}
}
func goTypeToDart(t string) string {
t = strings.Replace(t, "*", "", -1)
if strings.HasPrefix(t, "[]") {
return "List<" + goTypeToDart(t[2:]) + ">"
}
if strings.HasPrefix(t, "map") {
tys, e := util.DecomposeType(t)
if e != nil {
log.Fatal(e)
}
if len(tys) != 2 {
log.Fatal("Map type number !=2")
}
return "Map<String," + goTypeToDart(tys[1]) + ">"
}
switch t {
case "string":
return "String"
case "int", "int32", "int64":
return "int"
case "float", "float32", "float64":
return "double"
case "bool":
return "bool"
default:
return t
}
}
func isDirectType(s string) bool {
return isAtomicType(s) || isListType(s) && isAtomicType(getCoreType(s))
}

View File

@@ -41,7 +41,7 @@ func genDoc(api *spec.ApiSpec, dir string, filename string) error {
var builder strings.Builder
for index, route := range api.Service.Routes() {
routeComment, _ := util.GetAnnotationValue(route.Annotations, "doc", "summary")
routeComment := route.JoinedDoc()
if len(routeComment) == 0 {
routeComment = "N/A"
}
@@ -58,8 +58,8 @@ func genDoc(api *spec.ApiSpec, dir string, filename string) error {
"routeComment": routeComment,
"method": strings.ToUpper(route.Method),
"uri": route.Path,
"requestType": "`" + stringx.TakeOne(route.RequestType.Name, "-") + "`",
"responseType": "`" + stringx.TakeOne(route.ResponseType.Name, "-") + "`",
"requestType": "`" + stringx.TakeOne(route.RequestTypeName(), "-") + "`",
"responseType": "`" + stringx.TakeOne(route.ResponseTypeName(), "-") + "`",
"responseContent": responseContent,
})
if err != nil {
@@ -73,10 +73,28 @@ func genDoc(api *spec.ApiSpec, dir string, filename string) error {
}
func responseBody(api *spec.ApiSpec, route spec.Route) (string, error) {
tps := util.GetLocalTypes(api, route)
if len(route.ResponseTypeName()) == 0 {
return "", nil
}
var tps = make([]spec.Type, 0)
tps = append(tps, route.ResponseType)
if definedType, ok := route.ResponseType.(spec.DefineStruct); ok {
associatedTypes(definedType, &tps)
}
value, err := gogen.BuildTypes(tps)
if err != nil {
return "", err
}
return fmt.Sprintf("\n\n```golang\n%s\n```\n", value), nil
}
func associatedTypes(tp spec.DefineStruct, tps *[]spec.Type) {
*tps = append(*tps, tp)
for _, item := range tp.Members {
if definedType, ok := item.Type.(spec.DefineStruct); ok {
associatedTypes(definedType, tps)
}
}
}

View File

@@ -29,14 +29,11 @@ func DocCommand(c *cli.Context) error {
return err
}
for _, f := range files {
p, err := parser.NewParser(f)
api, err := parser.Parse(f)
if err != nil {
return errors.New(fmt.Sprintf("parse file: %s, err: %s", f, err.Error()))
}
api, err := p.Parse()
if err != nil {
return err
}
index := strings.Index(f, dir)
if index < 0 {
continue

View File

@@ -4,6 +4,7 @@ import (
"bufio"
"errors"
"fmt"
"go/format"
"go/scanner"
"io/ioutil"
"os"
@@ -13,6 +14,7 @@ import (
"github.com/tal-tech/go-zero/core/errorx"
"github.com/tal-tech/go-zero/tools/goctl/api/parser"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/urfave/cli"
)
@@ -52,10 +54,12 @@ func GoFormatApi(c *cli.Context) error {
})
be.Add(err)
}
if be.NotNil() {
scanner.PrintError(os.Stderr, be.Err())
os.Exit(1)
}
return be.Err()
}
@@ -71,10 +75,7 @@ func ApiFormatByStdin() error {
}
_, err = fmt.Print(result)
if err != nil {
return err
}
return nil
return err
}
func ApiFormatByPath(apiFilePath string) error {
@@ -88,14 +89,16 @@ func ApiFormatByPath(apiFilePath string) error {
return err
}
if err := ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm); err != nil {
_, err = parser.ParseContent(result)
if err != nil {
return err
}
return nil
return ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm)
}
func apiFormat(data string) (string, error) {
_, err := parser.ParseApi(data)
_, err := parser.ParseContent(data)
if err != nil {
return "", err
}
@@ -103,24 +106,114 @@ func apiFormat(data string) (string, error) {
var builder strings.Builder
s := bufio.NewScanner(strings.NewReader(data))
var tapCount = 0
var newLineCount = 0
var preLine string
for s.Scan() {
line := strings.TrimSpace(s.Text())
if len(line) == 0 {
if newLineCount > 0 {
continue
}
newLineCount++
} else {
if preLine == rightBrace {
builder.WriteString(ctlutil.NL)
}
newLineCount = 0
}
if tapCount == 0 {
format, err := formatGoTypeDef(line, s, &builder)
if err != nil {
return "", err
}
if format {
continue
}
}
noCommentLine := util.RemoveComment(line)
if noCommentLine == rightParenthesis || noCommentLine == rightBrace {
tapCount -= 1
}
if tapCount < 0 {
line = strings.TrimSuffix(line, rightBrace)
line := strings.TrimSuffix(noCommentLine, rightBrace)
line = strings.TrimSpace(line)
if strings.HasSuffix(line, leftBrace) {
tapCount += 1
}
}
util.WriteIndent(&builder, tapCount)
builder.WriteString(line + "\n")
builder.WriteString(line + ctlutil.NL)
if strings.HasSuffix(noCommentLine, leftParenthesis) || strings.HasSuffix(noCommentLine, leftBrace) {
tapCount += 1
}
preLine = line
}
return strings.TrimSpace(builder.String()), nil
}
func formatGoTypeDef(line string, scanner *bufio.Scanner, builder *strings.Builder) (bool, error) {
noCommentLine := util.RemoveComment(line)
tokenCount := 0
if strings.HasPrefix(noCommentLine, "type") && (strings.HasSuffix(noCommentLine, leftParenthesis) ||
strings.HasSuffix(noCommentLine, leftBrace)) {
var typeBuilder strings.Builder
typeBuilder.WriteString(mayInsertStructKeyword(line, &tokenCount) + ctlutil.NL)
for scanner.Scan() {
noCommentLine := util.RemoveComment(scanner.Text())
typeBuilder.WriteString(mayInsertStructKeyword(scanner.Text(), &tokenCount) + ctlutil.NL)
if noCommentLine == rightBrace || noCommentLine == rightParenthesis {
tokenCount--
}
if tokenCount == 0 {
ts, err := format.Source([]byte(typeBuilder.String()))
if err != nil {
return false, errors.New("error format \n" + typeBuilder.String())
}
result := strings.ReplaceAll(string(ts), " struct ", " ")
result = strings.ReplaceAll(result, "type ()", "")
builder.WriteString(result)
break
}
}
return true, nil
}
return false, nil
}
func mayInsertStructKeyword(line string, token *int) string {
insertStruct := func() string {
if strings.Contains(line, " struct") {
return line
}
index := strings.Index(line, leftBrace)
return line[:index] + " struct " + line[index:]
}
noCommentLine := util.RemoveComment(line)
if strings.HasSuffix(noCommentLine, leftBrace) {
*token++
return insertStruct()
}
if strings.HasSuffix(noCommentLine, rightBrace) {
noCommentLine = strings.TrimSuffix(noCommentLine, rightBrace)
noCommentLine = util.RemoveComment(noCommentLine)
if strings.HasSuffix(noCommentLine, leftBrace) {
return insertStruct()
}
}
if strings.HasSuffix(noCommentLine, leftParenthesis) {
*token++
}
if strings.Contains(noCommentLine, "`") {
return util.UpperFirst(strings.TrimSpace(line))
}
return line
}

View File

@@ -9,13 +9,11 @@ import (
const (
notFormattedStr = `
type Request struct {
Name string
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string
Message string ` + "`" + `json:"message"` + "`" + `
}
service A-api {
@server(
handler: GreetHandler
@@ -24,14 +22,12 @@ handler: GreetHandler
}
`
formattedStr = `type Request struct {
Name string
formattedStr = `type Request {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string
type Response {
Message string ` + "`" + `json:"message"` + "`" + `
}
service A-api {
@server(
handler: GreetHandler
@@ -40,7 +36,7 @@ service A-api {
}`
)
func TestInlineTypeNotExist(t *testing.T) {
func TestFormat(t *testing.T) {
r, err := apiFormat(notFormattedStr)
assert.Nil(t, err)
assert.Equal(t, r, formattedStr)

View File

@@ -41,11 +41,7 @@ func GoCommand(c *cli.Context) error {
}
func DoGenProject(apiFile, dir, style string) error {
p, err := parser.NewParser(apiFile)
if err != nil {
return err
}
api, err := p.Parse()
api, err := parser.Parse(apiFile)
if err != nil {
return err
}

View File

@@ -16,9 +16,9 @@ import (
const testApiTemplate = `
info(
title: doc title
desc: >
desc: ">
doc description first part,
doc description second part<
doc description second part<"
version: 1.0
)
@@ -55,9 +55,7 @@ service A-api {
const testMultiServiceTemplate = `
info(
title: doc title
desc: >
doc description first part,
doc description second part<
desc: doc description first part
version: 1.0
)
@@ -229,7 +227,7 @@ type Response struct {
}
service A-api {
@doc(helloworld)
@doc ("helloworld")
@server(
handler: GreetHandler
)
@@ -249,7 +247,7 @@ type Response struct {
}
service A-api {
@doc(helloworld)
@doc ("helloworld")
@server(
handler: GreetHandler
)
@@ -325,10 +323,7 @@ func TestParser(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
api, err := parser.Parse()
api, err := parser.Parse(filename)
assert.Nil(t, err)
assert.Equal(t, len(api.Types), 2)
@@ -337,8 +332,8 @@ func TestParser(t *testing.T) {
assert.Equal(t, api.Service.Routes()[0].Path, "/greet/from/:name")
assert.Equal(t, api.Service.Routes()[1].Path, "/greet/get")
assert.Equal(t, api.Service.Routes()[1].RequestType.Name, "Request")
assert.Equal(t, api.Service.Routes()[1].ResponseType.Name, "")
assert.Equal(t, api.Service.Routes()[1].RequestTypeName(), "Request")
assert.Equal(t, api.Service.Routes()[1].ResponseType, nil)
validate(t, filename)
}
@@ -349,10 +344,7 @@ func TestMultiService(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
api, err := parser.Parse()
api, err := parser.Parse(filename)
assert.Nil(t, err)
assert.Equal(t, len(api.Service.Routes()), 2)
@@ -367,10 +359,7 @@ func TestApiNoInfo(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
_, err = parser.Parse(filename)
assert.Nil(t, err)
validate(t, filename)
@@ -382,7 +371,7 @@ func TestInvalidApiFile(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
_, err = parser.NewParser(filename)
_, err = parser.Parse(filename)
assert.NotNil(t, err)
}
@@ -392,14 +381,11 @@ func TestAnonymousAnnotation(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
api, err := parser.Parse()
api, err := parser.Parse(filename)
assert.Nil(t, err)
assert.Equal(t, len(api.Service.Routes()), 1)
assert.Equal(t, api.Service.Routes()[0].Annotations[0].Value, "GreetHandler")
assert.Equal(t, api.Service.Routes()[0].Handler, "GreetHandler")
validate(t, filename)
}
@@ -410,10 +396,7 @@ func TestApiHasMiddleware(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
_, err = parser.Parse(filename)
assert.Nil(t, err)
validate(t, filename)
@@ -425,10 +408,7 @@ func TestApiHasJwt(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
_, err = parser.Parse(filename)
assert.Nil(t, err)
validate(t, filename)
@@ -440,10 +420,7 @@ func TestApiHasJwtAndMiddleware(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
_, err = parser.Parse(filename)
assert.Nil(t, err)
validate(t, filename)
@@ -455,13 +432,8 @@ func TestApiHasNoRequestBody(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
_, err = parser.Parse(filename)
assert.Nil(t, err)
_, err = parser.Parse()
assert.Nil(t, err)
validate(t, filename)
}
func TestApiRoutes(t *testing.T) {
@@ -470,10 +442,7 @@ func TestApiRoutes(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
_, err = parser.Parse(filename)
assert.Nil(t, err)
validate(t, filename)
@@ -485,10 +454,7 @@ func TestHasCommentRoutes(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
_, err = parser.Parse(filename)
assert.Nil(t, err)
validate(t, filename)
@@ -500,13 +466,8 @@ func TestInlineTypeNotExist(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
assert.Nil(t, err)
validate(t, filename)
_, err = parser.Parse(filename)
assert.NotNil(t, err)
}
func TestHasImportApi(t *testing.T) {
@@ -520,15 +481,12 @@ func TestHasImportApi(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(importApiName)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
api, err := parser.Parse()
api, err := parser.Parse(filename)
assert.Nil(t, err)
var hasInline bool
for _, ty := range api.Types {
if ty.Name == "ImportData" {
if ty.Name() == "ImportData" {
hasInline = true
break
}
@@ -544,10 +502,7 @@ func TestNoStructApi(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
spec, err := parser.Parse()
spec, err := parser.Parse(filename)
assert.Nil(t, err)
assert.Equal(t, len(spec.Types), 5)
@@ -559,8 +514,8 @@ func TestNestTypeApi(t *testing.T) {
err := ioutil.WriteFile(filename, []byte(nestTypeApi), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
_, err = parser.NewParser(filename)
_, err = parser.Parse(filename)
assert.NotNil(t, err)
}
@@ -569,7 +524,8 @@ func TestCamelStyle(t *testing.T) {
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
_, err = parser.NewParser(filename)
_, err = parser.Parse(filename)
assert.Nil(t, err)
validateWithCamel(t, filename, "GoZero")

View File

@@ -1,15 +1,11 @@
package gogen
import (
"bytes"
"fmt"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/config"
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/format"
"github.com/tal-tech/go-zero/tools/goctl/vars"
)
@@ -39,38 +35,24 @@ func genConfig(dir string, cfg *config.Config, api *spec.ApiSpec) error {
return err
}
fp, created, err := util.MaybeCreateFile(dir, configDir, filename+".go")
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
var authNames = getAuths(api)
var auths []string
for _, item := range authNames {
auths = append(auths, fmt.Sprintf("%s %s", item, jwtTemplate))
}
var authImportStr = fmt.Sprintf("\"%s/rest\"", vars.ProjectOpenSourceUrl)
text, err := ctlutil.LoadTemplate(category, configTemplateFile, configTemplate)
if err != nil {
return err
}
t := template.Must(template.New("configTemplate").Parse(text))
buffer := new(bytes.Buffer)
err = t.Execute(buffer, map[string]string{
"authImport": authImportStr,
"auth": strings.Join(auths, "\n"),
return genFile(fileGenConfig{
dir: dir,
subdir: configDir,
filename: filename + ".go",
templateName: "configTemplate",
category: category,
templateFile: configTemplateFile,
builtinTemplate: configTemplate,
data: map[string]string{
"authImport": authImportStr,
"auth": strings.Join(auths, "\n"),
},
})
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
}

View File

@@ -1,15 +1,11 @@
package gogen
import (
"bytes"
"fmt"
"strconv"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/config"
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/format"
)
@@ -28,42 +24,22 @@ func genEtc(dir string, cfg *config.Config, api *spec.ApiSpec) error {
return err
}
fp, created, err := util.MaybeCreateFile(dir, etcDir, fmt.Sprintf("%s.yaml", filename))
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
service := api.Service
host, ok := util.GetAnnotationValue(service.Groups[0].Annotations, "server", "host")
if !ok {
host = "0.0.0.0"
}
port, ok := util.GetAnnotationValue(service.Groups[0].Annotations, "server", "port")
if !ok {
port = strconv.Itoa(defaultPort)
}
host := "0.0.0.0"
port := strconv.Itoa(defaultPort)
text, err := ctlutil.LoadTemplate(category, etcTemplateFile, etcTemplate)
if err != nil {
return err
}
t := template.Must(template.New("etcTemplate").Parse(text))
buffer := new(bytes.Buffer)
err = t.Execute(buffer, map[string]string{
"serviceName": service.Name,
"host": host,
"port": port,
return genFile(fileGenConfig{
dir: dir,
subdir: etcDir,
filename: fmt.Sprintf("%s.yaml", filename),
templateName: "etcTemplate",
category: category,
templateFile: etcTemplateFile,
builtinTemplate: etcTemplate,
data: map[string]string{
"serviceName": service.Name,
"host": host,
"port": port,
},
})
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
}

View File

@@ -1,16 +1,11 @@
package gogen
import (
"bytes"
"errors"
"fmt"
"path"
"strings"
"text/template"
"unicode"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/config"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/format"
@@ -67,11 +62,11 @@ func genHandler(dir string, cfg *config.Config, group spec.Group, route spec.Rou
return doGenToFile(dir, handler, cfg, group, route, Handler{
ImportPackages: genHandlerImports(group, route, parentPkg),
HandlerName: handler,
RequestType: util.Title(route.RequestType.Name),
RequestType: util.Title(route.RequestTypeName()),
LogicType: strings.Title(getLogicName(route)),
Call: strings.Title(strings.TrimSuffix(handler, "Handler")),
HasResp: len(route.ResponseType.Name) > 0,
HasRequest: len(route.RequestType.Name) > 0,
HasResp: len(route.ResponseTypeName()) > 0,
HasRequest: len(route.RequestTypeName()) > 0,
})
}
@@ -82,30 +77,16 @@ func doGenToFile(dir, handler string, cfg *config.Config, group spec.Group,
return err
}
filename = filename + ".go"
fp, created, err := apiutil.MaybeCreateFile(dir, getHandlerFolderPath(group, route), filename)
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
text, err := util.LoadTemplate(category, handlerTemplateFile, handlerTemplate)
if err != nil {
return err
}
buffer := new(bytes.Buffer)
err = template.Must(template.New("handlerTemplate").Parse(text)).Execute(buffer, handleObj)
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
return genFile(fileGenConfig{
dir: dir,
subdir: getHandlerFolderPath(group, route),
filename: filename + ".go",
templateName: "handlerTemplate",
category: category,
templateFile: handlerTemplateFile,
builtinTemplate: handlerTemplate,
data: handleObj,
})
}
func genHandlers(dir string, cfg *config.Config, api *spec.ApiSpec) error {
@@ -125,7 +106,7 @@ func genHandlerImports(group spec.Group, route spec.Route, parentPkg string) str
imports = append(imports, fmt.Sprintf("\"%s\"",
util.JoinPackages(parentPkg, getLogicFolderPath(group, route))))
imports = append(imports, fmt.Sprintf("\"%s\"", util.JoinPackages(parentPkg, contextDir)))
if len(route.RequestType.Name) > 0 {
if len(route.RequestTypeName()) > 0 {
imports = append(imports, fmt.Sprintf("\"%s\"\n", util.JoinPackages(parentPkg, typesDir)))
}
imports = append(imports, fmt.Sprintf("\"%s/rest/httpx\"", vars.ProjectOpenSourceUrl))
@@ -134,18 +115,7 @@ func genHandlerImports(group spec.Group, route spec.Route, parentPkg string) str
}
func getHandlerBaseName(route spec.Route) (string, error) {
handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
if !ok {
return "", fmt.Errorf("missing handler annotation for %q", route.Path)
}
for _, char := range handler {
if !unicode.IsDigit(char) && !unicode.IsLetter(char) {
return "", errors.New(fmt.Sprintf("route [%s] handler [%s] invalid, handler name should only contains letter or digit",
route.Path, handler))
}
}
handler := route.Handler
handler = strings.TrimSpace(handler)
handler = strings.TrimSuffix(handler, "handler")
handler = strings.TrimSuffix(handler, "Handler")
@@ -153,10 +123,10 @@ func getHandlerBaseName(route spec.Route) (string, error) {
}
func getHandlerFolderPath(group spec.Group, route spec.Route) string {
folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", groupProperty)
if !ok {
folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", groupProperty)
if !ok {
folder := route.GetAnnotation(groupProperty)
if len(folder) == 0 {
folder = group.GetAnnotation(groupProperty)
if len(folder) == 0 {
return handlerDir
}
}

View File

@@ -1,14 +1,11 @@
package gogen
import (
"bytes"
"fmt"
"path"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/config"
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/format"
@@ -61,17 +58,6 @@ func genLogicByRoute(dir string, cfg *config.Config, group spec.Group, route spe
return err
}
goFile = goFile + ".go"
fp, created, err := util.MaybeCreateFile(dir, getLogicFolderPath(group, route), goFile)
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
parentPkg, err := getParentPackage(dir)
if err != nil {
return err
@@ -81,47 +67,46 @@ func genLogicByRoute(dir string, cfg *config.Config, group spec.Group, route spe
var responseString string
var returnString string
var requestString string
if len(route.ResponseType.Name) > 0 {
resp := strings.Title(route.ResponseType.Name)
responseString = "(*types." + resp + ", error)"
returnString = fmt.Sprintf("return &types.%s{}, nil", resp)
if len(route.ResponseTypeName()) > 0 {
resp := responseGoTypeName(route, typesPacket)
responseString = "(" + resp + ", error)"
if strings.HasPrefix(resp, "*") {
returnString = fmt.Sprintf("return &%s{}, nil", strings.TrimPrefix(resp, "*"))
} else {
returnString = fmt.Sprintf("return %s{}, nil", resp)
}
} else {
responseString = "error"
returnString = "return nil"
}
if len(route.RequestType.Name) > 0 {
requestString = "req " + "types." + strings.Title(route.RequestType.Name)
if len(route.RequestTypeName()) > 0 {
requestString = "req " + requestGoTypeName(route, typesPacket)
}
text, err := ctlutil.LoadTemplate(category, logicTemplateFile, logicTemplate)
if err != nil {
return err
}
t := template.Must(template.New("logicTemplate").Parse(text))
buffer := new(bytes.Buffer)
err = t.Execute(fp, map[string]string{
"imports": imports,
"logic": strings.Title(logic),
"function": strings.Title(strings.TrimSuffix(logic, "Logic")),
"responseType": responseString,
"returnString": returnString,
"request": requestString,
return genFile(fileGenConfig{
dir: dir,
subdir: getLogicFolderPath(group, route),
filename: goFile + ".go",
templateName: "logicTemplate",
category: category,
templateFile: logicTemplateFile,
builtinTemplate: logicTemplate,
data: map[string]string{
"imports": imports,
"logic": strings.Title(logic),
"function": strings.Title(strings.TrimSuffix(logic, "Logic")),
"responseType": responseString,
"returnString": returnString,
"request": requestString,
},
})
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
}
func getLogicFolderPath(group spec.Group, route spec.Route) string {
folder, ok := util.GetAnnotationValue(route.Annotations, "server", groupProperty)
if !ok {
folder, ok = util.GetAnnotationValue(group.Annotations, "server", groupProperty)
if !ok {
folder := route.GetAnnotation(groupProperty)
if len(folder) == 0 {
folder = group.GetAnnotation(groupProperty)
if len(folder) == 0 {
return logicDir
}
}
@@ -134,7 +119,7 @@ func genLogicImports(route spec.Route, parentPkg string) string {
var imports []string
imports = append(imports, `"context"`+"\n")
imports = append(imports, fmt.Sprintf("\"%s\"", ctlutil.JoinPackages(parentPkg, contextDir)))
if len(route.ResponseType.Name) > 0 || len(route.RequestType.Name) > 0 {
if len(route.ResponseTypeName()) > 0 || len(route.RequestTypeName()) > 0 {
imports = append(imports, fmt.Sprintf("\"%s\"\n", ctlutil.JoinPackages(parentPkg, typesDir)))
}
imports = append(imports, fmt.Sprintf("\"%s/core/logx\"", vars.ProjectOpenSourceUrl))

View File

@@ -1,13 +1,10 @@
package gogen
import (
"bytes"
"fmt"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/config"
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/format"
@@ -52,39 +49,24 @@ func genMain(dir string, cfg *config.Config, api *spec.ApiSpec) error {
return err
}
goFile := filename + ".go"
fp, created, err := util.MaybeCreateFile(dir, "", goFile)
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
parentPkg, err := getParentPackage(dir)
if err != nil {
return err
}
text, err := ctlutil.LoadTemplate(category, mainTemplateFile, mainTemplate)
if err != nil {
return err
}
t := template.Must(template.New("mainTemplate").Parse(text))
buffer := new(bytes.Buffer)
err = t.Execute(buffer, map[string]string{
"importPackages": genMainImports(parentPkg),
"serviceName": api.Service.Name,
return genFile(fileGenConfig{
dir: dir,
subdir: "",
filename: filename + ".go",
templateName: "mainTemplate",
category: category,
templateFile: mainTemplateFile,
builtinTemplate: mainTemplate,
data: map[string]string{
"importPackages": genMainImports(parentPkg),
"serviceName": api.Service.Name,
},
})
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
}
func genMainImports(parentPkg string) string {

View File

@@ -1,12 +1,9 @@
package gogen
import (
"bytes"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/config"
"github.com/tal-tech/go-zero/tools/goctl/util/format"
)
@@ -37,34 +34,26 @@ func genMiddleware(dir string, cfg *config.Config, api *spec.ApiSpec) error {
var middlewares = getMiddleware(api)
for _, item := range middlewares {
middlewareFilename := strings.TrimSuffix(strings.ToLower(item), "middleware") + "_middleware"
formatName, err := format.FileNamingFormat(cfg.NamingFormat, middlewareFilename)
filename, err := format.FileNamingFormat(cfg.NamingFormat, middlewareFilename)
if err != nil {
return err
}
filename := formatName + ".go"
fp, created, err := util.MaybeCreateFile(dir, middlewareDir, filename)
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
t := template.Must(template.New("contextTemplate").Parse(middlewareImplementCode))
buffer := new(bytes.Buffer)
err = t.Execute(buffer, map[string]string{
"name": strings.Title(name),
err = genFile(fileGenConfig{
dir: dir,
subdir: middlewareDir,
filename: filename + ".go",
templateName: "contextTemplate",
builtinTemplate: middlewareImplementCode,
data: map[string]string{
"name": strings.Title(name),
},
})
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
}
return nil
}

View File

@@ -1,7 +1,6 @@
package gogen
import (
"bytes"
"fmt"
"os"
"path"
@@ -11,7 +10,6 @@ import (
"github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/config"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/format"
@@ -132,28 +130,19 @@ func genRoutes(dir string, cfg *config.Config, api *spec.ApiSpec) error {
filename := path.Join(dir, handlerDir, routeFilename)
os.Remove(filename)
fp, created, err := apiutil.MaybeCreateFile(dir, handlerDir, routeFilename)
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
t := template.Must(template.New("routesTemplate").Parse(routesTemplate))
buffer := new(bytes.Buffer)
err = t.Execute(buffer, map[string]string{
"importPackages": genRouteImports(parentPkg, api),
"routesAdditions": strings.TrimSpace(builder.String()),
return genFile(fileGenConfig{
dir: dir,
subdir: handlerDir,
filename: routeFilename,
templateName: "routesTemplate",
category: "",
templateFile: "",
builtinTemplate: routesTemplate,
data: map[string]string{
"importPackages": genRouteImports(parentPkg, api),
"routesAdditions": strings.TrimSpace(builder.String()),
},
})
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
}
func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
@@ -161,10 +150,10 @@ func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
importSet.AddStr(fmt.Sprintf("\"%s\"", util.JoinPackages(parentPkg, contextDir)))
for _, group := range api.Service.Groups {
for _, route := range group.Routes {
folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", groupProperty)
if !ok {
folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", groupProperty)
if !ok {
folder := route.GetAnnotation(groupProperty)
if len(folder) == 0 {
folder = group.GetAnnotation(groupProperty)
if len(folder) == 0 {
continue
}
}
@@ -186,12 +175,12 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
for _, r := range g.Routes {
handler := getHandlerName(r)
handler = handler + "(serverCtx)"
folder, ok := apiutil.GetAnnotationValue(r.Annotations, "server", groupProperty)
if ok {
folder := r.GetAnnotation(groupProperty)
if len(folder) > 0 {
handler = toPrefix(folder) + "." + strings.ToUpper(handler[:1]) + handler[1:]
} else {
folder, ok = apiutil.GetAnnotationValue(g.Annotations, "server", groupProperty)
if ok {
folder = g.GetAnnotation(groupProperty)
if len(folder) > 0 {
handler = toPrefix(folder) + "." + strings.ToUpper(handler[:1]) + handler[1:]
}
}
@@ -202,12 +191,14 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
})
}
if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "jwt"); ok {
groupedRoutes.authName = value
jwt := g.GetAnnotation("jwt")
if len(jwt) > 0 {
groupedRoutes.authName = jwt
groupedRoutes.jwtEnabled = true
}
if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
for _, item := range strings.Split(value, ",") {
middleware := g.GetAnnotation("middleware")
if len(middleware) > 0 {
for _, item := range strings.Split(middleware, ",") {
groupedRoutes.middlewares = append(groupedRoutes.middlewares, item)
}
}

View File

@@ -1,13 +1,10 @@
package gogen
import (
"bytes"
"fmt"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/config"
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/format"
@@ -33,7 +30,6 @@ func NewServiceContext(c {{.config}}) *ServiceContext {
{{.middlewareAssignment}}
}
}
`
)
@@ -43,15 +39,6 @@ func genServiceContext(dir string, cfg *config.Config, api *spec.ApiSpec) error
return err
}
fp, created, err := util.MaybeCreateFile(dir, contextDir, filename+".go")
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
var authNames = getAuths(api)
var auths []string
for _, item := range authNames {
@@ -63,11 +50,6 @@ func genServiceContext(dir string, cfg *config.Config, api *spec.ApiSpec) error
return err
}
text, err := ctlutil.LoadTemplate(category, contextTemplateFile, contextTemplate)
if err != nil {
return err
}
var middlewareStr string
var middlewareAssignment string
var middlewares = getMiddleware(api)
@@ -75,7 +57,8 @@ func genServiceContext(dir string, cfg *config.Config, api *spec.ApiSpec) error
for _, item := range middlewares {
middlewareStr += fmt.Sprintf("%s rest.Middleware\n", item)
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
middlewareAssignment += fmt.Sprintf("%s: %s,\n", item, fmt.Sprintf("middleware.New%s().%s", strings.Title(name), "Handle"))
middlewareAssignment += fmt.Sprintf("%s: %s,\n", item,
fmt.Sprintf("middleware.New%s().%s", strings.Title(name), "Handle"))
}
var configImport = "\"" + ctlutil.JoinPackages(parentPkg, configDir) + "\""
@@ -84,19 +67,19 @@ func genServiceContext(dir string, cfg *config.Config, api *spec.ApiSpec) error
configImport += fmt.Sprintf("\n\t\"%s/rest\"", vars.ProjectOpenSourceUrl)
}
t := template.Must(template.New("contextTemplate").Parse(text))
buffer := new(bytes.Buffer)
err = t.Execute(buffer, map[string]string{
"configImport": configImport,
"config": "config.Config",
"middleware": middlewareStr,
"middlewareAssignment": middlewareAssignment,
return genFile(fileGenConfig{
dir: dir,
subdir: contextDir,
filename: filename + ".go",
templateName: "contextTemplate",
category: category,
templateFile: contextTemplateFile,
builtinTemplate: contextTemplate,
data: map[string]string{
"configImport": configImport,
"config": "config.Config",
"middleware": middlewareStr,
"middlewareAssignment": middlewareAssignment,
},
})
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
}

View File

@@ -1,14 +1,12 @@
package gogen
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
@@ -37,8 +35,8 @@ func BuildTypes(types []spec.Type) (string, error) {
} else {
builder.WriteString("\n\n")
}
if err := writeType(&builder, tp, types); err != nil {
return "", apiutil.WrapErr(err, "Type "+tp.Name+" generate error")
if err := writeType(&builder, tp); err != nil {
return "", apiutil.WrapErr(err, "Type "+tp.Name()+" generate error")
}
}
@@ -55,91 +53,43 @@ func genTypes(dir string, cfg *config.Config, api *spec.ApiSpec) error {
if err != nil {
return err
}
typeFilename = typeFilename + ".go"
filename := path.Join(dir, typesDir, typeFilename)
os.Remove(filename)
fp, created, err := apiutil.MaybeCreateFile(dir, typesDir, typeFilename)
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
t := template.Must(template.New("typesTemplate").Parse(typesTemplate))
buffer := new(bytes.Buffer)
err = t.Execute(buffer, map[string]interface{}{
"types": val,
"containsTime": api.ContainsTime(),
return genFile(fileGenConfig{
dir: dir,
subdir: typesDir,
filename: typeFilename,
templateName: "typesTemplate",
category: "",
templateFile: "",
builtinTemplate: typesTemplate,
data: map[string]interface{}{
"types": val,
"containsTime": false,
},
})
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
}
func convertTypeCase(types []spec.Type, t string) (string, error) {
ts, err := apiutil.DecomposeType(t)
if err != nil {
return "", err
func writeType(writer io.Writer, tp spec.Type) error {
structType, ok := tp.(spec.DefineStruct)
if !ok {
return errors.New(fmt.Sprintf("unspport struct type: %s", tp.Name()))
}
var defTypes []string
for _, tp := range ts {
for _, typ := range types {
if typ.Name == tp {
defTypes = append(defTypes, tp)
}
}
}
for _, tp := range defTypes {
t = strings.ReplaceAll(t, tp, util.Title(tp))
}
return t, nil
}
func writeType(writer io.Writer, tp spec.Type, types []spec.Type) error {
fmt.Fprintf(writer, "type %s struct {\n", util.Title(tp.Name))
for _, member := range tp.Members {
fmt.Fprintf(writer, "type %s struct {\n", util.Title(tp.Name()))
for _, member := range structType.Members {
if member.IsInline {
var found = false
for _, ty := range types {
if strings.ToLower(ty.Name) == strings.ToLower(member.Name) {
found = true
}
}
if !found {
return errors.New("inline type " + member.Name + " not exist, please correct api file")
}
if _, err := fmt.Fprintf(writer, "%s\n", strings.Title(member.Type)); err != nil {
if _, err := fmt.Fprintf(writer, "%s\n", strings.Title(member.Type.Name())); err != nil {
return err
} else {
continue
}
}
tpString, err := convertTypeCase(types, member.Type)
if err != nil {
return err
}
pm, err := member.GetPropertyName()
if err != nil {
return err
}
if !strings.Contains(pm, "_") {
if strings.Title(member.Name) != strings.Title(pm) {
fmt.Printf("type: %s, property name %s json tag illegal, "+
"should set json tag as `json:\"%s\"` \n", tp.Name, member.Name, util.Untitle(member.Name))
}
}
if err := writeProperty(writer, member.Name, tpString, member.Tag, member.GetComment(), 1); err != nil {
if err := writeProperty(writer, member.Name, member.Tag, member.GetComment(), member.Type, 1); err != nil {
return err
}
}

View File

@@ -38,11 +38,12 @@ func RevertTemplate(name string) error {
return util.CreateTemplate(category, name, content)
}
func Update(category string) error {
func Update() error {
err := Clean()
if err != nil {
return err
}
return util.InitTemplates(category, templates)
}
@@ -50,6 +51,6 @@ func Clean() error {
return util.Clean(category)
}
func GetCategory() string {
func Category() string {
return category
}

View File

@@ -84,7 +84,7 @@ func TestUpdate(t *testing.T) {
assert.Equal(t, string(data), modifyData)
assert.Nil(t, Update(category))
assert.Nil(t, Update())
data, err = ioutil.ReadFile(file)
assert.Nil(t, err)

View File

@@ -1,18 +1,64 @@
package gogen
import (
"bytes"
"fmt"
goformat "go/format"
"io"
"path/filepath"
"strings"
"text/template"
"github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
type fileGenConfig struct {
dir string
subdir string
filename string
templateName string
category string
templateFile string
builtinTemplate string
data interface{}
}
func genFile(c fileGenConfig) error {
fp, created, err := util.MaybeCreateFile(c.dir, c.subdir, c.filename)
if err != nil {
return err
}
if !created {
return nil
}
defer fp.Close()
var text string
if len(c.category) == 0 || len(c.templateFile) == 0 {
text = c.builtinTemplate
} else {
text, err = ctlutil.LoadTemplate(c.category, c.templateFile, c.builtinTemplate)
if err != nil {
return err
}
}
t := template.Must(template.New(c.templateName).Parse(text))
buffer := new(bytes.Buffer)
err = t.Execute(buffer, c.data)
if err != nil {
return err
}
code := formatCode(buffer.String())
_, err = fp.WriteString(code)
return err
}
func getParentPackage(dir string) (string, error) {
abs, err := filepath.Abs(dir)
if err != nil {
@@ -23,30 +69,34 @@ func getParentPackage(dir string) (string, error) {
if err != nil {
return "", err
}
return filepath.ToSlash(filepath.Join(projectCtx.Path, strings.TrimPrefix(projectCtx.WorkDir, projectCtx.Dir))), nil
}
func writeProperty(writer io.Writer, name, tp, tag, comment string, indent int) error {
func writeProperty(writer io.Writer, name, tag, comment string, tp spec.Type, indent int) error {
util.WriteIndent(writer, indent)
var err error
if len(comment) > 0 {
comment = strings.TrimPrefix(comment, "//")
comment = "//" + comment
_, err = fmt.Fprintf(writer, "%s %s %s %s\n", strings.Title(name), tp, tag, comment)
_, err = fmt.Fprintf(writer, "%s %s %s %s\n", strings.Title(name), tp.Name(), tag, comment)
} else {
_, err = fmt.Fprintf(writer, "%s %s %s\n", strings.Title(name), tp, tag)
_, err = fmt.Fprintf(writer, "%s %s %s\n", strings.Title(name), tp.Name(), tag)
}
return err
}
func getAuths(api *spec.ApiSpec) []string {
authNames := collection.NewSet()
for _, g := range api.Service.Groups {
if value, ok := util.GetAnnotationValue(g.Annotations, "server", "jwt"); ok {
authNames.Add(value)
jwt := g.GetAnnotation("jwt")
if len(jwt) > 0 {
authNames.Add(jwt)
}
if value, ok := util.GetAnnotationValue(g.Annotations, "server", "signature"); ok {
authNames.Add(value)
signature := g.GetAnnotation("signature")
if len(signature) > 0 {
authNames.Add(signature)
}
}
return authNames.KeysStr()
@@ -55,12 +105,14 @@ func getAuths(api *spec.ApiSpec) []string {
func getMiddleware(api *spec.ApiSpec) []string {
result := collection.NewSet()
for _, g := range api.Service.Groups {
if value, ok := util.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
for _, item := range strings.Split(value, ",") {
middleware := g.GetAnnotation("middleware")
if len(middleware) > 0 {
for _, item := range strings.Split(middleware, ",") {
result.Add(strings.TrimSpace(item))
}
}
}
return result.KeysStr()
}
@@ -72,3 +124,78 @@ func formatCode(code string) string {
return string(ret)
}
func responseGoTypeName(r spec.Route, pkg ...string) string {
if r.ResponseType == nil {
return ""
}
resp := golangExpr(r.ResponseType, pkg...)
switch r.ResponseType.(type) {
case spec.DefineStruct:
if !strings.HasPrefix(resp, "*") {
return "*" + resp
}
}
return resp
}
func requestGoTypeName(r spec.Route, pkg ...string) string {
if r.RequestType == nil {
return ""
}
return golangExpr(r.RequestType, pkg...)
}
func golangExpr(ty spec.Type, pkg ...string) string {
switch v := ty.(type) {
case spec.PrimitiveType:
return v.RawName
case spec.DefineStruct:
if len(pkg) > 1 {
panic("package cannot be more than 1")
}
if len(pkg) == 0 {
return v.RawName
}
return fmt.Sprintf("%s.%s", pkg[0], strings.Title(v.RawName))
case spec.ArrayType:
if len(pkg) > 1 {
panic("package cannot be more than 1")
}
if len(pkg) == 0 {
return v.RawName
}
return fmt.Sprintf("[]%s", golangExpr(v.Value, pkg...))
case spec.MapType:
if len(pkg) > 1 {
panic("package cannot be more than 1")
}
if len(pkg) == 0 {
return v.RawName
}
return fmt.Sprintf("map[%s]%s", v.Key, golangExpr(v.Value, pkg...))
case spec.PointerType:
if len(pkg) > 1 {
panic("package cannot be more than 1")
}
if len(pkg) == 0 {
return v.RawName
}
return fmt.Sprintf("*%s", golangExpr(v.Type, pkg...))
case spec.InterfaceType:
return v.RawName
}
return ""
}

View File

@@ -22,11 +22,7 @@ func JavaCommand(c *cli.Context) error {
return errors.New("missing -dir")
}
p, err := parser.NewParser(apiFile)
if err != nil {
return err
}
api, err := p.Parse()
api, err := parser.Parse(apiFile)
if err != nil {
return err
}

View File

@@ -1,12 +1,16 @@
package javagen
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"path"
"strings"
"text/template"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/util"
@@ -16,19 +20,81 @@ const (
componentTemplate = `// Code generated by goctl. DO NOT EDIT.
package com.xhb.logic.http.packet.{{.packet}}.model;
import com.xhb.logic.http.DeProguardable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
{{.imports}}
{{.componentType}}
public class {{.className}} extends {{.superClassName}} {
{{.properties}}
{{if .HasProperty}}
public {{.className}}() {
}
public {{.className}}({{.params}}) {
{{.constructorSetter}}
}
{{end}}
{{.getSet}}
}
`
getSetTemplate = `
{{.indent}}{{.decorator}}
{{.indent}}public {{.returnType}} get{{.property}}() {
{{.indent}} return this.{{.tagValue}};
{{.indent}}}
{{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) {
{{.indent}} this.{{.tagValue}} = {{.propertyValue}};
{{.indent}}}
`
boolTemplate = `
{{.indent}}{{.decorator}}
{{.indent}}public {{.returnType}} is{{.property}}() {
{{.indent}} return this.{{.tagValue}};
{{.indent}}}
{{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) {
{{.indent}} this.{{.tagValue}} = {{.propertyValue}};
{{.indent}}}
`
httpResponseData = "import com.xhb.core.response.HttpResponseData;"
httpData = "import com.xhb.core.packet.HttpData;"
)
type componentsContext struct {
api *spec.ApiSpec
requestTypes []spec.Type
responseTypes []spec.Type
imports []string
members []spec.Member
}
func genComponents(dir, packetName string, api *spec.ApiSpec) error {
types := apiutil.GetSharedTypes(api)
types := api.Types
if len(types) == 0 {
return nil
}
var requestTypes []spec.Type
var responseTypes []spec.Type
for _, group := range api.Service.Groups {
for _, route := range group.Routes {
if route.RequestType != nil {
requestTypes = append(requestTypes, route.RequestType)
}
if route.ResponseType != nil {
responseTypes = append(responseTypes, route.ResponseType)
}
}
}
context := componentsContext{api: api, requestTypes: requestTypes, responseTypes: responseTypes}
for _, ty := range types {
if err := createComponent(dir, packetName, ty); err != nil {
if err := context.createComponent(dir, packetName, ty); err != nil {
return err
}
}
@@ -36,13 +102,55 @@ func genComponents(dir, packetName string, api *spec.ApiSpec) error {
return nil
}
func createComponent(dir, packetName string, ty spec.Type) error {
modelFile := util.Title(ty.Name) + ".java"
func (c *componentsContext) createComponent(dir, packetName string, ty spec.Type) error {
defineStruct, ok := ty.(spec.DefineStruct)
if !ok {
return errors.New("unsupported type %s" + ty.Name())
}
for _, item := range c.requestTypes {
if item.Name() == defineStruct.Name() {
if len(defineStruct.GetFormMembers())+len(defineStruct.GetBodyMembers()) == 0 {
return nil
}
}
}
modelFile := util.Title(ty.Name()) + ".java"
filename := path.Join(dir, modelDir, modelFile)
if err := util.RemoveOrQuit(filename); err != nil {
return err
}
propertiesString, err := c.buildProperties(defineStruct)
if err != nil {
return err
}
getSetString, err := c.buildGetterSetter(defineStruct)
if err != nil {
return err
}
superClassName := "HttpData"
for _, item := range c.responseTypes {
if item.Name() == defineStruct.Name() {
superClassName = "HttpResponseData"
if !stringx.Contains(c.imports, httpResponseData) {
c.imports = append(c.imports, httpResponseData)
}
break
}
}
if superClassName == "HttpData" && !stringx.Contains(c.imports, httpData) {
c.imports = append(c.imports, httpData)
}
params, constructorSetter, err := c.buildConstructor()
if err != nil {
return err
}
fp, created, err := apiutil.MaybeCreateFile(dir, modelDir, modelFile)
if err != nil {
return err
@@ -52,34 +160,185 @@ func createComponent(dir, packetName string, ty spec.Type) error {
}
defer fp.Close()
tys, err := buildType(ty)
buffer := new(bytes.Buffer)
t := template.Must(template.New("componentType").Parse(componentTemplate))
err = t.Execute(buffer, map[string]interface{}{
"properties": propertiesString,
"params": params,
"constructorSetter": constructorSetter,
"getSet": getSetString,
"packet": packetName,
"imports": strings.Join(c.imports, "\n"),
"className": util.Title(defineStruct.Name()),
"superClassName": superClassName,
"HasProperty": len(strings.TrimSpace(propertiesString)) > 0,
})
if err != nil {
return err
}
t := template.Must(template.New("componentType").Parse(componentTemplate))
return t.Execute(fp, map[string]string{
"componentType": tys,
"packet": packetName,
})
_, err = fp.WriteString(formatSource(buffer.String()))
return err
}
func buildType(ty spec.Type) (string, error) {
func (c *componentsContext) buildProperties(defineStruct spec.DefineStruct) (string, error) {
var builder strings.Builder
if err := writeType(&builder, ty); err != nil {
return "", apiutil.WrapErr(err, "Type "+ty.Name+" generate error")
if err := c.writeType(&builder, defineStruct); err != nil {
return "", apiutil.WrapErr(err, "Type "+defineStruct.Name()+" generate error")
}
return builder.String(), nil
}
func writeType(writer io.Writer, tp spec.Type) error {
fmt.Fprintf(writer, "public class %s implements DeProguardable {\n", util.Title(tp.Name))
for _, member := range tp.Members {
if err := writeProperty(writer, member, 1); err != nil {
return err
}
func (c *componentsContext) buildGetterSetter(defineStruct spec.DefineStruct) (string, error) {
var builder strings.Builder
if err := c.genGetSet(&builder, 1); err != nil {
return "", apiutil.WrapErr(err, "Type "+defineStruct.Name()+" get or set generate error")
}
genGetSet(writer, tp, 1)
fmt.Fprintf(writer, "}\n")
return builder.String(), nil
}
func (c *componentsContext) writeType(writer io.Writer, defineStruct spec.DefineStruct) error {
c.members = make([]spec.Member, 0)
err := c.writeMembers(writer, defineStruct, 1)
if err != nil {
return err
}
return nil
}
func (c *componentsContext) writeMembers(writer io.Writer, tp spec.Type, indent int) error {
definedType, ok := tp.(spec.DefineStruct)
if !ok {
pointType, ok := tp.(spec.PointerType)
if ok {
return c.writeMembers(writer, pointType.Type, indent)
}
return errors.New(fmt.Sprintf("type %s not supported", tp.Name()))
}
for _, member := range definedType.Members {
if member.IsInline {
err := c.writeMembers(writer, member.Type, indent)
if err != nil {
return err
}
continue
}
if member.IsBodyMember() || member.IsFormMember() {
if err := writeProperty(writer, member, indent); err != nil {
return err
}
c.members = append(c.members, member)
}
}
return nil
}
func (c *componentsContext) buildConstructor() (string, string, error) {
var params strings.Builder
var constructorSetter strings.Builder
for index, member := range c.members {
tp, err := specTypeToJava(member.Type)
if err != nil {
return "", "", err
}
params.WriteString(fmt.Sprintf("%s %s", tp, util.Untitle(member.Name)))
pn, err := member.GetPropertyName()
if err != nil {
return "", "", err
}
if index != len(c.members)-1 {
params.WriteString(", ")
}
writeIndent(&constructorSetter, 2)
constructorSetter.WriteString(fmt.Sprintf("this.%s = %s;", pn, util.Untitle(member.Name)))
if index != len(c.members)-1 {
constructorSetter.WriteString(util.NL)
}
}
return params.String(), constructorSetter.String(), nil
}
func (c *componentsContext) genGetSet(writer io.Writer, indent int) error {
var members = c.members
for _, member := range members {
javaType, err := specTypeToJava(member.Type)
if err != nil {
return nil
}
var property = util.Title(member.Name)
var templateStr = getSetTemplate
if javaType == "boolean" {
templateStr = boolTemplate
property = strings.TrimPrefix(property, "Is")
property = strings.TrimPrefix(property, "is")
}
t := template.Must(template.New(templateStr).Parse(getSetTemplate))
var tmplBytes bytes.Buffer
tyString := javaType
decorator := ""
javaPrimitiveType := []string{"int", "long", "boolean", "float", "double", "short"}
if !stringx.Contains(javaPrimitiveType, javaType) {
if member.IsOptional() || member.IsOmitEmpty() {
decorator = "@Nullable "
} else {
decorator = "@NotNull "
}
tyString = decorator + tyString
}
tagName, err := member.GetPropertyName()
if err != nil {
return err
}
err = t.Execute(&tmplBytes, map[string]string{
"property": property,
"propertyValue": util.Untitle(member.Name),
"tagValue": tagName,
"type": tyString,
"decorator": decorator,
"returnType": javaType,
"indent": indentString(indent),
})
if err != nil {
return err
}
r := tmplBytes.String()
r = strings.Replace(r, " boolean get", " boolean is", 1)
writer.Write([]byte(r))
}
return nil
}
func formatSource(source string) string {
var builder strings.Builder
scanner := bufio.NewScanner(strings.NewReader(source))
preIsBreakLine := false
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text == "" && preIsBreakLine {
continue
}
preIsBreakLine = text == ""
builder.WriteString(scanner.Text() + "\n")
}
if err := scanner.Err(); err != nil {
fmt.Println(err)
}
return builder.String()
}

View File

@@ -1,11 +1,8 @@
package javagen
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"strings"
"text/template"
@@ -17,25 +14,17 @@ import (
const packetTemplate = `package com.xhb.logic.http.packet.{{.packet}};
import com.google.gson.Gson;
import com.xhb.commons.JSON;
import com.xhb.commons.JsonParser;
import com.xhb.core.packet.HttpPacket;
import com.xhb.core.network.HttpRequestClient;
import com.xhb.core.packet.HttpRequestPacket;
import com.xhb.core.response.HttpResponseData;
import com.xhb.logic.http.DeProguardable;
{{.import}}
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;
public class {{.packetName}} extends HttpRequestPacket<{{.packetName}}.{{.packetName}}Response> {
{{.imports}}
{{.doc}}
public class {{.packetName}} extends HttpPacket<{{.responseType}}> {
{{.paramsDeclaration}}
public {{.packetName}}({{.params}}{{.requestType}} request) {
super(request);
this.request = request;{{.paramsSet}}
public {{.packetName}}({{.params}}{{if .HasRequestBody}}{{.requestType}} request{{end}}) {
{{if .HasRequestBody}}super(request);{{else}}super(EmptyRequest.instance);{{end}}
{{if .HasRequestBody}}this.request = request;{{end}}{{.paramsSetter}}
}
@Override
@@ -47,32 +36,6 @@ public class {{.packetName}} extends HttpRequestPacket<{{.packetName}}.{{.packet
public String requestUri() {
return {{.uri}};
}
@Override
public {{.packetName}}Response newInstanceFrom(JSON json) {
return new {{.packetName}}Response(json);
}
public static class {{.packetName}}Response extends HttpResponseData {
private {{.responseType}} responseData;
{{.packetName}}Response(@NotNull JSON json) {
super(json);
JSONObject jsonObject = json.asObject();
if (JsonParser.hasKey(jsonObject, "data")) {
Gson gson = new Gson();
JSONObject dataJson = JsonParser.getJSONObject(jsonObject, "data");
responseData = gson.fromJson(dataJson.toString(), {{.responseType}}.class);
}
}
public {{.responseType}} get{{.responseType}} () {
return responseData;
}
}
{{.types}}
}
`
@@ -87,10 +50,11 @@ func genPacket(dir, packetName string, api *spec.ApiSpec) error {
}
func createWith(dir string, api *spec.ApiSpec, route spec.Route, packetName string) error {
packet, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
packet := route.Handler
packet = strings.Replace(packet, "Handler", "Packet", 1)
if !ok {
return fmt.Errorf("missing packet annotation for %q", route.Path)
packet = strings.Title(packet)
if !strings.HasSuffix(packet, "Packet") {
packet += "Packet"
}
javaFile := packet + ".java"
@@ -103,80 +67,69 @@ func createWith(dir string, api *spec.ApiSpec, route spec.Route, packetName stri
}
defer fp.Close()
var builder strings.Builder
var first bool
tps := apiutil.GetLocalTypes(api, route)
for _, tp := range tps {
if first {
first = false
} else {
fmt.Fprintln(&builder)
}
if err := genType(&builder, tp); err != nil {
return err
var hasRequestBody = false
if route.RequestType != nil {
if defineStruct, ok := route.RequestType.(spec.DefineStruct); ok {
hasRequestBody = len(defineStruct.GetBodyMembers()) > 0 || len(defineStruct.GetFormMembers()) > 0
}
}
types := builder.String()
writeIndent(&builder, 1)
params := paramsForRoute(route)
params := strings.TrimSpace(paramsForRoute(route))
if len(params) > 0 && hasRequestBody {
params += ", "
}
paramsDeclaration := declarationForRoute(route)
paramsSet := paramsSet(route)
paramsSetter := paramsSet(route)
imports := getImports(api, packetName)
if len(route.ResponseTypeName()) == 0 {
imports += fmt.Sprintf("\v%s", "import com.xhb.core.response.EmptyResponse;")
}
t := template.Must(template.New("packetTemplate").Parse(packetTemplate))
var tmplBytes bytes.Buffer
err = t.Execute(&tmplBytes, map[string]string{
err = t.Execute(&tmplBytes, map[string]interface{}{
"packetName": packet,
"method": strings.ToUpper(route.Method),
"uri": processUri(route),
"types": strings.TrimSpace(types),
"responseType": stringx.TakeOne(util.Title(route.ResponseType.Name), "Object"),
"responseType": stringx.TakeOne(util.Title(route.ResponseTypeName()), "EmptyResponse"),
"params": params,
"paramsDeclaration": strings.TrimSpace(paramsDeclaration),
"paramsSet": paramsSet,
"paramsSetter": paramsSetter,
"packet": packetName,
"requestType": util.Title(route.RequestType.Name),
"import": getImports(api, route, packetName),
"requestType": util.Title(route.RequestTypeName()),
"HasRequestBody": hasRequestBody,
"imports": imports,
"doc": doc(route),
})
if err != nil {
return err
}
formatFile(&tmplBytes, fp)
_, err = fp.WriteString(formatSource(tmplBytes.String()))
return nil
}
func getImports(api *spec.ApiSpec, route spec.Route, packetName string) string {
var builder strings.Builder
allTypes := apiutil.GetAllTypes(api, route)
sharedTypes := apiutil.GetSharedTypes(api)
for _, at := range allTypes {
for _, item := range sharedTypes {
if item.Name == at.Name {
fmt.Fprintf(&builder, "import com.xhb.logic.http.packet.%s.model.%s;\n", packetName, item.Name)
break
}
}
func doc(route spec.Route) string {
comment := route.JoinedDoc()
if len(comment) > 0 {
formatter := `
/*
%s
*/`
return fmt.Sprintf(formatter, comment)
}
return builder.String()
return ""
}
func formatFile(tmplBytes *bytes.Buffer, file *os.File) {
scanner := bufio.NewScanner(tmplBytes)
builder := bufio.NewWriter(file)
defer builder.Flush()
preIsBreakLine := false
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text == "" && preIsBreakLine {
continue
}
preIsBreakLine = text == ""
builder.WriteString(scanner.Text() + "\n")
}
if err := scanner.Err(); err != nil {
fmt.Println(err)
func getImports(api *spec.ApiSpec, packetName string) string {
var builder strings.Builder
allTypes := api.Types
if len(allTypes) > 0 {
fmt.Fprintf(&builder, "import com.xhb.logic.http.packet.%s.model.*;\n", packetName)
}
return builder.String()
}
func paramsSet(route spec.Route) string {
@@ -209,7 +162,7 @@ func paramsForRoute(route spec.Route) string {
builder.WriteString(fmt.Sprintf("String %s, ", cop[1:]))
}
}
return builder.String()
return strings.TrimSuffix(builder.String(), ", ")
}
func declarationForRoute(route spec.Route) string {
@@ -235,6 +188,7 @@ func declarationForRoute(route spec.Route) string {
func processUri(route spec.Route) string {
path := route.Path
var builder strings.Builder
cops := strings.Split(path, "/")
for index, cop := range cops {
@@ -255,25 +209,37 @@ func processUri(route spec.Route) string {
result = result[:len(result)-4]
}
if strings.HasPrefix(result, "/") {
result = strings.TrimPrefix(result, "/")
result = "\"" + result
}
return result
return result + formString(route)
}
func genType(writer io.Writer, tp spec.Type) error {
writeIndent(writer, 1)
fmt.Fprintf(writer, "static class %s implements DeProguardable {\n", util.Title(tp.Name))
for _, member := range tp.Members {
if err := writeProperty(writer, member, 2); err != nil {
return err
func formString(route spec.Route) string {
var keyValues []string
if defineStruct, ok := route.RequestType.(spec.DefineStruct); ok {
forms := defineStruct.GetFormMembers()
for _, item := range forms {
name, err := item.GetPropertyName()
if err != nil {
panic(err)
}
strcat := "?"
if len(keyValues) > 0 {
strcat = "&"
}
if item.Type.Name() == "bool" {
name = strings.TrimPrefix(name, "Is")
name = strings.TrimPrefix(name, "is")
keyValues = append(keyValues, fmt.Sprintf(`"%s%s=" + request.is%s()`, strcat, name, strings.Title(name)))
} else {
keyValues = append(keyValues, fmt.Sprintf(`"%s%s=" + request.get%s()`, strcat, name, strings.Title(name)))
}
}
if len(keyValues) > 0 {
return " + " + strings.Join(keyValues, " + ")
}
}
writeBreakline(writer)
writeIndent(writer, 1)
genGetSet(writer, tp, 2)
writeIndent(writer, 1)
fmt.Fprintln(writer, "}")
return nil
return ""
}

View File

@@ -1,52 +1,53 @@
package javagen
import (
"bytes"
"errors"
"fmt"
"io"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
const getSetTemplate = `
{{.indent}}{{.decorator}}
{{.indent}}public {{.returnType}} get{{.property}}() {
{{.indent}} return this.{{.propertyValue}};
{{.indent}}}
{{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) {
{{.indent}} this.{{.propertyValue}} = {{.propertyValue}};
{{.indent}}}
`
func writeProperty(writer io.Writer, member spec.Member, indent int) error {
if len(member.Comment) > 0 {
writeIndent(writer, indent)
fmt.Fprint(writer, member.Comment+util.NL)
}
writeIndent(writer, indent)
ty, err := goTypeToJava(member.Type)
ty, err := specTypeToJava(member.Type)
ty = strings.Replace(ty, "*", "", 1)
if err != nil {
return err
}
name, err := member.GetPropertyName()
if err != nil {
return err
}
_, err = fmt.Fprintf(writer, "private %s %s", ty, name)
if err != nil {
return err
}
writeDefaultValue(writer, member)
err = writeDefaultValue(writer, member)
if err != nil {
return err
}
fmt.Fprint(writer, ";\n")
return err
}
func writeDefaultValue(writer io.Writer, member spec.Member) error {
switch member.Type {
case "string":
javaType, err := specTypeToJava(member.Type)
if err != nil {
return err
}
if javaType == "String" {
_, err := fmt.Fprintf(writer, " = \"\"")
return err
}
@@ -67,97 +68,71 @@ func indentString(indent int) string {
return result
}
func writeBreakline(writer io.Writer) {
fmt.Fprint(writer, "\n")
func specTypeToJava(tp spec.Type) (string, error) {
switch v := tp.(type) {
case spec.DefineStruct:
return util.Title(tp.Name()), nil
case spec.PrimitiveType:
r, ok := primitiveType(tp.Name())
if !ok {
return "", errors.New("unsupported primitive type " + tp.Name())
}
return r, nil
case spec.MapType:
valueType, err := specTypeToJava(v.Value)
if err != nil {
return "", err
}
return fmt.Sprintf("java.util.HashMap<String, %s>", util.Title(valueType)), nil
case spec.ArrayType:
if tp.Name() == "[]byte" {
return "byte[]", nil
}
valueType, err := specTypeToJava(v.Value)
if err != nil {
return "", err
}
switch valueType {
case "int":
return "Integer[]", nil
case "long":
return "Long[]", nil
case "float":
return "Float[]", nil
case "double":
return "Double[]", nil
case "boolean":
return "Boolean[]", nil
}
return fmt.Sprintf("java.util.ArrayList<%s>", util.Title(valueType)), nil
case spec.InterfaceType:
return "Object", nil
case spec.PointerType:
return specTypeToJava(v.Type)
}
return "", errors.New("unsupported primitive type " + tp.Name())
}
func isPrimitiveType(tp string) bool {
switch tp {
case "int", "int32", "int64":
return true
case "float", "float32", "float64":
return true
case "bool":
return true
}
return false
}
func goTypeToJava(tp string) (string, error) {
if len(tp) == 0 {
return "", errors.New("property type empty")
}
if strings.HasPrefix(tp, "*") {
tp = tp[1:]
}
func primitiveType(tp string) (string, bool) {
switch tp {
case "string":
return "String", nil
case "int64":
return "long", nil
case "int", "int8", "int32":
return "int", nil
case "float", "float32", "float64":
return "double", nil
return "String", true
case "int64", "uint64":
return "long", true
case "int", "int8", "int32", "uint", "uint8", "uint16", "uint32":
return "int", true
case "float", "float32":
return "float", true
case "float64":
return "double", true
case "bool":
return "boolean", nil
return "boolean", true
}
if strings.HasPrefix(tp, "[]") {
tys, err := apiutil.DecomposeType(tp)
if err != nil {
return "", err
}
if len(tys) == 0 {
return "", fmt.Errorf("%s tp parse error", tp)
}
return fmt.Sprintf("java.util.ArrayList<%s>", util.Title(tys[0])), nil
} else if strings.HasPrefix(tp, "map") {
tys, err := apiutil.DecomposeType(tp)
if err != nil {
return "", err
}
if len(tys) == 2 {
return "", fmt.Errorf("%s tp parse error", tp)
}
return fmt.Sprintf("java.util.HashMap<String, %s>", util.Title(tys[1])), nil
}
return util.Title(tp), nil
}
func genGetSet(writer io.Writer, tp spec.Type, indent int) error {
t := template.Must(template.New("getSetTemplate").Parse(getSetTemplate))
for _, member := range tp.Members {
var tmplBytes bytes.Buffer
oty, err := goTypeToJava(member.Type)
if err != nil {
return err
}
tyString := oty
decorator := ""
if !isPrimitiveType(member.Type) {
if member.IsOptional() {
decorator = "@org.jetbrains.annotations.Nullable "
} else {
decorator = "@org.jetbrains.annotations.NotNull "
}
tyString = decorator + tyString
}
err = t.Execute(&tmplBytes, map[string]string{
"property": util.Title(member.Name),
"propertyValue": util.Untitle(member.Name),
"type": tyString,
"decorator": decorator,
"returnType": oty,
"indent": indentString(indent),
})
if err != nil {
return err
}
r := tmplBytes.String()
r = strings.Replace(r, " boolean get", " boolean is", 1)
writer.Write([]byte(r))
}
return nil
return "", false
}

View File

@@ -21,11 +21,7 @@ func KtCommand(c *cli.Context) error {
return errors.New("missing -pkg")
}
p, e := parser.NewParser(apiFile)
if e != nil {
return e
}
api, e := p.Parse()
api, e := parser.Parse(apiFile)
if e != nil {
return e
}

View File

@@ -1,6 +1,7 @@
package ktgen
import (
"fmt"
"log"
"strings"
"text/template"
@@ -44,7 +45,7 @@ func parseType(t string) string {
}
if strings.HasPrefix(t, "map") {
tys, e := util.DecomposeType(t)
tys, e := decomposeType(t)
if e != nil {
log.Fatal(e)
}
@@ -68,6 +69,47 @@ func parseType(t string) string {
}
}
func decomposeType(t string) (result []string, err error) {
add := func(tp string) error {
ret, err := decomposeType(tp)
if err != nil {
return err
}
result = append(result, ret...)
return nil
}
if strings.HasPrefix(t, "map") {
t = strings.ReplaceAll(t, "map", "")
if t[0] == '[' {
pos := strings.Index(t, "]")
if pos > 1 {
if err = add(t[1:pos]); err != nil {
return
}
if len(t) > pos+1 {
err = add(t[pos+1:])
return
}
}
}
} else if strings.HasPrefix(t, "[]") {
if len(t) > 2 {
err = add(t[2:])
return
}
} else if strings.HasPrefix(t, "*") {
err = add(t[1:])
return
} else {
result = append(result, t)
return
}
err = fmt.Errorf("bad type %q", t)
return
}
func add(a, i int) int {
return a + i
}

View File

@@ -126,10 +126,25 @@ func genBase(dir, pkg string, api *spec.ApiSpec) error {
}
func genApi(dir, pkg string, api *spec.ApiSpec) error {
name := strcase.ToCamel(api.Info.Title + "Api")
properties := api.Info.Properties
if properties == nil {
return fmt.Errorf("none properties")
}
title := properties["Title"]
if len(title) == 0 {
return fmt.Errorf("none title")
}
desc := properties["Desc"]
if len(desc) == 0 {
return fmt.Errorf("none desc")
}
name := strcase.ToCamel(title + "Api")
path := filepath.Join(dir, name+".kt")
api.Info.Title = name
api.Info.Desc = pkg
api.Info.Desc = desc
e := os.MkdirAll(dir, 0755)
if e != nil {

View File

@@ -1,268 +0,0 @@
package parser
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
)
const (
tokenInfo = "info"
tokenImport = "import"
tokenType = "type"
tokenService = "service"
tokenServiceAnnotation = "@server"
tokenStruct = "struct"
)
type (
ApiStruct struct {
Info string
Type string
Service string
Imports string
serviceBeginLine int
}
apiFileState interface {
process(api *ApiStruct, token string) (apiFileState, error)
}
apiRootState struct {
*baseState
}
apiInfoState struct {
*baseState
}
apiImportState struct {
*baseState
}
apiTypeState struct {
*baseState
}
apiServiceState struct {
*baseState
}
)
func ParseApi(src string) (*ApiStruct, error) {
var buffer = new(bytes.Buffer)
buffer.WriteString(src)
api := new(ApiStruct)
var lineNumber = api.serviceBeginLine
apiFile := baseState{r: bufio.NewReader(buffer), lineNumber: &lineNumber}
st := apiRootState{&apiFile}
for {
st, err := st.process(api, "")
if err == io.EOF {
return api, nil
}
if err != nil {
return nil, fmt.Errorf("near line: %d, %s", lineNumber, err.Error())
}
if st == nil {
return api, nil
}
}
}
func (s *apiRootState) process(api *ApiStruct, _ string) (apiFileState, error) {
var builder strings.Builder
for {
ch, err := s.readSkipComment()
if err != nil {
return nil, err
}
switch {
case isSpace(ch) || isNewline(ch) || ch == leftParenthesis:
token := builder.String()
token = strings.TrimSpace(token)
if len(token) == 0 {
continue
}
builder.Reset()
switch token {
case tokenInfo:
info := apiInfoState{s.baseState}
return info.process(api, token+string(ch))
case tokenImport:
tp := apiImportState{s.baseState}
return tp.process(api, token+string(ch))
case tokenType:
ty := apiTypeState{s.baseState}
return ty.process(api, token+string(ch))
case tokenService:
server := apiServiceState{s.baseState}
return server.process(api, token+string(ch))
case tokenServiceAnnotation:
server := apiServiceState{s.baseState}
return server.process(api, token+string(ch))
default:
if strings.HasPrefix(token, "//") {
continue
}
return nil, errors.New(fmt.Sprintf("invalid token %s at line %d", token, *s.lineNumber))
}
default:
builder.WriteRune(ch)
}
}
}
func (s *apiInfoState) process(api *ApiStruct, token string) (apiFileState, error) {
for {
line, err := s.readLine()
if err != nil {
return nil, err
}
api.Info += newline + token + line
token = ""
if strings.TrimSpace(line) == string(rightParenthesis) {
return &apiRootState{s.baseState}, nil
}
}
}
func (s *apiImportState) process(api *ApiStruct, token string) (apiFileState, error) {
line, err := s.readLine()
if err != nil {
return nil, err
}
line = token + line
line = util.RemoveComment(line)
if len(strings.Fields(line)) != 2 {
return nil, errors.New("import syntax error: " + line)
}
api.Imports += newline + line
return &apiRootState{s.baseState}, nil
}
func (s *apiTypeState) process(api *ApiStruct, token string) (apiFileState, error) {
var blockCount = 0
var braceCount = 0
for {
line, err := s.readLine()
if err != nil {
return nil, err
}
line = token + line
if braceCount == 0 {
line = mayInsertStructKeyword(line)
}
api.Type += newline + newline + line
line = strings.TrimSpace(line)
line = util.RemoveComment(line)
token = ""
if strings.HasSuffix(line, leftBrace) {
blockCount++
braceCount++
}
if strings.HasSuffix(line, string(leftParenthesis)) {
blockCount++
}
if strings.HasSuffix(line, string(rightBrace)) {
blockCount--
braceCount--
}
if strings.HasSuffix(line, string(rightParenthesis)) {
blockCount--
}
if braceCount >= 2 {
return nil, errors.New("nested type not supported: " + line)
}
if braceCount < 0 {
line = strings.TrimSuffix(line, string(rightBrace))
line = strings.TrimSpace(line)
if strings.HasSuffix(line, leftBrace) {
blockCount++
braceCount++
}
}
if blockCount == 0 {
return &apiRootState{s.baseState}, nil
}
}
}
func (s *apiServiceState) process(api *ApiStruct, token string) (apiFileState, error) {
var blockCount = 0
for {
line, err := s.readLineSkipComment()
if err != nil {
return nil, err
}
line = token + line
token = ""
api.Service += newline + line
line = strings.TrimSpace(line)
line = util.RemoveComment(line)
if strings.HasSuffix(line, leftBrace) {
blockCount++
}
if strings.HasSuffix(line, string(leftParenthesis)) {
blockCount++
}
if line == string(rightBrace) {
blockCount--
}
if line == string(rightParenthesis) {
blockCount--
}
if blockCount == 0 {
return &apiRootState{s.baseState}, nil
}
}
}
func mayInsertStructKeyword(line string) string {
line = util.RemoveComment(line)
if !strings.HasSuffix(line, leftBrace) && !strings.HasSuffix(line, string(rightBrace)) {
return line
}
fields := strings.Fields(line)
if stringx.Contains(fields, tokenStruct) ||
stringx.Contains(fields, tokenStruct+leftBrace) ||
stringx.Contains(fields, tokenStruct+leftBrace+string(rightBrace)) ||
len(fields) <= 1 {
return line
}
var insertIndex int
if fields[0] == tokenType {
insertIndex = 2
} else {
insertIndex = 1
}
if insertIndex >= len(fields) {
return line
}
var result []string
result = append(result, fields[:insertIndex]...)
result = append(result, tokenStruct)
result = append(result, fields[insertIndex:]...)
return strings.Join(result, " ")
}

View File

@@ -1,236 +0,0 @@
package parser
import (
"bufio"
"fmt"
"strings"
)
const (
startState = iota
attrNameState
attrValueState
attrColonState
multilineState
)
type baseState struct {
r *bufio.Reader
lineNumber *int
}
func newBaseState(r *bufio.Reader, lineNumber *int) *baseState {
return &baseState{
r: r,
lineNumber: lineNumber,
}
}
func (s *baseState) parseProperties() (map[string]string, error) {
var r = s.r
var attributes = make(map[string]string)
var builder strings.Builder
var key string
var st = startState
for {
ch, err := s.readSkipComment()
if err != nil {
return nil, err
}
switch st {
case startState:
switch {
case isNewline(ch):
return nil, fmt.Errorf("%q should be on the same line with %q", leftParenthesis, infoDirective)
case isSpace(ch):
continue
case ch == leftParenthesis:
st = attrNameState
default:
return nil, fmt.Errorf("unexpected char %q after %q", ch, infoDirective)
}
case attrNameState:
switch {
case isNewline(ch):
if builder.Len() > 0 {
return nil, fmt.Errorf("unexpected newline after %q", builder.String())
}
case isLetterDigit(ch):
builder.WriteRune(ch)
case isSpace(ch):
if builder.Len() > 0 {
key = builder.String()
builder.Reset()
st = attrColonState
}
case ch == colon:
if builder.Len() == 0 {
return nil, fmt.Errorf("unexpected leading %q", ch)
}
key = builder.String()
builder.Reset()
st = attrValueState
case ch == rightParenthesis:
return attributes, nil
}
case attrColonState:
switch {
case isSpace(ch):
continue
case ch == colon:
st = attrValueState
default:
return nil, fmt.Errorf("bad char %q after %q in %q", ch, key, infoDirective)
}
case attrValueState:
switch {
case ch == multilineBeginTag:
if builder.Len() > 0 {
return nil, fmt.Errorf("%q before %q", builder.String(), multilineBeginTag)
} else {
st = multilineState
}
case isSpace(ch):
if builder.Len() > 0 {
builder.WriteRune(ch)
}
case isNewline(ch):
attributes[key] = builder.String()
builder.Reset()
st = attrNameState
case ch == rightParenthesis:
attributes[key] = builder.String()
builder.Reset()
return attributes, nil
default:
builder.WriteRune(ch)
}
case multilineState:
switch {
case ch == multilineEndTag:
attributes[key] = builder.String()
builder.Reset()
st = attrNameState
case isNewline(ch):
var multipleNewlines bool
loopAfterNewline:
for {
next, err := read(r)
if err != nil {
return nil, err
}
switch {
case isSpace(next):
continue
case isNewline(next):
multipleNewlines = true
default:
if err := unread(r); err != nil {
return nil, err
}
break loopAfterNewline
}
}
if multipleNewlines {
fmt.Fprintln(&builder)
} else {
builder.WriteByte(' ')
}
case ch == rightParenthesis:
if builder.Len() > 0 {
attributes[key] = builder.String()
builder.Reset()
}
return attributes, nil
default:
builder.WriteRune(ch)
}
}
}
}
func (s *baseState) read() (rune, error) {
value, err := read(s.r)
if err != nil {
return 0, err
}
if isNewline(value) {
*s.lineNumber++
}
return value, nil
}
func (s *baseState) readSkipComment() (rune, error) {
ch, err := s.read()
if err != nil {
return 0, err
}
if isSlash(ch) {
value, err := s.mayReadToEndOfLine()
if err != nil {
return 0, err
}
if value > 0 {
ch = value
}
}
return ch, nil
}
func (s *baseState) mayReadToEndOfLine() (rune, error) {
ch, err := s.read()
if err != nil {
return 0, err
}
if isSlash(ch) {
for {
value, err := s.read()
if err != nil {
return 0, err
}
if isNewline(value) {
return value, nil
}
}
}
err = s.unread()
return 0, err
}
func (s *baseState) readLineSkipComment() (string, error) {
line, err := s.readLine()
if err != nil {
return "", err
}
var commentIdx = strings.Index(line, "//")
if commentIdx >= 0 {
return line[:commentIdx], nil
}
return line, nil
}
func (s *baseState) readLine() (string, error) {
line, _, err := s.r.ReadLine()
if err != nil {
return "", err
}
*s.lineNumber++
return string(line), nil
}
func (s *baseState) skipSpaces() error {
return skipSpaces(s.r)
}
func (s *baseState) unread() error {
return unread(s.r)
}

View File

@@ -1,20 +0,0 @@
package parser
import (
"bufio"
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestProperties(t *testing.T) {
const text = `(summary: hello world)`
var builder bytes.Buffer
builder.WriteString(text)
var lineNumber = 1
var state = newBaseState(bufio.NewReader(&builder), &lineNumber)
m, err := state.parseProperties()
assert.Nil(t, err)
assert.Equal(t, "hello world", m["summary"])
}

View File

@@ -1,146 +0,0 @@
package parser
import (
"errors"
"fmt"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
)
type (
entity struct {
state *baseState
api *spec.ApiSpec
parser entityParser
}
entityParser interface {
parseLine(line string, api *spec.ApiSpec, annos []spec.Annotation) error
setEntityName(name string)
}
)
func newEntity(state *baseState, api *spec.ApiSpec, parser entityParser) entity {
return entity{
state: state,
api: api,
parser: parser,
}
}
func (s *entity) process() error {
line, err := s.state.readLineSkipComment()
if err != nil {
return err
}
fields := strings.Fields(line)
if len(fields) < 2 {
return fmt.Errorf("invalid type definition for %q",
strings.TrimSpace(strings.Trim(string(line), "{")))
}
if len(fields) == 2 {
if fields[1] != leftBrace {
return fmt.Errorf("bad string %q after type", fields[1])
}
} else if len(fields) == 3 {
if fields[1] != typeStruct {
return fmt.Errorf("bad string %q after type", fields[1])
}
if fields[2] != leftBrace {
return fmt.Errorf("bad string %q after type", fields[2])
}
}
s.parser.setEntityName(fields[0])
var annos []spec.Annotation
memberLoop:
for {
ch, err := s.state.readSkipComment()
if err != nil {
return err
}
var annoName string
var builder strings.Builder
switch {
case ch == at:
annotationLoop:
for {
next, err := s.state.readSkipComment()
if err != nil {
return err
}
switch {
case isSpace(next):
if builder.Len() > 0 && annoName == "" {
annoName = builder.String()
builder.Reset()
}
case isNewline(next):
if builder.Len() == 0 {
return errors.New("invalid annotation format")
}
if len(annoName) > 0 {
value := builder.String()
if value != string(leftParenthesis) {
builder.Reset()
annos = append(annos, spec.Annotation{
Name: annoName,
Value: value,
})
annoName = ""
break annotationLoop
}
}
case next == leftParenthesis:
if builder.Len() == 0 {
return errors.New("invalid annotation format")
}
annoName = builder.String()
builder.Reset()
if err := s.state.unread(); err != nil {
return err
}
attrs, err := s.state.parseProperties()
if err != nil {
return err
}
annos = append(annos, spec.Annotation{
Name: annoName,
Properties: attrs,
})
annoName = ""
break annotationLoop
default:
builder.WriteRune(next)
}
}
case ch == rightBrace:
break memberLoop
case isLetterDigit(ch):
if err := s.state.unread(); err != nil {
return err
}
var line string
line, err = s.state.readLineSkipComment()
if err != nil {
return err
}
line = strings.TrimSpace(line)
if err := s.parser.parseLine(line, s.api, annos); err != nil {
return err
}
annos = nil
}
}
return nil
}

View File

@@ -0,0 +1,46 @@
lexer grammar ApiLexer;
// Keywords
ATDOC: '@doc';
ATHANDLER: '@handler';
INTERFACE: 'interface{}';
ATSERVER: '@server';
// Whitespace and comments
WS: [ \t\r\n\u000C]+ -> channel(HIDDEN);
COMMENT: '/*' .*? '*/' -> channel(88);
LINE_COMMENT: '//' ~[\r\n]* -> channel(88);
STRING: '"' (~["\\] | EscapeSequence)* '"';
RAW_STRING: '`' (~[`\\\r\n] | EscapeSequence)+ '`';
LINE_VALUE: ':' [ \t]* (STRING|(~[\r\n"`]*));
ID: Letter LetterOrDigit*;
fragment ExponentPart
: [eE] [+-]? Digits
;
fragment EscapeSequence
: '\\' [btnfr"'\\]
| '\\' ([0-3]? [0-7])? [0-7]
| '\\' 'u'+ HexDigit HexDigit HexDigit HexDigit
;
fragment HexDigits
: HexDigit ((HexDigit | '_')* HexDigit)?
;
fragment HexDigit
: [0-9a-fA-F]
;
fragment Digits
: [0-9] ([0-9_]* [0-9])?
;
fragment LetterOrDigit
: Letter
| [0-9]
;
fragment Letter
: [a-zA-Z$_] // these are the "java letters" below 0x7F
| ~[\u0000-\u007F\uD800-\uDBFF] // covers all characters above 0x7F which are not a surrogate
| [\uD800-\uDBFF] [\uDC00-\uDFFF] // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF
;

View File

@@ -0,0 +1,73 @@
grammar ApiParser;
import ApiLexer;
@lexer::members{
const COMEMNTS = 88
}
api: spec*;
spec: syntaxLit
|importSpec
|infoSpec
|typeSpec
|serviceSpec
;
// syntax
syntaxLit: {match(p,"syntax")}syntaxToken=ID assign='=' {checkVersion(p)}version=STRING;
// import
importSpec: importLit|importBlock;
importLit: {match(p,"import")}importToken=ID importValue ;
importBlock: {match(p,"import")}importToken=ID '(' importBlockValue+ ')';
importBlockValue: importValue;
importValue: {checkImportValue(p)}STRING;
// info
infoSpec: {match(p,"info")}infoToken=ID lp='(' kvLit+ rp=')';
// type
typeSpec: typeLit
|typeBlock;
// eg: type Foo int
typeLit: {match(p,"type")}typeToken=ID typeLitBody;
// eg: type (...)
typeBlock: {match(p,"type")}typeToken=ID lp='(' typeBlockBody* rp=')';
typeLitBody: typeStruct|typeAlias;
typeBlockBody: typeBlockStruct|typeBlockAlias;
typeStruct: {checkKeyword(p)}structName=ID structToken=ID? lbrace='{' field* rbrace='}';
typeAlias: {checkKeyword(p)}alias=ID assign='='? dataType;
typeBlockStruct: {checkKeyword(p)}structName=ID structToken=ID? lbrace='{' field* rbrace='}';
typeBlockAlias: {checkKeyword(p)}alias=ID assign='='? dataType;
field: {isNormal(p)}? normalField|anonymousFiled ;
normalField: {checkKeyword(p)}fieldName=ID dataType tag=RAW_STRING?;
anonymousFiled: star='*'? ID;
dataType: {isInterface(p)}ID
|mapType
|arrayType
|inter='interface{}'
|time='time.Time'
|pointerType
|typeStruct
;
pointerType: star='*' {checkKeyword(p)}ID;
mapType: {match(p,"map")}mapToken=ID lbrack='[' {checkKey(p)}key=ID rbrack=']' value=dataType;
arrayType: lbrack='[' rbrack=']' dataType;
// service
serviceSpec: atServer? serviceApi;
atServer: ATSERVER lp='(' kvLit+ rp=')';
serviceApi: {match(p,"service")}serviceToken=ID serviceName lbrace='{' serviceRoute* rbrace='}';
serviceRoute: atDoc? (atServer|atHandler) route;
atDoc: ATDOC lp='('? ((kvLit+)|STRING) rp=')'?;
atHandler: ATHANDLER ID;
route: {checkHttpMethod(p)}httpMethod=ID path request=body? returnToken=ID? response=replybody?;
body: lp='(' (ID)? rp=')';
replybody: lp='(' dataType? rp=')';
// kv
kvLit: key=ID {checkKeyValue(p)}value=LINE_VALUE;
serviceName: (ID '-'?)+;
path: (('/' (ID ('-' ID)*))|('/:' (ID ('-' ID)?)))+;

View File

@@ -0,0 +1,251 @@
package ast
import (
"fmt"
"sort"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
)
type Api struct {
LinePrefix string
Syntax *SyntaxExpr
Import []*ImportExpr
importM map[string]PlaceHolder
Info *InfoExpr
Type []TypeExpr
typeM map[string]PlaceHolder
Service []*Service
serviceM map[string]PlaceHolder
handlerM map[string]PlaceHolder
routeM map[string]PlaceHolder
}
func (v *ApiVisitor) VisitApi(ctx *api.ApiContext) interface{} {
defer func() {
if p := recover(); p != nil {
panic(fmt.Errorf("%+v", p))
}
}()
var final Api
final.importM = map[string]PlaceHolder{}
final.typeM = map[string]PlaceHolder{}
final.serviceM = map[string]PlaceHolder{}
final.handlerM = map[string]PlaceHolder{}
final.routeM = map[string]PlaceHolder{}
for _, each := range ctx.AllSpec() {
root := each.Accept(v).(*Api)
if root.Syntax != nil {
if final.Syntax != nil {
v.panic(root.Syntax.Syntax, fmt.Sprintf("mutiple syntax declaration"))
}
final.Syntax = root.Syntax
}
for _, imp := range root.Import {
if _, ok := final.importM[imp.Value.Text()]; ok {
v.panic(imp.Import, fmt.Sprintf("duplicate import '%s'", imp.Value.Text()))
}
final.importM[imp.Value.Text()] = Holder
final.Import = append(final.Import, imp)
}
if root.Info != nil {
infoM := map[string]PlaceHolder{}
if final.Info != nil {
v.panic(root.Info.Info, fmt.Sprintf("mutiple info declaration"))
}
for _, value := range root.Info.Kvs {
if _, ok := infoM[value.Key.Text()]; ok {
v.panic(value.Key, fmt.Sprintf("duplicate key '%s'", value.Key.Text()))
}
infoM[value.Key.Text()] = Holder
}
final.Info = root.Info
}
for _, tp := range root.Type {
if _, ok := final.typeM[tp.NameExpr().Text()]; ok {
v.panic(tp.NameExpr(), fmt.Sprintf("duplicate type '%s'", tp.NameExpr().Text()))
}
final.typeM[tp.NameExpr().Text()] = Holder
final.Type = append(final.Type, tp)
}
for _, service := range root.Service {
if _, ok := final.serviceM[service.ServiceApi.Name.Text()]; !ok && len(final.serviceM) > 0 {
v.panic(service.ServiceApi.Name, fmt.Sprintf("mutiple service declaration"))
}
if service.AtServer != nil {
atServerM := map[string]PlaceHolder{}
for _, kv := range service.AtServer.Kv {
if _, ok := atServerM[kv.Key.Text()]; ok {
v.panic(kv.Key, fmt.Sprintf("duplicate key '%s'", kv.Key.Text()))
}
atServerM[kv.Key.Text()] = Holder
}
}
for _, route := range service.ServiceApi.ServiceRoute {
uniqueRoute := fmt.Sprintf("%s %s", route.Route.Method.Text(), route.Route.Path.Text())
if _, ok := final.routeM[uniqueRoute]; ok {
v.panic(route.Route.Method, fmt.Sprintf("duplicate route '%s'", uniqueRoute))
}
final.routeM[uniqueRoute] = Holder
var handlerExpr Expr
if route.AtServer != nil {
atServerM := map[string]PlaceHolder{}
for _, kv := range route.AtServer.Kv {
if _, ok := atServerM[kv.Key.Text()]; ok {
v.panic(kv.Key, fmt.Sprintf("duplicate key '%s'", kv.Key.Text()))
}
atServerM[kv.Key.Text()] = Holder
if kv.Key.Text() == "handler" {
handlerExpr = kv.Value
}
}
}
if route.AtHandler != nil {
handlerExpr = route.AtHandler.Name
}
if handlerExpr == nil {
v.panic(route.Route.Method, fmt.Sprintf("mismtached handler"))
}
if handlerExpr.Text() == "" {
v.panic(handlerExpr, fmt.Sprintf("mismtached handler"))
}
if _, ok := final.handlerM[handlerExpr.Text()]; ok {
v.panic(handlerExpr, fmt.Sprintf("duplicate handler '%s'", handlerExpr.Text()))
}
final.handlerM[handlerExpr.Text()] = Holder
}
final.Service = append(final.Service, service)
}
}
return &final
}
func (v *ApiVisitor) VisitSpec(ctx *api.SpecContext) interface{} {
var root Api
if ctx.SyntaxLit() != nil {
root.Syntax = ctx.SyntaxLit().Accept(v).(*SyntaxExpr)
}
if ctx.ImportSpec() != nil {
root.Import = ctx.ImportSpec().Accept(v).([]*ImportExpr)
}
if ctx.InfoSpec() != nil {
root.Info = ctx.InfoSpec().Accept(v).(*InfoExpr)
}
if ctx.TypeSpec() != nil {
tp := ctx.TypeSpec().Accept(v)
root.Type = tp.([]TypeExpr)
}
if ctx.ServiceSpec() != nil {
root.Service = []*Service{ctx.ServiceSpec().Accept(v).(*Service)}
}
return &root
}
func (a *Api) Format() error {
// todo
return nil
}
func (a *Api) Equal(v interface{}) bool {
if v == nil {
return false
}
root, ok := v.(*Api)
if !ok {
return false
}
if !a.Syntax.Equal(root.Syntax) {
return false
}
if len(a.Import) != len(root.Import) {
return false
}
var expectingImport, actualImport []*ImportExpr
expectingImport = append(expectingImport, a.Import...)
actualImport = append(actualImport, root.Import...)
sort.Slice(expectingImport, func(i, j int) bool {
return expectingImport[i].Value.Text() < expectingImport[j].Value.Text()
})
sort.Slice(actualImport, func(i, j int) bool {
return actualImport[i].Value.Text() < actualImport[j].Value.Text()
})
for index, each := range expectingImport {
ac := actualImport[index]
if !each.Equal(ac) {
return false
}
}
if !a.Info.Equal(root.Info) {
return false
}
if len(a.Type) != len(root.Type) {
return false
}
var expectingType, actualType []TypeExpr
expectingType = append(expectingType, a.Type...)
actualType = append(actualType, root.Type...)
sort.Slice(expectingType, func(i, j int) bool {
return expectingType[i].NameExpr().Text() < expectingType[j].NameExpr().Text()
})
sort.Slice(actualType, func(i, j int) bool {
return actualType[i].NameExpr().Text() < actualType[j].NameExpr().Text()
})
for index, each := range expectingType {
ac := actualType[index]
if !each.Equal(ac) {
return false
}
}
if len(a.Service) != len(root.Service) {
return false
}
var expectingService, actualService []*Service
expectingService = append(expectingService, a.Service...)
actualService = append(actualService, root.Service...)
for index, each := range expectingService {
ac := actualService[index]
if !each.Equal(ac) {
return false
}
}
return true
}

View File

@@ -0,0 +1,405 @@
package ast
import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/antlr/antlr4/runtime/Go/antlr"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
)
type (
Parser struct {
linePrefix string
debug bool
log console.Console
antlr.DefaultErrorListener
}
ParserOption func(p *Parser)
)
func NewParser(options ...ParserOption) *Parser {
p := &Parser{
log: console.NewColorConsole(),
}
for _, opt := range options {
opt(p)
}
return p
}
// Accept can parse any terminalNode of api tree by fn.
// -- for debug
func (p *Parser) Accept(fn func(p *api.ApiParserParser, visitor *ApiVisitor) interface{}, content string) (v interface{}, err error) {
defer func() {
p := recover()
if p != nil {
switch e := p.(type) {
case error:
err = e
default:
err = fmt.Errorf("%+v", p)
}
}
}()
inputStream := antlr.NewInputStream(content)
lexer := api.NewApiParserLexer(inputStream)
lexer.RemoveErrorListeners()
tokens := antlr.NewCommonTokenStream(lexer, antlr.LexerDefaultTokenChannel)
apiParser := api.NewApiParserParser(tokens)
apiParser.RemoveErrorListeners()
apiParser.AddErrorListener(p)
var visitorOptions []VisitorOption
visitorOptions = append(visitorOptions, WithVisitorPrefix(p.linePrefix))
if p.debug {
visitorOptions = append(visitorOptions, WithVisitorDebug())
}
visitor := NewApiVisitor(visitorOptions...)
v = fn(apiParser, visitor)
return
}
// Parse is used to parse the api from the specified file name
func (p *Parser) Parse(filename string) (*Api, error) {
data, err := p.readContent(filename)
if err != nil {
return nil, err
}
return p.parse(filename, data)
}
// ParseContent is used to parse the api from the specified content
func (p *Parser) ParseContent(content string) (*Api, error) {
return p.parse("", content)
}
// parse is used to parse api from the content
// filename is only used to mark the file where the error is located
func (p *Parser) parse(filename, content string) (*Api, error) {
root, err := p.invoke(filename, content)
if err != nil {
return nil, err
}
var apiAstList []*Api
apiAstList = append(apiAstList, root)
for _, imp := range root.Import {
path := imp.Value.Text()
data, err := p.readContent(path)
if err != nil {
return nil, err
}
nestedApi, err := p.invoke(path, data)
if err != nil {
return nil, err
}
err = p.valid(root, nestedApi)
if err != nil {
return nil, err
}
apiAstList = append(apiAstList, nestedApi)
}
err = p.checkTypeDeclaration(apiAstList)
if err != nil {
return nil, err
}
allApi := p.memberFill(apiAstList)
return allApi, nil
}
func (p *Parser) invoke(linePrefix, content string) (v *Api, err error) {
defer func() {
p := recover()
if p != nil {
switch e := p.(type) {
case error:
err = e
default:
err = fmt.Errorf("%+v", p)
}
}
}()
if linePrefix != "" {
p.linePrefix = linePrefix
}
inputStream := antlr.NewInputStream(content)
lexer := api.NewApiParserLexer(inputStream)
lexer.RemoveErrorListeners()
tokens := antlr.NewCommonTokenStream(lexer, antlr.LexerDefaultTokenChannel)
apiParser := api.NewApiParserParser(tokens)
apiParser.RemoveErrorListeners()
apiParser.AddErrorListener(p)
var visitorOptions []VisitorOption
visitorOptions = append(visitorOptions, WithVisitorPrefix(p.linePrefix))
if p.debug {
visitorOptions = append(visitorOptions, WithVisitorDebug())
}
visitor := NewApiVisitor(visitorOptions...)
v = apiParser.Api().Accept(visitor).(*Api)
v.LinePrefix = p.linePrefix
return
}
func (p *Parser) valid(mainApi *Api, nestedApi *Api) error {
if len(nestedApi.Import) > 0 {
importToken := nestedApi.Import[0].Import
return fmt.Errorf("%s line %d:%d the nested api does not support import",
nestedApi.LinePrefix, importToken.Line(), importToken.Column())
}
if mainApi.Syntax != nil && nestedApi.Syntax != nil {
if mainApi.Syntax.Version.Text() != nestedApi.Syntax.Version.Text() {
syntaxToken := nestedApi.Syntax.Syntax
return fmt.Errorf("%s line %d:%d multiple syntax declaration, expecting syntax '%s', but found '%s'",
nestedApi.LinePrefix, syntaxToken.Line(), syntaxToken.Column(), mainApi.Syntax.Version.Text(), nestedApi.Syntax.Version.Text())
}
}
if len(mainApi.Service) > 0 {
mainService := mainApi.Service[0]
for _, service := range nestedApi.Service {
if mainService.ServiceApi.Name.Text() != service.ServiceApi.Name.Text() {
return fmt.Errorf("%s multiple service name declaration, expecting service name '%s', but found '%s'",
nestedApi.LinePrefix, mainService.ServiceApi.Name.Text(), service.ServiceApi.Name.Text())
}
}
}
mainHandlerMap := make(map[string]PlaceHolder)
mainRouteMap := make(map[string]PlaceHolder)
mainTypeMap := make(map[string]PlaceHolder)
routeMap := func(list []*ServiceRoute) (map[string]PlaceHolder, map[string]PlaceHolder) {
handlerMap := make(map[string]PlaceHolder)
routeMap := make(map[string]PlaceHolder)
for _, g := range list {
handler := g.GetHandler()
if handler.IsNotNil() {
var handlerName = handler.Text()
handlerMap[handlerName] = Holder
path := fmt.Sprintf("%s://%s", g.Route.Method.Text(), g.Route.Path.Text())
routeMap[path] = Holder
}
}
return handlerMap, routeMap
}
for _, each := range mainApi.Service {
h, r := routeMap(each.ServiceApi.ServiceRoute)
for k, v := range h {
mainHandlerMap[k] = v
}
for k, v := range r {
mainRouteMap[k] = v
}
}
for _, each := range mainApi.Type {
mainTypeMap[each.NameExpr().Text()] = Holder
}
// duplicate route check
for _, each := range nestedApi.Service {
for _, r := range each.ServiceApi.ServiceRoute {
handler := r.GetHandler()
if !handler.IsNotNil() {
return fmt.Errorf("%s handler not exist near line %d", nestedApi.LinePrefix, r.Route.Method.Line())
}
if _, ok := mainHandlerMap[handler.Text()]; ok {
return fmt.Errorf("%s line %d:%d duplicate handler '%s'",
nestedApi.LinePrefix, handler.Line(), handler.Column(), handler.Text())
}
path := fmt.Sprintf("%s://%s", r.Route.Method.Text(), r.Route.Path.Text())
if _, ok := mainRouteMap[path]; ok {
return fmt.Errorf("%s line %d:%d duplicate route '%s'",
nestedApi.LinePrefix, r.Route.Method.Line(), r.Route.Method.Column(), r.Route.Method.Text()+" "+r.Route.Path.Text())
}
}
}
// duplicate type check
for _, each := range nestedApi.Type {
if _, ok := mainTypeMap[each.NameExpr().Text()]; ok {
return fmt.Errorf("%s line %d:%d duplicate type declaration '%s'",
nestedApi.LinePrefix, each.NameExpr().Line(), each.NameExpr().Column(), each.NameExpr().Text())
}
}
return nil
}
func (p *Parser) memberFill(apiList []*Api) *Api {
var root Api
for index, each := range apiList {
if index == 0 {
root.Syntax = each.Syntax
root.Info = each.Info
root.Import = each.Import
}
root.Type = append(root.Type, each.Type...)
root.Service = append(root.Service, each.Service...)
}
return &root
}
// checkTypeDeclaration checks whether a struct type has been declared in context
func (p *Parser) checkTypeDeclaration(apiList []*Api) error {
types := make(map[string]TypeExpr)
for _, root := range apiList {
for _, each := range root.Type {
types[each.NameExpr().Text()] = each
}
}
for _, apiItem := range apiList {
linePrefix := apiItem.LinePrefix
for _, each := range apiItem.Type {
tp, ok := each.(*TypeStruct)
if !ok {
continue
}
for _, member := range tp.Fields {
err := p.checkType(linePrefix, types, member.DataType)
if err != nil {
return err
}
}
}
for _, service := range apiItem.Service {
for _, each := range service.ServiceApi.ServiceRoute {
route := each.Route
if route.Req != nil && route.Req.Name.IsNotNil() && route.Req.Name.Expr().IsNotNil() {
_, ok := types[route.Req.Name.Expr().Text()]
if !ok {
return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
linePrefix, route.Req.Name.Expr().Line(), route.Req.Name.Expr().Column(), route.Req.Name.Expr().Text())
}
}
if route.Reply != nil && route.Reply.Name.IsNotNil() && route.Reply.Name.Expr().IsNotNil() {
reply := route.Reply.Name
var structName string
switch tp := reply.(type) {
case *Literal:
structName = tp.Literal.Text()
case *Array:
switch innerTp := tp.Literal.(type) {
case *Literal:
structName = innerTp.Literal.Text()
case *Pointer:
structName = innerTp.Name.Text()
}
}
if api.IsBasicType(structName) {
continue
}
_, ok := types[structName]
if !ok {
return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
linePrefix, route.Reply.Name.Expr().Line(), route.Reply.Name.Expr().Column(), structName)
}
}
}
}
}
return nil
}
func (p *Parser) checkType(linePrefix string, types map[string]TypeExpr, expr DataType) error {
if expr == nil {
return nil
}
switch v := expr.(type) {
case *Literal:
name := v.Literal.Text()
if api.IsBasicType(name) {
return nil
}
_, ok := types[name]
if !ok {
return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
linePrefix, v.Literal.Line(), v.Literal.Column(), name)
}
case *Pointer:
name := v.Name.Text()
if api.IsBasicType(name) {
return nil
}
_, ok := types[name]
if !ok {
return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
linePrefix, v.Name.Line(), v.Name.Column(), name)
}
case *Map:
return p.checkType(linePrefix, types, v.Value)
case *Array:
return p.checkType(linePrefix, types, v.Literal)
default:
return nil
}
return nil
}
func (p *Parser) readContent(filename string) (string, error) {
filename = strings.ReplaceAll(filename, `"`, "")
abs, err := filepath.Abs(filename)
if err != nil {
return "", err
}
data, err := ioutil.ReadFile(abs)
if err != nil {
return "", err
}
return string(data), nil
}
func (p *Parser) SyntaxError(_ antlr.Recognizer, _ interface{}, line, column int, msg string, _ antlr.RecognitionException) {
str := fmt.Sprintf(`%s line %d:%d %s`, p.linePrefix, line, column, msg)
if p.debug {
p.log.Error(str)
}
panic(str)
}
func WithParserDebug() ParserOption {
return func(p *Parser) {
p.debug = true
}
}
func WithParserPrefix(prefix string) ParserOption {
return func(p *Parser) {
p.linePrefix = prefix
}
}

View File

@@ -0,0 +1,325 @@
package ast
import (
"fmt"
"sort"
"strings"
"github.com/antlr/antlr4/runtime/Go/antlr"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
)
type (
TokenStream interface {
GetStart() antlr.Token
GetStop() antlr.Token
GetParser() antlr.Parser
}
ApiVisitor struct {
api.BaseApiParserVisitor
debug bool
log console.Console
prefix string
infoFlag bool
}
VisitorOption func(v *ApiVisitor)
Spec interface {
Doc() []Expr
Comment() Expr
Format() error
Equal(v interface{}) bool
}
Expr interface {
Prefix() string
Line() int
Column() int
Text() string
SetText(text string)
Start() int
Stop() int
Equal(expr Expr) bool
IsNotNil() bool
}
)
func NewApiVisitor(options ...VisitorOption) *ApiVisitor {
v := &ApiVisitor{
log: console.NewColorConsole(),
}
for _, opt := range options {
opt(v)
}
return v
}
func (v *ApiVisitor) panic(expr Expr, msg string) {
errString := fmt.Sprintf("%s line %d:%d %s", v.prefix, expr.Line(), expr.Column(), msg)
if v.debug {
fmt.Println(errString)
}
panic(errString)
}
func WithVisitorPrefix(prefix string) VisitorOption {
return func(v *ApiVisitor) {
v.prefix = prefix
}
}
func WithVisitorDebug() VisitorOption {
return func(v *ApiVisitor) {
v.debug = true
}
}
type defaultExpr struct {
prefix, v string
line, column int
start, stop int
}
func NewTextExpr(v string) *defaultExpr {
return &defaultExpr{
v: v,
}
}
func (v *ApiVisitor) newExprWithTerminalNode(node antlr.TerminalNode) *defaultExpr {
if node == nil {
return nil
}
token := node.GetSymbol()
return v.newExprWithToken(token)
}
func (v *ApiVisitor) newExprWithToken(token antlr.Token) *defaultExpr {
if token == nil {
return nil
}
instance := &defaultExpr{}
instance.prefix = v.prefix
instance.v = token.GetText()
instance.line = token.GetLine()
instance.column = token.GetColumn()
instance.start = token.GetStart()
instance.stop = token.GetStop()
return instance
}
func (v *ApiVisitor) newExprWithText(text string, line, column, start, stop int) *defaultExpr {
instance := &defaultExpr{}
instance.prefix = v.prefix
instance.v = text
instance.line = line
instance.column = column
instance.start = start
instance.stop = stop
return instance
}
func (e *defaultExpr) Prefix() string {
if e == nil {
return ""
}
return e.prefix
}
func (e *defaultExpr) Line() int {
if e == nil {
return 0
}
return e.line
}
func (e *defaultExpr) Column() int {
if e == nil {
return 0
}
return e.column
}
func (e *defaultExpr) Text() string {
if e == nil {
return ""
}
return e.v
}
func (e *defaultExpr) SetText(text string) {
if e == nil {
return
}
e.v = text
}
func (e *defaultExpr) Start() int {
if e == nil {
return 0
}
return e.start
}
func (e *defaultExpr) Stop() int {
if e == nil {
return 0
}
return e.stop
}
func (e *defaultExpr) Equal(expr Expr) bool {
if e == nil {
if expr != nil {
return false
}
return true
}
if expr == nil {
return false
}
return e.v == expr.Text()
}
func (e *defaultExpr) IsNotNil() bool {
return e != nil
}
func EqualDoc(spec1, spec2 Spec) bool {
if spec1 == nil {
if spec2 != nil {
return false
}
return true
} else {
if spec2 == nil {
return false
}
var expectDoc, actualDoc []Expr
expectDoc = append(expectDoc, spec2.Doc()...)
actualDoc = append(actualDoc, spec1.Doc()...)
sort.Slice(expectDoc, func(i, j int) bool {
return expectDoc[i].Line() < expectDoc[j].Line()
})
for index, each := range actualDoc {
if !each.Equal(actualDoc[index]) {
return false
}
}
if spec1.Comment() != nil {
if spec2.Comment() == nil {
return false
}
if !spec1.Comment().Equal(spec2.Comment()) {
return false
}
} else {
if spec2.Comment() != nil {
return false
}
}
}
return true
}
func (v *ApiVisitor) getDoc(t TokenStream) []Expr {
list := v.getHiddenTokensToLeft(t, api.COMEMNTS, false)
return list
}
func (v *ApiVisitor) getComment(t TokenStream) Expr {
list := v.getHiddenTokensToRight(t, api.COMEMNTS)
if len(list) == 0 {
return nil
}
commentExpr := list[0]
stop := t.GetStop()
text := stop.GetText()
nlCount := strings.Count(text, "\n")
if commentExpr.Line() != stop.GetLine()+nlCount {
return nil
}
return commentExpr
}
func (v *ApiVisitor) getHiddenTokensToLeft(t TokenStream, channel int, containsCommentOfDefaultChannel bool) []Expr {
ct := t.GetParser().GetTokenStream().(*antlr.CommonTokenStream)
tokens := ct.GetHiddenTokensToLeft(t.GetStart().GetTokenIndex(), channel)
var tmp []antlr.Token
for _, each := range tokens {
tmp = append(tmp, each)
}
var list []Expr
for _, each := range tmp {
if !containsCommentOfDefaultChannel {
index := each.GetTokenIndex() - 1
if index > 0 {
allTokens := ct.GetAllTokens()
var flag = false
for i := index; i >= 0; i-- {
tk := allTokens[i]
if tk.GetChannel() == antlr.LexerDefaultTokenChannel {
if tk.GetLine() == each.GetLine() {
flag = true
break
}
}
}
if flag {
continue
}
}
}
list = append(list, v.newExprWithToken(each))
}
return list
}
func (v *ApiVisitor) getHiddenTokensToRight(t TokenStream, channel int) []Expr {
ct := t.GetParser().GetTokenStream().(*antlr.CommonTokenStream)
tokens := ct.GetHiddenTokensToRight(t.GetStop().GetTokenIndex(), channel)
var list []Expr
for _, each := range tokens {
list = append(list, v.newExprWithToken(each))
}
return list
}
func (v *ApiVisitor) exportCheck(expr Expr) {
if expr == nil || !expr.IsNotNil() {
return
}
if api.IsBasicType(expr.Text()) {
return
}
if util.UnExport(expr.Text()) {
v.log.Warning("%s line %d:%d unexported declaration '%s', use %s instead", expr.Prefix(), expr.Line(),
expr.Column(), expr.Text(), strings.Title(expr.Text()))
}
}

View File

@@ -0,0 +1,96 @@
package ast
import (
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
)
type ImportExpr struct {
Import Expr
Value Expr
DocExpr []Expr
CommentExpr Expr
}
func (v *ApiVisitor) VisitImportSpec(ctx *api.ImportSpecContext) interface{} {
var list []*ImportExpr
if ctx.ImportLit() != nil {
lits := ctx.ImportLit().Accept(v).([]*ImportExpr)
list = append(list, lits...)
}
if ctx.ImportBlock() != nil {
blocks := ctx.ImportBlock().Accept(v).([]*ImportExpr)
list = append(list, blocks...)
}
return list
}
func (v *ApiVisitor) VisitImportLit(ctx *api.ImportLitContext) interface{} {
importToken := v.newExprWithToken(ctx.GetImportToken())
valueExpr := ctx.ImportValue().Accept(v).(Expr)
return []*ImportExpr{
{
Import: importToken,
Value: valueExpr,
DocExpr: v.getDoc(ctx),
CommentExpr: v.getComment(ctx),
},
}
}
func (v *ApiVisitor) VisitImportBlock(ctx *api.ImportBlockContext) interface{} {
importToken := v.newExprWithToken(ctx.GetImportToken())
values := ctx.AllImportBlockValue()
var list []*ImportExpr
for _, value := range values {
importExpr := value.Accept(v).(*ImportExpr)
importExpr.Import = importToken
list = append(list, importExpr)
}
return list
}
func (v *ApiVisitor) VisitImportBlockValue(ctx *api.ImportBlockValueContext) interface{} {
value := ctx.ImportValue().Accept(v).(Expr)
return &ImportExpr{
Value: value,
DocExpr: v.getDoc(ctx),
CommentExpr: v.getComment(ctx),
}
}
func (v *ApiVisitor) VisitImportValue(ctx *api.ImportValueContext) interface{} {
return v.newExprWithTerminalNode(ctx.STRING())
}
func (i *ImportExpr) Format() error {
// todo
return nil
}
func (i *ImportExpr) Equal(v interface{}) bool {
if v == nil {
return false
}
imp, ok := v.(*ImportExpr)
if !ok {
return false
}
if !EqualDoc(i, imp) {
return false
}
return i.Import.Equal(imp.Import) && i.Value.Equal(imp.Value)
}
func (i *ImportExpr) Doc() []Expr {
return i.DocExpr
}
func (i *ImportExpr) Comment() Expr {
return i.CommentExpr
}

View File

@@ -0,0 +1,67 @@
package ast
import (
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
)
type InfoExpr struct {
Info Expr
Lp Expr
Rp Expr
Kvs []*KvExpr
}
func (v *ApiVisitor) VisitInfoSpec(ctx *api.InfoSpecContext) interface{} {
var expr InfoExpr
expr.Info = v.newExprWithToken(ctx.GetInfoToken())
expr.Lp = v.newExprWithToken(ctx.GetLp())
expr.Rp = v.newExprWithToken(ctx.GetRp())
list := ctx.AllKvLit()
for _, each := range list {
kvExpr := each.Accept(v).(*KvExpr)
expr.Kvs = append(expr.Kvs, kvExpr)
}
if v.infoFlag {
v.panic(expr.Info, "duplicate declaration 'info'")
}
return &expr
}
func (i *InfoExpr) Format() error {
// todo
return nil
}
func (i *InfoExpr) Equal(v interface{}) bool {
if v == nil {
return false
}
info, ok := v.(*InfoExpr)
if !ok {
return false
}
if !i.Info.Equal(info.Info) {
return false
}
var expected, actual []*KvExpr
expected = append(expected, i.Kvs...)
actual = append(actual, info.Kvs...)
if len(expected) != len(actual) {
return false
}
for index, each := range expected {
ac := actual[index]
if !each.Equal(ac) {
return false
}
}
return true
}

View File

@@ -0,0 +1,79 @@
package ast
import (
"strings"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
)
type KvExpr struct {
Key Expr
Value Expr
DocExpr []Expr
CommentExpr Expr
}
func (v *ApiVisitor) VisitKvLit(ctx *api.KvLitContext) interface{} {
var kvExpr KvExpr
kvExpr.Key = v.newExprWithToken(ctx.GetKey())
commentExpr := v.getComment(ctx)
if ctx.GetValue() != nil {
valueText := ctx.GetValue().GetText()
valueExpr := v.newExprWithToken(ctx.GetValue())
if strings.Contains(valueText, "//") {
if commentExpr == nil {
commentExpr = v.newExprWithToken(ctx.GetValue())
commentExpr.SetText("")
}
index := strings.Index(valueText, "//")
commentExpr.SetText(valueText[index:])
valueExpr.SetText(strings.TrimSpace(valueText[:index]))
} else if strings.Contains(valueText, "/*") {
if commentExpr == nil {
commentExpr = v.newExprWithToken(ctx.GetValue())
commentExpr.SetText("")
}
index := strings.Index(valueText, "/*")
commentExpr.SetText(valueText[index:])
valueExpr.SetText(strings.TrimSpace(valueText[:index]))
}
kvExpr.Value = valueExpr
}
kvExpr.DocExpr = v.getDoc(ctx)
kvExpr.CommentExpr = commentExpr
return &kvExpr
}
func (k *KvExpr) Format() error {
// todo
return nil
}
func (k *KvExpr) Equal(v interface{}) bool {
if v == nil {
return false
}
kv, ok := v.(*KvExpr)
if !ok {
return false
}
if !EqualDoc(k, kv) {
return false
}
return k.Key.Equal(kv.Key) && k.Value.Equal(kv.Value)
}
func (k *KvExpr) Doc() []Expr {
return k.DocExpr
}
func (k *KvExpr) Comment() Expr {
return k.CommentExpr
}

View File

@@ -0,0 +1,5 @@
package ast
var Holder PlaceHolder
type PlaceHolder struct{}

View File

@@ -0,0 +1,603 @@
package ast
import (
"fmt"
"sort"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
)
type Service struct {
AtServer *AtServer
ServiceApi *ServiceApi
}
type KV []*KvExpr
type AtServer struct {
AtServerToken Expr
Lp Expr
Rp Expr
Kv KV
}
type ServiceApi struct {
ServiceToken Expr
Name Expr
Lbrace Expr
Rbrace Expr
ServiceRoute []*ServiceRoute
}
type ServiceRoute struct {
AtDoc *AtDoc
AtServer *AtServer
AtHandler *AtHandler
Route *Route
}
type AtDoc struct {
AtDocToken Expr
Lp Expr
Rp Expr
LineDoc Expr
Kv []*KvExpr
}
type AtHandler struct {
AtHandlerToken Expr
Name Expr
DocExpr []Expr
CommentExpr Expr
}
type Route struct {
Method Expr
Path Expr
Req *Body
ReturnToken Expr
Reply *Body
DocExpr []Expr
CommentExpr Expr
}
type Body struct {
Lp Expr
Rp Expr
Name DataType
}
func (v *ApiVisitor) VisitServiceSpec(ctx *api.ServiceSpecContext) interface{} {
var serviceSpec Service
if ctx.AtServer() != nil {
serviceSpec.AtServer = ctx.AtServer().Accept(v).(*AtServer)
}
serviceSpec.ServiceApi = ctx.ServiceApi().Accept(v).(*ServiceApi)
return &serviceSpec
}
func (v *ApiVisitor) VisitAtServer(ctx *api.AtServerContext) interface{} {
var atServer AtServer
atServer.AtServerToken = v.newExprWithTerminalNode(ctx.ATSERVER())
atServer.Lp = v.newExprWithToken(ctx.GetLp())
atServer.Rp = v.newExprWithToken(ctx.GetRp())
for _, each := range ctx.AllKvLit() {
atServer.Kv = append(atServer.Kv, each.Accept(v).(*KvExpr))
}
return &atServer
}
func (v *ApiVisitor) VisitServiceApi(ctx *api.ServiceApiContext) interface{} {
var serviceApi ServiceApi
serviceApi.ServiceToken = v.newExprWithToken(ctx.GetServiceToken())
serviceName := ctx.ServiceName()
serviceApi.Name = v.newExprWithText(serviceName.GetText(), serviceName.GetStart().GetLine(), serviceName.GetStart().GetColumn(), serviceName.GetStart().GetStart(), serviceName.GetStop().GetStop())
serviceApi.Lbrace = v.newExprWithToken(ctx.GetLbrace())
serviceApi.Rbrace = v.newExprWithToken(ctx.GetRbrace())
for _, each := range ctx.AllServiceRoute() {
serviceApi.ServiceRoute = append(serviceApi.ServiceRoute, each.Accept(v).(*ServiceRoute))
}
return &serviceApi
}
func (v *ApiVisitor) VisitServiceRoute(ctx *api.ServiceRouteContext) interface{} {
var serviceRoute ServiceRoute
if ctx.AtDoc() != nil {
serviceRoute.AtDoc = ctx.AtDoc().Accept(v).(*AtDoc)
}
if ctx.AtServer() != nil {
serviceRoute.AtServer = ctx.AtServer().Accept(v).(*AtServer)
} else if ctx.AtHandler() != nil {
serviceRoute.AtHandler = ctx.AtHandler().Accept(v).(*AtHandler)
}
serviceRoute.Route = ctx.Route().Accept(v).(*Route)
return &serviceRoute
}
func (v *ApiVisitor) VisitAtDoc(ctx *api.AtDocContext) interface{} {
var atDoc AtDoc
atDoc.AtDocToken = v.newExprWithTerminalNode(ctx.ATDOC())
if ctx.STRING() != nil {
atDoc.LineDoc = v.newExprWithTerminalNode(ctx.STRING())
} else {
for _, each := range ctx.AllKvLit() {
atDoc.Kv = append(atDoc.Kv, each.Accept(v).(*KvExpr))
}
}
atDoc.Lp = v.newExprWithToken(ctx.GetLp())
atDoc.Rp = v.newExprWithToken(ctx.GetRp())
if ctx.GetLp() != nil {
if ctx.GetRp() == nil {
v.panic(atDoc.Lp, "mismatched ')'")
}
}
if ctx.GetRp() != nil {
if ctx.GetLp() == nil {
v.panic(atDoc.Rp, "mismatched '('")
}
}
return &atDoc
}
func (v *ApiVisitor) VisitAtHandler(ctx *api.AtHandlerContext) interface{} {
var atHandler AtHandler
astHandlerExpr := v.newExprWithTerminalNode(ctx.ATHANDLER())
atHandler.AtHandlerToken = astHandlerExpr
atHandler.Name = v.newExprWithTerminalNode(ctx.ID())
atHandler.DocExpr = v.getDoc(ctx)
atHandler.CommentExpr = v.getComment(ctx)
return &atHandler
}
func (v *ApiVisitor) VisitRoute(ctx *api.RouteContext) interface{} {
var route Route
path := ctx.Path()
methodExpr := v.newExprWithToken(ctx.GetHttpMethod())
route.Method = methodExpr
route.Path = v.newExprWithText(path.GetText(), path.GetStart().GetLine(), path.GetStart().GetColumn(), path.GetStart().GetStart(), path.GetStop().GetStop())
if ctx.GetRequest() != nil {
req := ctx.GetRequest().Accept(v)
if req != nil {
route.Req = req.(*Body)
}
}
if ctx.GetResponse() != nil {
reply := ctx.GetResponse().Accept(v)
if reply != nil {
route.Reply = reply.(*Body)
}
}
if ctx.GetReturnToken() != nil {
returnExpr := v.newExprWithToken(ctx.GetReturnToken())
if ctx.GetReturnToken().GetText() != "returns" {
v.panic(returnExpr, fmt.Sprintf("expecting returns, found input '%s'", ctx.GetReturnToken().GetText()))
}
route.ReturnToken = returnExpr
}
route.DocExpr = v.getDoc(ctx)
route.CommentExpr = v.getComment(ctx)
return &route
}
func (v *ApiVisitor) VisitBody(ctx *api.BodyContext) interface{} {
if ctx.ID() == nil {
return nil
}
idRxpr := v.newExprWithTerminalNode(ctx.ID())
if api.IsGolangKeyWord(idRxpr.Text()) {
v.panic(idRxpr, fmt.Sprintf("expecting 'ID', but found golang keyword '%s'", idRxpr.Text()))
}
v.exportCheck(idRxpr)
return &Body{
Lp: v.newExprWithToken(ctx.GetLp()),
Rp: v.newExprWithToken(ctx.GetRp()),
Name: &Literal{Literal: idRxpr},
}
}
// note: forward compatible
func (v *ApiVisitor) VisitReplybody(ctx *api.ReplybodyContext) interface{} {
if ctx.DataType() == nil {
return nil
}
dt := ctx.DataType().Accept(v).(DataType)
if dt == nil {
return nil
}
switch dataType := dt.(type) {
case *Array:
lit := dataType.Literal
switch lit.(type) {
case *Literal, *Pointer:
if api.IsGolangKeyWord(lit.Expr().Text()) {
v.panic(lit.Expr(), fmt.Sprintf("expecting 'ID', but found golang keyword '%s'", lit.Expr().Text()))
}
default:
v.panic(dt.Expr(), fmt.Sprintf("unsupport %s", dt.Expr().Text()))
}
v.log.Warning("%s %d:%d deprecated array type near '%s'", v.prefix, dataType.ArrayExpr.Line(), dataType.ArrayExpr.Column(), dataType.ArrayExpr.Text())
case *Literal:
lit := dataType.Literal.Text()
if api.IsGolangKeyWord(dataType.Literal.Text()) {
v.panic(dataType.Literal, fmt.Sprintf("expecting 'ID', but found golang keyword '%s'", dataType.Literal.Text()))
}
if api.IsBasicType(lit) {
v.panic(dt.Expr(), fmt.Sprintf("unsupport %s", dt.Expr().Text()))
}
default:
v.panic(dt.Expr(), fmt.Sprintf("unsupport %s", dt.Expr().Text()))
}
return &Body{
Lp: v.newExprWithToken(ctx.GetLp()),
Rp: v.newExprWithToken(ctx.GetRp()),
Name: dt,
}
}
func (b *Body) Format() error {
// todo
return nil
}
func (b *Body) Equal(v interface{}) bool {
if v == nil {
return false
}
body, ok := v.(*Body)
if !ok {
return false
}
if !b.Lp.Equal(body.Lp) {
return false
}
if !b.Rp.Equal(body.Rp) {
return false
}
return b.Name.Equal(body.Name)
}
func (r *Route) Format() error {
// todo
return nil
}
func (r *Route) Doc() []Expr {
return r.DocExpr
}
func (r *Route) Comment() Expr {
return r.CommentExpr
}
func (r *Route) Equal(v interface{}) bool {
if v == nil {
return false
}
route, ok := v.(*Route)
if !ok {
return false
}
if !r.Method.Equal(route.Method) {
return false
}
if !r.Path.Equal(route.Path) {
return false
}
if r.Req != nil {
if !r.Req.Equal(route.Req) {
return false
}
}
if r.ReturnToken != nil {
if !r.ReturnToken.Equal(route.ReturnToken) {
return false
}
}
if r.Reply != nil {
if !r.Reply.Equal(route.Reply) {
return false
}
}
return EqualDoc(r, route)
}
func (a *AtHandler) Doc() []Expr {
return a.DocExpr
}
func (a *AtHandler) Comment() Expr {
return a.CommentExpr
}
func (a *AtHandler) Format() error {
// todo
return nil
}
func (a *AtHandler) Equal(v interface{}) bool {
if v == nil {
return false
}
atHandler, ok := v.(*AtHandler)
if !ok {
return false
}
if !a.AtHandlerToken.Equal(atHandler.AtHandlerToken) {
return false
}
if !a.Name.Equal(atHandler.Name) {
return false
}
return EqualDoc(a, atHandler)
}
func (a *AtDoc) Format() error {
// todo
return nil
}
func (a *AtDoc) Equal(v interface{}) bool {
if v == nil {
return false
}
atDoc, ok := v.(*AtDoc)
if !ok {
return false
}
if !a.AtDocToken.Equal(atDoc.AtDocToken) {
return false
}
if a.Lp.IsNotNil() {
if !a.Lp.Equal(atDoc.Lp) {
return false
}
}
if a.Rp.IsNotNil() {
if !a.Rp.Equal(atDoc.Rp) {
return false
}
}
if a.LineDoc != nil {
if !a.LineDoc.Equal(atDoc.LineDoc) {
return false
}
}
var expecting, actual []*KvExpr
expecting = append(expecting, a.Kv...)
actual = append(actual, atDoc.Kv...)
if len(expecting) != len(actual) {
return false
}
for index, each := range expecting {
ac := actual[index]
if !each.Equal(ac) {
return false
}
}
return true
}
func (a *AtServer) Format() error {
// todo
return nil
}
func (a *AtServer) Equal(v interface{}) bool {
if v == nil {
return false
}
atServer, ok := v.(*AtServer)
if !ok {
return false
}
if !a.AtServerToken.Equal(atServer.AtServerToken) {
return false
}
if !a.Lp.Equal(atServer.Lp) {
return false
}
if !a.Rp.Equal(atServer.Rp) {
return false
}
var expecting, actual []*KvExpr
expecting = append(expecting, a.Kv...)
actual = append(actual, atServer.Kv...)
if len(expecting) != len(actual) {
return false
}
sort.Slice(expecting, func(i, j int) bool {
return expecting[i].Key.Text() < expecting[j].Key.Text()
})
sort.Slice(actual, func(i, j int) bool {
return actual[i].Key.Text() < actual[j].Key.Text()
})
for index, each := range expecting {
ac := actual[index]
if !each.Equal(ac) {
return false
}
}
return true
}
func (s *ServiceRoute) Equal(v interface{}) bool {
if v == nil {
return false
}
sr, ok := v.(*ServiceRoute)
if !ok {
return false
}
if !s.AtDoc.Equal(sr.AtDoc) {
return false
}
if s.AtServer != nil {
if !s.AtServer.Equal(sr.AtServer) {
return false
}
}
if s.AtHandler != nil {
if !s.AtHandler.Equal(sr.AtHandler) {
return false
}
}
return s.Route.Equal(sr.Route)
}
func (s *ServiceRoute) Format() error {
// todo
return nil
}
func (s *ServiceRoute) GetHandler() Expr {
if s.AtHandler != nil {
return s.AtHandler.Name
} else {
return s.AtServer.Kv.Get("handler")
}
}
func (a *ServiceApi) Format() error {
// todo
return nil
}
func (a *ServiceApi) Equal(v interface{}) bool {
if v == nil {
return false
}
api, ok := v.(*ServiceApi)
if !ok {
return false
}
if !a.ServiceToken.Equal(api.ServiceToken) {
return false
}
if !a.Name.Equal(api.Name) {
return false
}
if !a.Lbrace.Equal(api.Lbrace) {
return false
}
if !a.Rbrace.Equal(api.Rbrace) {
return false
}
var expecting, acutal []*ServiceRoute
expecting = append(expecting, a.ServiceRoute...)
acutal = append(acutal, api.ServiceRoute...)
if len(expecting) != len(acutal) {
return false
}
sort.Slice(expecting, func(i, j int) bool {
return expecting[i].Route.Path.Text() < expecting[j].Route.Path.Text()
})
sort.Slice(acutal, func(i, j int) bool {
return acutal[i].Route.Path.Text() < acutal[j].Route.Path.Text()
})
for index, each := range expecting {
ac := acutal[index]
if !each.Equal(ac) {
return false
}
}
return true
}
func (s *Service) Format() error {
// todo
return nil
}
func (s *Service) Equal(v interface{}) bool {
if v == nil {
return false
}
service, ok := v.(*Service)
if !ok {
return false
}
if s.AtServer != nil {
if !s.AtServer.Equal(service.AtServer) {
return false
}
}
return s.ServiceApi.Equal(service.ServiceApi)
}
func (kv KV) Get(key string) Expr {
for _, each := range kv {
if each.Key.Text() == key {
return each.Value
}
}
return nil
}

View File

@@ -0,0 +1,58 @@
package ast
import (
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
)
type SyntaxExpr struct {
Syntax Expr
Assign Expr
Version Expr
DocExpr []Expr
CommentExpr Expr
}
func (v *ApiVisitor) VisitSyntaxLit(ctx *api.SyntaxLitContext) interface{} {
syntax := v.newExprWithToken(ctx.GetSyntaxToken())
assign := v.newExprWithToken(ctx.GetAssign())
version := v.newExprWithToken(ctx.GetVersion())
return &SyntaxExpr{
Syntax: syntax,
Assign: assign,
Version: version,
DocExpr: v.getDoc(ctx),
CommentExpr: v.getComment(ctx),
}
}
func (s *SyntaxExpr) Format() error {
// todo
return nil
}
func (s *SyntaxExpr) Equal(v interface{}) bool {
if v == nil {
return false
}
syntax, ok := v.(*SyntaxExpr)
if !ok {
return false
}
if !EqualDoc(s, syntax) {
return false
}
return s.Syntax.Equal(syntax.Syntax) &&
s.Assign.Equal(syntax.Assign) &&
s.Version.Equal(syntax.Version)
}
func (s *SyntaxExpr) Doc() []Expr {
return s.DocExpr
}
func (s *SyntaxExpr) Comment() Expr {
return s.CommentExpr
}

View File

@@ -0,0 +1,677 @@
package ast
import (
"fmt"
"sort"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
)
type (
// TypeAlias、 TypeStruct
TypeExpr interface {
Doc() []Expr
Format() error
Equal(v interface{}) bool
NameExpr() Expr
}
TypeAlias struct {
Name Expr
Assign Expr
DataType DataType
DocExpr []Expr
CommentExpr Expr
}
TypeStruct struct {
Name Expr
Struct Expr
LBrace Expr
RBrace Expr
DocExpr []Expr
Fields []*TypeField
}
TypeField struct {
IsAnonymous bool
// Name is nil if IsAnonymous
Name Expr
DataType DataType
Tag Expr
DocExpr []Expr
CommentExpr Expr
}
// Literal, Interface, Map, Array, Time, Pointer
DataType interface {
Expr() Expr
Equal(dt DataType) bool
Format() error
IsNotNil() bool
}
// int, bool, Foo,...
Literal struct {
Literal Expr
}
Interface struct {
Literal Expr
}
Map struct {
MapExpr Expr
Map Expr
LBrack Expr
RBrack Expr
Key Expr
Value DataType
}
Array struct {
ArrayExpr Expr
LBrack Expr
RBrack Expr
Literal DataType
}
Time struct {
Literal Expr
}
Pointer struct {
PointerExpr Expr
Star Expr
Name Expr
}
)
func (v *ApiVisitor) VisitTypeSpec(ctx *api.TypeSpecContext) interface{} {
if ctx.TypeLit() != nil {
return []TypeExpr{ctx.TypeLit().Accept(v).(TypeExpr)}
}
return ctx.TypeBlock().Accept(v)
}
func (v *ApiVisitor) VisitTypeLit(ctx *api.TypeLitContext) interface{} {
typeLit := ctx.TypeLitBody().Accept(v)
alias, ok := typeLit.(*TypeAlias)
if ok {
return alias
}
st, ok := typeLit.(*TypeStruct)
if ok {
return st
}
return typeLit
}
func (v *ApiVisitor) VisitTypeBlock(ctx *api.TypeBlockContext) interface{} {
list := ctx.AllTypeBlockBody()
var types []TypeExpr
for _, each := range list {
types = append(types, each.Accept(v).(TypeExpr))
}
return types
}
func (v *ApiVisitor) VisitTypeLitBody(ctx *api.TypeLitBodyContext) interface{} {
if ctx.TypeAlias() != nil {
return ctx.TypeAlias().Accept(v)
}
return ctx.TypeStruct().Accept(v)
}
func (v *ApiVisitor) VisitTypeBlockBody(ctx *api.TypeBlockBodyContext) interface{} {
if ctx.TypeBlockAlias() != nil {
return ctx.TypeBlockAlias().Accept(v).(*TypeAlias)
}
return ctx.TypeBlockStruct().Accept(v).(*TypeStruct)
}
func (v *ApiVisitor) VisitTypeStruct(ctx *api.TypeStructContext) interface{} {
var st TypeStruct
st.Name = v.newExprWithToken(ctx.GetStructName())
v.exportCheck(st.Name)
if util.UnExport(ctx.GetStructName().GetText()) {
}
if ctx.GetStructToken() != nil {
structExpr := v.newExprWithToken(ctx.GetStructToken())
structTokenText := ctx.GetStructToken().GetText()
if structTokenText != "struct" {
v.panic(structExpr, fmt.Sprintf("expecting 'struct', found input '%s'", structTokenText))
}
if api.IsGolangKeyWord(structTokenText, "struct") {
v.panic(structExpr, fmt.Sprintf("expecting 'struct', but found golang keyword '%s'", structTokenText))
}
st.Struct = structExpr
}
st.LBrace = v.newExprWithToken(ctx.GetLbrace())
st.RBrace = v.newExprWithToken(ctx.GetRbrace())
fields := ctx.AllField()
for _, each := range fields {
f := each.Accept(v)
if f == nil {
continue
}
st.Fields = append(st.Fields, f.(*TypeField))
}
return &st
}
func (v *ApiVisitor) VisitTypeBlockStruct(ctx *api.TypeBlockStructContext) interface{} {
var st TypeStruct
st.Name = v.newExprWithToken(ctx.GetStructName())
v.exportCheck(st.Name)
if ctx.GetStructToken() != nil {
structExpr := v.newExprWithToken(ctx.GetStructToken())
structTokenText := ctx.GetStructToken().GetText()
if structTokenText != "struct" {
v.panic(structExpr, fmt.Sprintf("expecting 'struct', found imput '%s'", structTokenText))
}
if api.IsGolangKeyWord(structTokenText, "struct") {
v.panic(structExpr, fmt.Sprintf("expecting 'struct', but found golang keyword '%s'", structTokenText))
}
st.Struct = structExpr
}
st.DocExpr = v.getDoc(ctx)
st.LBrace = v.newExprWithToken(ctx.GetLbrace())
st.RBrace = v.newExprWithToken(ctx.GetRbrace())
fields := ctx.AllField()
for _, each := range fields {
f := each.Accept(v)
if f == nil {
continue
}
st.Fields = append(st.Fields, f.(*TypeField))
}
return &st
}
func (v *ApiVisitor) VisitTypeBlockAlias(ctx *api.TypeBlockAliasContext) interface{} {
var alias TypeAlias
alias.Name = v.newExprWithToken(ctx.GetAlias())
alias.Assign = v.newExprWithToken(ctx.GetAssign())
alias.DataType = ctx.DataType().Accept(v).(DataType)
alias.DocExpr = v.getDoc(ctx)
alias.CommentExpr = v.getComment(ctx)
// todo: reopen if necessary
v.panic(alias.Name, "unsupport alias")
return &alias
}
func (v *ApiVisitor) VisitTypeAlias(ctx *api.TypeAliasContext) interface{} {
var alias TypeAlias
alias.Name = v.newExprWithToken(ctx.GetAlias())
alias.Assign = v.newExprWithToken(ctx.GetAssign())
alias.DataType = ctx.DataType().Accept(v).(DataType)
alias.DocExpr = v.getDoc(ctx)
alias.CommentExpr = v.getComment(ctx)
// todo: reopen if necessary
v.panic(alias.Name, "unsupport alias")
return &alias
}
func (v *ApiVisitor) VisitField(ctx *api.FieldContext) interface{} {
iAnonymousFiled := ctx.AnonymousFiled()
iNormalFieldContext := ctx.NormalField()
if iAnonymousFiled != nil {
return iAnonymousFiled.Accept(v).(*TypeField)
}
if iNormalFieldContext != nil {
return iNormalFieldContext.Accept(v).(*TypeField)
}
return nil
}
func (v *ApiVisitor) VisitNormalField(ctx *api.NormalFieldContext) interface{} {
var field TypeField
field.Name = v.newExprWithToken(ctx.GetFieldName())
v.exportCheck(field.Name)
iDataTypeContext := ctx.DataType()
if iDataTypeContext != nil {
field.DataType = iDataTypeContext.Accept(v).(DataType)
field.CommentExpr = v.getComment(ctx)
}
if ctx.GetTag() != nil {
tagText := ctx.GetTag().GetText()
tagExpr := v.newExprWithToken(ctx.GetTag())
if !api.MatchTag(tagText) {
v.panic(tagExpr, fmt.Sprintf("mismatched tag, found input '%s'", tagText))
}
field.Tag = tagExpr
field.CommentExpr = v.getComment(ctx)
}
field.DocExpr = v.getDoc(ctx)
return &field
}
func (v *ApiVisitor) VisitAnonymousFiled(ctx *api.AnonymousFiledContext) interface{} {
start := ctx.GetStart()
stop := ctx.GetStop()
var field TypeField
field.IsAnonymous = true
if ctx.GetStar() != nil {
nameExpr := v.newExprWithTerminalNode(ctx.ID())
v.exportCheck(nameExpr)
field.DataType = &Pointer{
PointerExpr: v.newExprWithText(ctx.GetStar().GetText()+ctx.ID().GetText(), start.GetLine(), start.GetColumn(), start.GetStart(), stop.GetStop()),
Star: v.newExprWithToken(ctx.GetStar()),
Name: nameExpr,
}
} else {
nameExpr := v.newExprWithTerminalNode(ctx.ID())
v.exportCheck(nameExpr)
field.DataType = &Literal{Literal: nameExpr}
}
field.DocExpr = v.getDoc(ctx)
field.CommentExpr = v.getComment(ctx)
return &field
}
func (v *ApiVisitor) VisitDataType(ctx *api.DataTypeContext) interface{} {
if ctx.ID() != nil {
idExpr := v.newExprWithTerminalNode(ctx.ID())
v.exportCheck(idExpr)
return &Literal{Literal: idExpr}
}
if ctx.MapType() != nil {
t := ctx.MapType().Accept(v)
return t
}
if ctx.ArrayType() != nil {
return ctx.ArrayType().Accept(v)
}
if ctx.GetInter() != nil {
return &Interface{Literal: v.newExprWithToken(ctx.GetInter())}
}
if ctx.GetTime() != nil {
// todo: reopen if it is necessary
timeExpr := v.newExprWithToken(ctx.GetTime())
v.panic(timeExpr, "unsupport time.Time")
return &Time{Literal: timeExpr}
}
if ctx.PointerType() != nil {
return ctx.PointerType().Accept(v)
}
return ctx.TypeStruct().Accept(v)
}
func (v *ApiVisitor) VisitPointerType(ctx *api.PointerTypeContext) interface{} {
nameExpr := v.newExprWithTerminalNode(ctx.ID())
v.exportCheck(nameExpr)
return &Pointer{
PointerExpr: v.newExprWithText(ctx.GetText(), ctx.GetStar().GetLine(), ctx.GetStar().GetColumn(), ctx.GetStar().GetStart(), ctx.ID().GetSymbol().GetStop()),
Star: v.newExprWithToken(ctx.GetStar()),
Name: nameExpr,
}
}
func (v *ApiVisitor) VisitMapType(ctx *api.MapTypeContext) interface{} {
return &Map{
MapExpr: v.newExprWithText(ctx.GetText(), ctx.GetMapToken().GetLine(), ctx.GetMapToken().GetColumn(),
ctx.GetMapToken().GetStart(), ctx.GetValue().GetStop().GetStop()),
Map: v.newExprWithToken(ctx.GetMapToken()),
LBrack: v.newExprWithToken(ctx.GetLbrack()),
RBrack: v.newExprWithToken(ctx.GetRbrack()),
Key: v.newExprWithToken(ctx.GetKey()),
Value: ctx.GetValue().Accept(v).(DataType),
}
}
func (v *ApiVisitor) VisitArrayType(ctx *api.ArrayTypeContext) interface{} {
return &Array{
ArrayExpr: v.newExprWithText(ctx.GetText(), ctx.GetLbrack().GetLine(), ctx.GetLbrack().GetColumn(), ctx.GetLbrack().GetStart(), ctx.DataType().GetStop().GetStop()),
LBrack: v.newExprWithToken(ctx.GetLbrack()),
RBrack: v.newExprWithToken(ctx.GetRbrack()),
Literal: ctx.DataType().Accept(v).(DataType),
}
}
func (a *TypeAlias) NameExpr() Expr {
return a.Name
}
func (a *TypeAlias) Doc() []Expr {
return a.DocExpr
}
func (a *TypeAlias) Comment() Expr {
return a.CommentExpr
}
func (a *TypeAlias) Format() error {
return nil
}
func (a *TypeAlias) Equal(v interface{}) bool {
if v == nil {
return false
}
alias := v.(*TypeAlias)
if !a.Name.Equal(alias.Name) {
return false
}
if !a.Assign.Equal(alias.Assign) {
return false
}
if !a.DataType.Equal(alias.DataType) {
return false
}
return EqualDoc(a, alias)
}
func (l *Literal) Expr() Expr {
return l.Literal
}
func (l *Literal) Format() error {
// todo
return nil
}
func (l *Literal) Equal(dt DataType) bool {
if dt == nil {
return false
}
v, ok := dt.(*Literal)
if !ok {
return false
}
return l.Literal.Equal(v.Literal)
}
func (l *Literal) IsNotNil() bool {
return l != nil
}
func (i *Interface) Expr() Expr {
return i.Literal
}
func (i *Interface) Format() error {
// todo
return nil
}
func (i *Interface) Equal(dt DataType) bool {
if dt == nil {
return false
}
v, ok := dt.(*Interface)
if !ok {
return false
}
return i.Literal.Equal(v.Literal)
}
func (i *Interface) IsNotNil() bool {
return i != nil
}
func (m *Map) Expr() Expr {
return m.MapExpr
}
func (m *Map) Format() error {
// todo
return nil
}
func (m *Map) Equal(dt DataType) bool {
if dt == nil {
return false
}
v, ok := dt.(*Map)
if !ok {
return false
}
if !m.Key.Equal(v.Key) {
return false
}
if !m.Value.Equal(v.Value) {
return false
}
if !m.MapExpr.Equal(v.MapExpr) {
return false
}
return m.Map.Equal(v.Map)
}
func (m *Map) IsNotNil() bool {
return m != nil
}
func (a *Array) Expr() Expr {
return a.ArrayExpr
}
func (a *Array) Format() error {
// todo
return nil
}
func (a *Array) Equal(dt DataType) bool {
if dt == nil {
return false
}
v, ok := dt.(*Array)
if !ok {
return false
}
if !a.ArrayExpr.Equal(v.ArrayExpr) {
return false
}
return a.Literal.Equal(v.Literal)
}
func (a *Array) IsNotNil() bool {
return a != nil
}
func (t *Time) Expr() Expr {
return t.Literal
}
func (t *Time) Format() error {
// todo
return nil
}
func (t *Time) Equal(dt DataType) bool {
if dt == nil {
return false
}
v, ok := dt.(*Time)
if !ok {
return false
}
return t.Literal.Equal(v.Literal)
}
func (t *Time) IsNotNil() bool {
return t != nil
}
func (p *Pointer) Expr() Expr {
return p.PointerExpr
}
func (p *Pointer) Format() error {
return nil
}
func (p *Pointer) Equal(dt DataType) bool {
if dt == nil {
return false
}
v, ok := dt.(*Pointer)
if !ok {
return false
}
if !p.PointerExpr.Equal(v.PointerExpr) {
return false
}
if !p.Star.Equal(v.Star) {
return false
}
return p.Name.Equal(v.Name)
}
func (p *Pointer) IsNotNil() bool {
return p != nil
}
func (s *TypeStruct) NameExpr() Expr {
return s.Name
}
func (s *TypeStruct) Equal(dt interface{}) bool {
if dt == nil {
return false
}
v, ok := dt.(*TypeStruct)
if !ok {
return false
}
if !s.Name.Equal(v.Name) {
return false
}
var expectDoc, actualDoc []Expr
expectDoc = append(expectDoc, s.DocExpr...)
actualDoc = append(actualDoc, v.DocExpr...)
sort.Slice(expectDoc, func(i, j int) bool {
return expectDoc[i].Line() < expectDoc[j].Line()
})
for index, each := range actualDoc {
if !each.Equal(actualDoc[index]) {
return false
}
}
if s.Struct != nil {
if s.Struct != nil {
if !s.Struct.Equal(v.Struct) {
return false
}
}
}
if len(s.Fields) != len(v.Fields) {
return false
}
var expected, acual []*TypeField
expected = append(expected, s.Fields...)
acual = append(acual, v.Fields...)
sort.Slice(expected, func(i, j int) bool {
return expected[i].DataType.Expr().Line() < expected[j].DataType.Expr().Line()
})
sort.Slice(acual, func(i, j int) bool {
return acual[i].DataType.Expr().Line() < acual[j].DataType.Expr().Line()
})
for index, each := range expected {
ac := acual[index]
if !each.Equal(ac) {
return false
}
}
return true
}
func (s *TypeStruct) Doc() []Expr {
return s.DocExpr
}
func (s *TypeStruct) Format() error {
// todo
return nil
}
func (t *TypeField) Equal(v interface{}) bool {
if v == nil {
return false
}
f, ok := v.(*TypeField)
if !ok {
return false
}
if t.IsAnonymous != f.IsAnonymous {
return false
}
if !t.DataType.Equal(f.DataType) {
return false
}
if !t.IsAnonymous {
if !t.Name.Equal(f.Name) {
return false
}
if t.Tag != nil {
if !t.Tag.Equal(f.Tag) {
return false
}
}
}
return EqualDoc(t, f)
}
func (t *TypeField) Doc() []Expr {
return t.DocExpr
}
func (t *TypeField) Comment() Expr {
return t.CommentExpr
}
func (t *TypeField) Format() error {
// todo
return nil
}

View File

@@ -0,0 +1,156 @@
// Code generated from tools/goctl/api/parser/g4/ApiParser.g4 by ANTLR 4.9. DO NOT EDIT.
package api // ApiParser
import "github.com/antlr/antlr4/runtime/Go/antlr"
type BaseApiParserVisitor struct {
*antlr.BaseParseTreeVisitor
}
func (v *BaseApiParserVisitor) VisitApi(ctx *ApiContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitSpec(ctx *SpecContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitSyntaxLit(ctx *SyntaxLitContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitImportSpec(ctx *ImportSpecContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitImportLit(ctx *ImportLitContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitImportBlock(ctx *ImportBlockContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitImportBlockValue(ctx *ImportBlockValueContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitImportValue(ctx *ImportValueContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitInfoSpec(ctx *InfoSpecContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitTypeSpec(ctx *TypeSpecContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitTypeLit(ctx *TypeLitContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitTypeBlock(ctx *TypeBlockContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitTypeLitBody(ctx *TypeLitBodyContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitTypeBlockBody(ctx *TypeBlockBodyContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitTypeStruct(ctx *TypeStructContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitTypeAlias(ctx *TypeAliasContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitTypeBlockStruct(ctx *TypeBlockStructContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitTypeBlockAlias(ctx *TypeBlockAliasContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitField(ctx *FieldContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitNormalField(ctx *NormalFieldContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitAnonymousFiled(ctx *AnonymousFiledContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitDataType(ctx *DataTypeContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitPointerType(ctx *PointerTypeContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitMapType(ctx *MapTypeContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitArrayType(ctx *ArrayTypeContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitServiceSpec(ctx *ServiceSpecContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitAtServer(ctx *AtServerContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitServiceApi(ctx *ServiceApiContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitServiceRoute(ctx *ServiceRouteContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitAtDoc(ctx *AtDocContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitAtHandler(ctx *AtHandlerContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitRoute(ctx *RouteContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitBody(ctx *BodyContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitReplybody(ctx *ReplybodyContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitKvLit(ctx *KvLitContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitServiceName(ctx *ServiceNameContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseApiParserVisitor) VisitPath(ctx *PathContext) interface{} {
return v.VisitChildren(ctx)
}

View File

@@ -0,0 +1,234 @@
// Code generated from tools/goctl/api/parser/g4/ApiParser.g4 by ANTLR 4.9. DO NOT EDIT.
package api
import (
"fmt"
"unicode"
"github.com/antlr/antlr4/runtime/Go/antlr"
)
// Suppress unused import error
var _ = fmt.Printf
var _ = unicode.IsLetter
var serializedLexerAtn = []uint16{
3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 25, 266,
8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7,
9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12,
4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4,
18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23,
9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9,
28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 3, 2, 3, 2, 3, 3, 3, 3, 3,
4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 3, 8, 3,
8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11,
3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3,
15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16,
3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3,
17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 6, 18, 132,
10, 18, 13, 18, 14, 18, 133, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 19,
7, 19, 142, 10, 19, 12, 19, 14, 19, 145, 11, 19, 3, 19, 3, 19, 3, 19, 3,
19, 3, 19, 3, 20, 3, 20, 3, 20, 3, 20, 7, 20, 156, 10, 20, 12, 20, 14,
20, 159, 11, 20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 7, 21, 166, 10, 21,
12, 21, 14, 21, 169, 11, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 22, 6, 22,
176, 10, 22, 13, 22, 14, 22, 177, 3, 22, 3, 22, 3, 23, 3, 23, 7, 23, 184,
10, 23, 12, 23, 14, 23, 187, 11, 23, 3, 23, 3, 23, 7, 23, 191, 10, 23,
12, 23, 14, 23, 194, 11, 23, 5, 23, 196, 10, 23, 3, 24, 3, 24, 7, 24, 200,
10, 24, 12, 24, 14, 24, 203, 11, 24, 3, 25, 3, 25, 5, 25, 207, 10, 25,
3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 5, 26, 215, 10, 26, 3, 26, 5,
26, 218, 10, 26, 3, 26, 3, 26, 3, 26, 6, 26, 223, 10, 26, 13, 26, 14, 26,
224, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 5, 26, 232, 10, 26, 3, 27, 3, 27,
3, 27, 7, 27, 237, 10, 27, 12, 27, 14, 27, 240, 11, 27, 3, 27, 5, 27, 243,
10, 27, 3, 28, 3, 28, 3, 29, 3, 29, 7, 29, 249, 10, 29, 12, 29, 14, 29,
252, 11, 29, 3, 29, 5, 29, 255, 10, 29, 3, 30, 3, 30, 5, 30, 259, 10, 30,
3, 31, 3, 31, 3, 31, 3, 31, 5, 31, 265, 10, 31, 3, 143, 2, 32, 3, 3, 5,
4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 13, 25,
14, 27, 15, 29, 16, 31, 17, 33, 18, 35, 19, 37, 20, 39, 21, 41, 22, 43,
23, 45, 24, 47, 25, 49, 2, 51, 2, 53, 2, 55, 2, 57, 2, 59, 2, 61, 2, 3,
2, 20, 5, 2, 11, 12, 14, 15, 34, 34, 4, 2, 12, 12, 15, 15, 4, 2, 36, 36,
94, 94, 6, 2, 12, 12, 15, 15, 94, 94, 98, 98, 4, 2, 11, 11, 34, 34, 6,
2, 12, 12, 15, 15, 36, 36, 98, 98, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45,
47, 47, 10, 2, 36, 36, 41, 41, 94, 94, 100, 100, 104, 104, 112, 112, 116,
116, 118, 118, 3, 2, 50, 53, 3, 2, 50, 57, 5, 2, 50, 59, 67, 72, 99, 104,
3, 2, 50, 59, 4, 2, 50, 59, 97, 97, 6, 2, 38, 38, 67, 92, 97, 97, 99, 124,
4, 2, 2, 129, 55298, 56321, 3, 2, 55298, 56321, 3, 2, 56322, 57345, 2,
283, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2,
2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3,
2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25,
3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2,
33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2,
2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2,
2, 3, 63, 3, 2, 2, 2, 5, 65, 3, 2, 2, 2, 7, 67, 3, 2, 2, 2, 9, 69, 3, 2,
2, 2, 11, 71, 3, 2, 2, 2, 13, 73, 3, 2, 2, 2, 15, 75, 3, 2, 2, 2, 17, 85,
3, 2, 2, 2, 19, 87, 3, 2, 2, 2, 21, 89, 3, 2, 2, 2, 23, 91, 3, 2, 2, 2,
25, 93, 3, 2, 2, 2, 27, 96, 3, 2, 2, 2, 29, 101, 3, 2, 2, 2, 31, 110, 3,
2, 2, 2, 33, 122, 3, 2, 2, 2, 35, 131, 3, 2, 2, 2, 37, 137, 3, 2, 2, 2,
39, 151, 3, 2, 2, 2, 41, 162, 3, 2, 2, 2, 43, 172, 3, 2, 2, 2, 45, 181,
3, 2, 2, 2, 47, 197, 3, 2, 2, 2, 49, 204, 3, 2, 2, 2, 51, 231, 3, 2, 2,
2, 53, 233, 3, 2, 2, 2, 55, 244, 3, 2, 2, 2, 57, 246, 3, 2, 2, 2, 59, 258,
3, 2, 2, 2, 61, 264, 3, 2, 2, 2, 63, 64, 7, 63, 2, 2, 64, 4, 3, 2, 2, 2,
65, 66, 7, 42, 2, 2, 66, 6, 3, 2, 2, 2, 67, 68, 7, 43, 2, 2, 68, 8, 3,
2, 2, 2, 69, 70, 7, 125, 2, 2, 70, 10, 3, 2, 2, 2, 71, 72, 7, 127, 2, 2,
72, 12, 3, 2, 2, 2, 73, 74, 7, 44, 2, 2, 74, 14, 3, 2, 2, 2, 75, 76, 7,
118, 2, 2, 76, 77, 7, 107, 2, 2, 77, 78, 7, 111, 2, 2, 78, 79, 7, 103,
2, 2, 79, 80, 7, 48, 2, 2, 80, 81, 7, 86, 2, 2, 81, 82, 7, 107, 2, 2, 82,
83, 7, 111, 2, 2, 83, 84, 7, 103, 2, 2, 84, 16, 3, 2, 2, 2, 85, 86, 7,
93, 2, 2, 86, 18, 3, 2, 2, 2, 87, 88, 7, 95, 2, 2, 88, 20, 3, 2, 2, 2,
89, 90, 7, 47, 2, 2, 90, 22, 3, 2, 2, 2, 91, 92, 7, 49, 2, 2, 92, 24, 3,
2, 2, 2, 93, 94, 7, 49, 2, 2, 94, 95, 7, 60, 2, 2, 95, 26, 3, 2, 2, 2,
96, 97, 7, 66, 2, 2, 97, 98, 7, 102, 2, 2, 98, 99, 7, 113, 2, 2, 99, 100,
7, 101, 2, 2, 100, 28, 3, 2, 2, 2, 101, 102, 7, 66, 2, 2, 102, 103, 7,
106, 2, 2, 103, 104, 7, 99, 2, 2, 104, 105, 7, 112, 2, 2, 105, 106, 7,
102, 2, 2, 106, 107, 7, 110, 2, 2, 107, 108, 7, 103, 2, 2, 108, 109, 7,
116, 2, 2, 109, 30, 3, 2, 2, 2, 110, 111, 7, 107, 2, 2, 111, 112, 7, 112,
2, 2, 112, 113, 7, 118, 2, 2, 113, 114, 7, 103, 2, 2, 114, 115, 7, 116,
2, 2, 115, 116, 7, 104, 2, 2, 116, 117, 7, 99, 2, 2, 117, 118, 7, 101,
2, 2, 118, 119, 7, 103, 2, 2, 119, 120, 7, 125, 2, 2, 120, 121, 7, 127,
2, 2, 121, 32, 3, 2, 2, 2, 122, 123, 7, 66, 2, 2, 123, 124, 7, 117, 2,
2, 124, 125, 7, 103, 2, 2, 125, 126, 7, 116, 2, 2, 126, 127, 7, 120, 2,
2, 127, 128, 7, 103, 2, 2, 128, 129, 7, 116, 2, 2, 129, 34, 3, 2, 2, 2,
130, 132, 9, 2, 2, 2, 131, 130, 3, 2, 2, 2, 132, 133, 3, 2, 2, 2, 133,
131, 3, 2, 2, 2, 133, 134, 3, 2, 2, 2, 134, 135, 3, 2, 2, 2, 135, 136,
8, 18, 2, 2, 136, 36, 3, 2, 2, 2, 137, 138, 7, 49, 2, 2, 138, 139, 7, 44,
2, 2, 139, 143, 3, 2, 2, 2, 140, 142, 11, 2, 2, 2, 141, 140, 3, 2, 2, 2,
142, 145, 3, 2, 2, 2, 143, 144, 3, 2, 2, 2, 143, 141, 3, 2, 2, 2, 144,
146, 3, 2, 2, 2, 145, 143, 3, 2, 2, 2, 146, 147, 7, 44, 2, 2, 147, 148,
7, 49, 2, 2, 148, 149, 3, 2, 2, 2, 149, 150, 8, 19, 3, 2, 150, 38, 3, 2,
2, 2, 151, 152, 7, 49, 2, 2, 152, 153, 7, 49, 2, 2, 153, 157, 3, 2, 2,
2, 154, 156, 10, 3, 2, 2, 155, 154, 3, 2, 2, 2, 156, 159, 3, 2, 2, 2, 157,
155, 3, 2, 2, 2, 157, 158, 3, 2, 2, 2, 158, 160, 3, 2, 2, 2, 159, 157,
3, 2, 2, 2, 160, 161, 8, 20, 3, 2, 161, 40, 3, 2, 2, 2, 162, 167, 7, 36,
2, 2, 163, 166, 10, 4, 2, 2, 164, 166, 5, 51, 26, 2, 165, 163, 3, 2, 2,
2, 165, 164, 3, 2, 2, 2, 166, 169, 3, 2, 2, 2, 167, 165, 3, 2, 2, 2, 167,
168, 3, 2, 2, 2, 168, 170, 3, 2, 2, 2, 169, 167, 3, 2, 2, 2, 170, 171,
7, 36, 2, 2, 171, 42, 3, 2, 2, 2, 172, 175, 7, 98, 2, 2, 173, 176, 10,
5, 2, 2, 174, 176, 5, 51, 26, 2, 175, 173, 3, 2, 2, 2, 175, 174, 3, 2,
2, 2, 176, 177, 3, 2, 2, 2, 177, 175, 3, 2, 2, 2, 177, 178, 3, 2, 2, 2,
178, 179, 3, 2, 2, 2, 179, 180, 7, 98, 2, 2, 180, 44, 3, 2, 2, 2, 181,
185, 7, 60, 2, 2, 182, 184, 9, 6, 2, 2, 183, 182, 3, 2, 2, 2, 184, 187,
3, 2, 2, 2, 185, 183, 3, 2, 2, 2, 185, 186, 3, 2, 2, 2, 186, 195, 3, 2,
2, 2, 187, 185, 3, 2, 2, 2, 188, 196, 5, 41, 21, 2, 189, 191, 10, 7, 2,
2, 190, 189, 3, 2, 2, 2, 191, 194, 3, 2, 2, 2, 192, 190, 3, 2, 2, 2, 192,
193, 3, 2, 2, 2, 193, 196, 3, 2, 2, 2, 194, 192, 3, 2, 2, 2, 195, 188,
3, 2, 2, 2, 195, 192, 3, 2, 2, 2, 196, 46, 3, 2, 2, 2, 197, 201, 5, 61,
31, 2, 198, 200, 5, 59, 30, 2, 199, 198, 3, 2, 2, 2, 200, 203, 3, 2, 2,
2, 201, 199, 3, 2, 2, 2, 201, 202, 3, 2, 2, 2, 202, 48, 3, 2, 2, 2, 203,
201, 3, 2, 2, 2, 204, 206, 9, 8, 2, 2, 205, 207, 9, 9, 2, 2, 206, 205,
3, 2, 2, 2, 206, 207, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 209, 5, 57,
29, 2, 209, 50, 3, 2, 2, 2, 210, 211, 7, 94, 2, 2, 211, 232, 9, 10, 2,
2, 212, 217, 7, 94, 2, 2, 213, 215, 9, 11, 2, 2, 214, 213, 3, 2, 2, 2,
214, 215, 3, 2, 2, 2, 215, 216, 3, 2, 2, 2, 216, 218, 9, 12, 2, 2, 217,
214, 3, 2, 2, 2, 217, 218, 3, 2, 2, 2, 218, 219, 3, 2, 2, 2, 219, 232,
9, 12, 2, 2, 220, 222, 7, 94, 2, 2, 221, 223, 7, 119, 2, 2, 222, 221, 3,
2, 2, 2, 223, 224, 3, 2, 2, 2, 224, 222, 3, 2, 2, 2, 224, 225, 3, 2, 2,
2, 225, 226, 3, 2, 2, 2, 226, 227, 5, 55, 28, 2, 227, 228, 5, 55, 28, 2,
228, 229, 5, 55, 28, 2, 229, 230, 5, 55, 28, 2, 230, 232, 3, 2, 2, 2, 231,
210, 3, 2, 2, 2, 231, 212, 3, 2, 2, 2, 231, 220, 3, 2, 2, 2, 232, 52, 3,
2, 2, 2, 233, 242, 5, 55, 28, 2, 234, 237, 5, 55, 28, 2, 235, 237, 7, 97,
2, 2, 236, 234, 3, 2, 2, 2, 236, 235, 3, 2, 2, 2, 237, 240, 3, 2, 2, 2,
238, 236, 3, 2, 2, 2, 238, 239, 3, 2, 2, 2, 239, 241, 3, 2, 2, 2, 240,
238, 3, 2, 2, 2, 241, 243, 5, 55, 28, 2, 242, 238, 3, 2, 2, 2, 242, 243,
3, 2, 2, 2, 243, 54, 3, 2, 2, 2, 244, 245, 9, 13, 2, 2, 245, 56, 3, 2,
2, 2, 246, 254, 9, 14, 2, 2, 247, 249, 9, 15, 2, 2, 248, 247, 3, 2, 2,
2, 249, 252, 3, 2, 2, 2, 250, 248, 3, 2, 2, 2, 250, 251, 3, 2, 2, 2, 251,
253, 3, 2, 2, 2, 252, 250, 3, 2, 2, 2, 253, 255, 9, 14, 2, 2, 254, 250,
3, 2, 2, 2, 254, 255, 3, 2, 2, 2, 255, 58, 3, 2, 2, 2, 256, 259, 5, 61,
31, 2, 257, 259, 9, 14, 2, 2, 258, 256, 3, 2, 2, 2, 258, 257, 3, 2, 2,
2, 259, 60, 3, 2, 2, 2, 260, 265, 9, 16, 2, 2, 261, 265, 10, 17, 2, 2,
262, 263, 9, 18, 2, 2, 263, 265, 9, 19, 2, 2, 264, 260, 3, 2, 2, 2, 264,
261, 3, 2, 2, 2, 264, 262, 3, 2, 2, 2, 265, 62, 3, 2, 2, 2, 26, 2, 133,
143, 157, 165, 167, 175, 177, 185, 192, 195, 201, 206, 214, 217, 224, 231,
236, 238, 242, 250, 254, 258, 264, 4, 2, 3, 2, 2, 90, 2,
}
var lexerChannelNames = []string{
"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
}
var lexerModeNames = []string{
"DEFAULT_MODE",
}
var lexerLiteralNames = []string{
"", "'='", "'('", "')'", "'{'", "'}'", "'*'", "'time.Time'", "'['", "']'",
"'-'", "'/'", "'/:'", "'@doc'", "'@handler'", "'interface{}'", "'@server'",
}
var lexerSymbolicNames = []string{
"", "", "", "", "", "", "", "", "", "", "", "", "", "ATDOC", "ATHANDLER",
"INTERFACE", "ATSERVER", "WS", "COMMENT", "LINE_COMMENT", "STRING", "RAW_STRING",
"LINE_VALUE", "ID",
}
var lexerRuleNames = []string{
"T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8",
"T__9", "T__10", "T__11", "ATDOC", "ATHANDLER", "INTERFACE", "ATSERVER",
"WS", "COMMENT", "LINE_COMMENT", "STRING", "RAW_STRING", "LINE_VALUE",
"ID", "ExponentPart", "EscapeSequence", "HexDigits", "HexDigit", "Digits",
"LetterOrDigit", "Letter",
}
type ApiParserLexer struct {
*antlr.BaseLexer
channelNames []string
modeNames []string
// TODO: EOF string
}
// NewApiParserLexer produces a new lexer instance for the optional input antlr.CharStream.
//
// The *ApiParserLexer instance produced may be reused by calling the SetInputStream method.
// The initial lexer configuration is expensive to construct, and the object is not thread-safe;
// however, if used within a Golang sync.Pool, the construction cost amortizes well and the
// objects can be used in a thread-safe manner.
func NewApiParserLexer(input antlr.CharStream) *ApiParserLexer {
l := new(ApiParserLexer)
lexerDeserializer := antlr.NewATNDeserializer(nil)
lexerAtn := lexerDeserializer.DeserializeFromUInt16(serializedLexerAtn)
lexerDecisionToDFA := make([]*antlr.DFA, len(lexerAtn.DecisionToState))
for index, ds := range lexerAtn.DecisionToState {
lexerDecisionToDFA[index] = antlr.NewDFA(ds, index)
}
l.BaseLexer = antlr.NewBaseLexer(input)
l.Interpreter = antlr.NewLexerATNSimulator(l, lexerAtn, lexerDecisionToDFA, antlr.NewPredictionContextCache())
l.channelNames = lexerChannelNames
l.modeNames = lexerModeNames
l.RuleNames = lexerRuleNames
l.LiteralNames = lexerLiteralNames
l.SymbolicNames = lexerSymbolicNames
l.GrammarFileName = "ApiParser.g4"
// TODO: l.EOF = antlr.TokenEOF
return l
}
// ApiParserLexer tokens.
const (
ApiParserLexerT__0 = 1
ApiParserLexerT__1 = 2
ApiParserLexerT__2 = 3
ApiParserLexerT__3 = 4
ApiParserLexerT__4 = 5
ApiParserLexerT__5 = 6
ApiParserLexerT__6 = 7
ApiParserLexerT__7 = 8
ApiParserLexerT__8 = 9
ApiParserLexerT__9 = 10
ApiParserLexerT__10 = 11
ApiParserLexerT__11 = 12
ApiParserLexerATDOC = 13
ApiParserLexerATHANDLER = 14
ApiParserLexerINTERFACE = 15
ApiParserLexerATSERVER = 16
ApiParserLexerWS = 17
ApiParserLexerCOMMENT = 18
ApiParserLexerLINE_COMMENT = 19
ApiParserLexerSTRING = 20
ApiParserLexerRAW_STRING = 21
ApiParserLexerLINE_VALUE = 22
ApiParserLexerID = 23
)
const COMEMNTS = 88

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,120 @@
// Code generated from tools/goctl/api/parser/g4/ApiParser.g4 by ANTLR 4.9. DO NOT EDIT.
package api // ApiParser
import "github.com/antlr/antlr4/runtime/Go/antlr"
// A complete Visitor for a parse tree produced by ApiParserParser.
type ApiParserVisitor interface {
antlr.ParseTreeVisitor
// Visit a parse tree produced by ApiParserParser#api.
VisitApi(ctx *ApiContext) interface{}
// Visit a parse tree produced by ApiParserParser#spec.
VisitSpec(ctx *SpecContext) interface{}
// Visit a parse tree produced by ApiParserParser#syntaxLit.
VisitSyntaxLit(ctx *SyntaxLitContext) interface{}
// Visit a parse tree produced by ApiParserParser#importSpec.
VisitImportSpec(ctx *ImportSpecContext) interface{}
// Visit a parse tree produced by ApiParserParser#importLit.
VisitImportLit(ctx *ImportLitContext) interface{}
// Visit a parse tree produced by ApiParserParser#importBlock.
VisitImportBlock(ctx *ImportBlockContext) interface{}
// Visit a parse tree produced by ApiParserParser#importBlockValue.
VisitImportBlockValue(ctx *ImportBlockValueContext) interface{}
// Visit a parse tree produced by ApiParserParser#importValue.
VisitImportValue(ctx *ImportValueContext) interface{}
// Visit a parse tree produced by ApiParserParser#infoSpec.
VisitInfoSpec(ctx *InfoSpecContext) interface{}
// Visit a parse tree produced by ApiParserParser#typeSpec.
VisitTypeSpec(ctx *TypeSpecContext) interface{}
// Visit a parse tree produced by ApiParserParser#typeLit.
VisitTypeLit(ctx *TypeLitContext) interface{}
// Visit a parse tree produced by ApiParserParser#typeBlock.
VisitTypeBlock(ctx *TypeBlockContext) interface{}
// Visit a parse tree produced by ApiParserParser#typeLitBody.
VisitTypeLitBody(ctx *TypeLitBodyContext) interface{}
// Visit a parse tree produced by ApiParserParser#typeBlockBody.
VisitTypeBlockBody(ctx *TypeBlockBodyContext) interface{}
// Visit a parse tree produced by ApiParserParser#typeStruct.
VisitTypeStruct(ctx *TypeStructContext) interface{}
// Visit a parse tree produced by ApiParserParser#typeAlias.
VisitTypeAlias(ctx *TypeAliasContext) interface{}
// Visit a parse tree produced by ApiParserParser#typeBlockStruct.
VisitTypeBlockStruct(ctx *TypeBlockStructContext) interface{}
// Visit a parse tree produced by ApiParserParser#typeBlockAlias.
VisitTypeBlockAlias(ctx *TypeBlockAliasContext) interface{}
// Visit a parse tree produced by ApiParserParser#field.
VisitField(ctx *FieldContext) interface{}
// Visit a parse tree produced by ApiParserParser#normalField.
VisitNormalField(ctx *NormalFieldContext) interface{}
// Visit a parse tree produced by ApiParserParser#anonymousFiled.
VisitAnonymousFiled(ctx *AnonymousFiledContext) interface{}
// Visit a parse tree produced by ApiParserParser#dataType.
VisitDataType(ctx *DataTypeContext) interface{}
// Visit a parse tree produced by ApiParserParser#pointerType.
VisitPointerType(ctx *PointerTypeContext) interface{}
// Visit a parse tree produced by ApiParserParser#mapType.
VisitMapType(ctx *MapTypeContext) interface{}
// Visit a parse tree produced by ApiParserParser#arrayType.
VisitArrayType(ctx *ArrayTypeContext) interface{}
// Visit a parse tree produced by ApiParserParser#serviceSpec.
VisitServiceSpec(ctx *ServiceSpecContext) interface{}
// Visit a parse tree produced by ApiParserParser#atServer.
VisitAtServer(ctx *AtServerContext) interface{}
// Visit a parse tree produced by ApiParserParser#serviceApi.
VisitServiceApi(ctx *ServiceApiContext) interface{}
// Visit a parse tree produced by ApiParserParser#serviceRoute.
VisitServiceRoute(ctx *ServiceRouteContext) interface{}
// Visit a parse tree produced by ApiParserParser#atDoc.
VisitAtDoc(ctx *AtDocContext) interface{}
// Visit a parse tree produced by ApiParserParser#atHandler.
VisitAtHandler(ctx *AtHandlerContext) interface{}
// Visit a parse tree produced by ApiParserParser#route.
VisitRoute(ctx *RouteContext) interface{}
// Visit a parse tree produced by ApiParserParser#body.
VisitBody(ctx *BodyContext) interface{}
// Visit a parse tree produced by ApiParserParser#replybody.
VisitReplybody(ctx *ReplybodyContext) interface{}
// Visit a parse tree produced by ApiParserParser#kvLit.
VisitKvLit(ctx *KvLitContext) interface{}
// Visit a parse tree produced by ApiParserParser#serviceName.
VisitServiceName(ctx *ServiceNameContext) interface{}
// Visit a parse tree produced by ApiParserParser#path.
VisitPath(ctx *PathContext) interface{}
}

View File

@@ -0,0 +1,222 @@
package api
import (
"fmt"
"net/http"
"regexp"
"strings"
"unicode"
"github.com/antlr/antlr4/runtime/Go/antlr"
)
const (
versionRegex = `(?m)"v[1-9][0-9]*"`
importValueRegex = `(?m)"(/?[a-zA-Z0-9_#-])+\.api"`
tagRegex = `(?m)\x60[a-z]+:".+"\x60`
)
var holder = struct{}{}
var kind = map[string]struct{}{
"bool": holder,
"int": holder,
"int8": holder,
"int16": holder,
"int32": holder,
"int64": holder,
"uint": holder,
"uint8": holder,
"uint16": holder,
"uint32": holder,
"uint64": holder,
"uintptr": holder,
"float32": holder,
"float64": holder,
"complex64": holder,
"complex128": holder,
"string": holder,
"byte": holder,
"rune": holder,
}
func match(p *ApiParserParser, text string) {
v := getCurrentTokenText(p)
if v != text {
notifyErrorListeners(p, expecting(text, v))
}
}
func checkVersion(p *ApiParserParser) {
v := getCurrentTokenText(p)
if !matchRegex(v, versionRegex) {
notifyErrorListeners(p, mismatched("version", v))
}
}
func checkImportValue(p *ApiParserParser) {
v := getCurrentTokenText(p)
if !matchRegex(v, importValueRegex) {
notifyErrorListeners(p, mismatched("import value", v))
}
}
func checkKeyValue(p *ApiParserParser) {
v := getCurrentTokenText(p)
if !strings.HasPrefix(v, ":") {
notifyErrorListeners(p, mismatched(":", v))
}
v = strings.TrimPrefix(v, ":")
v = strings.TrimFunc(v, func(r rune) bool {
return unicode.IsSpace(r)
})
setCurrentTokenText(p, v)
}
func checkHttpMethod(p *ApiParserParser) {
method := getCurrentTokenText(p)
uppler := strings.ToUpper(method)
switch uppler {
case http.MethodPost, http.MethodGet, http.MethodHead,
http.MethodPut, http.MethodPatch, http.MethodDelete,
http.MethodConnect, http.MethodOptions, http.MethodTrace:
if method != strings.ToLower(method) {
notifyErrorListeners(p, expecting("http method lower case", method))
}
default:
notifyErrorListeners(p, expecting("http method", method))
}
}
func checkKeyword(p *ApiParserParser) {
v := getCurrentTokenText(p)
if IsGolangKeyWord(v) {
notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", v))
}
}
func checkKey(p *ApiParserParser) {
v := getCurrentTokenText(p)
if IsGolangKeyWord(v) {
notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", v))
}
if _, ok := kind[v]; !ok {
notifyErrorListeners(p, fmt.Sprintf("expecting golang basic type, found : '%s'", v))
}
}
func IsBasicType(text string) bool {
_, ok := kind[text]
return ok
}
func IsGolangKeyWord(text string, excepts ...string) bool {
for _, each := range excepts {
if text == each {
return false
}
}
switch text {
case "var", "const", "package", "import", "func", "return",
"defer", "go", "select", "interface", "struct", "break", "case",
"continue", "for", "fallthrough", "else", "if", "switch", "goto",
"default", "chan", "type", "map", "range":
return true
default:
return false
}
}
func isNormal(p *ApiParserParser) bool {
ct := p.GetTokenStream().(*antlr.CommonTokenStream)
line := p.GetCurrentToken().GetLine()
tokens := ct.GetAllTokens()
var list []string
for _, token := range tokens {
if token.GetLine() == line {
text := token.GetText()
if strings.HasPrefix(text, "//") {
continue
}
if strings.HasPrefix(text, "/*") {
continue
}
if text == "<EOF>" {
continue
}
if strings.TrimSpace(text) == "" {
continue
}
list = append(list, text)
}
}
if len(list) == 1 {
t := strings.TrimPrefix(list[0], "*")
if IsGolangKeyWord(t) {
notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", t))
}
}
if len(list) > 1 {
if list[0] == "*" {
t := strings.TrimPrefix(list[1], "*")
if IsGolangKeyWord(t) {
notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", t))
}
return false
}
}
return len(list) > 1
}
func MatchTag(v string) bool {
return matchRegex(v, tagRegex)
}
func isInterface(p *ApiParserParser) {
v := getCurrentTokenText(p)
if IsGolangKeyWord(v) {
notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", v))
}
}
func getCurrentTokenText(p *ApiParserParser) string {
token := p.GetCurrentToken()
if token == nil {
return ""
}
return token.GetText()
}
func setCurrentTokenText(p *ApiParserParser, text string) {
token := p.GetCurrentToken()
if token == nil {
return
}
token.SetText(text)
}
func notifyErrorListeners(p *ApiParserParser, msg string) {
p.NotifyErrorListeners(msg, nil, nil)
}
func matchRegex(text, str string) bool {
re := regexp.MustCompile(str)
v := re.FindString(text)
text = strings.TrimFunc(text, func(r rune) bool {
return unicode.IsSpace(r)
})
return v == text
}
func expecting(expecting, found string) string {
return fmt.Sprintf(`expecting '%s', found input '%s'`, expecting, found)
}
func mismatched(expecting, found string) string {
return fmt.Sprintf(`mismatched '%s', found input '%s'`, expecting, found)
}

View File

@@ -0,0 +1,11 @@
package api
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMatch(t *testing.T) {
assert.False(t, matchRegex("v1ddd", versionRegex))
}

View File

@@ -0,0 +1,99 @@
// only one
syntax = "v1"
// import
import "foo.api"
// import group
import(
"foo.api"
"foo/bar.api"
)
// only one
info(
title: "foo title"
desc: "foo
desc"
author: "foo author"
email: "foo email"
version: "foo version"
)
// ignore the following duplicate name
type Foo int
// type single
type Foo {
Bar string
}
type Foo {
Bar string `json:"bar"`
Inline
}
// go struct
type Foo struct {
Bar string `json:"bar"`
}
// type group
type (
Foo int
// go struct
Foo struct {
Bar string `json:"bar"`
}
Foo {
VString string `json:"vString"`
VBool bool `json:"vBool"`
VInt8 int8 `json:"vInt8"`
VInt16 int16 `json:"vInt16"`
VInt32 int32 `json:"vInt32"`
VInt64 int64 `json:"vInt64"`
VInt int `json:"vInt"`
VUInt8 uint8 `json:"vUInt8"`
VUInt16 uint16 `json:"vUInt16"`
VUInt32 uint32 `json:"vUInt32"`
VUInt64 uint64 `json:"vUInt64"`
VFloat32 float32 `json:"vFloat32"`
VFloat64 float64 `json:"vFloat64"`
VByte byte `json:"vByte"`
VRune rune `json:"vRune"`
VMap map[string]int `json:"vMap"`
VArray []int `json:"vArray"`
VStruct Foo `json:"vStruct"`
VStructPointer *Foo `json:"vStructPointer"`
VInterface interface{} `json:"vInterface"`
T time.Time
}
)
@server(
jwt: Foo
group: foo/bar
anotherKey: anotherValue
)
service example-api {
@doc(
summary: "foo1"
)
@server(
handler: fooHandler1
anotherKey: anotherValue
)
post /api/foo1 (SingleExample)
@doc "foo2"
@handler fooHandler2
get /api/foo2 (SingleExample) returns (SingleExample2)
@handler fooHandler3
post /api/foo3/:id returns (SingleExample2)
@handler fooHandler4
get /api/foo4
}

View File

@@ -0,0 +1,369 @@
package test
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
)
var (
normalApi = `
syntax="v1"
info (
foo: bar
)
type Foo {
Bar int
}
@server(
foo: bar
)
service foo-api{
@doc("foo")
@handler foo
post /foo (Foo) returns ([]int)
}
`
missDeclarationApi = `
@server(
foo: bar
)
service foo-api{
@doc("foo")
@handler foo
post /foo (Foo) returns (Foo)
}
`
missDeclarationInArrayApi = `
@server(
foo: bar
)
service foo-api{
@doc("foo")
@handler foo
post /foo returns ([]Foo)
}
`
missDeclarationInArrayApi2 = `
@server(
foo: bar
)
service foo-api{
@doc("foo")
@handler foo
post /foo returns ([]*Foo)
}
`
nestedApiImport = `
import "foo.api"
`
ambiguousSyntax = `
syntax = "v2"
`
ambiguousService = `
service bar-api{
@handler foo
post /foo
}
`
duplicateHandler = `
service bar-api{
@handler foo
post /foo
}
`
duplicateRoute = `
service bar-api{
@handler bar
post /foo
}
`
duplicateType = `
type Foo int
`
)
func TestApiParser(t *testing.T) {
t.Run("missDeclarationApi", func(t *testing.T) {
_, err := parser.ParseContent(missDeclarationApi)
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("missDeclarationApi", func(t *testing.T) {
_, err := parser.ParseContent(missDeclarationInArrayApi)
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("missDeclarationApi", func(t *testing.T) {
_, err := parser.ParseContent(missDeclarationInArrayApi2)
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("nestedImport", func(t *testing.T) {
file := filepath.Join(t.TempDir(), "foo.api")
err := ioutil.WriteFile(file, []byte(nestedApiImport), os.ModePerm)
if err != nil {
return
}
_, err = parser.ParseContent(fmt.Sprintf(`import "%s"`, file))
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("duplicateImport", func(t *testing.T) {
_, err := parser.ParseContent(`
import "foo.api"
import "foo.api"
`)
assert.Error(t, err)
})
t.Run("duplicateKey", func(t *testing.T) {
_, err := parser.ParseContent(`
info (
foo: bar
foo: bar
)
`)
assert.Error(t, err)
})
t.Run("ambiguousSyntax", func(t *testing.T) {
file := filepath.Join(t.TempDir(), "foo.api")
err := ioutil.WriteFile(file, []byte(ambiguousSyntax), os.ModePerm)
if err != nil {
return
}
_, err = parser.ParseContent(fmt.Sprintf(`
syntax = "v1"
import "%s"`, file))
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("ambiguousSyntax", func(t *testing.T) {
file := filepath.Join(t.TempDir(), "foo.api")
err := ioutil.WriteFile(file, []byte(ambiguousSyntax), os.ModePerm)
if err != nil {
return
}
_, err = parser.ParseContent(fmt.Sprintf(`
syntax = "v1"
import "%s"`, file))
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("ambiguousService", func(t *testing.T) {
file := filepath.Join(t.TempDir(), "foo.api")
err := ioutil.WriteFile(file, []byte(ambiguousService), os.ModePerm)
if err != nil {
return
}
_, err = parser.ParseContent(fmt.Sprintf(`
import "%s"
service foo-api{
@handler foo
post /foo
}
`, file))
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("duplicateHandler", func(t *testing.T) {
_, err := parser.ParseContent(`
service foo-api{
@handler foo
post /foo
@handler foo
post /bar
}
`)
assert.Error(t, err)
file := filepath.Join(t.TempDir(), "foo.api")
err = ioutil.WriteFile(file, []byte(duplicateHandler), os.ModePerm)
if err != nil {
return
}
_, err = parser.ParseContent(fmt.Sprintf(`
import "%s"
service bar-api{
@handler foo
post /foo
}
`, file))
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("duplicateRoute", func(t *testing.T) {
_, err := parser.ParseContent(`
service foo-api{
@handler foo
post /foo
@handler bar
post /foo
}
`)
assert.Error(t, err)
file := filepath.Join(t.TempDir(), "foo.api")
err = ioutil.WriteFile(file, []byte(duplicateRoute), os.ModePerm)
if err != nil {
return
}
_, err = parser.ParseContent(fmt.Sprintf(`
import "%s"
service bar-api{
@handler foo
post /foo
}
`, file))
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("duplicateType", func(t *testing.T) {
_, err := parser.ParseContent(`
type Foo int
type Foo bool
`)
assert.Error(t, err)
file := filepath.Join(t.TempDir(), "foo.api")
err = ioutil.WriteFile(file, []byte(duplicateType), os.ModePerm)
if err != nil {
return
}
_, err = parser.ParseContent(fmt.Sprintf(`
import "%s"
type Foo bool
`, file))
assert.Error(t, err)
fmt.Printf("%+v\n", err)
})
t.Run("normal", func(t *testing.T) {
v, err := parser.ParseContent(normalApi)
assert.Nil(t, err)
body := &ast.Body{
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
Name: &ast.Literal{Literal: ast.NewTextExpr("Foo")},
}
assert.True(t, v.Equal(&ast.Api{
Syntax: &ast.SyntaxExpr{
Syntax: ast.NewTextExpr("syntax"),
Assign: ast.NewTextExpr("="),
Version: ast.NewTextExpr(`"v1"`),
},
Info: &ast.InfoExpr{
Info: ast.NewTextExpr("info"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
Kvs: []*ast.KvExpr{
{
Key: ast.NewTextExpr("foo"),
Value: ast.NewTextExpr("bar"),
},
},
},
Type: []ast.TypeExpr{
&ast.TypeStruct{
Name: ast.NewTextExpr("Foo"),
LBrace: ast.NewTextExpr("{"),
RBrace: ast.NewTextExpr("}"),
Fields: []*ast.TypeField{
{
Name: ast.NewTextExpr("Bar"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
},
},
},
},
Service: []*ast.Service{
{
AtServer: &ast.AtServer{
AtServerToken: ast.NewTextExpr("@server"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
Kv: []*ast.KvExpr{
{
Key: ast.NewTextExpr("foo"),
Value: ast.NewTextExpr("bar"),
},
},
},
ServiceApi: &ast.ServiceApi{
ServiceToken: ast.NewTextExpr("service"),
Name: ast.NewTextExpr("foo-api"),
Lbrace: ast.NewTextExpr("{"),
Rbrace: ast.NewTextExpr("}"),
ServiceRoute: []*ast.ServiceRoute{
{
AtDoc: &ast.AtDoc{
AtDocToken: ast.NewTextExpr("@doc"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
LineDoc: ast.NewTextExpr(`"foo"`),
},
AtHandler: &ast.AtHandler{
AtHandlerToken: ast.NewTextExpr("@handler"),
Name: ast.NewTextExpr("foo"),
},
Route: &ast.Route{
Method: ast.NewTextExpr("post"),
Path: ast.NewTextExpr("/foo"),
Req: body,
ReturnToken: ast.NewTextExpr("returns"),
Reply: &ast.Body{
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
Name: &ast.Array{
ArrayExpr: ast.NewTextExpr("[]int"),
LBrack: ast.NewTextExpr("["),
RBrack: ast.NewTextExpr("]"),
Literal: &ast.Literal{Literal: ast.NewTextExpr("int")},
},
},
},
},
},
},
},
},
}))
})
}

View File

@@ -0,0 +1,72 @@
// syntax: specify the api syntax version,
// through this version can be a good control
// api syntax upgrade incompatibility issues
syntax = "v1"
// Info block is a key-value pair description body,
// you can add some descriptions of the current api
// file through this description body, you can add
// any key-value pair, which does not participate in the api generation
info(
title: sample of api
desc: "you can add a newline
description by quotes"
author: songmeizi
anyAnotherKey: anyTnotherValue
)
// The structure in the api evolved from the structure of golang,
// and it is also reserved to support the structure of golang.
// a golang structure
type Foo struct{
Foo int
}
// api structure
type Bar {
Bar int
}
// structure group
type (
FooBar {
Foo int
Bar bool
}
)
// Like the info block, @server can define any key-value pair.
// The difference is that @server is a description of the service
// block or route, which will participate in the api file generation.
// There are several important keys that need to be understood,
// which have special meanings. The jwt key is to declare that code
// generation needs to include jwt authentication logic. The group key
// is to declare that the files generated by the code need to be grouped
// according to the value corresponding to the group. The handler key
// determines the handler in golang. Layer file logic generation
@server(
jwt: Auth
group: foo
anyAnotherKey: anyTnotherValue
)
// service block is the description of the api service,
// including @doc block, @handler and api routing information
service foo-api {
// shortening doc declaration
@doc("foo")
// shortening handler declaration
@handler foo
// route
get /foo (Foo) returns (Bar)
@doc(
summary: foo
)
@server(
handler: bar
)
post /bar (Foo)
}

View File

@@ -0,0 +1,6 @@
info(
author: songmeizi
desc: "the sample of
info"
date: "2020-01-06"
)

View File

@@ -0,0 +1,29 @@
type Foo {}
@server(
foo: foo
bar: "bar"
fooBar: "foo
bar"
)
service foo-api {
@doc("foo")
@handler foo
get /foo (Foo) returns (Foo)
@handler bar
post /foo (Foo)
@handler fooBar
post /foo/bar
@server(
handler: getFoo
)
post /foo/:id returns(Foo)
}
service foo-api {
@doc(
summary:"post foo"
)
@handler postFoo
post /foo/bar/post (Foo)
}

View File

@@ -0,0 +1 @@
syntax = "v1"

View File

@@ -0,0 +1,111 @@
// syntax doc
syntax = "v1" // syntax comment
// import doc
import "foo.api" // import comment
import(
// import group doc
"bar.api" // import group comment
)
// info doc
info(// info comment
// author doc
author: "songmeizi" // author comment
// date doc
date: 2020-01-04 // date comment
// desc doc
desc: "break line
desc" // desc comment
)
type (
FooBar struct{
Foo int
}
// remove struct
Bar {
// vString
VString string `json:"vString"`
// vBool
VBool bool `json:"vBool"`
// vInt8
VInt8 int8 `json:"vInt8"`
// vInt16
VInt16 int16 `json:"vInt16"`
// vInt32
VInt32 int32 `json:"vInt32"`
// vInt64
VInt64 int64 `json:"vInt64"`
// vInt
VInt int `json:"vInt"`
// vUInt8
VUInt8 uint8 `json:"vUInt8"`
// vUInt16
VUInt16 uint16 `json:"vUInt16"`
// vUInt32
VUInt32 uint32 `json:"vUInt32"`
// vUInt64
VUInt64 uint64 `json:"vUInt64"`
// vFloat32
VFloat32 float32 `json:"vFloat32"`
// vFloat64
VFloat64 float64 `json:"vFloat64"`
// vByte
VByte byte `json:"vByte"`
// vRune
VRune rune `json:"vRune"`
// vMap
VMap map[string]int `json:"vMap"`
// vArray
VArray []int `json:"vArray"`
// vStruct
VStruct FooBar `json:"vStruct"`
// vStructPointer
VStructPointer *FooBar `json:"vStructPointer"`
// vInterface
VInterface interface{} `json:"vInterface"`
// inline
FooBar
}
)
@server(
host: 0.0.0.0
port: 8080
annotation: "break line
desc"
)
service foo-api{
@doc("foo")
@handler postFoo
// foo
post /foo (FooBar) returns (FooBar)
@doc(
summary: bar
)
@server(
handler: postBar
)
post /bar (FooBar)
@doc("foobar")
@handler postFooBar
/**
* httpmethod: post
* path: /foo/bar
* reply: FooBar
*/
post /foo/bar returns (FooBar)
@doc("barfoo")
@handler postBarFoo
post /bar/foo // post:/bar/foo
@doc("barfoo")
@handler getBarFoo
get /bar/foo returns (FooBar)
}

View File

@@ -0,0 +1,23 @@
type Foo{
}
type Bar struct{
}
type FooBar {
Foo int
Bar bool
Map map[string]int
Map1 map[string]Bar
Map2 map[string]*Bar
Map3 map[string][]int
Map4 map[string][]Bar
Map5 map[string][]*Bar
Map6 map[string]map[string]int
Array []int
Array1 []*Bar
Array2 []Bar
Pointer *Bar
Bar
}

View File

@@ -0,0 +1,457 @@
package test
import (
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
)
var parser = ast.NewParser(ast.WithParserPrefix("test.api"), ast.WithParserDebug())
func TestApi(t *testing.T) {
fn := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
return p.Api().Accept(visitor)
}
content, err := ioutil.ReadFile("./apis/test.api")
assert.Nil(t, err)
v, err := parser.Accept(fn, string(content))
assert.Nil(t, err)
api := v.(*ast.Api)
body := &ast.Body{
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
Name: &ast.Literal{Literal: ast.NewTextExpr("FooBar")},
}
returns := ast.NewTextExpr("returns")
assert.True(t, api.Equal(&ast.Api{
Syntax: &ast.SyntaxExpr{
Syntax: ast.NewTextExpr("syntax"),
Assign: ast.NewTextExpr("="),
Version: ast.NewTextExpr(`"v1"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("// syntax doc"),
},
CommentExpr: ast.NewTextExpr("// syntax comment"),
},
Import: []*ast.ImportExpr{
{
Import: ast.NewTextExpr("import"),
Value: ast.NewTextExpr(`"foo.api"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("// import doc"),
},
CommentExpr: ast.NewTextExpr("// import comment"),
},
{
Import: ast.NewTextExpr("import"),
Value: ast.NewTextExpr(`"bar.api"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("// import group doc"),
},
CommentExpr: ast.NewTextExpr("// import group comment"),
},
},
Info: &ast.InfoExpr{
Info: ast.NewTextExpr("info"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
Kvs: []*ast.KvExpr{
{
Key: ast.NewTextExpr("author"),
Value: ast.NewTextExpr(`"songmeizi"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("// author doc"),
},
CommentExpr: ast.NewTextExpr("// author comment"),
},
{
Key: ast.NewTextExpr("date"),
Value: ast.NewTextExpr(`2020-01-04`),
DocExpr: []ast.Expr{
ast.NewTextExpr("// date doc"),
},
CommentExpr: ast.NewTextExpr("// date comment"),
},
{
Key: ast.NewTextExpr("desc"),
Value: ast.NewTextExpr(`"break line
desc"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("// desc doc"),
},
CommentExpr: ast.NewTextExpr("// desc comment"),
},
},
},
Type: []ast.TypeExpr{
&ast.TypeStruct{
Name: ast.NewTextExpr("FooBar"),
Struct: ast.NewTextExpr("struct"),
LBrace: ast.NewTextExpr("{"),
RBrace: ast.NewTextExpr("}"),
Fields: []*ast.TypeField{
{
Name: ast.NewTextExpr("Foo"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
},
},
},
&ast.TypeStruct{
Name: ast.NewTextExpr("Bar"),
LBrace: ast.NewTextExpr("{"),
RBrace: ast.NewTextExpr("}"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// remove struct"),
},
Fields: []*ast.TypeField{
{
Name: ast.NewTextExpr("VString"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("string")},
Tag: ast.NewTextExpr("`json:\"vString\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vString"),
},
},
{
Name: ast.NewTextExpr("VBool"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("bool")},
Tag: ast.NewTextExpr("`json:\"vBool\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vBool"),
},
},
{
Name: ast.NewTextExpr("VInt8"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("int8")},
Tag: ast.NewTextExpr("`json:\"vInt8\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vInt8"),
},
},
{
Name: ast.NewTextExpr("VInt16"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("int16")},
Tag: ast.NewTextExpr("`json:\"vInt16\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vInt16"),
},
},
{
Name: ast.NewTextExpr("VInt32"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("int32")},
Tag: ast.NewTextExpr("`json:\"vInt32\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vInt32"),
},
},
{
Name: ast.NewTextExpr("VInt64"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("int64")},
Tag: ast.NewTextExpr("`json:\"vInt64\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vInt64"),
},
},
{
Name: ast.NewTextExpr("VInt"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
Tag: ast.NewTextExpr("`json:\"vInt\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vInt"),
},
},
{
Name: ast.NewTextExpr("VUInt8"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("uint8")},
Tag: ast.NewTextExpr("`json:\"vUInt8\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vUInt8"),
},
},
{
Name: ast.NewTextExpr("VUInt16"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("uint16")},
Tag: ast.NewTextExpr("`json:\"vUInt16\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vUInt16"),
},
},
{
Name: ast.NewTextExpr("VUInt32"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("uint32")},
Tag: ast.NewTextExpr("`json:\"vUInt32\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vUInt32"),
},
},
{
Name: ast.NewTextExpr("VUInt64"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("uint64")},
Tag: ast.NewTextExpr("`json:\"vUInt64\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vUInt64"),
},
},
{
Name: ast.NewTextExpr("VFloat32"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("float32")},
Tag: ast.NewTextExpr("`json:\"vFloat32\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vFloat32"),
},
},
{
Name: ast.NewTextExpr("VFloat64"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("float64")},
Tag: ast.NewTextExpr("`json:\"vFloat64\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vFloat64"),
},
},
{
Name: ast.NewTextExpr("VByte"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("byte")},
Tag: ast.NewTextExpr("`json:\"vByte\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vByte"),
},
},
{
Name: ast.NewTextExpr("VRune"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("rune")},
Tag: ast.NewTextExpr("`json:\"vRune\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vRune"),
},
},
{
Name: ast.NewTextExpr("VMap"),
DataType: &ast.Map{
MapExpr: ast.NewTextExpr("map[string]int"),
Map: ast.NewTextExpr("map"),
LBrack: ast.NewTextExpr("["),
RBrack: ast.NewTextExpr("]"),
Key: ast.NewTextExpr("string"),
Value: &ast.Literal{Literal: ast.NewTextExpr("int")},
},
Tag: ast.NewTextExpr("`json:\"vMap\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vMap"),
},
},
{
Name: ast.NewTextExpr("VArray"),
DataType: &ast.Array{
ArrayExpr: ast.NewTextExpr("[]int"),
LBrack: ast.NewTextExpr("["),
RBrack: ast.NewTextExpr("]"),
Literal: &ast.Literal{Literal: ast.NewTextExpr("int")},
},
Tag: ast.NewTextExpr("`json:\"vArray\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vArray"),
},
},
{
Name: ast.NewTextExpr("VStruct"),
DataType: &ast.Literal{Literal: ast.NewTextExpr("FooBar")},
Tag: ast.NewTextExpr("`json:\"vStruct\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vStruct"),
},
},
{
Name: ast.NewTextExpr("VStructPointer"),
DataType: &ast.Pointer{
PointerExpr: ast.NewTextExpr("*FooBar"),
Star: ast.NewTextExpr("*"),
Name: ast.NewTextExpr("FooBar"),
},
Tag: ast.NewTextExpr("`json:\"vStructPointer\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vStructPointer"),
},
},
{
Name: ast.NewTextExpr("VInterface"),
DataType: &ast.Interface{Literal: ast.NewTextExpr("interface{}")},
Tag: ast.NewTextExpr("`json:\"vInterface\"`"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// vInterface"),
},
},
{
IsAnonymous: true,
DataType: &ast.Literal{Literal: ast.NewTextExpr("FooBar")},
DocExpr: []ast.Expr{
ast.NewTextExpr("// inline"),
},
},
},
},
},
Service: []*ast.Service{
{
AtServer: &ast.AtServer{
AtServerToken: ast.NewTextExpr("@server"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
Kv: []*ast.KvExpr{
{
Key: ast.NewTextExpr("host"),
Value: ast.NewTextExpr("0.0.0.0"),
},
{
Key: ast.NewTextExpr("port"),
Value: ast.NewTextExpr("8080"),
},
{
Key: ast.NewTextExpr("annotation"),
Value: ast.NewTextExpr(`"break line
desc"`),
},
},
},
ServiceApi: &ast.ServiceApi{
ServiceToken: ast.NewTextExpr("service"),
Name: ast.NewTextExpr("foo-api"),
Lbrace: ast.NewTextExpr("{"),
Rbrace: ast.NewTextExpr("}"),
ServiceRoute: []*ast.ServiceRoute{
{
AtDoc: &ast.AtDoc{
AtDocToken: ast.NewTextExpr("@doc"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
LineDoc: ast.NewTextExpr(`"foo"`),
},
AtHandler: &ast.AtHandler{
AtHandlerToken: ast.NewTextExpr("@handler"),
Name: ast.NewTextExpr("postFoo"),
},
Route: &ast.Route{
Method: ast.NewTextExpr("post"),
Path: ast.NewTextExpr("/foo"),
Req: body,
ReturnToken: returns,
Reply: body,
DocExpr: []ast.Expr{
ast.NewTextExpr("// foo"),
},
},
},
{
AtDoc: &ast.AtDoc{
AtDocToken: ast.NewTextExpr("@doc"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
Kv: []*ast.KvExpr{
{
Key: ast.NewTextExpr("summary"),
Value: ast.NewTextExpr("bar"),
},
},
},
AtServer: &ast.AtServer{
AtServerToken: ast.NewTextExpr("@server"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
Kv: []*ast.KvExpr{
{
Key: ast.NewTextExpr("handler"),
Value: ast.NewTextExpr("postBar"),
},
},
},
Route: &ast.Route{
Method: ast.NewTextExpr("post"),
Path: ast.NewTextExpr("/bar"),
Req: body,
},
},
{
AtDoc: &ast.AtDoc{
AtDocToken: ast.NewTextExpr("@doc"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
LineDoc: ast.NewTextExpr(`"foobar"`),
},
AtHandler: &ast.AtHandler{
AtHandlerToken: ast.NewTextExpr("@handler"),
Name: ast.NewTextExpr("postFooBar"),
},
Route: &ast.Route{
Method: ast.NewTextExpr("post"),
Path: ast.NewTextExpr("/foo/bar"),
ReturnToken: returns,
Reply: body,
DocExpr: []ast.Expr{
ast.NewTextExpr(`/**
* httpmethod: post
* path: /foo/bar
* reply: FooBar
*/`),
},
},
},
{
AtDoc: &ast.AtDoc{
AtDocToken: ast.NewTextExpr("@doc"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
LineDoc: ast.NewTextExpr(`"barfoo"`),
},
AtHandler: &ast.AtHandler{
AtHandlerToken: ast.NewTextExpr("@handler"),
Name: ast.NewTextExpr("postBarFoo"),
},
Route: &ast.Route{
Method: ast.NewTextExpr("post"),
Path: ast.NewTextExpr("/bar/foo"),
ReturnToken: returns,
Reply: body,
CommentExpr: ast.NewTextExpr("// post:/bar/foo"),
},
},
{
AtDoc: &ast.AtDoc{
AtDocToken: ast.NewTextExpr("@doc"),
Lp: ast.NewTextExpr("("),
Rp: ast.NewTextExpr(")"),
LineDoc: ast.NewTextExpr(`"barfoo"`),
},
AtHandler: &ast.AtHandler{
AtHandlerToken: ast.NewTextExpr("@handler"),
Name: ast.NewTextExpr("getBarFoo"),
},
Route: &ast.Route{
Method: ast.NewTextExpr("get"),
Path: ast.NewTextExpr("/bar/foo"),
ReturnToken: returns,
Reply: body,
},
},
},
},
},
},
}))
}
func TestApiSyntax(t *testing.T) {
fn := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
return p.Api().Accept(visitor)
}
parser.Accept(fn, `
// doc 1
// doc 2
syntax = "v1" // comment 1
// comment 2
import "foo.api"
`)
}

View File

@@ -0,0 +1,25 @@
package test
import (
"testing"
"github.com/stretchr/testify/assert"
)
var files = []string{
"example",
"empty",
"syntax",
"info",
"types",
"service",
}
func TestGrammar(t *testing.T) {
for _, file := range files {
t.Run(file, func(t *testing.T) {
_, err := parser.Parse("./apis/" + file + ".api")
assert.Nil(t, err)
})
}
}

View File

@@ -0,0 +1,143 @@
package test
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
)
var importAccept = func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
return p.ImportSpec().Accept(visitor)
}
func TestImport(t *testing.T) {
t.Run("matched", func(t *testing.T) {
v, err := parser.Accept(importAccept, `import "foo.api"`)
assert.Nil(t, err)
list := v.([]*ast.ImportExpr)
for _, each := range list {
assert.True(t, each.Equal(&ast.ImportExpr{
Import: ast.NewTextExpr("import"),
Value: ast.NewTextExpr(`"foo.api"`),
}))
}
})
t.Run("matched block", func(t *testing.T) {
v, err := parser.Accept(importAccept, `
import (
/**foo*/
"foo.api"
/**bar*/
"bar.api"
/**foobar*/
"foo/bar.api"/**foobar*/
)
`)
assert.Nil(t, err)
list := v.([]*ast.ImportExpr)
expected := []*ast.ImportExpr{
{
Import: ast.NewTextExpr("import"),
Value: ast.NewTextExpr(`"foo.api"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("/**foo*/"),
},
},
{
Import: ast.NewTextExpr("import"),
Value: ast.NewTextExpr(`"bar.api"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("/**bar*/"),
},
},
{
Import: ast.NewTextExpr("import"),
Value: ast.NewTextExpr(`"foo/bar.api"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("/**foobar*/"),
},
CommentExpr: ast.NewTextExpr("/**foobar*/"),
},
}
sort.Slice(list, func(i, j int) bool {
return list[i].Value.Line() < list[j].Value.Line()
})
sort.Slice(expected, func(i, j int) bool {
return expected[i].Value.Line() < expected[j].Value.Line()
})
assert.True(t, len(list) == len(expected))
for index, each := range list {
assert.True(t, each.Equal(expected[index]))
}
})
t.Run("matched doc", func(t *testing.T) {
v, err := parser.Accept(importAccept, `
/**doc*/
import "foo.api" /**line doc*/`)
assert.Nil(t, err)
list := v.([]*ast.ImportExpr)
for _, each := range list {
assert.True(t, each.Equal(&ast.ImportExpr{
Import: ast.NewTextExpr("import"),
Value: ast.NewTextExpr(`"foo.api"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("/**doc*/"),
},
CommentExpr: ast.NewTextExpr("/**line doc*/"),
}))
}
})
t.Run("matched comment", func(t *testing.T) {
v, err := parser.Accept(importAccept, `
// comment block
import "foo.api" // line comment`)
assert.Nil(t, err)
list := v.([]*ast.ImportExpr)
for _, each := range list {
assert.True(t, each.Equal(&ast.ImportExpr{
Import: ast.NewTextExpr("import"),
Value: ast.NewTextExpr(`"foo.api"`),
DocExpr: []ast.Expr{
ast.NewTextExpr("// comment block"),
},
CommentExpr: ast.NewTextExpr("// line comment"),
}))
}
})
t.Run("mismatched import", func(t *testing.T) {
_, err := parser.Accept(importAccept, `
"foo.api"`)
assert.Error(t, err)
_, err = parser.Accept(importAccept, `
impor "foo.api"`)
assert.Error(t, err)
})
t.Run("mismatched value", func(t *testing.T) {
_, err := parser.Accept(importAccept, `
import "foo"`)
assert.Error(t, err)
_, err = parser.Accept(importAccept, `
import ""`)
assert.Error(t, err)
_, err = parser.Accept(importAccept, `
import `)
assert.Error(t, err)
})
}

View File

@@ -0,0 +1,186 @@
package test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
)
var infoAccept = func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
return p.InfoSpec().Accept(visitor)
}
func TestInfo(t *testing.T) {
t.Run("matched", func(t *testing.T) {
v, err := parser.Accept(infoAccept, `
info(
title: foo
)
`)
assert.Nil(t, err)
info := v.(*ast.InfoExpr)
assert.True(t, info.Equal(&ast.InfoExpr{
Info: ast.NewTextExpr("info"),
Kvs: []*ast.KvExpr{
{
Key: ast.NewTextExpr("title"),
Value: ast.NewTextExpr("foo"),
},
},
}))
v, err = parser.Accept(infoAccept, `
info(
title: 中文(bar)
)
`)
assert.Nil(t, err)
info = v.(*ast.InfoExpr)
assert.True(t, info.Equal(&ast.InfoExpr{
Info: ast.NewTextExpr("info"),
Kvs: []*ast.KvExpr{
{
Key: ast.NewTextExpr("title"),
Value: ast.NewTextExpr("中文(bar)"),
},
},
}))
v, err = parser.Accept(infoAccept, `
info(
foo: "new
line"
)
`)
assert.Nil(t, err)
info = v.(*ast.InfoExpr)
assert.True(t, info.Equal(&ast.InfoExpr{
Info: ast.NewTextExpr("info"),
Kvs: []*ast.KvExpr{
{
Key: ast.NewTextExpr("foo"),
Value: ast.NewTextExpr(`"new
line"`),
},
},
}))
})
t.Run("matched doc", func(t *testing.T) {
v, err := parser.Accept(infoAccept, `
// doc
info( // comment
// foo
title: foo // bar
)
`)
assert.Nil(t, err)
info := v.(*ast.InfoExpr)
assert.True(t, info.Equal(&ast.InfoExpr{
Info: ast.NewTextExpr("info"),
Kvs: []*ast.KvExpr{
{
Key: ast.NewTextExpr("title"),
Value: ast.NewTextExpr("foo"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// foo"),
},
CommentExpr: ast.NewTextExpr("// bar"),
},
},
}))
v, err = parser.Accept(infoAccept, `
/**doc block*/
info( /**line block*/
/**foo*/
title: foo /*bar**/
)
`)
assert.Nil(t, err)
info = v.(*ast.InfoExpr)
assert.True(t, info.Equal(&ast.InfoExpr{
Info: ast.NewTextExpr("info"),
Kvs: []*ast.KvExpr{
{
Key: ast.NewTextExpr("title"),
Value: ast.NewTextExpr("foo"),
DocExpr: []ast.Expr{
ast.NewTextExpr("/**foo*/"),
},
CommentExpr: ast.NewTextExpr("/*bar**/"),
},
},
}))
v, err = parser.Accept(infoAccept, `
info(
// doc
title: foo
// doc
author: bar
)
`)
assert.Nil(t, err)
info = v.(*ast.InfoExpr)
assert.True(t, info.Equal(&ast.InfoExpr{
Info: ast.NewTextExpr("info"),
Kvs: []*ast.KvExpr{
{
Key: ast.NewTextExpr("title"),
Value: ast.NewTextExpr("foo"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// doc"),
},
},
{
Key: ast.NewTextExpr("author"),
Value: ast.NewTextExpr("bar"),
DocExpr: []ast.Expr{
ast.NewTextExpr("// doc"),
},
},
},
}))
})
t.Run("mismatched", func(t *testing.T) {
_, err := parser.Accept(infoAccept, `
info(
title
)
`)
assert.Error(t, err)
_, err = parser.Accept(infoAccept, `
info(
:title
)
`)
assert.Error(t, err)
_, err = parser.Accept(infoAccept, `
info(
foo bar
)
`)
assert.Error(t, err)
_, err = parser.Accept(infoAccept, `
info(
foo : new
line
)
`)
assert.Error(t, err)
_, err = parser.Accept(infoAccept, `
info()
`)
assert.Error(t, err)
})
}

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