From f32ebe4a4fc5d97c4d9cb6d17b6fd38b4805de11 Mon Sep 17 00:00:00 2001 From: Mikhail Knyazhev Date: Thu, 19 Feb 2026 04:46:10 +0300 Subject: [PATCH 1/6] add web server generator --- _example/basic/main.go | 5 +- .../001000_users_table.sql | 0 .../{go-gen => orm-gen}/001001_meta_table.sql | 0 _example/{go-gen => orm-gen}/empty.go | 2 +- _example/{go-gen => orm-gen}/model.go | 2 +- _example/{go-gen => orm-gen}/repo_init.go | 2 +- _example/{go-gen => orm-gen}/repo_meta.go | 2 +- _example/{go-gen => orm-gen}/repo_user.go | 2 +- _example/web-server-gen/interfaces.go | 34 +++ _example/web-server-gen/wsg.go | 8 + cmd/goppy/main.go | 11 +- console/io.go | 6 + go.mod | 25 +- go.sum | 50 ++-- internal/applog/logger.go | 3 +- internal/commands/build.go | 3 +- internal/commands/generate.go | 4 +- internal/commands/gosite.go | 3 +- internal/commands/license.go | 3 +- internal/commands/lint.go | 3 +- internal/commands/setup.go | 3 +- internal/commands/tag.go | 3 +- internal/commands/tests.go | 2 +- internal/gen/ormb/command.go | 3 +- internal/gen/ormb/common/utils.go | 2 +- internal/gen/ormb/generate_sql.go | 3 +- internal/gen/ormb/visitor/visitor.go | 3 +- internal/gen/wsg/command.go | 70 +++++ internal/gen/wsg/module/type.go | 12 + internal/gen/wsg/types/object.go | 33 ++ internal/gen/wsg/visitor/visitor.go | 282 ++++++++++++++++++ internal/global/exec.go | 3 +- internal/global/global.go | 3 +- 33 files changed, 524 insertions(+), 66 deletions(-) rename _example/{go-gen => orm-gen}/001000_users_table.sql (100%) rename _example/{go-gen => orm-gen}/001001_meta_table.sql (100%) rename _example/{go-gen => orm-gen}/empty.go (93%) rename _example/{go-gen => orm-gen}/model.go (98%) rename _example/{go-gen => orm-gen}/repo_init.go (98%) rename _example/{go-gen => orm-gen}/repo_meta.go (99%) rename _example/{go-gen => orm-gen}/repo_user.go (99%) create mode 100644 _example/web-server-gen/interfaces.go create mode 100644 _example/web-server-gen/wsg.go create mode 100644 internal/gen/wsg/command.go create mode 100644 internal/gen/wsg/module/type.go create mode 100644 internal/gen/wsg/types/object.go create mode 100644 internal/gen/wsg/visitor/visitor.go diff --git a/_example/basic/main.go b/_example/basic/main.go index a2b8b77..1d0a1c7 100644 --- a/_example/basic/main.go +++ b/_example/basic/main.go @@ -11,14 +11,15 @@ import ( "fmt" "os" + "go.osspkg.com/logx" + "go.osspkg.com/xc" + "go.osspkg.com/goppy/v3" "go.osspkg.com/goppy/v3/console" "go.osspkg.com/goppy/v3/dic/broker" "go.osspkg.com/goppy/v3/metrics" "go.osspkg.com/goppy/v3/plugins" "go.osspkg.com/goppy/v3/web" - "go.osspkg.com/logx" - "go.osspkg.com/xc" ) type IStatus interface { diff --git a/_example/go-gen/001000_users_table.sql b/_example/orm-gen/001000_users_table.sql similarity index 100% rename from _example/go-gen/001000_users_table.sql rename to _example/orm-gen/001000_users_table.sql diff --git a/_example/go-gen/001001_meta_table.sql b/_example/orm-gen/001001_meta_table.sql similarity index 100% rename from _example/go-gen/001001_meta_table.sql rename to _example/orm-gen/001001_meta_table.sql diff --git a/_example/go-gen/empty.go b/_example/orm-gen/empty.go similarity index 93% rename from _example/go-gen/empty.go rename to _example/orm-gen/empty.go index 1fa4759..ae1670b 100644 --- a/_example/go-gen/empty.go +++ b/_example/orm-gen/empty.go @@ -3,7 +3,7 @@ * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. */ -package go_gen +package orm_gen type Empty struct { A string diff --git a/_example/go-gen/model.go b/_example/orm-gen/model.go similarity index 98% rename from _example/go-gen/model.go rename to _example/orm-gen/model.go index f5d88a3..622585f 100644 --- a/_example/go-gen/model.go +++ b/_example/orm-gen/model.go @@ -3,7 +3,7 @@ * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. */ -package go_gen +package orm_gen import ( "time" diff --git a/_example/go-gen/repo_init.go b/_example/orm-gen/repo_init.go similarity index 98% rename from _example/go-gen/repo_init.go rename to _example/orm-gen/repo_init.go index 9c674de..2990f79 100755 --- a/_example/go-gen/repo_init.go +++ b/_example/orm-gen/repo_init.go @@ -4,7 +4,7 @@ */ // Code generated by goppy-cli for goppy.orm. DO NOT EDIT. -package go_gen +package orm_gen import ( "fmt" diff --git a/_example/go-gen/repo_meta.go b/_example/orm-gen/repo_meta.go similarity index 99% rename from _example/go-gen/repo_meta.go rename to _example/orm-gen/repo_meta.go index 4e2a91b..799d312 100755 --- a/_example/go-gen/repo_meta.go +++ b/_example/orm-gen/repo_meta.go @@ -4,7 +4,7 @@ */ // Code generated by goppy-cli for goppy.orm. DO NOT EDIT. -package go_gen +package orm_gen import ( "context" diff --git a/_example/go-gen/repo_user.go b/_example/orm-gen/repo_user.go similarity index 99% rename from _example/go-gen/repo_user.go rename to _example/orm-gen/repo_user.go index 00a94f9..a78cf32 100755 --- a/_example/go-gen/repo_user.go +++ b/_example/orm-gen/repo_user.go @@ -4,7 +4,7 @@ */ // Code generated by goppy-cli for goppy.orm. DO NOT EDIT. -package go_gen +package orm_gen import ( "context" diff --git a/_example/web-server-gen/interfaces.go b/_example/web-server-gen/interfaces.go new file mode 100644 index 0000000..e098015 --- /dev/null +++ b/_example/web-server-gen/interfaces.go @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package web_server_gen + +import "context" + +// Api +// @wsg description="Методы апи" +// @wsg module=json-rpc,http +/* +@wsg module=json-rpc,http2 +@wsg module=json-rpc,http3 +*/ +type Api interface { + // Root + // @wsg in.userID=cookie:x-user-id + Root( + ctx context.Context, + userID int64, + userName string, + ) (status bool, err error) + + // Auth + // @wsg in.userID=cookie:x-user-id + // @wsg module=authz + Auth( + ctx context.Context, + userID int64, + userName string, + ) (status bool, err error) +} diff --git a/_example/web-server-gen/wsg.go b/_example/web-server-gen/wsg.go new file mode 100644 index 0000000..b426ba9 --- /dev/null +++ b/_example/web-server-gen/wsg.go @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package web_server_gen + +//go:generate goppy wsg --out=./transport diff --git a/cmd/goppy/main.go b/cmd/goppy/main.go index 7fb404e..f4794a7 100644 --- a/cmd/goppy/main.go +++ b/cmd/goppy/main.go @@ -6,11 +6,10 @@ package main import ( - "go.osspkg.com/console" - - "go.osspkg.com/goppy/v3/internal/gen/ormb" - + "go.osspkg.com/goppy/v3/console" "go.osspkg.com/goppy/v3/internal/commands" + "go.osspkg.com/goppy/v3/internal/gen/ormb" + "go.osspkg.com/goppy/v3/internal/gen/wsg" "go.osspkg.com/goppy/v3/internal/global" ) @@ -29,9 +28,7 @@ func main() { commands.CmdSetupApp(), commands.CmdGoSite(), ormb.Command(), - - //gen.Command(), - // commands.CmdGenerate(), + wsg.Command(), ) app.Exec() diff --git a/console/io.go b/console/io.go index f091f73..9b107f0 100644 --- a/console/io.go +++ b/console/io.go @@ -325,6 +325,12 @@ func FatalIfErr(err error, msg string, args ...interface{}) { } } +func FatalIfTrue(condition bool, msg string, args ...interface{}) { + if condition { + Fatalf(fmt.Sprintf(msg, args...)) + } +} + func WarnIfErr(err error, msg string, args ...interface{}) { if err != nil { Warnf(errors.Wrapf(err, msg, args...).Error()) diff --git a/go.mod b/go.mod index 6834782..819d3ca 100644 --- a/go.mod +++ b/go.mod @@ -7,17 +7,17 @@ require ( github.com/go-sql-driver/mysql v1.9.3 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 - github.com/lib/pq v1.10.9 + github.com/lib/pq v1.11.2 github.com/mailru/easyjson v0.9.1 - github.com/mattn/go-sqlite3 v1.14.33 - github.com/miekg/dns v1.1.70 + github.com/mattn/go-sqlite3 v1.14.34 + github.com/miekg/dns v1.1.72 github.com/oschwald/geoip2-golang v1.13.0 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 go.osspkg.com/algorithms v1.6.1 + go.osspkg.com/bb v1.0.0 go.osspkg.com/casecheck v0.3.0 go.osspkg.com/config v0.2.0 - go.osspkg.com/console v0.3.3 go.osspkg.com/do v0.2.1 go.osspkg.com/errors v0.4.0 go.osspkg.com/events v0.3.0 @@ -31,10 +31,10 @@ require ( go.osspkg.com/validate v0.1.0 go.osspkg.com/xc v0.4.0 go.uber.org/automaxprocs v1.6.0 - golang.org/x/crypto v0.47.0 - golang.org/x/exp v0.0.0-20260112195511-716be5621a96 - golang.org/x/mod v0.32.0 - golang.org/x/oauth2 v0.34.0 + golang.org/x/crypto v0.48.0 + golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa + golang.org/x/mod v0.33.0 + golang.org/x/oauth2 v0.35.0 google.golang.org/protobuf v1.36.11 ) @@ -79,12 +79,13 @@ require ( github.com/prometheus/procfs v0.16.1 // indirect github.com/quic-go/quic-go v0.50.1 // indirect go.etcd.io/bbolt v1.4.3 // indirect + go.osspkg.com/gogen v0.1.0 // indirect go.uber.org/mock v0.5.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.49.0 // indirect + golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 329bf8a..a5880c1 100644 --- a/go.sum +++ b/go.sum @@ -81,14 +81,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= -github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA= -github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= +github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -130,18 +130,20 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.osspkg.com/algorithms v1.6.1 h1:Boduh7kuqdw9r/mOkAvN4cyZX1yqUVp7gZtLqBeIHkY= go.osspkg.com/algorithms v1.6.1/go.mod h1:QojMfdersOcebMu/VfERA8Jsn+s/9TGELgaRFiyGuq0= +go.osspkg.com/bb v1.0.0 h1:6rGK0Gioyo7EbgLaTTIRxhF+me2zoMlOef1mGJoO/L0= +go.osspkg.com/bb v1.0.0/go.mod h1:bh7v2dIXJC0ge8BZLsh10EFRbJSLjXCmxq4IwSogc58= go.osspkg.com/casecheck v0.3.0 h1:x15blEszElbrHrEH5H02JIIhGIg/lGZzIt1kQlD3pwM= go.osspkg.com/casecheck v0.3.0/go.mod h1:TRFXDMFJEOtnlp3ET2Hix3osbxwPWhvaiT/HfD3+gBA= go.osspkg.com/config v0.2.0 h1:nPf14TX+HnVgOtlX1vobeTl//bQ3T/fAhXASaDRZ5rI= go.osspkg.com/config v0.2.0/go.mod h1:lYvJ3OfeWIQLr9ToRcDU+7fVCJLirDtKFpdxtAR8K1A= -go.osspkg.com/console v0.3.3 h1:UB/pPoPsgWbyNFix8pEMQHbsXdMv/UK/dgsbRknCH2A= -go.osspkg.com/console v0.3.3/go.mod h1:IknBCliH6mX/ogHa6wbycnGDFYixCGH3WuNc5W5tQe8= go.osspkg.com/do v0.2.1 h1:1JYkt54VHVGVUFxUzTTUvmHcGSezjijvGAaD70Z8/Q8= go.osspkg.com/do v0.2.1/go.mod h1:ZViMiWrU6AfobhImtZvqe5n8ZiC/eT6KMF+5JHbZyAg= go.osspkg.com/errors v0.4.0 h1:E17+WyUzTXEHCTxGm8lOMPOOojzHG1lsOuQtTVGoATQ= go.osspkg.com/errors v0.4.0/go.mod h1:s75ZovPemYtrCtRPVsbQNq9MgMbmLMK1NEypr+uwjXI= go.osspkg.com/events v0.3.0 h1:W2IngTsKs0BKYIglqhrETwtpo6uNSZXWRIt0/l7c6dY= go.osspkg.com/events v0.3.0/go.mod h1:Cjpx+qNM1y2MIAygFyZWYagTuRiYirmKppZQdaZumd4= +go.osspkg.com/gogen v0.1.0 h1:2JV95VJUGBnPwSzNtcpmhF0Kah3uhtVFERqUyxglmrU= +go.osspkg.com/gogen v0.1.0/go.mod h1:BkoU7OC3LV/kmtjvCnQB1rF9hcK0rF3hZKoaZlP+Uv4= go.osspkg.com/ioutils v0.7.4 h1:Z8Y4jYYmLGWcvHZMLjbai+s48GmHxjMuepsxZcjF5X4= go.osspkg.com/ioutils v0.7.4/go.mod h1:pPIsTL1w1+ESrGTeHDCd6cKsujeWvschxGGP5FqrAqc= go.osspkg.com/logx v0.6.1 h1:JqHk1iFBOKnwO0dZtC7n4sO/0MkUD9c7eulmYEDCnao= @@ -168,27 +170,27 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/applog/logger.go b/internal/applog/logger.go index ee93fc7..7425fba 100644 --- a/internal/applog/logger.go +++ b/internal/applog/logger.go @@ -12,8 +12,9 @@ import ( "os" "strings" - "go.osspkg.com/console" "go.osspkg.com/logx" + + "go.osspkg.com/goppy/v3/console" ) const formatSyslog = "syslog" diff --git a/internal/commands/build.go b/internal/commands/build.go index 4b5a4ae..afe8788 100644 --- a/internal/commands/build.go +++ b/internal/commands/build.go @@ -8,10 +8,11 @@ package commands import ( "strings" - "go.osspkg.com/console" "go.osspkg.com/do" "go.osspkg.com/ioutils/fs" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/global" ) diff --git a/internal/commands/generate.go b/internal/commands/generate.go index 2bf13e5..5bdfa2b 100644 --- a/internal/commands/generate.go +++ b/internal/commands/generate.go @@ -14,9 +14,9 @@ import ( "strings" "text/template" - "go.osspkg.com/console" "go.osspkg.com/ioutils/fs" + "go.osspkg.com/goppy/v3/console" "go.osspkg.com/goppy/v3/internal/global" ) @@ -28,7 +28,7 @@ func CmdGenerate() console.CommandGetter { data := make(map[string]any, 100) data["go_version"] = strings.TrimLeft(global.GoVersion(), "go") - data["app_module"] = console.Input("Input project name", nil, "app") + data["app_module"] = console.Select("Input project name", nil, "app") data["app_name"] = func() string { vv := strings.Split(data["app_module"].(string), "/") //nolint: errcheck return vv[len(vv)-1] diff --git a/internal/commands/gosite.go b/internal/commands/gosite.go index 95748ad..ada06db 100644 --- a/internal/commands/gosite.go +++ b/internal/commands/gosite.go @@ -12,10 +12,11 @@ import ( "sort" "strings" - "go.osspkg.com/console" "go.osspkg.com/ioutils/codec" "go.osspkg.com/ioutils/fs" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/global" ) diff --git a/internal/commands/license.go b/internal/commands/license.go index 11c4719..302116b 100644 --- a/internal/commands/license.go +++ b/internal/commands/license.go @@ -12,11 +12,12 @@ import ( "strings" "time" - "go.osspkg.com/console" "go.osspkg.com/do" "go.osspkg.com/ioutils/codec" "go.osspkg.com/ioutils/fs" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/global" ) diff --git a/internal/commands/lint.go b/internal/commands/lint.go index 8f978e9..89080bd 100644 --- a/internal/commands/lint.go +++ b/internal/commands/lint.go @@ -9,9 +9,10 @@ import ( "fmt" "path/filepath" - "go.osspkg.com/console" "go.osspkg.com/ioutils/fs" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/global" ) diff --git a/internal/commands/setup.go b/internal/commands/setup.go index f47f74b..babf586 100644 --- a/internal/commands/setup.go +++ b/internal/commands/setup.go @@ -12,9 +12,10 @@ import ( "path/filepath" "strings" - "go.osspkg.com/console" "go.osspkg.com/ioutils/fs" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/global" ) diff --git a/internal/commands/tag.go b/internal/commands/tag.go index 629c536..974005a 100644 --- a/internal/commands/tag.go +++ b/internal/commands/tag.go @@ -12,11 +12,12 @@ import ( "strings" "go.osspkg.com/algorithms/graph/kahn" - "go.osspkg.com/console" "go.osspkg.com/ioutils/fs" "go.osspkg.com/validate" "golang.org/x/mod/modfile" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/global" ) diff --git a/internal/commands/tests.go b/internal/commands/tests.go index deae0a3..6b8dde1 100644 --- a/internal/commands/tests.go +++ b/internal/commands/tests.go @@ -8,7 +8,7 @@ package commands import ( "os" - "go.osspkg.com/console" + "go.osspkg.com/goppy/v3/console" "go.osspkg.com/goppy/v3/internal/global" ) diff --git a/internal/gen/ormb/command.go b/internal/gen/ormb/command.go index 1e4240d..9f53afd 100644 --- a/internal/gen/ormb/command.go +++ b/internal/gen/ormb/command.go @@ -10,10 +10,11 @@ import ( "go/parser" "go/token" - "go.osspkg.com/console" "go.osspkg.com/ioutils/fs" "go.osspkg.com/syncing" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/gen/ormb/common" "go.osspkg.com/goppy/v3/internal/gen/ormb/dialects" "go.osspkg.com/goppy/v3/internal/gen/ormb/visitor" diff --git a/internal/gen/ormb/common/utils.go b/internal/gen/ormb/common/utils.go index 0495ee8..50e2d8e 100644 --- a/internal/gen/ormb/common/utils.go +++ b/internal/gen/ormb/common/utils.go @@ -10,7 +10,7 @@ import ( "io" "strings" - "go.osspkg.com/console" + "go.osspkg.com/goppy/v3/console" ) func SplitLast(s, sep string) string { diff --git a/internal/gen/ormb/generate_sql.go b/internal/gen/ormb/generate_sql.go index e8a869c..23eabb7 100644 --- a/internal/gen/ormb/generate_sql.go +++ b/internal/gen/ormb/generate_sql.go @@ -10,9 +10,10 @@ import ( "os" "strings" - "go.osspkg.com/console" "go.osspkg.com/ioutils/data" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/gen/ormb/common" "go.osspkg.com/goppy/v3/internal/gen/ormb/dialects" "go.osspkg.com/goppy/v3/internal/gen/ormb/visitor" diff --git a/internal/gen/ormb/visitor/visitor.go b/internal/gen/ormb/visitor/visitor.go index f8ab5ce..441d341 100644 --- a/internal/gen/ormb/visitor/visitor.go +++ b/internal/gen/ormb/visitor/visitor.go @@ -9,9 +9,10 @@ import ( "go/ast" "strings" - "go.osspkg.com/console" "go.osspkg.com/syncing" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/gen/ormb/common" "go.osspkg.com/goppy/v3/internal/gen/ormb/table" ) diff --git a/internal/gen/wsg/command.go b/internal/gen/wsg/command.go new file mode 100644 index 0000000..2748f16 --- /dev/null +++ b/internal/gen/wsg/command.go @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package wsg + +import ( + "go/ast" + "go/parser" + "go/token" + "os" + "strings" + + "go.osspkg.com/do" + "go.osspkg.com/ioutils/fs" + "go.osspkg.com/syncing" + + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/gen/wsg/visitor" +) + +func Command() console.CommandGetter { + return console.NewCommand(func(setter console.CommandSetter) { + setter.Setup("wsg", "generate web server api") + setter.Flag(func(flagsSetter console.FlagsSetter) { + flagsSetter.String("out", "output file specified") + }) + setter.ExecFunc(func(out string) { + //console.ShowDebug(false) + console.Infof("--- GENERATE ---") + + curDir := fs.CurrentDir() + + console.FatalIfTrue(len(out) == 0, "no output file specified") + console.FatalIfTrue(out == curDir, "output dir equals current dir") + + console.FatalIfErr(os.RemoveAll(out), "failed to remove old output directory") + console.FatalIfErr(os.MkdirAll(out, 0755), "failed to create directory") + + files, err := fs.SearchFilesByExt(curDir, ".go") + console.FatalIfErr(err, "search files in %s", curDir) + + files = do.Filter[string](files, func(value string, index int) bool { + return !strings.HasPrefix(value, out) + }) + + for _, filePath := range files { + console.Debugf("> PARSE FILE: %s", filePath) + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) + console.FatalIfErr(err, "parse go file: %s", filePath) + + if ast.IsGenerated(f) { + continue + } + + vv := &visitor.Visitor{ + Imports: syncing.NewMap[string, string](1), + FilePath: filePath, + } + + ast.Walk(vv, f) + + vv.Debug() + } + }) + }) +} diff --git a/internal/gen/wsg/module/type.go b/internal/gen/wsg/module/type.go new file mode 100644 index 0000000..eb45c4d --- /dev/null +++ b/internal/gen/wsg/module/type.go @@ -0,0 +1,12 @@ +package module + +import ( + "go.osspkg.com/gogen/golang" + "go.osspkg.com/goppy/v3/internal/gen/wsg/types" +) + +type Module interface { + Name() string + CreateObject(object types.Object) *golang.Tokens + CreateMethod(object types.Method) *golang.Tokens +} diff --git a/internal/gen/wsg/types/object.go b/internal/gen/wsg/types/object.go new file mode 100644 index 0000000..be89618 --- /dev/null +++ b/internal/gen/wsg/types/object.go @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package types + +type Object struct { + Name string + Methods []Method + Tags Tags +} + +type Method struct { + Name string + Tags Tags + InParams []Param + OutParams []Param +} + +type Param struct { + Name string + Type string + Pkg string + Omitempty bool +} + +type KV struct { + Key string + Value string +} + +type Tags map[string][]string diff --git a/internal/gen/wsg/visitor/visitor.go b/internal/gen/wsg/visitor/visitor.go new file mode 100644 index 0000000..ae4e88a --- /dev/null +++ b/internal/gen/wsg/visitor/visitor.go @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package visitor + +import ( + "errors" + "fmt" + "go/ast" + "io" + "strings" + + "go.osspkg.com/bb" + "go.osspkg.com/ioutils/fs" + "go.osspkg.com/syncing" + + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/gen/ormb/common" + "go.osspkg.com/goppy/v3/internal/gen/wsg/types" +) + +const tag = "@wsg" + +type Visitor struct { + FilePath string + PkgName string + Imports *syncing.Map[string, string] + Objects []types.Object +} + +func (v *Visitor) Debug() { + fmt.Println("=============================================================") + fmt.Println("FilePath:", strings.TrimPrefix(v.FilePath, fs.CurrentDir())) + fmt.Println("PkgName:", v.PkgName) + fmt.Println("Import:") + for alias, path := range v.Imports.Yield() { + fmt.Println(" ", alias, path) + } + for _, object := range v.Objects { + fmt.Println("Interface:", object.Name) + fmt.Println(" tags:") + for key, value := range object.Tags { + fmt.Println(" ", key, ":", value) + } + + for _, method := range object.Methods { + fmt.Println(" method:", method.Name) + + fmt.Println(" tags:") + for key, value := range method.Tags { + fmt.Println(" ", key, ":", value) + } + + fmt.Println(" in:") + for _, value := range method.InParams { + fmt.Println(" ", + "name:", value.Name, ", type:", value.Name, + ", pkg:", value.Pkg, ", omit:", value.Omitempty) + } + + fmt.Println(" out:") + for _, value := range method.OutParams { + fmt.Println(" ", + "name:", value.Name, ", type:", value.Name, + ", pkg:", value.Pkg, ", omit:", value.Omitempty) + } + } + } + fmt.Println("=============================================================") +} + +func (v *Visitor) Visit(node ast.Node) ast.Visitor { + switch nodeType := node.(type) { + case *ast.File: + return v.astFile(nodeType) + + case *ast.ImportSpec: + return v.astImportSpec(nodeType) + + case *ast.GenDecl: + for _, spec := range nodeType.Specs { + switch specType := spec.(type) { + case *ast.TypeSpec: + if nodeType.Doc != nil { + specType.Doc = nodeType.Doc + } + v.astTypeSpec(specType) + } + } + return v + + default: + return nil + } +} + +func (v *Visitor) astFile(node *ast.File) ast.Visitor { + v.PkgName = node.Name.String() + + console.Debugf("Parsed PkgName: %s", v.PkgName) + + return v +} + +func (v *Visitor) astImportSpec(node *ast.ImportSpec) ast.Visitor { + path := strings.Trim(node.Path.Value, `"`) + name := common.SplitLast(path, "/") + + if node.Name != nil { + name = node.Name.String() + } + + console.Debugf("Import: name='%s' path='%s'", name, path) + + v.Imports.Set(name, path) + + return v +} + +func (v *Visitor) parseDoc(comment *ast.CommentGroup, tags *types.Tags) { + if comment == nil { + return + } + for _, doc := range comment.List { + i := strings.Index(doc.Text, tag) + if i < 0 { + continue + } + v.parseComment(doc.Text[i+len(tag):], tags) + } +} + +func (v *Visitor) parseComment(comment string, tags *types.Tags) { + comment = strings.TrimPrefix(comment, "//") + comment = strings.TrimSpace(comment) + + console.Debugf("-- parse comment: %s", comment) + + buf := bb.FromBytes([]byte(comment)) + + for { + key, err := buf.ReadString('=') + if errors.Is(err, io.EOF) { + break + } + console.FatalIfErr(err, "parse comment") + key = strings.Trim(strings.TrimSpace(key), "=") + + r, _, err := buf.ReadRune() + if errors.Is(err, io.EOF) { + break + } + console.FatalIfErr(err, "parse comment") + + var value string + switch r { + case '"': + value, err = buf.ReadString('"') + value = strings.Trim(strings.TrimSpace(value), "\"") + case '\'': + value, err = buf.ReadString('\'') + value = strings.Trim(strings.TrimSpace(value), "'") + case '`': + value, err = buf.ReadString('`') + value = strings.Trim(strings.TrimSpace(value), "`") + default: + console.FatalIfErr(buf.UnreadRune(), "parse comment") + value, err = buf.ReadString(' ') + } + + if errors.Is(err, io.EOF) { + break + } + console.FatalIfErr(err, "parse comment") + + (*tags)[key] = append((*tags)[key], value) + } +} + +func (v *Visitor) parseMethods(fields *ast.FieldList) (result []types.Method) { + for _, field := range fields.List { + if !field.Names[0].IsExported() { + continue + } + + funcType, ok := field.Type.(*ast.FuncType) + if !ok { + continue + } + + console.Debugf("** Parse method: %v", field.Names[0].String()) + + method := types.Method{ + Name: field.Names[0].String(), + Tags: make(types.Tags, 10), + InParams: nil, + OutParams: nil, + } + + v.parseDoc(field.Doc, &method.Tags) + //v.parseDoc(field.Comment, &method.Tags) + + if funcType.Params != nil { + for _, param := range funcType.Params.List { + method.InParams = append(method.InParams, getParam(param)) + } + } + + if funcType.Results != nil { + for _, param := range funcType.Results.List { + method.OutParams = append(method.OutParams, getParam(param)) + } + } + + result = append(result, method) + + } + + return +} + +func (v *Visitor) astTypeSpec(node *ast.TypeSpec) { + faceNode, ok := node.Type.(*ast.InterfaceType) + if !ok { + return + } + + obj := types.Object{ + Name: node.Name.String(), + Tags: make(types.Tags, 10), + } + + console.Debugf("* Parse interface: %s", obj.Name) + + v.parseDoc(node.Doc, &obj.Tags) + //v.parseDoc(node.Comment, &obj.Tags) + + obj.Methods = append(obj.Methods, v.parseMethods(faceNode.Methods)...) + + v.Objects = append(v.Objects, obj) + + return +} + +func getParam(param *ast.Field) types.Param { + paramType := getTypeName(param.Type) + paramPkg := func() string { + if !strings.Contains(paramType, ".") { + return "" + } + pkg := strings.Split(paramType, ".")[0] + return strings.Trim(pkg, "*[].") + }() + + console.Debugf("---- parse arg: Name: %s, Type: %s, Pkg: %s", + param.Names[0].String(), paramType, paramPkg) + + return types.Param{ + Name: param.Names[0].String(), + Type: paramType, + Pkg: paramPkg, + Omitempty: strings.HasPrefix(paramType, "*") || + strings.HasPrefix(paramType, "[]"), + } +} + +func getTypeName(expr ast.Expr) string { + switch t := expr.(type) { + case *ast.Ident: + return t.Name + case *ast.SelectorExpr: + return fmt.Sprintf("%s.%s", getTypeName(t.X), t.Sel.Name) + case *ast.StarExpr: + return "*" + getTypeName(t.X) + case *ast.ArrayType: + return "[]" + getTypeName(t.Elt) + default: + return "" + } +} diff --git a/internal/global/exec.go b/internal/global/exec.go index 61bf9ff..8d17c31 100644 --- a/internal/global/exec.go +++ b/internal/global/exec.go @@ -11,10 +11,11 @@ import ( "io" "os" - "go.osspkg.com/console" "go.osspkg.com/events" "go.osspkg.com/ioutils/fs" "go.osspkg.com/ioutils/shell" + + "go.osspkg.com/goppy/v3/console" ) type logger struct { diff --git a/internal/global/global.go b/internal/global/global.go index 3e07398..9d72e95 100644 --- a/internal/global/global.go +++ b/internal/global/global.go @@ -11,9 +11,10 @@ import ( "os" "regexp" - "go.osspkg.com/console" "go.osspkg.com/ioutils/fs" "go.osspkg.com/ioutils/shell" + + "go.osspkg.com/goppy/v3/console" ) const ( From 7f401cbb31904aa2808445950e71165c22821bcc Mon Sep 17 00:00:00 2001 From: Mikhail Knyazhev Date: Mon, 2 Mar 2026 06:54:39 +0300 Subject: [PATCH 2/6] add web server generator --- _example/web-server-gen/Makefile | 7 + _example/web-server-gen/config.yaml | 17 + _example/web-server-gen/main.go | 109 +++++ .../transport/jsonrpc_Api_handlers.go | 32 ++ .../transport/jsonrpc_Api_models.go | 26 ++ .../transport/jsonrpc_Api_models_easyjson.go | 37 ++ .../transport/jsonrpc_Api_transport.go | 73 ++++ .../web-server-gen/transport/jsonrpc_model.go | 53 +++ .../web-server-gen/{ => types}/interfaces.go | 8 +- _example/web-server-gen/types/wsg.go | 3 + _example/web-server-gen/wsg.go | 8 - go.mod | 4 +- go.sum | 2 - internal/commands/license.go | 2 +- internal/gen/wsg/builder/builder.go | 91 ++++ internal/gen/wsg/builder/common.go | 15 + internal/gen/wsg/command.go | 70 ++- .../gen/wsg/module/mod-json-rpc/common.go | 34 ++ .../gen/wsg/module/mod-json-rpc/transport.go | 408 ++++++++++++++++++ internal/gen/wsg/module/type.go | 12 - internal/gen/wsg/types/object.go | 15 + internal/gen/wsg/types/storage.go | 16 + internal/gen/wsg/types/type.go | 25 ++ internal/gen/wsg/util/util.go | 61 +++ internal/gen/wsg/visitor/visitor.go | 64 ++- internal/global/skip_files.go | 11 + 26 files changed, 1161 insertions(+), 42 deletions(-) create mode 100644 _example/web-server-gen/Makefile create mode 100755 _example/web-server-gen/config.yaml create mode 100644 _example/web-server-gen/main.go create mode 100644 _example/web-server-gen/transport/jsonrpc_Api_handlers.go create mode 100644 _example/web-server-gen/transport/jsonrpc_Api_models.go create mode 100644 _example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go create mode 100644 _example/web-server-gen/transport/jsonrpc_Api_transport.go create mode 100644 _example/web-server-gen/transport/jsonrpc_model.go rename _example/web-server-gen/{ => types}/interfaces.go (88%) create mode 100644 _example/web-server-gen/types/wsg.go delete mode 100644 _example/web-server-gen/wsg.go create mode 100644 internal/gen/wsg/builder/builder.go create mode 100644 internal/gen/wsg/builder/common.go create mode 100644 internal/gen/wsg/module/mod-json-rpc/common.go create mode 100644 internal/gen/wsg/module/mod-json-rpc/transport.go delete mode 100644 internal/gen/wsg/module/type.go create mode 100644 internal/gen/wsg/types/storage.go create mode 100644 internal/gen/wsg/types/type.go create mode 100644 internal/gen/wsg/util/util.go create mode 100644 internal/global/skip_files.go diff --git a/_example/web-server-gen/Makefile b/_example/web-server-gen/Makefile new file mode 100644 index 0000000..23fc67a --- /dev/null +++ b/_example/web-server-gen/Makefile @@ -0,0 +1,7 @@ +SHELL=/bin/bash + +run: + go generate ./types/wsg.go + go generate ./transport + go run main.go --config=config.yaml + diff --git a/_example/web-server-gen/config.yaml b/_example/web-server-gen/config.yaml new file mode 100755 index 0000000..9418c8c --- /dev/null +++ b/_example/web-server-gen/config.yaml @@ -0,0 +1,17 @@ +env: dev + +log: + file_path: /dev/stdout + format: json + level: 4 + +http: + - tag: main + addr: 0.0.0.0:10000 + +metrics: + addr: 0.0.0.0:12000 + gauge: + - users_request + +custom: diff --git a/_example/web-server-gen/main.go b/_example/web-server-gen/main.go new file mode 100644 index 0000000..8890645 --- /dev/null +++ b/_example/web-server-gen/main.go @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "go.osspkg.com/logx" + "go.osspkg.com/xc" + + "go.osspkg.com/goppy/v3" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/dic/broker" + "go.osspkg.com/goppy/v3/metrics" + "go.osspkg.com/goppy/v3/plugins" + "go.osspkg.com/goppy/v3/web" +) + +func main() { + // Specify the path to the config via the argument: `--config`. + // Specify the path to the pidfile via the argument: `--pid`. + app := goppy.New("app_name", "v1.0.0", "app description") + app.Plugins( + web.WithServer(), + ) + app.Plugins( + NewController, + func(routes web.ServerPool, c *Controller) { + router, ok := routes.Main() + if !ok { + return + } + + router.Use(web.ThrottlingMiddleware(100)) + router.Get("/users", c.Users) + + api := router.Collection("/api/v1", web.ThrottlingMiddleware(100)) + api.Get("/user/{id}", c.User) + }, + broker.WithUniversalBroker[IStatus]( + func(_ xc.Context, status IStatus) error { + fmt.Println(">> UniversalBroker got status", status.GetStatus()) + return nil + }, + func(status IStatus) error { + return nil + }, + ), + ) + app.Command(func(ctx context.Context, _ plugins.DIResolver, setter console.CommandSetter) { + setter.Setup("env", "show all envs") + setter.ExecFunc(func() { + fmt.Println(os.Environ()) + }) + }) + app.Command(func(ctx context.Context, r plugins.DIResolver, setter console.CommandSetter) { + setter.Setup("ctrl", "call ctrl") + setter.ExecFunc(func() { + logx.SetLevel(0) + + console.FatalIfErr(r.Resolve(func(c *Controller) { + fmt.Println(c.GetStatus()) + }), "can't find controller") + + console.FatalIfErr(r.Resolve(func(c *Controller) { + fmt.Println(c.GetStatus()) + }), "can't find controller") + }) + }) + app.Run() +} + +type Controller struct{} + +func NewController() *Controller { + return &Controller{} +} + +func (v *Controller) Users(ctx web.Ctx) { + metrics.Gauge("users_request").Inc() + data := Model{ + data: []int64{1, 2, 3, 4}, + } + ctx.JSON(200, data) +} + +func (v *Controller) User(ctx web.Ctx) { + id, _ := ctx.Param("id").Int() // nolint: errcheck + ctx.String(200, "user id: %d", id) + logx.Info("user - %d", id) +} + +func (v *Controller) GetStatus() int { + return 200 +} + +type Model struct { + data []int64 +} + +func (m Model) MarshalJSON() ([]byte, error) { + return json.Marshal(m.data) +} diff --git a/_example/web-server-gen/transport/jsonrpc_Api_handlers.go b/_example/web-server-gen/transport/jsonrpc_Api_handlers.go new file mode 100644 index 0000000..09f6154 --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_Api_handlers.go @@ -0,0 +1,32 @@ +// Code generated by goppy-cli wsg. DO NOT EDIT. +package transport + +import context "context" +import stdjson "encoding/json" + +func (v *JSONRPCApiHandle) methodRoot(ctx context.Context, param stdjson.RawMessage) (stdjson.Marshaler, error) { + var req jsonrpcRootModelRequest + err := stdjson.Unmarshal(param, &req) + if err != nil { + return nil, err + } + var res jsonrpcRootModelResponse + res.Status, err = v.handle.Root(ctx, req.UserID, req.UserName) + if err != nil { + return nil, err + } + return res, nil +} +func (v *JSONRPCApiHandle) methodAuth(ctx context.Context, param stdjson.RawMessage) (stdjson.Marshaler, error) { + var req jsonrpcAuthModelRequest + err := stdjson.Unmarshal(param, &req) + if err != nil { + return nil, err + } + var res jsonrpcAuthModelResponse + res.Status, err = v.handle.Auth(ctx, req.UserID, req.UserName) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/_example/web-server-gen/transport/jsonrpc_Api_models.go b/_example/web-server-gen/transport/jsonrpc_Api_models.go new file mode 100644 index 0000000..1f3145d --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_Api_models.go @@ -0,0 +1,26 @@ +// Code generated by goppy-cli wsg. DO NOT EDIT. +package transport + +//go:generate easyjson + +//easyjson:json +type jsonrpcRootModelResponse struct { + Status bool `json:"status"` +} + +//easyjson:json +type jsonrpcRootModelRequest struct { + UserID int64 `json:"userID"` + UserName string `json:"userName"` +} + +//easyjson:json +type jsonrpcAuthModelResponse struct { + Status bool `json:"status"` +} + +//easyjson:json +type jsonrpcAuthModelRequest struct { + UserID int64 `json:"userID"` + UserName string `json:"userName"` +} diff --git a/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go b/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go new file mode 100644 index 0000000..2874714 --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go @@ -0,0 +1,37 @@ +// TEMPORARY AUTOGENERATED FILE: easyjson stub code to make the package +// compilable during generation. + +package transport + +import ( + "github.com/mailru/easyjson/jwriter" + "github.com/mailru/easyjson/jlexer" +) + +func ( jsonrpcAuthModelRequest ) MarshalJSON() ([]byte, error) { return nil, nil } +func (* jsonrpcAuthModelRequest ) UnmarshalJSON([]byte) error { return nil } +func ( jsonrpcAuthModelRequest ) MarshalEasyJSON(w *jwriter.Writer) {} +func (* jsonrpcAuthModelRequest ) UnmarshalEasyJSON(l *jlexer.Lexer) {} + +type EasyJSON_exporter_jsonrpcAuthModelRequest *jsonrpcAuthModelRequest + +func ( jsonrpcAuthModelResponse ) MarshalJSON() ([]byte, error) { return nil, nil } +func (* jsonrpcAuthModelResponse ) UnmarshalJSON([]byte) error { return nil } +func ( jsonrpcAuthModelResponse ) MarshalEasyJSON(w *jwriter.Writer) {} +func (* jsonrpcAuthModelResponse ) UnmarshalEasyJSON(l *jlexer.Lexer) {} + +type EasyJSON_exporter_jsonrpcAuthModelResponse *jsonrpcAuthModelResponse + +func ( jsonrpcRootModelRequest ) MarshalJSON() ([]byte, error) { return nil, nil } +func (* jsonrpcRootModelRequest ) UnmarshalJSON([]byte) error { return nil } +func ( jsonrpcRootModelRequest ) MarshalEasyJSON(w *jwriter.Writer) {} +func (* jsonrpcRootModelRequest ) UnmarshalEasyJSON(l *jlexer.Lexer) {} + +type EasyJSON_exporter_jsonrpcRootModelRequest *jsonrpcRootModelRequest + +func ( jsonrpcRootModelResponse ) MarshalJSON() ([]byte, error) { return nil, nil } +func (* jsonrpcRootModelResponse ) UnmarshalJSON([]byte) error { return nil } +func ( jsonrpcRootModelResponse ) MarshalEasyJSON(w *jwriter.Writer) {} +func (* jsonrpcRootModelResponse ) UnmarshalEasyJSON(l *jlexer.Lexer) {} + +type EasyJSON_exporter_jsonrpcRootModelResponse *jsonrpcRootModelResponse diff --git a/_example/web-server-gen/transport/jsonrpc_Api_transport.go b/_example/web-server-gen/transport/jsonrpc_Api_transport.go new file mode 100644 index 0000000..7bf2f87 --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_Api_transport.go @@ -0,0 +1,73 @@ +// Code generated by goppy-cli wsg. DO NOT EDIT. +package transport + +import strings "strings" +import errors "errors" +import web "go.osspkg.com/goppy/v3/web" +import context "context" +import stdjson "encoding/json" +import syncing "go.osspkg.com/syncing" +import types "go.osspkg.com/goppy/v3/_example/web-server-gen/types" + +type JSONRPCApiHandle struct { + routes web.ServerPool + handle types.Api +} + +func NewJSONRPCApiHandle(routes web.ServerPool, handle types.Api) *JSONRPCApiHandle { + return &JSONRPCApiHandle{ + routes: routes, + handle: handle, + } +} +func (v *JSONRPCApiHandle) Down() error { + return nil +} +func (v *JSONRPCApiHandle) Up(ctx context.Context) error { + v.routes.All(func(tag string, r web.Router) { + switch tag { + case "main", "admin": + r.Post("/api", func(ctx web.Ctx) { + var req bulkRequest + if err := ctx.BindJSON(&req); err != nil { + ctx.String(400, err.Error()) + return + } + res := syncing.NewSlice[baseResponse](uint(len(req))) + wg := syncing.NewGroup(ctx.Context()) + for _, item := range req { + item := item + wg.Background("api."+strings.ToLower(item.Method), func(ctx context.Context) { + out := baseResponse{ + Id: item.Id, + } + var err error + var result stdjson.Marshaler + switch strings.ToLower(item.Method) { + case "root": + result, err = v.methodRoot(ctx, item.Params) + + case "auth": + result, err = v.methodAuth(ctx, item.Params) + + default: + err = errors.New("unsupported method") + + } + if err != nil { + out.Error = toJSONRPCError(err) + } else { + out.Result = result + } + res.Append(out) + }) + } + wg.Wait() + ctx.JSON(200, bulkResponse(res.Extract())) + }) + + } + return + }) + return nil +} diff --git a/_example/web-server-gen/transport/jsonrpc_model.go b/_example/web-server-gen/transport/jsonrpc_model.go new file mode 100644 index 0000000..e318353 --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_model.go @@ -0,0 +1,53 @@ +// Code generated by goppy-cli wsg. DO NOT EDIT. +package transport + +//go:generate easyjson +import stdjson "encoding/json" + +//easyjson:json +type baseRequest struct { + Id string `json:"id"` + Method string `json:"method"` + Params stdjson.RawMessage `json:"params"` +} + +//easyjson:json +type bulkRequest []baseRequest + +//easyjson:json +type baseResponse struct { + Id string `json:"id"` + Result any `json:"result,omitempty"` + Error *errResponse `json:"error,omitempty"` +} + +//easyjson:json +type bulkResponse []baseResponse + +//easyjson:json +type errResponse struct { + Message string `json:"message"` + Code int64 `json:"code"` + Ctx map[string]string `json:"ctx,omitempty"` +} +type TError interface { + GetCode() int64 + GetMessage() string + GetContext() map[string]string +} + +func toJSONRPCError(e error) *errResponse { + if e == nil { + return nil + } + err := &errResponse{} + te, ok := e.(TError) + if ok { + err.Code = te.GetCode() + err.Message = te.GetMessage() + err.Ctx = te.GetContext() + } else { + err.Message = e.Error() + } + return err +} diff --git a/_example/web-server-gen/interfaces.go b/_example/web-server-gen/types/interfaces.go similarity index 88% rename from _example/web-server-gen/interfaces.go rename to _example/web-server-gen/types/interfaces.go index e098015..10e423f 100644 --- a/_example/web-server-gen/interfaces.go +++ b/_example/web-server-gen/types/interfaces.go @@ -3,17 +3,15 @@ * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. */ -package web_server_gen +package types import "context" // Api // @wsg description="Методы апи" // @wsg module=json-rpc,http -/* -@wsg module=json-rpc,http2 -@wsg module=json-rpc,http3 -*/ +// @wsg web-pool=main,admin +// @wsg route-prefix=/api/v1 type Api interface { // Root // @wsg in.userID=cookie:x-user-id diff --git a/_example/web-server-gen/types/wsg.go b/_example/web-server-gen/types/wsg.go new file mode 100644 index 0000000..81ef797 --- /dev/null +++ b/_example/web-server-gen/types/wsg.go @@ -0,0 +1,3 @@ +package types + +//go:generate goppy wsg --iface=Api --out=./../transport diff --git a/_example/web-server-gen/wsg.go b/_example/web-server-gen/wsg.go deleted file mode 100644 index b426ba9..0000000 --- a/_example/web-server-gen/wsg.go +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. - * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. - */ - -package web_server_gen - -//go:generate goppy wsg --out=./transport diff --git a/go.mod b/go.mod index 819d3ca..a3e9a0d 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( go.osspkg.com/do v0.2.1 go.osspkg.com/errors v0.4.0 go.osspkg.com/events v0.3.0 + go.osspkg.com/gogen v0.1.1-0.20260301133223-79de3fce706d go.osspkg.com/ioutils v0.7.4 go.osspkg.com/logx v0.6.1 go.osspkg.com/network v0.6.0 @@ -79,7 +80,6 @@ require ( github.com/prometheus/procfs v0.16.1 // indirect github.com/quic-go/quic-go v0.50.1 // indirect go.etcd.io/bbolt v1.4.3 // indirect - go.osspkg.com/gogen v0.1.0 // indirect go.uber.org/mock v0.5.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/net v0.50.0 // indirect @@ -89,3 +89,5 @@ require ( golang.org/x/tools v0.42.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace go.osspkg.com/gogen => ../gogen diff --git a/go.sum b/go.sum index a5880c1..da3333f 100644 --- a/go.sum +++ b/go.sum @@ -142,8 +142,6 @@ go.osspkg.com/errors v0.4.0 h1:E17+WyUzTXEHCTxGm8lOMPOOojzHG1lsOuQtTVGoATQ= go.osspkg.com/errors v0.4.0/go.mod h1:s75ZovPemYtrCtRPVsbQNq9MgMbmLMK1NEypr+uwjXI= go.osspkg.com/events v0.3.0 h1:W2IngTsKs0BKYIglqhrETwtpo6uNSZXWRIt0/l7c6dY= go.osspkg.com/events v0.3.0/go.mod h1:Cjpx+qNM1y2MIAygFyZWYagTuRiYirmKppZQdaZumd4= -go.osspkg.com/gogen v0.1.0 h1:2JV95VJUGBnPwSzNtcpmhF0Kah3uhtVFERqUyxglmrU= -go.osspkg.com/gogen v0.1.0/go.mod h1:BkoU7OC3LV/kmtjvCnQB1rF9hcK0rF3hZKoaZlP+Uv4= go.osspkg.com/ioutils v0.7.4 h1:Z8Y4jYYmLGWcvHZMLjbai+s48GmHxjMuepsxZcjF5X4= go.osspkg.com/ioutils v0.7.4/go.mod h1:pPIsTL1w1+ESrGTeHDCd6cKsujeWvschxGGP5FqrAqc= go.osspkg.com/logx v0.6.1 h1:JqHk1iFBOKnwO0dZtC7n4sO/0MkUD9c7eulmYEDCnao= diff --git a/internal/commands/license.go b/internal/commands/license.go index 302116b..f4e54f2 100644 --- a/internal/commands/license.go +++ b/internal/commands/license.go @@ -68,7 +68,7 @@ func CmdLicense() console.CommandGetter { goFiles, err := fs.SearchFilesByExt(currDir, ".go") console.FatalIfErr(err, "Get go files") for _, file := range goFiles { - if strings.HasSuffix(file, "_easyjson.go") || strings.Contains(file, "/vendor/") { + if global.NeedSkipFile(file) { continue } if _, ok := ignoreFiles[strings.TrimLeft(strings.TrimPrefix(file, currDir), "/")]; ok { diff --git a/internal/gen/wsg/builder/builder.go b/internal/gen/wsg/builder/builder.go new file mode 100644 index 0000000..15a8921 --- /dev/null +++ b/internal/gen/wsg/builder/builder.go @@ -0,0 +1,91 @@ +package builder + +import ( + "fmt" + "os" + "path/filepath" + + "go.osspkg.com/bb" + "go.osspkg.com/gogen/golang" + "go.osspkg.com/gogen/types" + "go.osspkg.com/goppy/v3/console" + wsgt "go.osspkg.com/goppy/v3/internal/gen/wsg/types" +) + +type Builder struct { + Out string + IFace map[string]struct{} + Files []wsgt.File +} + +func (b *Builder) filterObjects(objs []wsgt.Object) []wsgt.Object { + if len(b.IFace) == 0 { + return objs + } + result := make([]wsgt.Object, 0, len(objs)) + for _, obj := range objs { + if _, ok := b.IFace[obj.Name]; ok { + result = append(result, obj) + } + } + + return result +} + +func (b *Builder) WriteFile(fileName string, tok types.Token) error { + buf := bb.New(1024) + if err := golang.Render(buf, tok); err != nil { + return err + } + fullPath := b.Out + "/" + fileName + dir := filepath.Dir(fullPath) + if err := os.MkdirAll(dir, 0766); err != nil { + return fmt.Errorf("mkdir %q: %v", dir, err) + } + console.Debugf("Writing file %s", fullPath) + return os.WriteFile(fullPath, buf.Bytes(), 0666) +} + +func (b *Builder) Build() error { + //golang.SetRawMode() + + pkgName := filepath.Base(b.Out) + + for _, file := range b.Files { + file.Objects = b.filterObjects(file.Objects) + } + + for _, file := range b.Files { + for _, obj := range file.Objects { + tags, ok := obj.Tags[TagModule] + if !ok { + continue + } + + for _, tag := range tags { + mod, ok := wsgt.Resolve(wsgt.IFace + tag) + if !ok { + continue + } + + if err := mod.Build(wsgt.Ctx{ + W: b, + PkgName: pkgName, + File: file, + Object: obj, + Tags: obj.Tags, + }); err != nil { + return fmt.Errorf("build module `%s`, file `%s`: %w", + mod.Name(), file.FilePath, err) + } + } + } + } + + //return errors.Queue( + // func() error { return b.buildInterface(pkgName) }, + // func() error { return b.WriteFile("transport.go", module.TypeTransport(pkgName)) }, + //) + + return nil +} diff --git a/internal/gen/wsg/builder/common.go b/internal/gen/wsg/builder/common.go new file mode 100644 index 0000000..16acee6 --- /dev/null +++ b/internal/gen/wsg/builder/common.go @@ -0,0 +1,15 @@ +package builder + +import ( + modjsonrpc "go.osspkg.com/goppy/v3/internal/gen/wsg/module/mod-json-rpc" + "go.osspkg.com/goppy/v3/internal/gen/wsg/types" +) + +func init() { + types.Register(modjsonrpc.JSONRPCTransport{FilePrefix: "jsonrpc"}) + //types.Register(HTTPTransport{}) +} + +const ( + TagModule = "module" +) diff --git a/internal/gen/wsg/command.go b/internal/gen/wsg/command.go index 2748f16..62258e0 100644 --- a/internal/gen/wsg/command.go +++ b/internal/gen/wsg/command.go @@ -6,15 +6,20 @@ package wsg import ( + "fmt" "go/ast" "go/parser" "go/token" "os" + "path/filepath" "strings" "go.osspkg.com/do" + "go.osspkg.com/goppy/v3/internal/gen/wsg/builder" + "go.osspkg.com/goppy/v3/internal/global" "go.osspkg.com/ioutils/fs" "go.osspkg.com/syncing" + "golang.org/x/mod/modfile" "go.osspkg.com/goppy/v3/console" "go.osspkg.com/goppy/v3/internal/gen/wsg/visitor" @@ -25,9 +30,10 @@ func Command() console.CommandGetter { setter.Setup("wsg", "generate web server api") setter.Flag(func(flagsSetter console.FlagsSetter) { flagsSetter.String("out", "output file specified") + flagsSetter.StringVar("iface", "", "interface names (optional)") }) - setter.ExecFunc(func(out string) { - //console.ShowDebug(false) + setter.ExecFunc(func(out, _iface string) { + console.ShowDebug(true) console.Infof("--- GENERATE ---") curDir := fs.CurrentDir() @@ -45,9 +51,25 @@ func Command() console.CommandGetter { return !strings.HasPrefix(value, out) }) + iface := do.Entries[string, string, struct{}](strings.Split(_iface, ","), func(s string) (string, struct{}) { + return strings.ToLower(strings.TrimSpace(s)), struct{}{} + }) + + build := &builder.Builder{ + Out: out, + IFace: iface, + } + for _, filePath := range files { + if global.NeedSkipFile(filePath) { + continue + } + console.Debugf("> PARSE FILE: %s", filePath) + gomod, root, err := detectGoMod(filePath) + console.FatalIfErr(err, "failed to detect gomod") + fset := token.NewFileSet() f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) console.FatalIfErr(err, "parse go file: %s", filePath) @@ -58,13 +80,55 @@ func Command() console.CommandGetter { vv := &visitor.Visitor{ Imports: syncing.NewMap[string, string](1), - FilePath: filePath, + FilePath: strings.TrimPrefix(filePath, root), + GoMod: gomod, } ast.Walk(vv, f) vv.Debug() + + build.Files = append(build.Files, vv.ToFile()) } + + console.FatalIfErr(build.Build(), "") }) }) } + +func detectGoMod(curPath string) (mod string, root string, err error) { + root = filepath.Dir(curPath) + for { + + mods, e := fs.SearchFilesByExt(root, ".mod") + if e != nil { + return "", "", e + } + + if len(mods) != 0 { + for _, s := range mods { + b, e := os.ReadFile(s) + if e != nil { + return "", "", e + } + + f, e := modfile.Parse("go.mod", b, nil) + if e != nil { + return "", "", e + } + mod = f.Module.Mod.Path + return + } + } + + root, err = filepath.Abs(root + "/..") + if err != nil { + return + } + + if root == "/" { + err = fmt.Errorf("failed to detect go.mod") + return + } + } +} diff --git a/internal/gen/wsg/module/mod-json-rpc/common.go b/internal/gen/wsg/module/mod-json-rpc/common.go new file mode 100644 index 0000000..d978a52 --- /dev/null +++ b/internal/gen/wsg/module/mod-json-rpc/common.go @@ -0,0 +1,34 @@ +package mod_json_rpc + +const ( + tagWebPool = "web-pool" +) + +const ( + transportName = "JSONRPC%sHandle" + + modelNameReq = "jsonrpc%sModelRequest" + modelNameRes = "jsonrpc%sModelResponse" + + modelBaseReq = "baseRequest" + modelBulkBaseReq = "bulkRequest" + modelBaseRes = "baseResponse" + modelBulkBaseRes = "bulkResponse" + modelBaseErr = "errResponse" + errInterface = "TError" +) + +const ( + fieldMethod = "method" + fieldParams = "params" + fieldID = "id" + fieldResult = "result" + fieldError = "error" + fieldMessage = "message" + fieldCode = "code" + fieldCtx = "ctx" +) + +const ( + jsonGenComment = "easyjson:json" +) diff --git a/internal/gen/wsg/module/mod-json-rpc/transport.go b/internal/gen/wsg/module/mod-json-rpc/transport.go new file mode 100644 index 0000000..25f0981 --- /dev/null +++ b/internal/gen/wsg/module/mod-json-rpc/transport.go @@ -0,0 +1,408 @@ +package mod_json_rpc + +import ( + "fmt" + "strings" + + "go.osspkg.com/do" + "go.osspkg.com/errors" + . "go.osspkg.com/gogen/golang" + "go.osspkg.com/gogen/types" + wsgt "go.osspkg.com/goppy/v3/internal/gen/wsg/types" + "go.osspkg.com/goppy/v3/internal/gen/wsg/util" + "go.osspkg.com/syncing" +) + +type JSONRPCTransport struct { + FilePrefix string +} + +func (JSONRPCTransport) Name() string { + return wsgt.IFace + "json-rpc" +} + +func (v JSONRPCTransport) Build(ctx wsgt.Ctx) error { + return errors.Queue( + func() error { return v.buildBaseRPCModel(ctx) }, + func() error { return v.buildTransport(ctx) }, + func() error { return v.buildTransportModels(ctx) }, + func() error { return v.buildTransportHandlers(ctx) }, + ) +} + +func (v JSONRPCTransport) buildBaseRPCModel(ctx wsgt.Ctx) error { + baseReq := Comment(jsonGenComment). + Type().ID(modelBaseReq).Struct(). + Block( + ID(util.ToUpperCamelCase(fieldID)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), + ID(util.ToUpperCamelCase(fieldMethod)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldMethod)), + ID(util.ToUpperCamelCase(fieldParams)).Pkg("stdjson").ID("RawMessage"). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldParams)), + ) + + bulkBaseReq := Comment(jsonGenComment). + Type().ID(modelBulkBaseReq).Slice().ID(modelBaseReq) + + errRes := Comment(jsonGenComment). + Type().ID(modelBaseErr).Struct(). + Block( + ID(util.ToUpperCamelCase(fieldMessage)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldMessage)), + ID(util.ToUpperCamelCase(fieldCode)).Int64(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldCode)), + ID(util.ToUpperCamelCase(fieldCtx)).Map(String(), String()). + Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldCtx)), + ) + + errType := Type().ID(errInterface).Interface(). + Block( + ID("GetCode").Bracket().Int64(), + ID("GetMessage").Bracket().String(), + ID("GetContext").Bracket().Map(String(), String()), + ) + + baseRes := Comment(jsonGenComment). + Type().ID(modelBaseRes).Struct(). + Block( + ID(util.ToUpperCamelCase(fieldID)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), + ID(util.ToUpperCamelCase(fieldResult)).Any(). + Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldResult)), + ID(util.ToUpperCamelCase(fieldError)).Op("*").ID("errResponse"). + Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldError)), + ) + + bulkBaseRes := Comment(jsonGenComment). + Type().ID(modelBulkBaseRes).Slice().ID(modelBaseRes) + + toErr := Func().ID("toJSONRPCError"). + Bracket(ID("e").Error()). + Bracket(Op("*").ID(modelBaseErr)).Block( + If().ID("e").Op("==").Nil().Block(Return().Nil()), + ID("err").Op(":=").Op("&").ID(modelBaseErr).Block(), + List(ID("te"), ID("ok")).Op(":=").ID("e").Op(".").Bracket(ID(errInterface)), + If().ID("ok").Block( + ID("err").Op(".").ID("Code").Op("="). + ID("te").Op(".").ID("GetCode").Bracket(), + ID("err").Op(".").ID("Message").Op("="). + ID("te").Op(".").ID("GetMessage").Bracket(), + ID("err").Op(".").ID("Ctx").Op("="). + ID("te").Op(".").ID("GetContext").Bracket(), + ).Else().Block( + ID("err").Op(".").ID("Message").Op("="). + ID("e").Op(".").ID("Error").Bracket(), + ), + Return().ID("err"), + ) + + t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). + Package(ctx.PkgName). + Comment("go:generate easyjson"). + Import("stdjson", "encoding/json"). + Join(baseReq, bulkBaseReq, baseRes, bulkBaseRes, errRes, errType, toErr) + + return ctx.W.WriteFile(v.FilePrefix+"_model.go", t) +} + +func (v JSONRPCTransport) buildTransportModels(ctx wsgt.Ctx) error { + t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). + Package(ctx.PkgName). + Comment("go:generate easyjson") + + list := syncing.NewMap[string, string](10) + + var models []types.Token + + for _, method := range ctx.Object.Methods { + var ( + argsOut []types.Token + ) + for _, p := range method.OutParams { + switch { + case p.Type == "error": + continue + default: + } + + if link, ok := ctx.File.Imports.Get(p.Pkg); ok { + list.Set(p.Pkg, link) + } + + argsOut = append(argsOut, + ID(util.ToUpperCamelCase(p.Name)).Pkg(p.Pkg).ID(p.Type). + Raw(fmt.Sprintf("`json:\"%s%s\"`", p.Name, + do.IfElse(p.Omitempty, ",omitempty", ""))), + ) + } + models = append(models, Line().Comment(jsonGenComment). + Type().ID(fmt.Sprintf(modelNameRes, method.Name)).Struct().Block(argsOut...), + ) + + var ( + argsIn []types.Token + ) + for _, p := range method.InParams { + switch { + case p.Pkg == "context" && p.Type == "Context": + continue + default: + } + + if link, ok := ctx.File.Imports.Get(p.Pkg); ok { + list.Set(p.Pkg, link) + } + + argsIn = append(argsIn, + ID(util.ToUpperCamelCase(p.Name)).Pkg(p.Pkg).ID(p.Type). + Raw(fmt.Sprintf("`json:\"%s%s\"`", p.Name, + do.IfElse(p.Omitempty, ",omitempty", ""))), + ) + } + models = append(models, Line().Comment(jsonGenComment). + Type().ID(fmt.Sprintf(modelNameReq, method.Name)).Struct().Block(argsIn...), + ) + } + + for alias, link := range list.Yield() { + t.Import(alias, link) + } + + return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_models.go", t.Join(models...)) +} + +func (v JSONRPCTransport) buildTransportHandlers(ctx wsgt.Ctx) error { + objectName := fmt.Sprintf(transportName, ctx.Object.Name) + + t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). + Package(ctx.PkgName) + + list := syncing.NewMap[string, string](10) + list.Set("context", "context") + list.Set("stdjson", "encoding/json") + + var models []types.Token + + for _, method := range ctx.Object.Methods { + handle := Func().Bracket(ID("v").Op("*").ID(objectName)). + ID("method"+method.Name).Bracket( + ID("ctx").Pkg("context").ID("Context"), + ID("param").Pkg("stdjson").ID("RawMessage"), + ).Bracket( + Pkg("stdjson").ID("Marshaler"), + Error(), + ) + + var handleSrc []types.Token + + handleSrc = append(handleSrc, + Var().ID("req").ID(fmt.Sprintf(modelNameReq, method.Name)), + ID("err").Op(":=").Pkg("stdjson").ID("Unmarshal").Bracket( + ID("param"), Op("&").ID("req"), + ), + If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), + Var().ID("res").ID(fmt.Sprintf(modelNameRes, method.Name)), + ) + + var ( + handleOut []types.Token + ) + for _, p := range method.OutParams { + if link, ok := ctx.File.Imports.Get(p.Pkg); ok { + list.Set(p.Pkg, link) + } + + switch { + case p.Type == "error": + handleOut = append(handleOut, ID("err")) + default: + handleOut = append(handleOut, ID("res").Op(".").ID(util.ToUpperCamelCase(p.Name))) + } + } + + var ( + handleIn []types.Token + ) + for _, p := range method.InParams { + if link, ok := ctx.File.Imports.Get(p.Pkg); ok { + list.Set(p.Pkg, link) + } + + switch { + case p.Pkg == "context" && p.Type == "Context": + handleIn = append(handleIn, ID("ctx")) + default: + handleIn = append(handleIn, ID("req").Op(".").ID(util.ToUpperCamelCase(p.Name))) + } + } + + handleSrc = append(handleSrc, + List(handleOut...).Op("=").ID("v").Op(".").ID("handle").Op(".").ID(method.Name).Call(handleIn...), + If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), + Return().List(ID("res"), Nil()), + ) + + models = append(models, handle.Block(handleSrc...)) + } + + for alias, link := range list.Yield() { + t.Import(alias, link) + } + + return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_handlers.go", t.Join(models...)) +} + +func (v JSONRPCTransport) buildTransport(ctx wsgt.Ctx) error { + objectName := fmt.Sprintf(transportName, ctx.Object.Name) + + t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). + Package(ctx.PkgName) + + list := syncing.NewMap[string, string](10) + list.Set("context", "context") + list.Set("strings", "strings") + list.Set("errors", "errors") + list.Set("stdjson", "encoding/json") + list.Set("syncing", "go.osspkg.com/syncing") + list.Set("web", "go.osspkg.com/goppy/v3/web") + list.Set(ctx.Object.Alias, ctx.Object.Pkg) + for alias, link := range ctx.File.Imports.Yield() { + list.Set(alias, link) + } + + for alias, link := range list.Yield() { + t.Import(alias, link) + } + + t = t.Join( + Type().ID(objectName).Struct().Block( + ID("routes").Pkg("web").ID("ServerPool"), + ID("handle").Pkg(ctx.Object.Alias).ID(ctx.Object.Name), + ), + //-- New + Func().ID("New"+objectName).Bracket( + ID("routes").Pkg("web").ID("ServerPool"), + ID("handle").Pkg(ctx.Object.Alias).ID(ctx.Object.Name), + ).Op("*").ID(objectName).Block( + Return().Op("&").ID(objectName).Block( + ID("routes").Op(":").ID("routes").Op(","), + ID("handle").Op(":").ID("handle").Op(","), + ), + ), + // -- Down + Func().Bracket(ID("v").Op("*").ID(objectName)). + ID("Down").Bracket().Error().Block(Return().Nil()), + // -- Up + Func().Bracket(ID("v").Op("*").ID(objectName)). + ID("Up").Bracket(ID("ctx").Pkg("context").ID("Context")).Error(). + Block(v.buildUp(ctx)), + ) + + return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_transport.go", t) +} + +func (v JSONRPCTransport) buildUp(ctx wsgt.Ctx) *Tokens { + return ID("v").Op(".").ID("routes").Op(".").ID("All").Bracket( + Func().Bracket(ID("tag").String(), ID("r").Pkg("web").ID("Router")).Block( + func() *Tokens { + tags, ok := ctx.Tags[tagWebPool] + if !ok { + return Line() + } + var list []types.Token + for _, tag := range tags { + list = append(list, Text(tag)) + } + return Switch().ID("tag").Block( + Case().List(list...).Op(":").Line().Join( + ID("r").Op(".").ID("Post").Call( + Text("/"+strings.ToLower(ctx.Object.Name)), + Func().Bracket(ID("ctx").Pkg("web").ID("Ctx")).Block( + Var().ID("req").ID(modelBulkBaseReq), + If(). + ID("err").Op(":="). + ID("ctx").Op(".").ID("BindJSON").Bracket(Op("&").ID("req")).Op(";"). + ID("err").Op("!=").Nil().Block( + ID("ctx").Op(".").ID("String").Bracket( + Raw("400"), ID("err").Op(".").ID("Error").Call(), + ), + Return(), + ), + ID("res").Op(":="). + Pkg("syncing").ID("NewSlice").Raw("[").ID(modelBaseRes).Raw("]").Call( + Uint().Bracket(Raw("len").Bracket(ID("req"))), + ), + ID("wg").Op(":=").Pkg("syncing").ID("NewGroup").Bracket( + ID("ctx").Op(".").ID("Context").Call(), + ), + For().List(Raw("_"), ID("item")).Op(":=").Range().ID("req").Block( + ID("item").Op(":=").ID("item"), + + ID("wg").Op(".").ID("Background").Call( + Text(strings.ToLower(ctx.Object.Name)+".").Op("+"). + Pkg("strings").ID("ToLower").Bracket( + ID("item").Op(".").ID(util.ToUpperCamelCase(fieldMethod)), + ), + Func().Bracket(ID("ctx").Pkg("context").ID("Context")).Block( + ID("out").Op(":=").ID(modelBaseRes).Block( + ID(util.ToUpperCamelCase(fieldID)).Op(":"). + ID("item").Op(".").ID(util.ToUpperCamelCase(fieldID)).Op(","), + ), + Var().ID("err").Error(), + Var().ID("result").Pkg("stdjson").ID("Marshaler"), + v.buildMethods(ctx), + If().ID("err").Op("!=").Nil().Block( + ID("out").Op(".").ID(util.ToUpperCamelCase(fieldError)).Op("="). + ID("toJSONRPCError").Bracket(ID("err")), + ).Else().Block( + ID("out").Op(".").ID(util.ToUpperCamelCase(fieldResult)).Op("="). + ID("result"), + ), + ID("res").Op(".").ID("Append").Bracket(ID("out")), + ), + ), + ), + ID("wg").Op(".").ID("Wait").Call(), + ID("ctx").Op(".").ID("JSON").Bracket( + Raw("200"), ID(modelBulkBaseRes).Bracket( + ID("res").Op(".").ID("Extract").Bracket(), + ), + ), + ), + ), + ), + ).Line().Return() + }(), + ), + ). + Line().Return().Nil() + +} + +func (v JSONRPCTransport) buildMethods(ctx wsgt.Ctx) *Tokens { + var list []types.Token + + for _, method := range ctx.Object.Methods { + list = append(list, + Case().Text(strings.ToLower(method.Name)).Op(":").Join( + List(ID("result"), ID("err")).Op("="). + ID("v").Op(".").ID("method"+method.Name). + Call( + ID("ctx"), + ID("item").Op(".").ID(util.ToUpperCamelCase(fieldParams)), + ), + ), + ) + } + + list = append(list, + Default().Op(":").Join( + ID("err").Op("=").Pkg("errors").ID("New").Bracket(Text("unsupported method")), + ), + ) + + return Switch().Pkg("strings").ID("ToLower").Bracket( + ID("item").Op(".").ID(util.ToUpperCamelCase(fieldMethod)), + ).Block(list...) +} diff --git a/internal/gen/wsg/module/type.go b/internal/gen/wsg/module/type.go deleted file mode 100644 index eb45c4d..0000000 --- a/internal/gen/wsg/module/type.go +++ /dev/null @@ -1,12 +0,0 @@ -package module - -import ( - "go.osspkg.com/gogen/golang" - "go.osspkg.com/goppy/v3/internal/gen/wsg/types" -) - -type Module interface { - Name() string - CreateObject(object types.Object) *golang.Tokens - CreateMethod(object types.Method) *golang.Tokens -} diff --git a/internal/gen/wsg/types/object.go b/internal/gen/wsg/types/object.go index be89618..cfe824b 100644 --- a/internal/gen/wsg/types/object.go +++ b/internal/gen/wsg/types/object.go @@ -5,7 +5,22 @@ package types +import ( + "go.osspkg.com/syncing" +) + +type File struct { + FilePath string + PkgName string + PkgPath string + GoMod string + Imports *syncing.Map[string, string] + Objects []Object +} + type Object struct { + Alias string + Pkg string Name string Methods []Method Tags Tags diff --git a/internal/gen/wsg/types/storage.go b/internal/gen/wsg/types/storage.go new file mode 100644 index 0000000..2a8c9e3 --- /dev/null +++ b/internal/gen/wsg/types/storage.go @@ -0,0 +1,16 @@ +package types + +import "go.osspkg.com/syncing" + +var _storage = syncing.NewMap[string, Module](10) + +func Register(module Module) { + if _, ok := _storage.Get(module.Name()); ok { + panic("duplicate register module: " + module.Name()) + } + _storage.Set(module.Name(), module) +} + +func Resolve(name string) (Module, bool) { + return _storage.Get(name) +} diff --git a/internal/gen/wsg/types/type.go b/internal/gen/wsg/types/type.go new file mode 100644 index 0000000..b8ff4f4 --- /dev/null +++ b/internal/gen/wsg/types/type.go @@ -0,0 +1,25 @@ +package types + +import "go.osspkg.com/gogen/types" + +const ( + IFace = "iface." + Methods = "method." +) + +type Module interface { + Name() string + Build(ctx Ctx) error +} + +type Writer interface { + WriteFile(fileName string, tok types.Token) error +} + +type Ctx struct { + W Writer + PkgName string + File File + Object Object + Tags Tags +} diff --git a/internal/gen/wsg/util/util.go b/internal/gen/wsg/util/util.go new file mode 100644 index 0000000..3faa562 --- /dev/null +++ b/internal/gen/wsg/util/util.go @@ -0,0 +1,61 @@ +package util + +import ( + "fmt" + "hash/crc32" + "strings" + "unicode" +) + +func ToKebabCase(s string) string { + if s == "" { + return "" + } + + var builder strings.Builder + builder.Grow(len(s) + 2) + + for i, r := range s { + if unicode.IsUpper(r) { + if i > 0 { + builder.WriteByte('-') + } + builder.WriteRune(unicode.ToLower(r)) + } else { + builder.WriteRune(r) + } + } + + return builder.String() +} + +func ToUpperCamelCase(s string) string { + if s == "" { + return "" + } + + var builder strings.Builder + builder.Grow(len(s) + 2) + + for i, r := range s { + if i == 0 && !unicode.IsUpper(r) { + builder.WriteRune(unicode.ToUpper(r)) + } else { + builder.WriteRune(r) + } + } + + return builder.String() +} + +var crc32q = crc32.MakeTable(0xD5828281) + +func CRC32(s string) string { + bl := len(s) + + if bl <= 0 { + return "" + } + + return fmt.Sprintf("%08X", crc32.Checksum([]byte(s), crc32q)) +} diff --git a/internal/gen/wsg/visitor/visitor.go b/internal/gen/wsg/visitor/visitor.go index ae4e88a..85c60cd 100644 --- a/internal/gen/wsg/visitor/visitor.go +++ b/internal/gen/wsg/visitor/visitor.go @@ -10,9 +10,11 @@ import ( "fmt" "go/ast" "io" + "path/filepath" "strings" "go.osspkg.com/bb" + "go.osspkg.com/do" "go.osspkg.com/ioutils/fs" "go.osspkg.com/syncing" @@ -26,14 +28,27 @@ const tag = "@wsg" type Visitor struct { FilePath string PkgName string + PkgPath string + GoMod string Imports *syncing.Map[string, string] Objects []types.Object } +func (v *Visitor) ToFile() types.File { + return types.File{ + FilePath: v.FilePath, + PkgName: v.PkgName, + PkgPath: v.PkgPath, + GoMod: v.GoMod, + Imports: v.Imports, + Objects: v.Objects, + } +} + func (v *Visitor) Debug() { fmt.Println("=============================================================") fmt.Println("FilePath:", strings.TrimPrefix(v.FilePath, fs.CurrentDir())) - fmt.Println("PkgName:", v.PkgName) + fmt.Println("PkgName:", v.PkgName, "PkgPath:", v.PkgPath) fmt.Println("Import:") for alias, path := range v.Imports.Yield() { fmt.Println(" ", alias, path) @@ -56,14 +71,14 @@ func (v *Visitor) Debug() { fmt.Println(" in:") for _, value := range method.InParams { fmt.Println(" ", - "name:", value.Name, ", type:", value.Name, + "name:", value.Name, ", type:", value.Type, ", pkg:", value.Pkg, ", omit:", value.Omitempty) } fmt.Println(" out:") for _, value := range method.OutParams { fmt.Println(" ", - "name:", value.Name, ", type:", value.Name, + "name:", value.Name, ", type:", value.Type, ", pkg:", value.Pkg, ", omit:", value.Omitempty) } } @@ -98,8 +113,9 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor { func (v *Visitor) astFile(node *ast.File) ast.Visitor { v.PkgName = node.Name.String() + v.PkgPath = v.GoMod + filepath.Dir(v.FilePath) - console.Debugf("Parsed PkgName: %s", v.PkgName) + console.Debugf("Parsed PkgName: %s -> %s", v.PkgName, v.PkgPath) return v } @@ -139,6 +155,8 @@ func (v *Visitor) parseComment(comment string, tags *types.Tags) { console.Debugf("-- parse comment: %s", comment) buf := bb.FromBytes([]byte(comment)) + _, err := buf.Seek(0, io.SeekStart) + console.FatalIfErr(err, "parse comment") for { key, err := buf.ReadString('=') @@ -175,7 +193,12 @@ func (v *Visitor) parseComment(comment string, tags *types.Tags) { } console.FatalIfErr(err, "parse comment") - (*tags)[key] = append((*tags)[key], value) + (*tags)[key] = append((*tags)[key], do.Convert[string, string]( + strings.Split(value, ","), + func(value string, index int) string { + return strings.TrimSpace(value) + }, + )...) } } @@ -228,8 +251,10 @@ func (v *Visitor) astTypeSpec(node *ast.TypeSpec) { } obj := types.Object{ - Name: node.Name.String(), - Tags: make(types.Tags, 10), + Pkg: v.PkgPath, + Alias: v.PkgName, + Name: node.Name.String(), + Tags: make(types.Tags, 10), } console.Debugf("* Parse interface: %s", obj.Name) @@ -247,11 +272,17 @@ func (v *Visitor) astTypeSpec(node *ast.TypeSpec) { func getParam(param *ast.Field) types.Param { paramType := getTypeName(param.Type) paramPkg := func() string { - if !strings.Contains(paramType, ".") { + list := strings.Split(paramType, ".") + switch len(list) { + case 1: + return "" + case 2: + paramType = list[1] + return strings.Trim(list[0], "*[].") + default: + console.Fatalf("invalid type: %s", paramType) return "" } - pkg := strings.Split(paramType, ".")[0] - return strings.Trim(pkg, "*[].") }() console.Debugf("---- parse arg: Name: %s, Type: %s, Pkg: %s", @@ -266,6 +297,19 @@ func getParam(param *ast.Field) types.Param { } } +/* +TODO: change to +import "go/printer" +import "strings" +import "bytes" + +func exprToString(fset *token.FileSet, expr ast.Expr) string { + var buf bytes.Buffer + printer.Fprint(&buf, fset, expr) + return buf.String() +} +*/ + func getTypeName(expr ast.Expr) string { switch t := expr.(type) { case *ast.Ident: diff --git a/internal/global/skip_files.go b/internal/global/skip_files.go new file mode 100644 index 0000000..6554e06 --- /dev/null +++ b/internal/global/skip_files.go @@ -0,0 +1,11 @@ +package global + +import "strings" + +func NeedSkipFile(filepath string) bool { + return strings.HasSuffix(filepath, "_gen.go") || + strings.HasSuffix(filepath, "_test.go") || + strings.HasSuffix(filepath, "_easyjson.go") || + strings.HasSuffix(filepath, "_mock.go") || + strings.Contains(filepath, "/vendor/") +} From 44fb9cbece75dbd2096cc727446956f4aefb07e4 Mon Sep 17 00:00:00 2001 From: Mikhail Knyazhev Date: Mon, 2 Mar 2026 06:59:26 +0300 Subject: [PATCH 3/6] add web server generator --- .../transport/jsonrpc_Api_models_easyjson.go | 314 +++++++++++- .../transport/jsonrpc_Api_transport.go | 8 +- .../transport/jsonrpc_model_easyjson.go | 470 ++++++++++++++++++ web/ctx.go | 9 +- web/encoders/json.go | 2 +- 5 files changed, 768 insertions(+), 35 deletions(-) create mode 100644 _example/web-server-gen/transport/jsonrpc_model_easyjson.go diff --git a/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go b/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go index 2874714..14eab36 100644 --- a/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go +++ b/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go @@ -1,37 +1,301 @@ -// TEMPORARY AUTOGENERATED FILE: easyjson stub code to make the package -// compilable during generation. +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. -package transport +package transport import ( - "github.com/mailru/easyjson/jwriter" - "github.com/mailru/easyjson/jlexer" + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" ) -func ( jsonrpcAuthModelRequest ) MarshalJSON() ([]byte, error) { return nil, nil } -func (* jsonrpcAuthModelRequest ) UnmarshalJSON([]byte) error { return nil } -func ( jsonrpcAuthModelRequest ) MarshalEasyJSON(w *jwriter.Writer) {} -func (* jsonrpcAuthModelRequest ) UnmarshalEasyJSON(l *jlexer.Lexer) {} +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(in *jlexer.Lexer, out *jsonrpcRootModelResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "status": + if in.IsNull() { + in.Skip() + } else { + out.Status = bool(in.Bool()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(out *jwriter.Writer, in jsonrpcRootModelResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.Bool(bool(in.Status)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcRootModelResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcRootModelResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcRootModelResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcRootModelResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(l, v) +} +func easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(in *jlexer.Lexer, out *jsonrpcRootModelRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "userID": + if in.IsNull() { + in.Skip() + } else { + out.UserID = int64(in.Int64()) + } + case "userName": + if in.IsNull() { + in.Skip() + } else { + out.UserName = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(out *jwriter.Writer, in jsonrpcRootModelRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"userID\":" + out.RawString(prefix[1:]) + out.Int64(int64(in.UserID)) + } + { + const prefix string = ",\"userName\":" + out.RawString(prefix) + out.String(string(in.UserName)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcRootModelRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcRootModelRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcRootModelRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcRootModelRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(l, v) +} +func easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(in *jlexer.Lexer, out *jsonrpcAuthModelResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "status": + if in.IsNull() { + in.Skip() + } else { + out.Status = bool(in.Bool()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(out *jwriter.Writer, in jsonrpcAuthModelResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.Bool(bool(in.Status)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcAuthModelResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} -type EasyJSON_exporter_jsonrpcAuthModelRequest *jsonrpcAuthModelRequest +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcAuthModelResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(w, v) +} -func ( jsonrpcAuthModelResponse ) MarshalJSON() ([]byte, error) { return nil, nil } -func (* jsonrpcAuthModelResponse ) UnmarshalJSON([]byte) error { return nil } -func ( jsonrpcAuthModelResponse ) MarshalEasyJSON(w *jwriter.Writer) {} -func (* jsonrpcAuthModelResponse ) UnmarshalEasyJSON(l *jlexer.Lexer) {} +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcAuthModelResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&r, v) + return r.Error() +} -type EasyJSON_exporter_jsonrpcAuthModelResponse *jsonrpcAuthModelResponse +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcAuthModelResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(l, v) +} +func easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(in *jlexer.Lexer, out *jsonrpcAuthModelRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "userID": + if in.IsNull() { + in.Skip() + } else { + out.UserID = int64(in.Int64()) + } + case "userName": + if in.IsNull() { + in.Skip() + } else { + out.UserName = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(out *jwriter.Writer, in jsonrpcAuthModelRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"userID\":" + out.RawString(prefix[1:]) + out.Int64(int64(in.UserID)) + } + { + const prefix string = ",\"userName\":" + out.RawString(prefix) + out.String(string(in.UserName)) + } + out.RawByte('}') +} -func ( jsonrpcRootModelRequest ) MarshalJSON() ([]byte, error) { return nil, nil } -func (* jsonrpcRootModelRequest ) UnmarshalJSON([]byte) error { return nil } -func ( jsonrpcRootModelRequest ) MarshalEasyJSON(w *jwriter.Writer) {} -func (* jsonrpcRootModelRequest ) UnmarshalEasyJSON(l *jlexer.Lexer) {} +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcAuthModelRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&w, v) + return w.Buffer.BuildBytes(), w.Error +} -type EasyJSON_exporter_jsonrpcRootModelRequest *jsonrpcRootModelRequest +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcAuthModelRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(w, v) +} -func ( jsonrpcRootModelResponse ) MarshalJSON() ([]byte, error) { return nil, nil } -func (* jsonrpcRootModelResponse ) UnmarshalJSON([]byte) error { return nil } -func ( jsonrpcRootModelResponse ) MarshalEasyJSON(w *jwriter.Writer) {} -func (* jsonrpcRootModelResponse ) UnmarshalEasyJSON(l *jlexer.Lexer) {} +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcAuthModelRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&r, v) + return r.Error() +} -type EasyJSON_exporter_jsonrpcRootModelResponse *jsonrpcRootModelResponse +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcAuthModelRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(l, v) +} diff --git a/_example/web-server-gen/transport/jsonrpc_Api_transport.go b/_example/web-server-gen/transport/jsonrpc_Api_transport.go index 7bf2f87..1adedc3 100644 --- a/_example/web-server-gen/transport/jsonrpc_Api_transport.go +++ b/_example/web-server-gen/transport/jsonrpc_Api_transport.go @@ -1,13 +1,13 @@ // Code generated by goppy-cli wsg. DO NOT EDIT. package transport -import strings "strings" -import errors "errors" +import syncing "go.osspkg.com/syncing" import web "go.osspkg.com/goppy/v3/web" +import types "go.osspkg.com/goppy/v3/_example/web-server-gen/types" import context "context" +import strings "strings" +import errors "errors" import stdjson "encoding/json" -import syncing "go.osspkg.com/syncing" -import types "go.osspkg.com/goppy/v3/_example/web-server-gen/types" type JSONRPCApiHandle struct { routes web.ServerPool diff --git a/_example/web-server-gen/transport/jsonrpc_model_easyjson.go b/_example/web-server-gen/transport/jsonrpc_model_easyjson.go new file mode 100644 index 0000000..e675782 --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_model_easyjson.go @@ -0,0 +1,470 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package transport + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(in *jlexer.Lexer, out *errResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "message": + if in.IsNull() { + in.Skip() + } else { + out.Message = string(in.String()) + } + case "code": + if in.IsNull() { + in.Skip() + } else { + out.Code = int64(in.Int64()) + } + case "ctx": + if in.IsNull() { + in.Skip() + } else { + in.Delim('{') + if !in.IsDelim('}') { + out.Ctx = make(map[string]string) + } else { + out.Ctx = nil + } + for !in.IsDelim('}') { + key := string(in.String()) + in.WantColon() + var v1 string + if in.IsNull() { + in.Skip() + } else { + v1 = string(in.String()) + } + (out.Ctx)[key] = v1 + in.WantComma() + } + in.Delim('}') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(out *jwriter.Writer, in errResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"message\":" + out.RawString(prefix[1:]) + out.String(string(in.Message)) + } + { + const prefix string = ",\"code\":" + out.RawString(prefix) + out.Int64(int64(in.Code)) + } + if len(in.Ctx) != 0 { + const prefix string = ",\"ctx\":" + out.RawString(prefix) + { + out.RawByte('{') + v2First := true + for v2Name, v2Value := range in.Ctx { + if v2First { + v2First = false + } else { + out.RawByte(',') + } + out.String(string(v2Name)) + out.RawByte(':') + out.String(string(v2Value)) + } + out.RawByte('}') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v errResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v errResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *errResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *errResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(in *jlexer.Lexer, out *bulkResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + in.Skip() + *out = nil + } else { + in.Delim('[') + if *out == nil { + if !in.IsDelim(']') { + *out = make(bulkResponse, 0, 1) + } else { + *out = bulkResponse{} + } + } else { + *out = (*out)[:0] + } + for !in.IsDelim(']') { + var v3 baseResponse + if in.IsNull() { + in.Skip() + } else { + (v3).UnmarshalEasyJSON(in) + } + *out = append(*out, v3) + in.WantComma() + } + in.Delim(']') + } + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(out *jwriter.Writer, in bulkResponse) { + if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v4, v5 := range in { + if v4 > 0 { + out.RawByte(',') + } + (v5).MarshalEasyJSON(out) + } + out.RawByte(']') + } +} + +// MarshalJSON supports json.Marshaler interface +func (v bulkResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v bulkResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *bulkResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *bulkResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(in *jlexer.Lexer, out *bulkRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + in.Skip() + *out = nil + } else { + in.Delim('[') + if *out == nil { + if !in.IsDelim(']') { + *out = make(bulkRequest, 0, 1) + } else { + *out = bulkRequest{} + } + } else { + *out = (*out)[:0] + } + for !in.IsDelim(']') { + var v6 baseRequest + if in.IsNull() { + in.Skip() + } else { + (v6).UnmarshalEasyJSON(in) + } + *out = append(*out, v6) + in.WantComma() + } + in.Delim(']') + } + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(out *jwriter.Writer, in bulkRequest) { + if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v7, v8 := range in { + if v7 > 0 { + out.RawByte(',') + } + (v8).MarshalEasyJSON(out) + } + out.RawByte(']') + } +} + +// MarshalJSON supports json.Marshaler interface +func (v bulkRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v bulkRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *bulkRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *bulkRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(in *jlexer.Lexer, out *baseResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "id": + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } + case "result": + if m, ok := out.Result.(easyjson.Unmarshaler); ok { + m.UnmarshalEasyJSON(in) + } else if m, ok := out.Result.(json.Unmarshaler); ok { + _ = m.UnmarshalJSON(in.Raw()) + } else { + out.Result = in.Interface() + } + case "error": + if in.IsNull() { + in.Skip() + out.Error = nil + } else { + if out.Error == nil { + out.Error = new(errResponse) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Error).UnmarshalEasyJSON(in) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(out *jwriter.Writer, in baseResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.String(string(in.Id)) + } + if in.Result != nil { + const prefix string = ",\"result\":" + out.RawString(prefix) + if m, ok := in.Result.(easyjson.Marshaler); ok { + m.MarshalEasyJSON(out) + } else if m, ok := in.Result.(json.Marshaler); ok { + out.Raw(m.MarshalJSON()) + } else { + out.Raw(json.Marshal(in.Result)) + } + } + if in.Error != nil { + const prefix string = ",\"error\":" + out.RawString(prefix) + (*in.Error).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v baseResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v baseResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *baseResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *baseResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(in *jlexer.Lexer, out *baseRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "id": + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } + case "method": + if in.IsNull() { + in.Skip() + } else { + out.Method = string(in.String()) + } + case "params": + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Params).UnmarshalJSON(data)) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(out *jwriter.Writer, in baseRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.String(string(in.Id)) + } + { + const prefix string = ",\"method\":" + out.RawString(prefix) + out.String(string(in.Method)) + } + { + const prefix string = ",\"params\":" + out.RawString(prefix) + out.Raw((in.Params).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v baseRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v baseRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *baseRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *baseRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(l, v) +} diff --git a/web/ctx.go b/web/ctx.go index 70517c0..48fd779 100644 --- a/web/ctx.go +++ b/web/ctx.go @@ -9,7 +9,6 @@ package web import ( "context" - "encoding/json" "fmt" "io" "net/http" @@ -41,12 +40,12 @@ type ( BindRaw() (*data.Buffer, error) BindBytes(in *[]byte) error - BindJSON(in json.Unmarshaler) error + BindJSON(in any) error BindXML(in any) error Bytes(code int, b []byte) String(code int, b string, args ...any) - JSON(code int, in json.Marshaler) + JSON(code int, in any) Stream(code int, in []byte, filename string) StreamFile(code int, in io.Reader, filename string) @@ -223,7 +222,7 @@ func (v *_ctx) BindRaw() (*data.Buffer, error) { return buf, nil } -func (v *_ctx) BindJSON(in json.Unmarshaler) error { +func (v *_ctx) BindJSON(in any) error { return encoders.JSONDecode(v.r, in) } @@ -280,7 +279,7 @@ func (v *_ctx) String(code int, b string, args ...any) { } // JSON recording the response in json format -func (v *_ctx) JSON(code int, in json.Marshaler) { +func (v *_ctx) JSON(code int, in any) { encoders.JSONEncode(v.w, code, in) } diff --git a/web/encoders/json.go b/web/encoders/json.go index 5942191..e9c375f 100644 --- a/web/encoders/json.go +++ b/web/encoders/json.go @@ -27,7 +27,7 @@ func JSONEncode(w http.ResponseWriter, code int, obj any) { } } -func JSONDecode(r *http.Request, obj json.Unmarshaler) error { +func JSONDecode(r *http.Request, obj any) error { b, err := ioutils.ReadAll(r.Body) if err != nil { return err From 44c9acfab9a20f0b5e67a6b01d1c480addb849ec Mon Sep 17 00:00:00 2001 From: Mikhail Knyazhev Date: Tue, 10 Mar 2026 08:30:42 +0300 Subject: [PATCH 4/6] add web server generator --- .../transport/jsonrpc_Api_handlers.go | 32 -- .../transport/jsonrpc_Api_models.go | 26 - .../transport/jsonrpc_Api_models_easyjson.go | 301 ----------- .../transport/jsonrpc_Api_transport.go | 73 --- .../transport/jsonrpc_model_easyjson.go | 470 ------------------ _example/web-server-gen/types/interfaces.go | 1 - _example/web-server-gen/types/wsg.go | 2 +- .../gen/wsg => apigen}/builder/builder.go | 65 ++- apigen/builder/common.go | 14 + .../module/mod-json-rpc/common.go | 0 apigen/module/mod-json-rpc/transport.go | 403 +++++++++++++++ apigen/parser/errors.go | 5 + .../wsg/visitor => apigen/parser}/visitor.go | 119 +++-- {internal/gen/wsg => apigen}/types/object.go | 0 apigen/types/storage.go | 59 +++ apigen/types/type.go | 34 ++ {internal/gen/wsg => apigen}/util/util.go | 5 + cmd/goppy/main.go | 3 +- internal/commands/wsg.go | 80 +++ internal/gen/wsg/builder/common.go | 15 - internal/gen/wsg/command.go | 134 ----- .../gen/wsg/module/mod-json-rpc/transport.go | 408 --------------- internal/gen/wsg/types/storage.go | 16 - internal/gen/wsg/types/type.go | 25 - internal/global/mods.go | 40 ++ 25 files changed, 746 insertions(+), 1584 deletions(-) delete mode 100644 _example/web-server-gen/transport/jsonrpc_Api_handlers.go delete mode 100644 _example/web-server-gen/transport/jsonrpc_Api_models.go delete mode 100644 _example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go delete mode 100644 _example/web-server-gen/transport/jsonrpc_Api_transport.go delete mode 100644 _example/web-server-gen/transport/jsonrpc_model_easyjson.go rename {internal/gen/wsg => apigen}/builder/builder.go (54%) create mode 100644 apigen/builder/common.go rename {internal/gen/wsg => apigen}/module/mod-json-rpc/common.go (100%) create mode 100644 apigen/module/mod-json-rpc/transport.go create mode 100644 apigen/parser/errors.go rename {internal/gen/wsg/visitor => apigen/parser}/visitor.go (74%) rename {internal/gen/wsg => apigen}/types/object.go (100%) create mode 100644 apigen/types/storage.go create mode 100644 apigen/types/type.go rename {internal/gen/wsg => apigen}/util/util.go (89%) create mode 100644 internal/commands/wsg.go delete mode 100644 internal/gen/wsg/builder/common.go delete mode 100644 internal/gen/wsg/command.go delete mode 100644 internal/gen/wsg/module/mod-json-rpc/transport.go delete mode 100644 internal/gen/wsg/types/storage.go delete mode 100644 internal/gen/wsg/types/type.go diff --git a/_example/web-server-gen/transport/jsonrpc_Api_handlers.go b/_example/web-server-gen/transport/jsonrpc_Api_handlers.go deleted file mode 100644 index 09f6154..0000000 --- a/_example/web-server-gen/transport/jsonrpc_Api_handlers.go +++ /dev/null @@ -1,32 +0,0 @@ -// Code generated by goppy-cli wsg. DO NOT EDIT. -package transport - -import context "context" -import stdjson "encoding/json" - -func (v *JSONRPCApiHandle) methodRoot(ctx context.Context, param stdjson.RawMessage) (stdjson.Marshaler, error) { - var req jsonrpcRootModelRequest - err := stdjson.Unmarshal(param, &req) - if err != nil { - return nil, err - } - var res jsonrpcRootModelResponse - res.Status, err = v.handle.Root(ctx, req.UserID, req.UserName) - if err != nil { - return nil, err - } - return res, nil -} -func (v *JSONRPCApiHandle) methodAuth(ctx context.Context, param stdjson.RawMessage) (stdjson.Marshaler, error) { - var req jsonrpcAuthModelRequest - err := stdjson.Unmarshal(param, &req) - if err != nil { - return nil, err - } - var res jsonrpcAuthModelResponse - res.Status, err = v.handle.Auth(ctx, req.UserID, req.UserName) - if err != nil { - return nil, err - } - return res, nil -} diff --git a/_example/web-server-gen/transport/jsonrpc_Api_models.go b/_example/web-server-gen/transport/jsonrpc_Api_models.go deleted file mode 100644 index 1f3145d..0000000 --- a/_example/web-server-gen/transport/jsonrpc_Api_models.go +++ /dev/null @@ -1,26 +0,0 @@ -// Code generated by goppy-cli wsg. DO NOT EDIT. -package transport - -//go:generate easyjson - -//easyjson:json -type jsonrpcRootModelResponse struct { - Status bool `json:"status"` -} - -//easyjson:json -type jsonrpcRootModelRequest struct { - UserID int64 `json:"userID"` - UserName string `json:"userName"` -} - -//easyjson:json -type jsonrpcAuthModelResponse struct { - Status bool `json:"status"` -} - -//easyjson:json -type jsonrpcAuthModelRequest struct { - UserID int64 `json:"userID"` - UserName string `json:"userName"` -} diff --git a/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go b/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go deleted file mode 100644 index 14eab36..0000000 --- a/_example/web-server-gen/transport/jsonrpc_Api_models_easyjson.go +++ /dev/null @@ -1,301 +0,0 @@ -// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. - -package transport - -import ( - json "encoding/json" - easyjson "github.com/mailru/easyjson" - jlexer "github.com/mailru/easyjson/jlexer" - jwriter "github.com/mailru/easyjson/jwriter" -) - -// suppress unused package warning -var ( - _ *json.RawMessage - _ *jlexer.Lexer - _ *jwriter.Writer - _ easyjson.Marshaler -) - -func easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(in *jlexer.Lexer, out *jsonrpcRootModelResponse) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "status": - if in.IsNull() { - in.Skip() - } else { - out.Status = bool(in.Bool()) - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(out *jwriter.Writer, in jsonrpcRootModelResponse) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"status\":" - out.RawString(prefix[1:]) - out.Bool(bool(in.Status)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v jsonrpcRootModelResponse) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v jsonrpcRootModelResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *jsonrpcRootModelResponse) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *jsonrpcRootModelResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(l, v) -} -func easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(in *jlexer.Lexer, out *jsonrpcRootModelRequest) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "userID": - if in.IsNull() { - in.Skip() - } else { - out.UserID = int64(in.Int64()) - } - case "userName": - if in.IsNull() { - in.Skip() - } else { - out.UserName = string(in.String()) - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(out *jwriter.Writer, in jsonrpcRootModelRequest) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"userID\":" - out.RawString(prefix[1:]) - out.Int64(int64(in.UserID)) - } - { - const prefix string = ",\"userName\":" - out.RawString(prefix) - out.String(string(in.UserName)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v jsonrpcRootModelRequest) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v jsonrpcRootModelRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *jsonrpcRootModelRequest) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *jsonrpcRootModelRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(l, v) -} -func easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(in *jlexer.Lexer, out *jsonrpcAuthModelResponse) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "status": - if in.IsNull() { - in.Skip() - } else { - out.Status = bool(in.Bool()) - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(out *jwriter.Writer, in jsonrpcAuthModelResponse) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"status\":" - out.RawString(prefix[1:]) - out.Bool(bool(in.Status)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v jsonrpcAuthModelResponse) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v jsonrpcAuthModelResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *jsonrpcAuthModelResponse) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *jsonrpcAuthModelResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(l, v) -} -func easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(in *jlexer.Lexer, out *jsonrpcAuthModelRequest) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "userID": - if in.IsNull() { - in.Skip() - } else { - out.UserID = int64(in.Int64()) - } - case "userName": - if in.IsNull() { - in.Skip() - } else { - out.UserName = string(in.String()) - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(out *jwriter.Writer, in jsonrpcAuthModelRequest) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"userID\":" - out.RawString(prefix[1:]) - out.Int64(int64(in.UserID)) - } - { - const prefix string = ",\"userName\":" - out.RawString(prefix) - out.String(string(in.UserName)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v jsonrpcAuthModelRequest) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v jsonrpcAuthModelRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6363947EncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *jsonrpcAuthModelRequest) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *jsonrpcAuthModelRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6363947DecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(l, v) -} diff --git a/_example/web-server-gen/transport/jsonrpc_Api_transport.go b/_example/web-server-gen/transport/jsonrpc_Api_transport.go deleted file mode 100644 index 1adedc3..0000000 --- a/_example/web-server-gen/transport/jsonrpc_Api_transport.go +++ /dev/null @@ -1,73 +0,0 @@ -// Code generated by goppy-cli wsg. DO NOT EDIT. -package transport - -import syncing "go.osspkg.com/syncing" -import web "go.osspkg.com/goppy/v3/web" -import types "go.osspkg.com/goppy/v3/_example/web-server-gen/types" -import context "context" -import strings "strings" -import errors "errors" -import stdjson "encoding/json" - -type JSONRPCApiHandle struct { - routes web.ServerPool - handle types.Api -} - -func NewJSONRPCApiHandle(routes web.ServerPool, handle types.Api) *JSONRPCApiHandle { - return &JSONRPCApiHandle{ - routes: routes, - handle: handle, - } -} -func (v *JSONRPCApiHandle) Down() error { - return nil -} -func (v *JSONRPCApiHandle) Up(ctx context.Context) error { - v.routes.All(func(tag string, r web.Router) { - switch tag { - case "main", "admin": - r.Post("/api", func(ctx web.Ctx) { - var req bulkRequest - if err := ctx.BindJSON(&req); err != nil { - ctx.String(400, err.Error()) - return - } - res := syncing.NewSlice[baseResponse](uint(len(req))) - wg := syncing.NewGroup(ctx.Context()) - for _, item := range req { - item := item - wg.Background("api."+strings.ToLower(item.Method), func(ctx context.Context) { - out := baseResponse{ - Id: item.Id, - } - var err error - var result stdjson.Marshaler - switch strings.ToLower(item.Method) { - case "root": - result, err = v.methodRoot(ctx, item.Params) - - case "auth": - result, err = v.methodAuth(ctx, item.Params) - - default: - err = errors.New("unsupported method") - - } - if err != nil { - out.Error = toJSONRPCError(err) - } else { - out.Result = result - } - res.Append(out) - }) - } - wg.Wait() - ctx.JSON(200, bulkResponse(res.Extract())) - }) - - } - return - }) - return nil -} diff --git a/_example/web-server-gen/transport/jsonrpc_model_easyjson.go b/_example/web-server-gen/transport/jsonrpc_model_easyjson.go deleted file mode 100644 index e675782..0000000 --- a/_example/web-server-gen/transport/jsonrpc_model_easyjson.go +++ /dev/null @@ -1,470 +0,0 @@ -// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. - -package transport - -import ( - json "encoding/json" - easyjson "github.com/mailru/easyjson" - jlexer "github.com/mailru/easyjson/jlexer" - jwriter "github.com/mailru/easyjson/jwriter" -) - -// suppress unused package warning -var ( - _ *json.RawMessage - _ *jlexer.Lexer - _ *jwriter.Writer - _ easyjson.Marshaler -) - -func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(in *jlexer.Lexer, out *errResponse) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "message": - if in.IsNull() { - in.Skip() - } else { - out.Message = string(in.String()) - } - case "code": - if in.IsNull() { - in.Skip() - } else { - out.Code = int64(in.Int64()) - } - case "ctx": - if in.IsNull() { - in.Skip() - } else { - in.Delim('{') - if !in.IsDelim('}') { - out.Ctx = make(map[string]string) - } else { - out.Ctx = nil - } - for !in.IsDelim('}') { - key := string(in.String()) - in.WantColon() - var v1 string - if in.IsNull() { - in.Skip() - } else { - v1 = string(in.String()) - } - (out.Ctx)[key] = v1 - in.WantComma() - } - in.Delim('}') - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(out *jwriter.Writer, in errResponse) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"message\":" - out.RawString(prefix[1:]) - out.String(string(in.Message)) - } - { - const prefix string = ",\"code\":" - out.RawString(prefix) - out.Int64(int64(in.Code)) - } - if len(in.Ctx) != 0 { - const prefix string = ",\"ctx\":" - out.RawString(prefix) - { - out.RawByte('{') - v2First := true - for v2Name, v2Value := range in.Ctx { - if v2First { - v2First = false - } else { - out.RawByte(',') - } - out.String(string(v2Name)) - out.RawByte(':') - out.String(string(v2Value)) - } - out.RawByte('}') - } - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v errResponse) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v errResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *errResponse) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *errResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(l, v) -} -func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(in *jlexer.Lexer, out *bulkResponse) { - isTopLevel := in.IsStart() - if in.IsNull() { - in.Skip() - *out = nil - } else { - in.Delim('[') - if *out == nil { - if !in.IsDelim(']') { - *out = make(bulkResponse, 0, 1) - } else { - *out = bulkResponse{} - } - } else { - *out = (*out)[:0] - } - for !in.IsDelim(']') { - var v3 baseResponse - if in.IsNull() { - in.Skip() - } else { - (v3).UnmarshalEasyJSON(in) - } - *out = append(*out, v3) - in.WantComma() - } - in.Delim(']') - } - if isTopLevel { - in.Consumed() - } -} -func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(out *jwriter.Writer, in bulkResponse) { - if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v4, v5 := range in { - if v4 > 0 { - out.RawByte(',') - } - (v5).MarshalEasyJSON(out) - } - out.RawByte(']') - } -} - -// MarshalJSON supports json.Marshaler interface -func (v bulkResponse) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v bulkResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *bulkResponse) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *bulkResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(l, v) -} -func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(in *jlexer.Lexer, out *bulkRequest) { - isTopLevel := in.IsStart() - if in.IsNull() { - in.Skip() - *out = nil - } else { - in.Delim('[') - if *out == nil { - if !in.IsDelim(']') { - *out = make(bulkRequest, 0, 1) - } else { - *out = bulkRequest{} - } - } else { - *out = (*out)[:0] - } - for !in.IsDelim(']') { - var v6 baseRequest - if in.IsNull() { - in.Skip() - } else { - (v6).UnmarshalEasyJSON(in) - } - *out = append(*out, v6) - in.WantComma() - } - in.Delim(']') - } - if isTopLevel { - in.Consumed() - } -} -func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(out *jwriter.Writer, in bulkRequest) { - if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v7, v8 := range in { - if v7 > 0 { - out.RawByte(',') - } - (v8).MarshalEasyJSON(out) - } - out.RawByte(']') - } -} - -// MarshalJSON supports json.Marshaler interface -func (v bulkRequest) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v bulkRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *bulkRequest) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *bulkRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(l, v) -} -func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(in *jlexer.Lexer, out *baseResponse) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "id": - if in.IsNull() { - in.Skip() - } else { - out.Id = string(in.String()) - } - case "result": - if m, ok := out.Result.(easyjson.Unmarshaler); ok { - m.UnmarshalEasyJSON(in) - } else if m, ok := out.Result.(json.Unmarshaler); ok { - _ = m.UnmarshalJSON(in.Raw()) - } else { - out.Result = in.Interface() - } - case "error": - if in.IsNull() { - in.Skip() - out.Error = nil - } else { - if out.Error == nil { - out.Error = new(errResponse) - } - if in.IsNull() { - in.Skip() - } else { - (*out.Error).UnmarshalEasyJSON(in) - } - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(out *jwriter.Writer, in baseResponse) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"id\":" - out.RawString(prefix[1:]) - out.String(string(in.Id)) - } - if in.Result != nil { - const prefix string = ",\"result\":" - out.RawString(prefix) - if m, ok := in.Result.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := in.Result.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(in.Result)) - } - } - if in.Error != nil { - const prefix string = ",\"error\":" - out.RawString(prefix) - (*in.Error).MarshalEasyJSON(out) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v baseResponse) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v baseResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *baseResponse) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *baseResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(l, v) -} -func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(in *jlexer.Lexer, out *baseRequest) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - switch key { - case "id": - if in.IsNull() { - in.Skip() - } else { - out.Id = string(in.String()) - } - case "method": - if in.IsNull() { - in.Skip() - } else { - out.Method = string(in.String()) - } - case "params": - if in.IsNull() { - in.Skip() - } else { - if data := in.Raw(); in.Ok() { - in.AddError((out.Params).UnmarshalJSON(data)) - } - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(out *jwriter.Writer, in baseRequest) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"id\":" - out.RawString(prefix[1:]) - out.String(string(in.Id)) - } - { - const prefix string = ",\"method\":" - out.RawString(prefix) - out.String(string(in.Method)) - } - { - const prefix string = ",\"params\":" - out.RawString(prefix) - out.Raw((in.Params).MarshalJSON()) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v baseRequest) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v baseRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *baseRequest) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *baseRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(l, v) -} diff --git a/_example/web-server-gen/types/interfaces.go b/_example/web-server-gen/types/interfaces.go index 10e423f..8e2b61e 100644 --- a/_example/web-server-gen/types/interfaces.go +++ b/_example/web-server-gen/types/interfaces.go @@ -9,7 +9,6 @@ import "context" // Api // @wsg description="Методы апи" -// @wsg module=json-rpc,http // @wsg web-pool=main,admin // @wsg route-prefix=/api/v1 type Api interface { diff --git a/_example/web-server-gen/types/wsg.go b/_example/web-server-gen/types/wsg.go index 81ef797..8e5a9c5 100644 --- a/_example/web-server-gen/types/wsg.go +++ b/_example/web-server-gen/types/wsg.go @@ -1,3 +1,3 @@ package types -//go:generate goppy wsg --iface=Api --out=./../transport +//go:generate goppy wsg --iface=Api --mod=json-rpc,rest --out=./../transport diff --git a/internal/gen/wsg/builder/builder.go b/apigen/builder/builder.go similarity index 54% rename from internal/gen/wsg/builder/builder.go rename to apigen/builder/builder.go index 15a8921..b39bee7 100644 --- a/internal/gen/wsg/builder/builder.go +++ b/apigen/builder/builder.go @@ -8,21 +8,22 @@ import ( "go.osspkg.com/bb" "go.osspkg.com/gogen/golang" "go.osspkg.com/gogen/types" + at "go.osspkg.com/goppy/v3/apigen/types" "go.osspkg.com/goppy/v3/console" - wsgt "go.osspkg.com/goppy/v3/internal/gen/wsg/types" ) type Builder struct { Out string + Mods []string IFace map[string]struct{} - Files []wsgt.File + Files []at.File } -func (b *Builder) filterObjects(objs []wsgt.Object) []wsgt.Object { +func (b *Builder) filterObjects(objs []at.Object) []at.Object { if len(b.IFace) == 0 { return objs } - result := make([]wsgt.Object, 0, len(objs)) + result := make([]at.Object, 0, len(objs)) for _, obj := range objs { if _, ok := b.IFace[obj.Name]; ok { result = append(result, obj) @@ -32,16 +33,31 @@ func (b *Builder) filterObjects(objs []wsgt.Object) []wsgt.Object { return result } +func (b *Builder) getWorkFiles() []at.File { + workFiles := make([]at.File, 0, len(b.Files)) + + for _, file := range b.Files { + file.Objects = b.filterObjects(file.Objects) + if len(file.Objects) > 0 { + workFiles = append(workFiles, file) + } + } + + return workFiles +} + func (b *Builder) WriteFile(fileName string, tok types.Token) error { buf := bb.New(1024) if err := golang.Render(buf, tok); err != nil { return err } + fullPath := b.Out + "/" + fileName dir := filepath.Dir(fullPath) if err := os.MkdirAll(dir, 0766); err != nil { return fmt.Errorf("mkdir %q: %v", dir, err) } + console.Debugf("Writing file %s", fullPath) return os.WriteFile(fullPath, buf.Bytes(), 0666) } @@ -50,42 +66,19 @@ func (b *Builder) Build() error { //golang.SetRawMode() pkgName := filepath.Base(b.Out) + files := b.getWorkFiles() - for _, file := range b.Files { - file.Objects = b.filterObjects(file.Objects) - } + for _, name := range b.Mods { + mod, ok := at.Resolve[at.GlobalModule](name) + if !ok { + continue + } - for _, file := range b.Files { - for _, obj := range file.Objects { - tags, ok := obj.Tags[TagModule] - if !ok { - continue - } - - for _, tag := range tags { - mod, ok := wsgt.Resolve(wsgt.IFace + tag) - if !ok { - continue - } - - if err := mod.Build(wsgt.Ctx{ - W: b, - PkgName: pkgName, - File: file, - Object: obj, - Tags: obj.Tags, - }); err != nil { - return fmt.Errorf("build module `%s`, file `%s`: %w", - mod.Name(), file.FilePath, err) - } - } + err := mod.Build(b, at.GlobalMeta{PkgName: pkgName}, files) + if err != nil { + return fmt.Errorf("build module %q: %v", name, err) } } - //return errors.Queue( - // func() error { return b.buildInterface(pkgName) }, - // func() error { return b.WriteFile("transport.go", module.TypeTransport(pkgName)) }, - //) - return nil } diff --git a/apigen/builder/common.go b/apigen/builder/common.go new file mode 100644 index 0000000..b456949 --- /dev/null +++ b/apigen/builder/common.go @@ -0,0 +1,14 @@ +package builder + +import ( + modjsonrpc "go.osspkg.com/goppy/v3/apigen/module/mod-json-rpc" + "go.osspkg.com/goppy/v3/apigen/types" +) + +func init() { + types.Register[types.GlobalModule](modjsonrpc.JSONRPCTransport{FilePrefix: "jsonrpc"}) +} + +const ( + TagModule = "module" +) diff --git a/internal/gen/wsg/module/mod-json-rpc/common.go b/apigen/module/mod-json-rpc/common.go similarity index 100% rename from internal/gen/wsg/module/mod-json-rpc/common.go rename to apigen/module/mod-json-rpc/common.go diff --git a/apigen/module/mod-json-rpc/transport.go b/apigen/module/mod-json-rpc/transport.go new file mode 100644 index 0000000..35e0097 --- /dev/null +++ b/apigen/module/mod-json-rpc/transport.go @@ -0,0 +1,403 @@ +package mod_json_rpc + +import ( + "fmt" + + "go.osspkg.com/errors" + . "go.osspkg.com/gogen/golang" + at "go.osspkg.com/goppy/v3/apigen/types" + "go.osspkg.com/goppy/v3/apigen/util" +) + +type JSONRPCTransport struct { + FilePrefix string +} + +func (JSONRPCTransport) Name() string { + return "json-rpc" +} + +func (v JSONRPCTransport) Build(w at.Writer, m at.GlobalMeta, files []at.File) error { + return errors.Queue( + func() error { return v.buildBaseRPCModel(w, m) }, + //func() error { return v.buildTransport(ctx) }, + //func() error { return v.buildTransportModels(ctx) }, + //func() error { return v.buildTransportHandlers(ctx) }, + ) +} + +func (v JSONRPCTransport) buildBaseRPCModel(w at.Writer, m at.GlobalMeta) error { + baseReq := Comment(jsonGenComment). + Type().ID(modelBaseReq).Struct(). + Block( + ID(util.ToUpperCamelCase(fieldID)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), + ID(util.ToUpperCamelCase(fieldMethod)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldMethod)), + ID(util.ToUpperCamelCase(fieldParams)).Pkg("stdjson").ID("RawMessage"). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldParams)), + ) + + bulkBaseReq := Comment(jsonGenComment). + Type().ID(modelBulkBaseReq).Slice().ID(modelBaseReq) + + errRes := Comment(jsonGenComment). + Type().ID(modelBaseErr).Struct(). + Block( + ID(util.ToUpperCamelCase(fieldMessage)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldMessage)), + ID(util.ToUpperCamelCase(fieldCode)).Int64(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldCode)), + ID(util.ToUpperCamelCase(fieldCtx)).Map(String(), String()). + Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldCtx)), + ) + + errType := Type().ID(errInterface).Interface(). + Block( + ID("GetCode").Bracket().Int64(), + ID("GetMessage").Bracket().String(), + ID("GetContext").Bracket().Map(String(), String()), + ) + + baseRes := Comment(jsonGenComment). + Type().ID(modelBaseRes).Struct(). + Block( + ID(util.ToUpperCamelCase(fieldID)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), + ID(util.ToUpperCamelCase(fieldResult)).Any(). + Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldResult)), + ID(util.ToUpperCamelCase(fieldError)).Op("*").ID("errResponse"). + Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldError)), + ) + + bulkBaseRes := Comment(jsonGenComment). + Type().ID(modelBulkBaseRes).Slice().ID(modelBaseRes) + + toErr := Func().ID("toJSONRPCError"). + Bracket(ID("e").Error()). + Bracket(Op("*").ID(modelBaseErr)).Block( + If().ID("e").Op("==").Nil().Block(Return().Nil()), + ID("err").Op(":=").Op("&").ID(modelBaseErr).Block(), + List(ID("te"), ID("ok")).Op(":=").ID("e").Op(".").Bracket(ID(errInterface)), + If().ID("ok").Block( + ID("err").Op(".").ID("Code").Op("="). + ID("te").Op(".").ID("GetCode").Bracket(), + ID("err").Op(".").ID("Message").Op("="). + ID("te").Op(".").ID("GetMessage").Bracket(), + ID("err").Op(".").ID("Ctx").Op("="). + ID("te").Op(".").ID("GetContext").Bracket(), + ).Else().Block( + ID("err").Op(".").ID("Message").Op("="). + ID("e").Op(".").ID("Error").Bracket(), + ), + Return().ID("err"), + ) + + t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). + Package(m.PkgName). + Comment("go:generate easyjson"). + Import("stdjson", "encoding/json"). + Join(baseReq, bulkBaseReq, baseRes, bulkBaseRes, errRes, errType, toErr) + + return w.WriteFile(v.FilePrefix+"_model.go", t) +} + +// func (v JSONRPCTransport) buildTransportModels(ctx at.Ctx) error { +// t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). +// Package(ctx.PkgName). +// Comment("go:generate easyjson") +// +// list := syncing.NewMap[string, string](10) +// +// var models []types.Token +// +// for _, method := range ctx.Object.Methods { +// var ( +// argsOut []types.Token +// ) +// for _, p := range method.OutParams { +// switch { +// case p.Type == "error": +// continue +// default: +// } +// +// if link, ok := ctx.File.Imports.Get(p.Pkg); ok { +// list.Set(p.Pkg, link) +// } +// +// argsOut = append(argsOut, +// ID(util.ToUpperCamelCase(p.Name)).Pkg(p.Pkg).ID(p.Type). +// Raw(fmt.Sprintf("`json:\"%s%s\"`", p.Name, +// do.IfElse(p.Omitempty, ",omitempty", ""))), +// ) +// } +// models = append(models, Line().Comment(jsonGenComment). +// Type().ID(fmt.Sprintf(modelNameRes, method.Name)).Struct().Block(argsOut...), +// ) +// +// var ( +// argsIn []types.Token +// ) +// for _, p := range method.InParams { +// switch { +// case p.Pkg == "context" && p.Type == "Context": +// continue +// default: +// } +// +// if link, ok := ctx.File.Imports.Get(p.Pkg); ok { +// list.Set(p.Pkg, link) +// } +// +// argsIn = append(argsIn, +// ID(util.ToUpperCamelCase(p.Name)).Pkg(p.Pkg).ID(p.Type). +// Raw(fmt.Sprintf("`json:\"%s%s\"`", p.Name, +// do.IfElse(p.Omitempty, ",omitempty", ""))), +// ) +// } +// models = append(models, Line().Comment(jsonGenComment). +// Type().ID(fmt.Sprintf(modelNameReq, method.Name)).Struct().Block(argsIn...), +// ) +// } +// +// for alias, link := range list.Yield() { +// t.Import(alias, link) +// } +// +// return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_models.go", t.Join(models...)) +// } +// +// func (v JSONRPCTransport) buildTransportHandlers(ctx at.Ctx) error { +// objectName := fmt.Sprintf(transportName, ctx.Object.Name) +// +// t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). +// Package(ctx.PkgName) +// +// list := syncing.NewMap[string, string](10) +// list.Set("context", "context") +// list.Set("stdjson", "encoding/json") +// +// var models []types.Token +// +// for _, method := range ctx.Object.Methods { +// handle := Func().Bracket(ID("v").Op("*").ID(objectName)). +// ID("method"+method.Name).Bracket( +// ID("ctx").Pkg("context").ID("Context"), +// ID("param").Pkg("stdjson").ID("RawMessage"), +// ).Bracket( +// Pkg("stdjson").ID("Marshaler"), +// Error(), +// ) +// +// var handleSrc []types.Token +// +// handleSrc = append(handleSrc, +// Var().ID("req").ID(fmt.Sprintf(modelNameReq, method.Name)), +// ID("err").Op(":=").Pkg("stdjson").ID("Unmarshal").Bracket( +// ID("param"), Op("&").ID("req"), +// ), +// If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), +// Var().ID("res").ID(fmt.Sprintf(modelNameRes, method.Name)), +// ) +// +// var ( +// handleOut []types.Token +// ) +// for _, p := range method.OutParams { +// if link, ok := ctx.File.Imports.Get(p.Pkg); ok { +// list.Set(p.Pkg, link) +// } +// +// switch { +// case p.Type == "error": +// handleOut = append(handleOut, ID("err")) +// default: +// handleOut = append(handleOut, ID("res").Op(".").ID(util.ToUpperCamelCase(p.Name))) +// } +// } +// +// var ( +// handleIn []types.Token +// ) +// for _, p := range method.InParams { +// if link, ok := ctx.File.Imports.Get(p.Pkg); ok { +// list.Set(p.Pkg, link) +// } +// +// switch { +// case p.Pkg == "context" && p.Type == "Context": +// handleIn = append(handleIn, ID("ctx")) +// default: +// handleIn = append(handleIn, ID("req").Op(".").ID(util.ToUpperCamelCase(p.Name))) +// } +// } +// +// handleSrc = append(handleSrc, +// List(handleOut...).Op("=").ID("v").Op(".").ID("handle").Op(".").ID(method.Name).Call(handleIn...), +// If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), +// Return().List(ID("res"), Nil()), +// ) +// +// models = append(models, handle.Block(handleSrc...)) +// } +// +// for alias, link := range list.Yield() { +// t.Import(alias, link) +// } +// +// return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_handlers.go", t.Join(models...)) +// } +// +// func (v JSONRPCTransport) buildTransport(ctx at.Ctx) error { +// objectName := fmt.Sprintf(transportName, ctx.Object.Name) +// +// t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). +// Package(ctx.PkgName) +// +// list := syncing.NewMap[string, string](10) +// list.Set("context", "context") +// list.Set("strings", "strings") +// list.Set("errors", "errors") +// list.Set("stdjson", "encoding/json") +// list.Set("syncing", "go.osspkg.com/syncing") +// list.Set("web", "go.osspkg.com/goppy/v3/web") +// list.Set(ctx.Object.Alias, ctx.Object.Pkg) +// for alias, link := range ctx.File.Imports.Yield() { +// list.Set(alias, link) +// } +// +// for alias, link := range list.Yield() { +// t.Import(alias, link) +// } +// +// t = t.Join( +// Type().ID(objectName).Struct().Block( +// ID("routes").Pkg("web").ID("ServerPool"), +// ID("handle").Pkg(ctx.Object.Alias).ID(ctx.Object.Name), +// ), +// //-- New +// Func().ID("New"+objectName).Bracket( +// ID("routes").Pkg("web").ID("ServerPool"), +// ID("handle").Pkg(ctx.Object.Alias).ID(ctx.Object.Name), +// ).Op("*").ID(objectName).Block( +// Return().Op("&").ID(objectName).Block( +// ID("routes").Op(":").ID("routes").Op(","), +// ID("handle").Op(":").ID("handle").Op(","), +// ), +// ), +// // -- Down +// Func().Bracket(ID("v").Op("*").ID(objectName)). +// ID("Down").Bracket().Error().Block(Return().Nil()), +// // -- Up +// Func().Bracket(ID("v").Op("*").ID(objectName)). +// ID("Up").Bracket(ID("ctx").Pkg("context").ID("Context")).Error(). +// Block(v.buildUp(ctx)), +// ) +// +// return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_transport.go", t) +// } +// +// func (v JSONRPCTransport) buildUp(ctx at.Ctx) *Tokens { +// return ID("v").Op(".").ID("routes").Op(".").ID("All").Bracket( +// Func().Bracket(ID("tag").String(), ID("r").Pkg("web").ID("Router")).Block( +// func() *Tokens { +// tags, ok := ctx.Tags[tagWebPool] +// if !ok { +// return Line() +// } +// var list []types.Token +// for _, tag := range tags { +// list = append(list, Text(tag)) +// } +// return Switch().ID("tag").Block( +// Case().List(list...).Op(":").Line().Join( +// ID("r").Op(".").ID("Post").Call( +// Text("/"+strings.ToLower(ctx.Object.Name)), +// Func().Bracket(ID("ctx").Pkg("web").ID("Ctx")).Block( +// Var().ID("req").ID(modelBulkBaseReq), +// If(). +// ID("err").Op(":="). +// ID("ctx").Op(".").ID("BindJSON").Bracket(Op("&").ID("req")).Op(";"). +// ID("err").Op("!=").Nil().Block( +// ID("ctx").Op(".").ID("String").Bracket( +// Raw("400"), ID("err").Op(".").ID("Error").Call(), +// ), +// Return(), +// ), +// ID("res").Op(":="). +// Pkg("syncing").ID("NewSlice").Raw("[").ID(modelBaseRes).Raw("]").Call( +// Uint().Bracket(Raw("len").Bracket(ID("req"))), +// ), +// ID("wg").Op(":=").Pkg("syncing").ID("NewGroup").Bracket( +// ID("ctx").Op(".").ID("Context").Call(), +// ), +// For().List(Raw("_"), ID("item")).Op(":=").Range().ID("req").Block( +// ID("item").Op(":=").ID("item"), +// +// ID("wg").Op(".").ID("Background").Call( +// Text(strings.ToLower(ctx.Object.Name)+".").Op("+"). +// Pkg("strings").ID("ToLower").Bracket( +// ID("item").Op(".").ID(util.ToUpperCamelCase(fieldMethod)), +// ), +// Func().Bracket(ID("ctx").Pkg("context").ID("Context")).Block( +// ID("out").Op(":=").ID(modelBaseRes).Block( +// ID(util.ToUpperCamelCase(fieldID)).Op(":"). +// ID("item").Op(".").ID(util.ToUpperCamelCase(fieldID)).Op(","), +// ), +// Var().ID("err").Error(), +// Var().ID("result").Pkg("stdjson").ID("Marshaler"), +// v.buildMethods(ctx), +// If().ID("err").Op("!=").Nil().Block( +// ID("out").Op(".").ID(util.ToUpperCamelCase(fieldError)).Op("="). +// ID("toJSONRPCError").Bracket(ID("err")), +// ).Else().Block( +// ID("out").Op(".").ID(util.ToUpperCamelCase(fieldResult)).Op("="). +// ID("result"), +// ), +// ID("res").Op(".").ID("Append").Bracket(ID("out")), +// ), +// ), +// ), +// ID("wg").Op(".").ID("Wait").Call(), +// ID("ctx").Op(".").ID("JSON").Bracket( +// Raw("200"), ID(modelBulkBaseRes).Bracket( +// ID("res").Op(".").ID("Extract").Bracket(), +// ), +// ), +// ), +// ), +// ), +// ).Line().Return() +// }(), +// ), +// ). +// Line().Return().Nil() +// +// } +//func (v JSONRPCTransport) buildMethods(ctx at.Ctx) *Tokens { +// var list []types.Token +// +// for _, method := range ctx.Object.Methods { +// list = append(list, +// Case().Text(strings.ToLower(method.Name)).Op(":").Join( +// List(ID("result"), ID("err")).Op("="). +// ID("v").Op(".").ID("method"+method.Name). +// Call( +// ID("ctx"), +// ID("item").Op(".").ID(util.ToUpperCamelCase(fieldParams)), +// ), +// ), +// ) +// } +// +// list = append(list, +// Default().Op(":").Join( +// ID("err").Op("=").Pkg("errors").ID("New").Bracket(Text("unsupported method")), +// ), +// ) +// +// return Switch().Pkg("strings").ID("ToLower").Bracket( +// ID("item").Op(".").ID(util.ToUpperCamelCase(fieldMethod)), +// ).Block(list...) +//} diff --git a/apigen/parser/errors.go b/apigen/parser/errors.go new file mode 100644 index 0000000..ba2c9f6 --- /dev/null +++ b/apigen/parser/errors.go @@ -0,0 +1,5 @@ +package parser + +import "go.osspkg.com/errors" + +var ErrIsGenerated = errors.New("file is generated") diff --git a/internal/gen/wsg/visitor/visitor.go b/apigen/parser/visitor.go similarity index 74% rename from internal/gen/wsg/visitor/visitor.go rename to apigen/parser/visitor.go index 85c60cd..d6e0c8c 100644 --- a/internal/gen/wsg/visitor/visitor.go +++ b/apigen/parser/visitor.go @@ -3,38 +3,74 @@ * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. */ -package visitor +package parser import ( - "errors" "fmt" "go/ast" + "go/parser" + "go/token" "io" "path/filepath" "strings" "go.osspkg.com/bb" "go.osspkg.com/do" + "go.osspkg.com/errors" + "go.osspkg.com/goppy/v3/apigen/types" + "go.osspkg.com/goppy/v3/apigen/util" + "go.osspkg.com/goppy/v3/internal/global" "go.osspkg.com/ioutils/fs" "go.osspkg.com/syncing" +) + +type ( + visitor struct { + TAG string + + FilePath string + PkgName string + PkgPath string + GoMod string + Imports *syncing.Map[string, string] + Objects []types.Object + } - "go.osspkg.com/goppy/v3/console" - "go.osspkg.com/goppy/v3/internal/gen/ormb/common" - "go.osspkg.com/goppy/v3/internal/gen/wsg/types" + Parser interface { + ToFile() types.File + DumpStdout() + } ) -const tag = "@wsg" +func New(tag, filePath string) (Parser, error) { + gomod, root, err := global.DetectGoMod(filePath) + if err != nil { + return nil, err + } + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) + if err != nil { + return nil, err + } + + if ast.IsGenerated(f) { + return nil, ErrIsGenerated + } + + vis := &visitor{ + TAG: tag, + Imports: syncing.NewMap[string, string](10), + FilePath: strings.TrimPrefix(filePath, root), + GoMod: gomod, + } -type Visitor struct { - FilePath string - PkgName string - PkgPath string - GoMod string - Imports *syncing.Map[string, string] - Objects []types.Object + ast.Walk(vis, f) + + return vis, nil } -func (v *Visitor) ToFile() types.File { +func (v *visitor) ToFile() types.File { return types.File{ FilePath: v.FilePath, PkgName: v.PkgName, @@ -45,7 +81,7 @@ func (v *Visitor) ToFile() types.File { } } -func (v *Visitor) Debug() { +func (v *visitor) DumpStdout() { fmt.Println("=============================================================") fmt.Println("FilePath:", strings.TrimPrefix(v.FilePath, fs.CurrentDir())) fmt.Println("PkgName:", v.PkgName, "PkgPath:", v.PkgPath) @@ -86,7 +122,7 @@ func (v *Visitor) Debug() { fmt.Println("=============================================================") } -func (v *Visitor) Visit(node ast.Node) ast.Visitor { +func (v *visitor) Visit(node ast.Node) ast.Visitor { switch nodeType := node.(type) { case *ast.File: return v.astFile(nodeType) @@ -111,66 +147,60 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor { } } -func (v *Visitor) astFile(node *ast.File) ast.Visitor { +func (v *visitor) astFile(node *ast.File) ast.Visitor { v.PkgName = node.Name.String() v.PkgPath = v.GoMod + filepath.Dir(v.FilePath) - console.Debugf("Parsed PkgName: %s -> %s", v.PkgName, v.PkgPath) - return v } -func (v *Visitor) astImportSpec(node *ast.ImportSpec) ast.Visitor { +func (v *visitor) astImportSpec(node *ast.ImportSpec) ast.Visitor { path := strings.Trim(node.Path.Value, `"`) - name := common.SplitLast(path, "/") + name := util.SplitLast(path, "/") if node.Name != nil { name = node.Name.String() } - console.Debugf("Import: name='%s' path='%s'", name, path) - v.Imports.Set(name, path) return v } -func (v *Visitor) parseDoc(comment *ast.CommentGroup, tags *types.Tags) { +func (v *visitor) parseDoc(comment *ast.CommentGroup, tags *types.Tags) { if comment == nil { return } for _, doc := range comment.List { - i := strings.Index(doc.Text, tag) + i := strings.Index(doc.Text, v.TAG) if i < 0 { continue } - v.parseComment(doc.Text[i+len(tag):], tags) + v.parseComment(doc.Text[i+len(v.TAG):], tags) } } -func (v *Visitor) parseComment(comment string, tags *types.Tags) { +func (v *visitor) parseComment(comment string, tags *types.Tags) { comment = strings.TrimPrefix(comment, "//") comment = strings.TrimSpace(comment) - console.Debugf("-- parse comment: %s", comment) - buf := bb.FromBytes([]byte(comment)) _, err := buf.Seek(0, io.SeekStart) - console.FatalIfErr(err, "parse comment") + panicIfError(err, "parse comment") for { key, err := buf.ReadString('=') if errors.Is(err, io.EOF) { break } - console.FatalIfErr(err, "parse comment") + panicIfError(err, "parse comment") key = strings.Trim(strings.TrimSpace(key), "=") r, _, err := buf.ReadRune() if errors.Is(err, io.EOF) { break } - console.FatalIfErr(err, "parse comment") + panicIfError(err, "parse comment") var value string switch r { @@ -184,14 +214,14 @@ func (v *Visitor) parseComment(comment string, tags *types.Tags) { value, err = buf.ReadString('`') value = strings.Trim(strings.TrimSpace(value), "`") default: - console.FatalIfErr(buf.UnreadRune(), "parse comment") + panicIfError(buf.UnreadRune(), "parse comment") value, err = buf.ReadString(' ') } if errors.Is(err, io.EOF) { break } - console.FatalIfErr(err, "parse comment") + panicIfError(err, "parse comment") (*tags)[key] = append((*tags)[key], do.Convert[string, string]( strings.Split(value, ","), @@ -202,7 +232,7 @@ func (v *Visitor) parseComment(comment string, tags *types.Tags) { } } -func (v *Visitor) parseMethods(fields *ast.FieldList) (result []types.Method) { +func (v *visitor) parseMethods(fields *ast.FieldList) (result []types.Method) { for _, field := range fields.List { if !field.Names[0].IsExported() { continue @@ -213,8 +243,6 @@ func (v *Visitor) parseMethods(fields *ast.FieldList) (result []types.Method) { continue } - console.Debugf("** Parse method: %v", field.Names[0].String()) - method := types.Method{ Name: field.Names[0].String(), Tags: make(types.Tags, 10), @@ -244,7 +272,7 @@ func (v *Visitor) parseMethods(fields *ast.FieldList) (result []types.Method) { return } -func (v *Visitor) astTypeSpec(node *ast.TypeSpec) { +func (v *visitor) astTypeSpec(node *ast.TypeSpec) { faceNode, ok := node.Type.(*ast.InterfaceType) if !ok { return @@ -257,8 +285,6 @@ func (v *Visitor) astTypeSpec(node *ast.TypeSpec) { Tags: make(types.Tags, 10), } - console.Debugf("* Parse interface: %s", obj.Name) - v.parseDoc(node.Doc, &obj.Tags) //v.parseDoc(node.Comment, &obj.Tags) @@ -280,14 +306,11 @@ func getParam(param *ast.Field) types.Param { paramType = list[1] return strings.Trim(list[0], "*[].") default: - console.Fatalf("invalid type: %s", paramType) + panicIfError(fmt.Errorf("invalid type: %s", paramType), "get param") return "" } }() - console.Debugf("---- parse arg: Name: %s, Type: %s, Pkg: %s", - param.Names[0].String(), paramType, paramPkg) - return types.Param{ Name: param.Names[0].String(), Type: paramType, @@ -324,3 +347,11 @@ func getTypeName(expr ast.Expr) string { return "" } } + +func panicIfError(err error, msg string, args ...interface{}) { + if err == nil { + return + } + err = errors.Wrapf(err, msg, args...) + panic(err.Error()) +} diff --git a/internal/gen/wsg/types/object.go b/apigen/types/object.go similarity index 100% rename from internal/gen/wsg/types/object.go rename to apigen/types/object.go diff --git a/apigen/types/storage.go b/apigen/types/storage.go new file mode 100644 index 0000000..6fbd0c1 --- /dev/null +++ b/apigen/types/storage.go @@ -0,0 +1,59 @@ +package types + +import ( + "fmt" + + "go.osspkg.com/syncing" +) + +const ( + globalMod = "gg" + faceMod = "fg" + methodMod = "mg" +) + +var ( + _storage = syncing.NewMap[string, any](10) +) + +func Register[T any](module T) { + addr := "" + + switch vv := any(module).(type) { + case GlobalModule: + addr = globalMod + "/" + vv.Name() + case FaceModule: + addr = faceMod + "/" + vv.Name() + case MethodModule: + addr = methodMod + "/" + vv.Name() + default: + panic("unknown type") + } + + _storage.Set(addr, module) +} + +func Resolve[T any](name string) (T, bool) { + addr := "" + nt := new(T) + + switch any(nt).(type) { + case *GlobalModule: + addr = globalMod + "/" + name + case *FaceModule: + addr = faceMod + "/" + name + case *MethodModule: + addr = methodMod + "/" + name + default: + panic(fmt.Sprintf("unknown type: %T", *nt)) + } + + raw, ok := _storage.Get(addr) + if !ok { + var zeroValue T + return zeroValue, false + } + + module, ok := raw.(T) + return module, ok +} diff --git a/apigen/types/type.go b/apigen/types/type.go new file mode 100644 index 0000000..afb6326 --- /dev/null +++ b/apigen/types/type.go @@ -0,0 +1,34 @@ +package types + +import "go.osspkg.com/gogen/types" + +type GlobalModule interface { + Name() string + Build(w Writer, m GlobalMeta, value []File) error +} + +type FaceModule interface { + Name() string + Build(w Writer, m FaceMeta, value File) error +} + +type MethodModule interface { + Name() string + Build(w Writer, m MethodMeta, value Method) error +} + +type Writer interface { + WriteFile(fileName string, tok types.Token) error +} + +type GlobalMeta struct { + PkgName string +} + +type FaceMeta struct { + PkgName string +} + +type MethodMeta struct { + PkgName string +} diff --git a/internal/gen/wsg/util/util.go b/apigen/util/util.go similarity index 89% rename from internal/gen/wsg/util/util.go rename to apigen/util/util.go index 3faa562..8189157 100644 --- a/internal/gen/wsg/util/util.go +++ b/apigen/util/util.go @@ -59,3 +59,8 @@ func CRC32(s string) string { return fmt.Sprintf("%08X", crc32.Checksum([]byte(s), crc32q)) } + +func SplitLast(s, sep string) string { + result := strings.Split(s, sep) + return result[len(result)-1] +} diff --git a/cmd/goppy/main.go b/cmd/goppy/main.go index f4794a7..79de584 100644 --- a/cmd/goppy/main.go +++ b/cmd/goppy/main.go @@ -9,7 +9,6 @@ import ( "go.osspkg.com/goppy/v3/console" "go.osspkg.com/goppy/v3/internal/commands" "go.osspkg.com/goppy/v3/internal/gen/ormb" - "go.osspkg.com/goppy/v3/internal/gen/wsg" "go.osspkg.com/goppy/v3/internal/global" ) @@ -27,8 +26,8 @@ func main() { commands.CmdSetupLib(), commands.CmdSetupApp(), commands.CmdGoSite(), + commands.CmdWSG(), ormb.Command(), - wsg.Command(), ) app.Exec() diff --git a/internal/commands/wsg.go b/internal/commands/wsg.go new file mode 100644 index 0000000..11513dd --- /dev/null +++ b/internal/commands/wsg.go @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package commands + +import ( + "os" + "strings" + + "go.osspkg.com/do" + "go.osspkg.com/errors" + "go.osspkg.com/goppy/v3/apigen/builder" + "go.osspkg.com/goppy/v3/apigen/parser" + "go.osspkg.com/goppy/v3/console" + "go.osspkg.com/goppy/v3/internal/global" + "go.osspkg.com/ioutils/fs" +) + +func CmdWSG() console.CommandGetter { + return console.NewCommand(func(setter console.CommandSetter) { + setter.Setup("wsg", "generate web server api") + setter.Flag(func(flagsSetter console.FlagsSetter) { + flagsSetter.String("out", "output file specified") + flagsSetter.StringVar("iface", "", "interface names (optional)") + flagsSetter.StringVar("mod", "json-rpc", "generation modules (optional)") + }) + setter.ExecFunc(func(out, _iface, _mod string) { + console.ShowDebug(true) + console.Infof("--- GENERATE ---") + + curDir := fs.CurrentDir() + + console.FatalIfTrue(len(out) == 0, "no output file specified") + console.FatalIfTrue(out == curDir, "output dir equals current dir") + + console.FatalIfErr(os.RemoveAll(out), "failed to remove old output directory") + console.FatalIfErr(os.MkdirAll(out, 0755), "failed to create directory") + + files, err := fs.SearchFilesByExt(curDir, ".go") + console.FatalIfErr(err, "search files in %s", curDir) + + files = do.Filter[string](files, func(value string, index int) bool { + return !strings.HasPrefix(value, out) + }) + + build := &builder.Builder{ + Out: out, + IFace: do.Entries[string, string, struct{}](strings.Split(_iface, ","), func(s string) (string, struct{}) { + return strings.ToLower(strings.TrimSpace(s)), struct{}{} + }), + Mods: do.Treat[string](strings.Split(_mod, ","), func(value string, index int) string { + return strings.ToLower(strings.TrimSpace(value)) + }), + } + + for _, filePath := range files { + if global.NeedSkipFile(filePath) { + continue + } + + console.Debugf("> PARSE FILE: %s", filePath) + + vv, e := parser.New("@wsg", filePath) + if e != nil { + if errors.Is(e, parser.ErrIsGenerated) { + continue + } + console.FatalIfErr(e, "parse go file: %s", filePath) + } + + vv.DumpStdout() + build.Files = append(build.Files, vv.ToFile()) + } + + console.FatalIfErr(build.Build(), "") + }) + }) +} diff --git a/internal/gen/wsg/builder/common.go b/internal/gen/wsg/builder/common.go deleted file mode 100644 index 16acee6..0000000 --- a/internal/gen/wsg/builder/common.go +++ /dev/null @@ -1,15 +0,0 @@ -package builder - -import ( - modjsonrpc "go.osspkg.com/goppy/v3/internal/gen/wsg/module/mod-json-rpc" - "go.osspkg.com/goppy/v3/internal/gen/wsg/types" -) - -func init() { - types.Register(modjsonrpc.JSONRPCTransport{FilePrefix: "jsonrpc"}) - //types.Register(HTTPTransport{}) -} - -const ( - TagModule = "module" -) diff --git a/internal/gen/wsg/command.go b/internal/gen/wsg/command.go deleted file mode 100644 index 62258e0..0000000 --- a/internal/gen/wsg/command.go +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. - * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. - */ - -package wsg - -import ( - "fmt" - "go/ast" - "go/parser" - "go/token" - "os" - "path/filepath" - "strings" - - "go.osspkg.com/do" - "go.osspkg.com/goppy/v3/internal/gen/wsg/builder" - "go.osspkg.com/goppy/v3/internal/global" - "go.osspkg.com/ioutils/fs" - "go.osspkg.com/syncing" - "golang.org/x/mod/modfile" - - "go.osspkg.com/goppy/v3/console" - "go.osspkg.com/goppy/v3/internal/gen/wsg/visitor" -) - -func Command() console.CommandGetter { - return console.NewCommand(func(setter console.CommandSetter) { - setter.Setup("wsg", "generate web server api") - setter.Flag(func(flagsSetter console.FlagsSetter) { - flagsSetter.String("out", "output file specified") - flagsSetter.StringVar("iface", "", "interface names (optional)") - }) - setter.ExecFunc(func(out, _iface string) { - console.ShowDebug(true) - console.Infof("--- GENERATE ---") - - curDir := fs.CurrentDir() - - console.FatalIfTrue(len(out) == 0, "no output file specified") - console.FatalIfTrue(out == curDir, "output dir equals current dir") - - console.FatalIfErr(os.RemoveAll(out), "failed to remove old output directory") - console.FatalIfErr(os.MkdirAll(out, 0755), "failed to create directory") - - files, err := fs.SearchFilesByExt(curDir, ".go") - console.FatalIfErr(err, "search files in %s", curDir) - - files = do.Filter[string](files, func(value string, index int) bool { - return !strings.HasPrefix(value, out) - }) - - iface := do.Entries[string, string, struct{}](strings.Split(_iface, ","), func(s string) (string, struct{}) { - return strings.ToLower(strings.TrimSpace(s)), struct{}{} - }) - - build := &builder.Builder{ - Out: out, - IFace: iface, - } - - for _, filePath := range files { - if global.NeedSkipFile(filePath) { - continue - } - - console.Debugf("> PARSE FILE: %s", filePath) - - gomod, root, err := detectGoMod(filePath) - console.FatalIfErr(err, "failed to detect gomod") - - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) - console.FatalIfErr(err, "parse go file: %s", filePath) - - if ast.IsGenerated(f) { - continue - } - - vv := &visitor.Visitor{ - Imports: syncing.NewMap[string, string](1), - FilePath: strings.TrimPrefix(filePath, root), - GoMod: gomod, - } - - ast.Walk(vv, f) - - vv.Debug() - - build.Files = append(build.Files, vv.ToFile()) - } - - console.FatalIfErr(build.Build(), "") - }) - }) -} - -func detectGoMod(curPath string) (mod string, root string, err error) { - root = filepath.Dir(curPath) - for { - - mods, e := fs.SearchFilesByExt(root, ".mod") - if e != nil { - return "", "", e - } - - if len(mods) != 0 { - for _, s := range mods { - b, e := os.ReadFile(s) - if e != nil { - return "", "", e - } - - f, e := modfile.Parse("go.mod", b, nil) - if e != nil { - return "", "", e - } - mod = f.Module.Mod.Path - return - } - } - - root, err = filepath.Abs(root + "/..") - if err != nil { - return - } - - if root == "/" { - err = fmt.Errorf("failed to detect go.mod") - return - } - } -} diff --git a/internal/gen/wsg/module/mod-json-rpc/transport.go b/internal/gen/wsg/module/mod-json-rpc/transport.go deleted file mode 100644 index 25f0981..0000000 --- a/internal/gen/wsg/module/mod-json-rpc/transport.go +++ /dev/null @@ -1,408 +0,0 @@ -package mod_json_rpc - -import ( - "fmt" - "strings" - - "go.osspkg.com/do" - "go.osspkg.com/errors" - . "go.osspkg.com/gogen/golang" - "go.osspkg.com/gogen/types" - wsgt "go.osspkg.com/goppy/v3/internal/gen/wsg/types" - "go.osspkg.com/goppy/v3/internal/gen/wsg/util" - "go.osspkg.com/syncing" -) - -type JSONRPCTransport struct { - FilePrefix string -} - -func (JSONRPCTransport) Name() string { - return wsgt.IFace + "json-rpc" -} - -func (v JSONRPCTransport) Build(ctx wsgt.Ctx) error { - return errors.Queue( - func() error { return v.buildBaseRPCModel(ctx) }, - func() error { return v.buildTransport(ctx) }, - func() error { return v.buildTransportModels(ctx) }, - func() error { return v.buildTransportHandlers(ctx) }, - ) -} - -func (v JSONRPCTransport) buildBaseRPCModel(ctx wsgt.Ctx) error { - baseReq := Comment(jsonGenComment). - Type().ID(modelBaseReq).Struct(). - Block( - ID(util.ToUpperCamelCase(fieldID)).String(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), - ID(util.ToUpperCamelCase(fieldMethod)).String(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldMethod)), - ID(util.ToUpperCamelCase(fieldParams)).Pkg("stdjson").ID("RawMessage"). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldParams)), - ) - - bulkBaseReq := Comment(jsonGenComment). - Type().ID(modelBulkBaseReq).Slice().ID(modelBaseReq) - - errRes := Comment(jsonGenComment). - Type().ID(modelBaseErr).Struct(). - Block( - ID(util.ToUpperCamelCase(fieldMessage)).String(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldMessage)), - ID(util.ToUpperCamelCase(fieldCode)).Int64(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldCode)), - ID(util.ToUpperCamelCase(fieldCtx)).Map(String(), String()). - Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldCtx)), - ) - - errType := Type().ID(errInterface).Interface(). - Block( - ID("GetCode").Bracket().Int64(), - ID("GetMessage").Bracket().String(), - ID("GetContext").Bracket().Map(String(), String()), - ) - - baseRes := Comment(jsonGenComment). - Type().ID(modelBaseRes).Struct(). - Block( - ID(util.ToUpperCamelCase(fieldID)).String(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), - ID(util.ToUpperCamelCase(fieldResult)).Any(). - Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldResult)), - ID(util.ToUpperCamelCase(fieldError)).Op("*").ID("errResponse"). - Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldError)), - ) - - bulkBaseRes := Comment(jsonGenComment). - Type().ID(modelBulkBaseRes).Slice().ID(modelBaseRes) - - toErr := Func().ID("toJSONRPCError"). - Bracket(ID("e").Error()). - Bracket(Op("*").ID(modelBaseErr)).Block( - If().ID("e").Op("==").Nil().Block(Return().Nil()), - ID("err").Op(":=").Op("&").ID(modelBaseErr).Block(), - List(ID("te"), ID("ok")).Op(":=").ID("e").Op(".").Bracket(ID(errInterface)), - If().ID("ok").Block( - ID("err").Op(".").ID("Code").Op("="). - ID("te").Op(".").ID("GetCode").Bracket(), - ID("err").Op(".").ID("Message").Op("="). - ID("te").Op(".").ID("GetMessage").Bracket(), - ID("err").Op(".").ID("Ctx").Op("="). - ID("te").Op(".").ID("GetContext").Bracket(), - ).Else().Block( - ID("err").Op(".").ID("Message").Op("="). - ID("e").Op(".").ID("Error").Bracket(), - ), - Return().ID("err"), - ) - - t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). - Package(ctx.PkgName). - Comment("go:generate easyjson"). - Import("stdjson", "encoding/json"). - Join(baseReq, bulkBaseReq, baseRes, bulkBaseRes, errRes, errType, toErr) - - return ctx.W.WriteFile(v.FilePrefix+"_model.go", t) -} - -func (v JSONRPCTransport) buildTransportModels(ctx wsgt.Ctx) error { - t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). - Package(ctx.PkgName). - Comment("go:generate easyjson") - - list := syncing.NewMap[string, string](10) - - var models []types.Token - - for _, method := range ctx.Object.Methods { - var ( - argsOut []types.Token - ) - for _, p := range method.OutParams { - switch { - case p.Type == "error": - continue - default: - } - - if link, ok := ctx.File.Imports.Get(p.Pkg); ok { - list.Set(p.Pkg, link) - } - - argsOut = append(argsOut, - ID(util.ToUpperCamelCase(p.Name)).Pkg(p.Pkg).ID(p.Type). - Raw(fmt.Sprintf("`json:\"%s%s\"`", p.Name, - do.IfElse(p.Omitempty, ",omitempty", ""))), - ) - } - models = append(models, Line().Comment(jsonGenComment). - Type().ID(fmt.Sprintf(modelNameRes, method.Name)).Struct().Block(argsOut...), - ) - - var ( - argsIn []types.Token - ) - for _, p := range method.InParams { - switch { - case p.Pkg == "context" && p.Type == "Context": - continue - default: - } - - if link, ok := ctx.File.Imports.Get(p.Pkg); ok { - list.Set(p.Pkg, link) - } - - argsIn = append(argsIn, - ID(util.ToUpperCamelCase(p.Name)).Pkg(p.Pkg).ID(p.Type). - Raw(fmt.Sprintf("`json:\"%s%s\"`", p.Name, - do.IfElse(p.Omitempty, ",omitempty", ""))), - ) - } - models = append(models, Line().Comment(jsonGenComment). - Type().ID(fmt.Sprintf(modelNameReq, method.Name)).Struct().Block(argsIn...), - ) - } - - for alias, link := range list.Yield() { - t.Import(alias, link) - } - - return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_models.go", t.Join(models...)) -} - -func (v JSONRPCTransport) buildTransportHandlers(ctx wsgt.Ctx) error { - objectName := fmt.Sprintf(transportName, ctx.Object.Name) - - t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). - Package(ctx.PkgName) - - list := syncing.NewMap[string, string](10) - list.Set("context", "context") - list.Set("stdjson", "encoding/json") - - var models []types.Token - - for _, method := range ctx.Object.Methods { - handle := Func().Bracket(ID("v").Op("*").ID(objectName)). - ID("method"+method.Name).Bracket( - ID("ctx").Pkg("context").ID("Context"), - ID("param").Pkg("stdjson").ID("RawMessage"), - ).Bracket( - Pkg("stdjson").ID("Marshaler"), - Error(), - ) - - var handleSrc []types.Token - - handleSrc = append(handleSrc, - Var().ID("req").ID(fmt.Sprintf(modelNameReq, method.Name)), - ID("err").Op(":=").Pkg("stdjson").ID("Unmarshal").Bracket( - ID("param"), Op("&").ID("req"), - ), - If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), - Var().ID("res").ID(fmt.Sprintf(modelNameRes, method.Name)), - ) - - var ( - handleOut []types.Token - ) - for _, p := range method.OutParams { - if link, ok := ctx.File.Imports.Get(p.Pkg); ok { - list.Set(p.Pkg, link) - } - - switch { - case p.Type == "error": - handleOut = append(handleOut, ID("err")) - default: - handleOut = append(handleOut, ID("res").Op(".").ID(util.ToUpperCamelCase(p.Name))) - } - } - - var ( - handleIn []types.Token - ) - for _, p := range method.InParams { - if link, ok := ctx.File.Imports.Get(p.Pkg); ok { - list.Set(p.Pkg, link) - } - - switch { - case p.Pkg == "context" && p.Type == "Context": - handleIn = append(handleIn, ID("ctx")) - default: - handleIn = append(handleIn, ID("req").Op(".").ID(util.ToUpperCamelCase(p.Name))) - } - } - - handleSrc = append(handleSrc, - List(handleOut...).Op("=").ID("v").Op(".").ID("handle").Op(".").ID(method.Name).Call(handleIn...), - If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), - Return().List(ID("res"), Nil()), - ) - - models = append(models, handle.Block(handleSrc...)) - } - - for alias, link := range list.Yield() { - t.Import(alias, link) - } - - return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_handlers.go", t.Join(models...)) -} - -func (v JSONRPCTransport) buildTransport(ctx wsgt.Ctx) error { - objectName := fmt.Sprintf(transportName, ctx.Object.Name) - - t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). - Package(ctx.PkgName) - - list := syncing.NewMap[string, string](10) - list.Set("context", "context") - list.Set("strings", "strings") - list.Set("errors", "errors") - list.Set("stdjson", "encoding/json") - list.Set("syncing", "go.osspkg.com/syncing") - list.Set("web", "go.osspkg.com/goppy/v3/web") - list.Set(ctx.Object.Alias, ctx.Object.Pkg) - for alias, link := range ctx.File.Imports.Yield() { - list.Set(alias, link) - } - - for alias, link := range list.Yield() { - t.Import(alias, link) - } - - t = t.Join( - Type().ID(objectName).Struct().Block( - ID("routes").Pkg("web").ID("ServerPool"), - ID("handle").Pkg(ctx.Object.Alias).ID(ctx.Object.Name), - ), - //-- New - Func().ID("New"+objectName).Bracket( - ID("routes").Pkg("web").ID("ServerPool"), - ID("handle").Pkg(ctx.Object.Alias).ID(ctx.Object.Name), - ).Op("*").ID(objectName).Block( - Return().Op("&").ID(objectName).Block( - ID("routes").Op(":").ID("routes").Op(","), - ID("handle").Op(":").ID("handle").Op(","), - ), - ), - // -- Down - Func().Bracket(ID("v").Op("*").ID(objectName)). - ID("Down").Bracket().Error().Block(Return().Nil()), - // -- Up - Func().Bracket(ID("v").Op("*").ID(objectName)). - ID("Up").Bracket(ID("ctx").Pkg("context").ID("Context")).Error(). - Block(v.buildUp(ctx)), - ) - - return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_transport.go", t) -} - -func (v JSONRPCTransport) buildUp(ctx wsgt.Ctx) *Tokens { - return ID("v").Op(".").ID("routes").Op(".").ID("All").Bracket( - Func().Bracket(ID("tag").String(), ID("r").Pkg("web").ID("Router")).Block( - func() *Tokens { - tags, ok := ctx.Tags[tagWebPool] - if !ok { - return Line() - } - var list []types.Token - for _, tag := range tags { - list = append(list, Text(tag)) - } - return Switch().ID("tag").Block( - Case().List(list...).Op(":").Line().Join( - ID("r").Op(".").ID("Post").Call( - Text("/"+strings.ToLower(ctx.Object.Name)), - Func().Bracket(ID("ctx").Pkg("web").ID("Ctx")).Block( - Var().ID("req").ID(modelBulkBaseReq), - If(). - ID("err").Op(":="). - ID("ctx").Op(".").ID("BindJSON").Bracket(Op("&").ID("req")).Op(";"). - ID("err").Op("!=").Nil().Block( - ID("ctx").Op(".").ID("String").Bracket( - Raw("400"), ID("err").Op(".").ID("Error").Call(), - ), - Return(), - ), - ID("res").Op(":="). - Pkg("syncing").ID("NewSlice").Raw("[").ID(modelBaseRes).Raw("]").Call( - Uint().Bracket(Raw("len").Bracket(ID("req"))), - ), - ID("wg").Op(":=").Pkg("syncing").ID("NewGroup").Bracket( - ID("ctx").Op(".").ID("Context").Call(), - ), - For().List(Raw("_"), ID("item")).Op(":=").Range().ID("req").Block( - ID("item").Op(":=").ID("item"), - - ID("wg").Op(".").ID("Background").Call( - Text(strings.ToLower(ctx.Object.Name)+".").Op("+"). - Pkg("strings").ID("ToLower").Bracket( - ID("item").Op(".").ID(util.ToUpperCamelCase(fieldMethod)), - ), - Func().Bracket(ID("ctx").Pkg("context").ID("Context")).Block( - ID("out").Op(":=").ID(modelBaseRes).Block( - ID(util.ToUpperCamelCase(fieldID)).Op(":"). - ID("item").Op(".").ID(util.ToUpperCamelCase(fieldID)).Op(","), - ), - Var().ID("err").Error(), - Var().ID("result").Pkg("stdjson").ID("Marshaler"), - v.buildMethods(ctx), - If().ID("err").Op("!=").Nil().Block( - ID("out").Op(".").ID(util.ToUpperCamelCase(fieldError)).Op("="). - ID("toJSONRPCError").Bracket(ID("err")), - ).Else().Block( - ID("out").Op(".").ID(util.ToUpperCamelCase(fieldResult)).Op("="). - ID("result"), - ), - ID("res").Op(".").ID("Append").Bracket(ID("out")), - ), - ), - ), - ID("wg").Op(".").ID("Wait").Call(), - ID("ctx").Op(".").ID("JSON").Bracket( - Raw("200"), ID(modelBulkBaseRes).Bracket( - ID("res").Op(".").ID("Extract").Bracket(), - ), - ), - ), - ), - ), - ).Line().Return() - }(), - ), - ). - Line().Return().Nil() - -} - -func (v JSONRPCTransport) buildMethods(ctx wsgt.Ctx) *Tokens { - var list []types.Token - - for _, method := range ctx.Object.Methods { - list = append(list, - Case().Text(strings.ToLower(method.Name)).Op(":").Join( - List(ID("result"), ID("err")).Op("="). - ID("v").Op(".").ID("method"+method.Name). - Call( - ID("ctx"), - ID("item").Op(".").ID(util.ToUpperCamelCase(fieldParams)), - ), - ), - ) - } - - list = append(list, - Default().Op(":").Join( - ID("err").Op("=").Pkg("errors").ID("New").Bracket(Text("unsupported method")), - ), - ) - - return Switch().Pkg("strings").ID("ToLower").Bracket( - ID("item").Op(".").ID(util.ToUpperCamelCase(fieldMethod)), - ).Block(list...) -} diff --git a/internal/gen/wsg/types/storage.go b/internal/gen/wsg/types/storage.go deleted file mode 100644 index 2a8c9e3..0000000 --- a/internal/gen/wsg/types/storage.go +++ /dev/null @@ -1,16 +0,0 @@ -package types - -import "go.osspkg.com/syncing" - -var _storage = syncing.NewMap[string, Module](10) - -func Register(module Module) { - if _, ok := _storage.Get(module.Name()); ok { - panic("duplicate register module: " + module.Name()) - } - _storage.Set(module.Name(), module) -} - -func Resolve(name string) (Module, bool) { - return _storage.Get(name) -} diff --git a/internal/gen/wsg/types/type.go b/internal/gen/wsg/types/type.go deleted file mode 100644 index b8ff4f4..0000000 --- a/internal/gen/wsg/types/type.go +++ /dev/null @@ -1,25 +0,0 @@ -package types - -import "go.osspkg.com/gogen/types" - -const ( - IFace = "iface." - Methods = "method." -) - -type Module interface { - Name() string - Build(ctx Ctx) error -} - -type Writer interface { - WriteFile(fileName string, tok types.Token) error -} - -type Ctx struct { - W Writer - PkgName string - File File - Object Object - Tags Tags -} diff --git a/internal/global/mods.go b/internal/global/mods.go index c035d40..e086eb3 100644 --- a/internal/global/mods.go +++ b/internal/global/mods.go @@ -6,13 +6,16 @@ package global import ( + "fmt" "os" + "path/filepath" "regexp" "strings" "go.osspkg.com/errors" "go.osspkg.com/ioutils/fs" "go.osspkg.com/validate" + "golang.org/x/mod/modfile" ) var rexModule = regexp.MustCompile(`(?mU)module (.*)\n`) @@ -73,3 +76,40 @@ func ReadModule(filepath string) (*Module, error) { File: filepath, }, nil } + +func DetectGoMod(curPath string) (mod string, root string, err error) { + root = filepath.Dir(curPath) + for { + + mods, e := fs.SearchFilesByExt(root, ".mod") + if e != nil { + return "", "", e + } + + if len(mods) != 0 { + for _, s := range mods { + b, e := os.ReadFile(s) + if e != nil { + return "", "", e + } + + f, e := modfile.Parse("go.mod", b, nil) + if e != nil { + return "", "", e + } + mod = f.Module.Mod.Path + return + } + } + + root, err = filepath.Abs(root + "/..") + if err != nil { + return + } + + if root == "/" { + err = fmt.Errorf("failed to detect go.mod") + return + } + } +} From 7c833fea7200fd13121dbb84e018956a2821b59b Mon Sep 17 00:00:00 2001 From: Mikhail Knyazhev Date: Mon, 16 Mar 2026 10:08:23 +0300 Subject: [PATCH 5/6] add web server generator --- .../transport/jsonrpc_handler.go | 90 ++++ .../web-server-gen/transport/jsonrpc_model.go | 66 +-- .../transport/jsonrpc_model_base.go | 53 +++ .../transport/jsonrpc_transport.go | 172 ++++++++ .../types/{interfaces.go => interfaces1.go} | 15 +- _example/web-server-gen/types/interfaces2.go | 25 ++ _example/web-server-gen/types/wsg.go | 2 +- apigen/builder/builder.go | 20 +- apigen/builder/common.go | 6 +- .../module/mod-json-rpc/build_base_model.go | 85 ++++ apigen/module/mod-json-rpc/build_transport.go | 218 ++++++++++ .../mod-json-rpc/build_transport_handlers.go | 199 +++++++++ .../mod-json-rpc/build_transport_models.go | 85 ++++ apigen/module/mod-json-rpc/common.go | 41 +- apigen/module/mod-json-rpc/module.go | 23 + apigen/module/mod-json-rpc/transport.go | 403 ------------------ apigen/module/mod-param-cookie/module.go | 65 +++ apigen/module/mod-param-header/module.go | 54 +++ apigen/parser/visitor.go | 98 +++-- apigen/types/common.go | 38 ++ apigen/types/object.go | 16 +- apigen/types/storage.go | 11 +- apigen/types/type.go | 35 +- apigen/util/util.go | 12 +- internal/commands/wsg.go | 33 +- web/ctx.go | 110 +++++ 26 files changed, 1465 insertions(+), 510 deletions(-) create mode 100644 _example/web-server-gen/transport/jsonrpc_handler.go create mode 100644 _example/web-server-gen/transport/jsonrpc_model_base.go create mode 100644 _example/web-server-gen/transport/jsonrpc_transport.go rename _example/web-server-gen/types/{interfaces.go => interfaces1.go} (74%) create mode 100644 _example/web-server-gen/types/interfaces2.go create mode 100644 apigen/module/mod-json-rpc/build_base_model.go create mode 100644 apigen/module/mod-json-rpc/build_transport.go create mode 100644 apigen/module/mod-json-rpc/build_transport_handlers.go create mode 100644 apigen/module/mod-json-rpc/build_transport_models.go create mode 100644 apigen/module/mod-json-rpc/module.go delete mode 100644 apigen/module/mod-json-rpc/transport.go create mode 100644 apigen/module/mod-param-cookie/module.go create mode 100644 apigen/module/mod-param-header/module.go create mode 100644 apigen/types/common.go diff --git a/_example/web-server-gen/transport/jsonrpc_handler.go b/_example/web-server-gen/transport/jsonrpc_handler.go new file mode 100644 index 0000000..f18aebe --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_handler.go @@ -0,0 +1,90 @@ +// Code generated by goppy-cli wsg. DO NOT EDIT. +package transport + +import context "context" +import stdjson "encoding/json" +import web "go.osspkg.com/goppy/v3/web" +import fmt "fmt" + +func (v *JSONRPCHandler) callApiRoot(ctx context.Context, webCtx web.Ctx, param stdjson.RawMessage) (any, error) { + var req jsonrpcApiRootModelRequest + err := stdjson.Unmarshal(param, &req) + if err != nil { + return nil, err + } + var res jsonrpcApiRootModelResponse + var inUserID int64 + //Module: cookie + inUserID, _ = web.StrTo[int64](webCtx.Cookie().Get("x-user-id")) + + res.Status, err = v.handleApi.Root(ctx, inUserID, req.UserName) + if err != nil { + return nil, err + } + return res, nil +} +func (v *JSONRPCHandler) callApiAuth(ctx context.Context, webCtx web.Ctx, param stdjson.RawMessage) (any, error) { + var req jsonrpcApiAuthModelRequest + err := stdjson.Unmarshal(param, &req) + if err != nil { + return nil, err + } + var res jsonrpcApiAuthModelResponse + var inUserID int64 + //Module: header + inUserID, _ = web.StrTo[int64](webCtx.Header().Get("x-user-id")) + + var outStatus bool + defer func() { + //Module: header + if err != nil { + return + } + webCtx.Header().Set("x-user-id", fmt.Sprintf("%v", outStatus)) + + }() + outStatus, err = v.handleApi.Auth(ctx, inUserID, req.UserName) + if err != nil { + return nil, err + } + return res, nil +} +func (v *JSONRPCHandler) callUserName(ctx context.Context, webCtx web.Ctx, param stdjson.RawMessage) (any, error) { + var req jsonrpcUserNameModelRequest + err := stdjson.Unmarshal(param, &req) + if err != nil { + return nil, err + } + var res jsonrpcUserNameModelResponse + res.Name, err = v.handleUser.Name(ctx, req.UserID) + if err != nil { + return nil, err + } + return res, nil +} +func (v *JSONRPCHandler) callPostByID(ctx context.Context, webCtx web.Ctx, param stdjson.RawMessage) (any, error) { + var req jsonrpcPostByIDModelRequest + err := stdjson.Unmarshal(param, &req) + if err != nil { + return nil, err + } + var res jsonrpcPostByIDModelResponse + res.Text, err = v.handlePost.ByID(ctx, req.ID) + if err != nil { + return nil, err + } + return res, nil +} +func (v *JSONRPCHandler) callPostList(ctx context.Context, webCtx web.Ctx, param stdjson.RawMessage) (any, error) { + var req jsonrpcPostListModelRequest + err := stdjson.Unmarshal(param, &req) + if err != nil { + return nil, err + } + var res jsonrpcPostListModelResponse + res.Text, err = v.handlePost.List(ctx, req.UserID) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/_example/web-server-gen/transport/jsonrpc_model.go b/_example/web-server-gen/transport/jsonrpc_model.go index e318353..6aeb1a1 100644 --- a/_example/web-server-gen/transport/jsonrpc_model.go +++ b/_example/web-server-gen/transport/jsonrpc_model.go @@ -2,52 +2,52 @@ package transport //go:generate easyjson -import stdjson "encoding/json" +import types "go.osspkg.com/goppy/v3/_example/web-server-gen/types" //easyjson:json -type baseRequest struct { - Id string `json:"id"` - Method string `json:"method"` - Params stdjson.RawMessage `json:"params"` +type jsonrpcApiRootModelRequest struct { + UserName string `json:"userName"` } //easyjson:json -type bulkRequest []baseRequest +type jsonrpcApiRootModelResponse struct { + Status bool `json:"status"` +} //easyjson:json -type baseResponse struct { - Id string `json:"id"` - Result any `json:"result,omitempty"` - Error *errResponse `json:"error,omitempty"` +type jsonrpcApiAuthModelRequest struct { + UserName string `json:"userName"` } //easyjson:json -type bulkResponse []baseResponse +type jsonrpcApiAuthModelResponse struct{} //easyjson:json -type errResponse struct { - Message string `json:"message"` - Code int64 `json:"code"` - Ctx map[string]string `json:"ctx,omitempty"` +type jsonrpcUserNameModelRequest struct { + UserID int64 `json:"userID"` } -type TError interface { - GetCode() int64 - GetMessage() string - GetContext() map[string]string + +//easyjson:json +type jsonrpcUserNameModelResponse struct { + Name string `json:"name"` } -func toJSONRPCError(e error) *errResponse { - if e == nil { - return nil - } - err := &errResponse{} - te, ok := e.(TError) - if ok { - err.Code = te.GetCode() - err.Message = te.GetMessage() - err.Ctx = te.GetContext() - } else { - err.Message = e.Error() - } - return err +//easyjson:json +type jsonrpcPostByIDModelRequest struct { + ID int64 `json:"ID"` +} + +//easyjson:json +type jsonrpcPostByIDModelResponse struct { + Text bool `json:"text"` +} + +//easyjson:json +type jsonrpcPostListModelRequest struct { + UserID int64 `json:"userID"` +} + +//easyjson:json +type jsonrpcPostListModelResponse struct { + Text []types.Text `json:"text,omitempty"` } diff --git a/_example/web-server-gen/transport/jsonrpc_model_base.go b/_example/web-server-gen/transport/jsonrpc_model_base.go new file mode 100644 index 0000000..e318353 --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_model_base.go @@ -0,0 +1,53 @@ +// Code generated by goppy-cli wsg. DO NOT EDIT. +package transport + +//go:generate easyjson +import stdjson "encoding/json" + +//easyjson:json +type baseRequest struct { + Id string `json:"id"` + Method string `json:"method"` + Params stdjson.RawMessage `json:"params"` +} + +//easyjson:json +type bulkRequest []baseRequest + +//easyjson:json +type baseResponse struct { + Id string `json:"id"` + Result any `json:"result,omitempty"` + Error *errResponse `json:"error,omitempty"` +} + +//easyjson:json +type bulkResponse []baseResponse + +//easyjson:json +type errResponse struct { + Message string `json:"message"` + Code int64 `json:"code"` + Ctx map[string]string `json:"ctx,omitempty"` +} +type TError interface { + GetCode() int64 + GetMessage() string + GetContext() map[string]string +} + +func toJSONRPCError(e error) *errResponse { + if e == nil { + return nil + } + err := &errResponse{} + te, ok := e.(TError) + if ok { + err.Code = te.GetCode() + err.Message = te.GetMessage() + err.Ctx = te.GetContext() + } else { + err.Message = e.Error() + } + return err +} diff --git a/_example/web-server-gen/transport/jsonrpc_transport.go b/_example/web-server-gen/transport/jsonrpc_transport.go new file mode 100644 index 0000000..89c9d8f --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_transport.go @@ -0,0 +1,172 @@ +// Code generated by goppy-cli wsg. DO NOT EDIT. +package transport + +import context "context" +import errors "errors" +import stdjson "encoding/json" +import syncing "go.osspkg.com/syncing" +import time "time" +import types "go.osspkg.com/goppy/v3/_example/web-server-gen/types" +import strings "strings" +import pool "go.osspkg.com/ioutils/pool" +import web "go.osspkg.com/goppy/v3/web" +import logx "go.osspkg.com/logx" + +var ( + poolBaseResponse = pool.New[*syncing.Slice[baseResponse]](func() *syncing.Slice[baseResponse] { + return syncing.NewSlice[baseResponse](uint(2)) + }) + + errUnsupportedMethod = errors.New("unsupported method") +) + +type JSONRPCHandler struct { + routes web.ServerPool + handleApi types.Api + handleUser types.User + handlePost types.Post +} + +func NewJSONRPCHandler(routes web.ServerPool, handleApi types.Api, handleUser types.User, handlePost types.Post) *JSONRPCHandler { + return &JSONRPCHandler{ + routes: routes, + handleApi: handleApi, + handleUser: handleUser, + handlePost: handlePost, + } +} + +func (v *JSONRPCHandler) Down() error { + return nil +} + +func (v *JSONRPCHandler) Up(ctx context.Context) error { + v.routes.All(func(tag string, r web.Router) { + switch tag { + case "main", "admin": + r.Post("/rpc", func(webCtx web.Ctx) { + var req bulkRequest + if err := webCtx.BindJSON(&req); err != nil { + webCtx.String(400, err.Error()) + return + } + + res := poolBaseResponse.Get() + defer poolBaseResponse.Put(res) + + wg := syncing.NewGroup(webCtx.Context()) + wg.OnPanic(func(err error) { + logx.Error("panic", "err", err) + }) + + for _, item := range req { + item := item + switch strings.ToLower(item.Method) { + case "api.root": + wg.Background("api.root", func(ctx context.Context) { + out := baseResponse{ + Id: item.Id, + } + var err error + var result stdjson.Marshaler + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + result, err = v.callApiRoot(ctx, webCtx, item.Params) + if err != nil { + out.Error = toJSONRPCError(err) + } else { + out.Result = result + } + res.Append(out) + }) + + case "api.auth": + wg.Background("api.auth", func(ctx context.Context) { + out := baseResponse{ + Id: item.Id, + } + var err error + var result stdjson.Marshaler + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + result, err = v.callApiAuth(ctx, webCtx, item.Params) + if err != nil { + out.Error = toJSONRPCError(err) + } else { + out.Result = result + } + res.Append(out) + }) + + case "user.name": + wg.Background("user.name", func(ctx context.Context) { + out := baseResponse{ + Id: item.Id, + } + var err error + var result stdjson.Marshaler + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + result, err = v.callUserName(ctx, webCtx, item.Params) + if err != nil { + out.Error = toJSONRPCError(err) + } else { + out.Result = result + } + res.Append(out) + }) + + case "post.byid": + wg.Background("post.byid", func(ctx context.Context) { + out := baseResponse{ + Id: item.Id, + } + var err error + var result stdjson.Marshaler + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + result, err = v.callPostByID(ctx, webCtx, item.Params) + if err != nil { + out.Error = toJSONRPCError(err) + } else { + out.Result = result + } + res.Append(out) + }) + + case "post.list": + wg.Background("post.list", func(ctx context.Context) { + out := baseResponse{ + Id: item.Id, + } + var err error + var result stdjson.Marshaler + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + result, err = v.callPostList(ctx, webCtx, item.Params) + if err != nil { + out.Error = toJSONRPCError(err) + } else { + out.Result = result + } + res.Append(out) + }) + + default: + out := baseResponse{ + Id: item.Id, + } + err := errUnsupportedMethod + out.Error = toJSONRPCError(err) + res.Append(out) + + } + } + wg.Wait() + ctx.JSON(200, bulkResponse(res.Extract())) + }) + + } + }) + return nil +} diff --git a/_example/web-server-gen/types/interfaces.go b/_example/web-server-gen/types/interfaces1.go similarity index 74% rename from _example/web-server-gen/types/interfaces.go rename to _example/web-server-gen/types/interfaces1.go index 8e2b61e..9727213 100644 --- a/_example/web-server-gen/types/interfaces.go +++ b/_example/web-server-gen/types/interfaces1.go @@ -7,10 +7,6 @@ package types import "context" -// Api -// @wsg description="Методы апи" -// @wsg web-pool=main,admin -// @wsg route-prefix=/api/v1 type Api interface { // Root // @wsg in.userID=cookie:x-user-id @@ -21,11 +17,18 @@ type Api interface { ) (status bool, err error) // Auth - // @wsg in.userID=cookie:x-user-id - // @wsg module=authz + // @wsg in.userID=header:x-user-id + // @wsg out.status=header:x-user-id Auth( ctx context.Context, userID int64, userName string, ) (status bool, err error) } + +type User interface { + Name( + ctx context.Context, + userID int64, + ) (name string, err error) +} diff --git a/_example/web-server-gen/types/interfaces2.go b/_example/web-server-gen/types/interfaces2.go new file mode 100644 index 0000000..90aafcb --- /dev/null +++ b/_example/web-server-gen/types/interfaces2.go @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package types + +import "context" + +type Post interface { + ByID( + ctx context.Context, + ID int64, + ) (text bool, err error) + + List( + ctx context.Context, + userID int64, + ) (text []Text, err error) +} + +type Text struct { + ID int64 + Text string +} diff --git a/_example/web-server-gen/types/wsg.go b/_example/web-server-gen/types/wsg.go index 8e5a9c5..837f0c7 100644 --- a/_example/web-server-gen/types/wsg.go +++ b/_example/web-server-gen/types/wsg.go @@ -1,3 +1,3 @@ package types -//go:generate goppy wsg --iface=Api --mod=json-rpc,rest --out=./../transport +//go:generate goppy wsg --mod=json-rpc,rest --pool=main,admin --out=./../transport diff --git a/apigen/builder/builder.go b/apigen/builder/builder.go index b39bee7..5dc39f9 100644 --- a/apigen/builder/builder.go +++ b/apigen/builder/builder.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "path/filepath" + "slices" + "strings" "go.osspkg.com/bb" "go.osspkg.com/gogen/golang" @@ -15,17 +17,19 @@ import ( type Builder struct { Out string Mods []string + Pool []string IFace map[string]struct{} Files []at.File } -func (b *Builder) filterObjects(objs []at.Object) []at.Object { +func (b *Builder) filterObjects(objs []at.Face) []at.Face { if len(b.IFace) == 0 { return objs } - result := make([]at.Object, 0, len(objs)) + + result := make([]at.Face, 0, len(objs)) for _, obj := range objs { - if _, ok := b.IFace[obj.Name]; ok { + if _, ok := b.IFace[strings.ToLower(obj.Name)]; ok { result = append(result, obj) } } @@ -37,12 +41,16 @@ func (b *Builder) getWorkFiles() []at.File { workFiles := make([]at.File, 0, len(b.Files)) for _, file := range b.Files { - file.Objects = b.filterObjects(file.Objects) - if len(file.Objects) > 0 { + file.Faces = b.filterObjects(file.Faces) + if len(file.Faces) > 0 { workFiles = append(workFiles, file) } } + slices.SortFunc(workFiles, func(a, b at.File) int { + return strings.Compare(a.FilePath, b.FilePath) + }) + return workFiles } @@ -74,7 +82,7 @@ func (b *Builder) Build() error { continue } - err := mod.Build(b, at.GlobalMeta{PkgName: pkgName}, files) + err := mod.Build(b, at.GlobalMeta{PkgName: pkgName, Pool: b.Pool}, files) if err != nil { return fmt.Errorf("build module %q: %v", name, err) } diff --git a/apigen/builder/common.go b/apigen/builder/common.go index b456949..e283ff2 100644 --- a/apigen/builder/common.go +++ b/apigen/builder/common.go @@ -2,11 +2,15 @@ package builder import ( modjsonrpc "go.osspkg.com/goppy/v3/apigen/module/mod-json-rpc" + modparamcookie "go.osspkg.com/goppy/v3/apigen/module/mod-param-cookie" + modparamheader "go.osspkg.com/goppy/v3/apigen/module/mod-param-header" "go.osspkg.com/goppy/v3/apigen/types" ) func init() { - types.Register[types.GlobalModule](modjsonrpc.JSONRPCTransport{FilePrefix: "jsonrpc"}) + types.Register[types.GlobalModule](modjsonrpc.Module{FilePrefix: "jsonrpc"}) + types.Register[types.ParamModule](modparamcookie.Module{}) + types.Register[types.ParamModule](modparamheader.Module{}) } const ( diff --git a/apigen/module/mod-json-rpc/build_base_model.go b/apigen/module/mod-json-rpc/build_base_model.go new file mode 100644 index 0000000..60df6a0 --- /dev/null +++ b/apigen/module/mod-json-rpc/build_base_model.go @@ -0,0 +1,85 @@ +package mod_json_rpc + +import ( + "fmt" + + . "go.osspkg.com/gogen/golang" + at "go.osspkg.com/goppy/v3/apigen/types" + "go.osspkg.com/goppy/v3/apigen/util" +) + +func (v Module) buildBaseRPCModel(w at.Writer, m at.GlobalMeta) error { + baseReq := Comment(jsonGenComment). + Type().ID(modelBaseReq).Struct(). + Block( + ID(util.ToUpperCamelCase(fieldID)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), + ID(util.ToUpperCamelCase(fieldMethod)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldMethod)), + ID(util.ToUpperCamelCase(fieldParams)).Pkg("stdjson").ID("RawMessage"). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldParams)), + ) + + bulkBaseReq := Comment(jsonGenComment). + Type().ID(modelBulkBaseReq).Slice().ID(modelBaseReq) + + errRes := Comment(jsonGenComment). + Type().ID(modelBaseErr).Struct(). + Block( + ID(util.ToUpperCamelCase(fieldMessage)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldMessage)), + ID(util.ToUpperCamelCase(fieldCode)).Int64(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldCode)), + ID(util.ToUpperCamelCase(fieldCtx)).Map(String(), String()). + Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldCtx)), + ) + + errType := Type().ID(errInterface).Interface(). + Block( + ID("GetCode").Bracket().Int64(), + ID("GetMessage").Bracket().String(), + ID("GetContext").Bracket().Map(String(), String()), + ) + + baseRes := Comment(jsonGenComment). + Type().ID(modelBaseRes).Struct(). + Block( + ID(util.ToUpperCamelCase(fieldID)).String(). + Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), + ID(util.ToUpperCamelCase(fieldResult)).Any(). + Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldResult)), + ID(util.ToUpperCamelCase(fieldError)).Op("*").ID("errResponse"). + Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldError)), + ) + + bulkBaseRes := Comment(jsonGenComment). + Type().ID(modelBulkBaseRes).Slice().ID(modelBaseRes) + + toErr := Func().ID("toJSONRPCError"). + Bracket(ID("e").Error()). + Bracket(Op("*").ID(modelBaseErr)).Block( + If().ID("e").Op("==").Nil().Block(Return().Nil()), + ID("err").Op(":=").Op("&").ID(modelBaseErr).Block(), + List(ID("te"), ID("ok")).Op(":=").ID("e").Op(".").Bracket(ID(errInterface)), + If().ID("ok").Block( + ID("err").Op(".").ID("Code").Op("="). + ID("te").Op(".").ID("GetCode").Bracket(), + ID("err").Op(".").ID("Message").Op("="). + ID("te").Op(".").ID("GetMessage").Bracket(), + ID("err").Op(".").ID("Ctx").Op("="). + ID("te").Op(".").ID("GetContext").Bracket(), + ).Else().Block( + ID("err").Op(".").ID("Message").Op("="). + ID("e").Op(".").ID("Error").Bracket(), + ), + Return().ID("err"), + ) + + t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). + Package(m.PkgName). + Comment("go:generate easyjson"). + Import("stdjson", "encoding/json"). + Join(baseReq, bulkBaseReq, baseRes, bulkBaseRes, errRes, errType, toErr) + + return w.WriteFile(v.FilePrefix+"_model_base.go", t) +} diff --git a/apigen/module/mod-json-rpc/build_transport.go b/apigen/module/mod-json-rpc/build_transport.go new file mode 100644 index 0000000..c8d8a08 --- /dev/null +++ b/apigen/module/mod-json-rpc/build_transport.go @@ -0,0 +1,218 @@ +package mod_json_rpc + +import ( + "strings" + + . "go.osspkg.com/gogen/golang" + "go.osspkg.com/gogen/types" + at "go.osspkg.com/goppy/v3/apigen/types" + "go.osspkg.com/goppy/v3/apigen/util" + "go.osspkg.com/syncing" +) + +func (v Module) buildTransport(w at.Writer, m at.GlobalMeta, files []at.File) error { + + t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). + Package(m.PkgName) + + list := syncing.NewMap[string, string](10) + list.Set("context", "context") + list.Set("strings", "strings") + list.Set("errors", "errors") + list.Set("stdjson", "encoding/json") + list.Set("syncing", "go.osspkg.com/syncing") + list.Set("pool", "go.osspkg.com/ioutils/pool") + list.Set("web", "go.osspkg.com/goppy/v3/web") + list.Set("logx", "go.osspkg.com/logx") + list.Set("time", "time") + + for _, file := range files { + for _, object := range file.Faces { + list.Set(object.Alias, object.Pkg) + for alias, link := range file.Imports.Yield() { + list.Set(alias, link) + } + } + } + + for alias, link := range list.Yield() { + t.Import(alias, link) + } + + fields := []types.Token{ + ID("routes").Pkg("web").ID("ServerPool"), + } + fieldsInit := []types.Token{ + ID("routes").Op(":").ID("routes").Op(","), + } + for _, file := range files { + for _, object := range file.Faces { + fields = append(fields, + ID("handle"+object.Name).Pkg(object.Alias).ID(object.Name), + ) + fieldsInit = append(fieldsInit, + ID("handle"+object.Name).Op(":").ID("handle"+object.Name).Op(","), + ) + } + } + + t = t.Join( + Raw(` +var ( + poolBaseResponse = pool.New[*syncing.Slice[baseResponse]](func() *syncing.Slice[baseResponse] { + return syncing.NewSlice[baseResponse](uint(2)) + }) + + errUnsupportedMethod = errors.New("unsupported method") +) +`), + Type().ID(transportName).Struct().Block(fields...), + Line(), + //-- New + Func().ID("New"+transportName).Bracket(fields...).Op("*").ID(transportName).Block( + Return().Op("&").ID(transportName).Block(fieldsInit...), + ), + Line(), + // -- Down + Func().Bracket(ID("v").Op("*").ID(transportName)). + ID("Down").Bracket().Error().Block(Return().Nil()), + // -- Up + Line(), + Func().Bracket(ID("v").Op("*").ID(transportName)). + ID("Up").Bracket(ID("ctx").Pkg("context").ID("Context")).Error(). + Block(v.buildUp(m, files)), + ) + + if err := w.WriteFile(v.FilePrefix+"_transport.go", t); err != nil { + return err + } + + return nil +} + +func (v Module) buildUp(m at.GlobalMeta, files []at.File) types.Token { + tokBase := ID("r").Op(".").ID("Post").Call( + Text("/rpc"), + Func().Bracket(ID("webCtx").Pkg("web").ID("Ctx")).Block( + // --- + Var().ID("req").ID(modelBulkBaseReq), + If(). + ID("err").Op(":="). + ID("webCtx").Op(".").ID("BindJSON").Bracket(Op("&").ID("req")).Op(";"). + ID("err").Op("!=").Nil().Block( + ID("webCtx").Op(".").ID("String").Bracket( + Raw("400"), ID("err").Op(".").ID("Error").Call(), + ), + Return(), + ), + // --- + Line(), + ID("res").Op(":=").ID("poolBaseResponse").Op(".").ID("Get").Call(), + Defer().ID("poolBaseResponse").Op(".").ID("Put").Call(ID("res")), + Line(), + ID("wg").Op(":=").Pkg("syncing").ID("NewGroup").Bracket( + ID("webCtx").Op(".").ID("Context").Call(), + ), + ID("wg").Op(".").ID("OnPanic").Bracket( + Func().Bracket(ID("err").Error()).Block( + Pkg("logx").ID("Error").Call( + Text("panic"), Text("err"), ID("err"), + ), + ), + ), + // --- + Line(), + For().List(Raw("_"), ID("item")).Op(":=").Range().ID("req").Block( + ID("item").Op(":=").ID("item"), + v.buildMethods(files), + ), + ID("wg").Op(".").ID("Wait").Call(), + ID("ctx").Op(".").ID("JSON").Bracket( + Raw("200"), ID(modelBulkBaseRes).Bracket( + ID("res").Op(".").ID("Extract").Bracket(), + ), + ), + ), + ) + + var poolTags []types.Token + for _, s := range m.Pool { + poolTags = append(poolTags, Text(s)) + } + + return ID("v").Op(".").ID("routes").Op(".").ID("All"). + Bracket( + Func().Bracket(ID("tag").String(), ID("r").Pkg("web").ID("Router")).Block( + Switch().ID("tag").Block( + Case().List(poolTags...).Op(":").Line().Join(tokBase), + ), + ), + ). + Line().Return().Nil() +} + +func (v Module) buildMethods(files []at.File) *Tokens { + var list []types.Token + + for _, file := range files { + for _, object := range file.Faces { + for _, method := range object.Methods { + methodName := strings.ToLower(object.Name + "." + method.Name) + list = append(list, + Case().Text(methodName).Op(":").Join( + ID("wg").Op(".").ID("Background").Call( + Text(methodName), + Func().Bracket(ID("ctx").Pkg("context").ID("Context")).Block( + ID("out").Op(":=").ID(modelBaseRes).Block( + ID(util.ToUpperCamelCase(fieldID)).Op(":"). + ID("item").Op(".").ID(util.ToUpperCamelCase(fieldID)).Op(","), + ), + + Var().ID("err").Error(), + Var().ID("result").Pkg("stdjson").ID("Marshaler"), + List(ID("ctx"), ID("cancel")).Op(":=").Pkg("context").ID("WithTimeout").Call(ID( + "ctx"), Raw("5").Op("*").Pkg("time").ID("Second")), + Defer().ID("cancel").Call(), + + List(ID("result"), ID("err")).Op("="). + ID("v").Op(".").ID("call"+object.Name+method.Name). + Call( + ID("ctx"), + ID("webCtx"), + ID("item").Op(".").ID(util.ToUpperCamelCase(fieldParams)), + ), + + If().ID("err").Op("!=").Nil().Block( + ID("out").Op(".").ID(util.ToUpperCamelCase(fieldError)).Op("="). + ID("toJSONRPCError").Bracket(ID("err")), + ).Else().Block( + ID("out").Op(".").ID(util.ToUpperCamelCase(fieldResult)).Op("="). + ID("result"), + ), + ID("res").Op(".").ID("Append").Bracket(ID("out")), + ), + ), + ), + ) + } + } + } + + list = append(list, + Default().Op(":").Join( + ID("out").Op(":=").ID(modelBaseRes).Block( + ID(util.ToUpperCamelCase(fieldID)).Op(":"). + ID("item").Op(".").ID(util.ToUpperCamelCase(fieldID)).Op(","), + ), + ID("err").Op(":=").ID("errUnsupportedMethod"), + ID("out").Op(".").ID(util.ToUpperCamelCase(fieldError)).Op("="). + ID("toJSONRPCError").Bracket(ID("err")), + + ID("res").Op(".").ID("Append").Bracket(ID("out")), + ), + ) + + return Switch().Pkg("strings").ID("ToLower").Bracket( + ID("item").Op(".").ID(util.ToUpperCamelCase(fieldMethod)), + ).Block(list...) +} diff --git a/apigen/module/mod-json-rpc/build_transport_handlers.go b/apigen/module/mod-json-rpc/build_transport_handlers.go new file mode 100644 index 0000000..93d0e4c --- /dev/null +++ b/apigen/module/mod-json-rpc/build_transport_handlers.go @@ -0,0 +1,199 @@ +package mod_json_rpc + +import ( + "fmt" + + "go.osspkg.com/do" + . "go.osspkg.com/gogen/golang" + "go.osspkg.com/gogen/types" + at "go.osspkg.com/goppy/v3/apigen/types" + "go.osspkg.com/goppy/v3/apigen/util" + "go.osspkg.com/syncing" +) + +func (v Module) buildTransportHandlers(w at.Writer, m at.GlobalMeta, files []at.File) error { + t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). + Package(m.PkgName) + + list := syncing.NewMap[string, string](10) + list.Set("context", "context") + list.Set("stdjson", "encoding/json") + list.Set("web", "go.osspkg.com/goppy/v3/web") + + var handlers []types.Token + for _, file := range files { + handlers = append(handlers, v.buildTransportHandler(list, file)...) + } + + for alias, link := range list.Yield() { + t.Import(alias, link) + } + + if err := w.WriteFile(v.FilePrefix+"_handler.go", t.Join(handlers...)); err != nil { + return err + } + + return nil +} + +func (v Module) buildTransportHandler(imp at.ImportSetter, file at.File) []types.Token { + var models []types.Token + + for _, object := range file.Faces { + for _, method := range object.Methods { + handle := Func().Bracket(ID("v").Op("*").ID(transportName)). + ID("call"+object.Name+method.Name).Bracket( + ID("ctx").Pkg("context").ID("Context"), + ID("webCtx").Pkg("web").ID("Ctx"), + ID("param").Pkg("stdjson").ID("RawMessage"), + ).Bracket( + Any(), + Error(), + ) + + var handleSrc []types.Token + + handleSrc = append(handleSrc, + Var().ID("req").ID(fmt.Sprintf(modelNameReq, object.Name+method.Name)), + ID("err").Op(":=").Pkg("stdjson").ID("Unmarshal").Bracket( + ID("param"), Op("&").ID("req"), + ), + If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), + Var().ID("res").ID(fmt.Sprintf(modelNameRes, object.Name+method.Name)), + ) + + // -------------------------------------- + + inParamArgs := make(map[string]string) + for _, p := range method.InParams { + vals, ok := method.Tags["in."+p.Name] + if !ok { + continue + } + paramName := "in" + util.ToUpperCamelCase(p.Name) + inParamArgs[p.Name] = paramName + handleSrc = append(handleSrc, + Var().ID(paramName). + Raw(do.IfElse(p.Slice, "[]", "")). + Raw(do.IfElse(p.Ptr, "*", "")). + Pkg(p.Pkg).ID(p.Type), + ) + for _, item := range vals { + modName, modVal, modArgs := at.TagSplit(item) + mod, ok := at.Resolve[at.ParamModule](modName) + if !ok { + continue + } + j := &at.Join{Tok: Comment("Module: " + mod.Name())} + err := mod.Build(j, at.ParamMeta{ + Type: at.ParamIn, + CodeName: paramName, + Import: imp, + Value: modVal, + Args: modArgs, + }, p) + util.PanicIfError(err, + "failed to build module %s: method: %s.%s, param: %s", + modName, object.Name, method.Name, p.Name, + ) + handleSrc = append(handleSrc, j.Tok) + } + } + + var ( + handleIn []types.Token + ) + for _, p := range method.InParams { + + switch { + case p.Pkg == "context" && p.Type == "Context": + handleIn = append(handleIn, ID("ctx")) + default: + if name, ok := inParamArgs[p.Name]; ok { + if link, ok := file.Imports.Get(p.Pkg); ok { + imp.Set(p.Pkg, link) + } + + handleIn = append(handleIn, ID(name)) + } else { + handleIn = append(handleIn, ID("req").Op(".").ID(util.ToUpperCamelCase(p.Name))) + } + } + } + + // -------------------------------------- + + outParamArgs := make(map[string]string) + for _, p := range method.OutParams { + vals, ok := method.Tags["out."+p.Name] + fmt.Println(vals, ok, p.Name) + if !ok { + continue + } + paramName := "out" + util.ToUpperCamelCase(p.Name) + outParamArgs[p.Name] = paramName + handleSrc = append(handleSrc, + Var().ID(paramName). + Raw(do.IfElse(p.Slice, "[]", "")). + Raw(do.IfElse(p.Ptr, "*", "")). + Pkg(p.Pkg).ID(p.Type), + ) + for _, item := range vals { + modName, modVal, modArgs := at.TagSplit(item) + mod, ok := at.Resolve[at.ParamModule](modName) + if !ok { + continue + } + j := &at.Join{Tok: Comment("Module: " + mod.Name())} + err := mod.Build(j, at.ParamMeta{ + Type: at.ParamOut, + CodeName: paramName, + Import: imp, + Value: modVal, + Args: modArgs, + }, p) + util.PanicIfError(err, + "failed to build module %s: method: %s.%s, param: %s", + modName, object.Name, method.Name, p.Name, + ) + handleSrc = append(handleSrc, + Defer().Func().Bracket().Block(j.Tok).Bracket(), + ) + } + } + + var ( + handleOut []types.Token + ) + for _, p := range method.OutParams { + + switch { + case p.Type == "error": + handleOut = append(handleOut, ID("err")) + default: + if name, ok := outParamArgs[p.Name]; ok { + if link, ok := file.Imports.Get(p.Pkg); ok { + imp.Set(p.Pkg, link) + } + + handleOut = append(handleOut, ID(name)) + } else { + handleOut = append(handleOut, ID("res").Op(".").ID(util.ToUpperCamelCase(p.Name))) + } + } + } + + // -------------------------------------- + + handleSrc = append(handleSrc, + List(handleOut...).Op("=").ID("v").Op(".").ID("handle"+object.Name).Op(".").ID(method.Name).Call(handleIn...), + If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), + Return().List(ID("res"), Nil()), + ) + + models = append(models, handle.Block(handleSrc...)) + } + } + + return models +} diff --git a/apigen/module/mod-json-rpc/build_transport_models.go b/apigen/module/mod-json-rpc/build_transport_models.go new file mode 100644 index 0000000..d1eaaac --- /dev/null +++ b/apigen/module/mod-json-rpc/build_transport_models.go @@ -0,0 +1,85 @@ +package mod_json_rpc + +import ( + "fmt" + + "go.osspkg.com/do" + . "go.osspkg.com/gogen/golang" + "go.osspkg.com/gogen/types" + at "go.osspkg.com/goppy/v3/apigen/types" + "go.osspkg.com/goppy/v3/apigen/util" + "go.osspkg.com/syncing" +) + +func (v Module) buildTransportModels(w at.Writer, m at.GlobalMeta, files []at.File) error { + t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). + Package(m.PkgName). + Comment("go:generate easyjson") + + list := syncing.NewMap[string, string](10) + + var models []types.Token + for _, file := range files { + models = append(models, v.buildTransportModel(list, file)...) + } + + for alias, link := range list.Yield() { + t.Import(alias, link) + } + + if err := w.WriteFile(v.FilePrefix+"_model.go", t.Join(models...)); err != nil { + return err + } + + return nil +} + +func (v Module) buildTransportModel(imp at.ImportSetter, file at.File) []types.Token { + var models []types.Token + + for _, object := range file.Faces { + for _, method := range object.Methods { + + args := map[string][]at.Param{ + modelNameReq: method.InParams, + modelNameRes: method.OutParams, + } + + for tmpl, params := range args { + + var ( + argsOut []types.Token + ) + + for _, p := range params { + if ignoreModelParam(tmpl, p.Type, p.Pkg) { + continue + } + + if vals, ok := method.Tags[do.IfElse(tmpl == modelNameReq, "in.", "out.")+p.Name]; ok && noBodyParam(vals) { + continue + } + + if link, ok := file.Imports.Get(p.Pkg); ok { + imp.Set(p.Pkg, link) + } + + argsOut = append(argsOut, + ID(util.ToUpperCamelCase(p.Name)). + Raw(do.IfElse(p.Slice, "[]", "")). + Raw(do.IfElse(p.Ptr, "*", "")). + Pkg(p.Pkg).ID(p.Type). + Raw(fmt.Sprintf("`json:\"%s%s\"`", p.Name, + do.IfElse(p.Omitempty, ",omitempty", ""))), + ) + } + + models = append(models, Line().Comment(jsonGenComment). + Type().ID(fmt.Sprintf(tmpl, object.Name+method.Name)).Struct().Block(argsOut...), + ) + } + } + } + + return models +} diff --git a/apigen/module/mod-json-rpc/common.go b/apigen/module/mod-json-rpc/common.go index d978a52..f376326 100644 --- a/apigen/module/mod-json-rpc/common.go +++ b/apigen/module/mod-json-rpc/common.go @@ -1,11 +1,9 @@ package mod_json_rpc -const ( - tagWebPool = "web-pool" -) +import "strings" const ( - transportName = "JSONRPC%sHandle" + transportName = "JSONRPCHandler" modelNameReq = "jsonrpc%sModelRequest" modelNameRes = "jsonrpc%sModelResponse" @@ -32,3 +30,38 @@ const ( const ( jsonGenComment = "easyjson:json" ) + +func ignoreModelParam(tmpl, pt, pp string) bool { + if tmpl == modelNameRes { + switch { + case pt == "error": + return true + default: + } + } + + if tmpl == modelNameReq { + switch { + case pp == "context" && pt == "Context": + return true + default: + } + } + + return false +} + +func noBodyParam(vals []string) bool { + for _, val := range vals { + i := strings.Index(val, ":") + if i == -1 { + continue + } + switch strings.ToLower(val[0:i]) { + case "cookie", "header": + return true + default: + } + } + return false +} diff --git a/apigen/module/mod-json-rpc/module.go b/apigen/module/mod-json-rpc/module.go new file mode 100644 index 0000000..1840670 --- /dev/null +++ b/apigen/module/mod-json-rpc/module.go @@ -0,0 +1,23 @@ +package mod_json_rpc + +import ( + "go.osspkg.com/errors" + at "go.osspkg.com/goppy/v3/apigen/types" +) + +type Module struct { + FilePrefix string +} + +func (Module) Name() string { + return "json-rpc" +} + +func (v Module) Build(w at.Writer, m at.GlobalMeta, files []at.File) error { + return errors.Queue( + func() error { return v.buildBaseRPCModel(w, m) }, + func() error { return v.buildTransportModels(w, m, files) }, + func() error { return v.buildTransport(w, m, files) }, + func() error { return v.buildTransportHandlers(w, m, files) }, + ) +} diff --git a/apigen/module/mod-json-rpc/transport.go b/apigen/module/mod-json-rpc/transport.go deleted file mode 100644 index 35e0097..0000000 --- a/apigen/module/mod-json-rpc/transport.go +++ /dev/null @@ -1,403 +0,0 @@ -package mod_json_rpc - -import ( - "fmt" - - "go.osspkg.com/errors" - . "go.osspkg.com/gogen/golang" - at "go.osspkg.com/goppy/v3/apigen/types" - "go.osspkg.com/goppy/v3/apigen/util" -) - -type JSONRPCTransport struct { - FilePrefix string -} - -func (JSONRPCTransport) Name() string { - return "json-rpc" -} - -func (v JSONRPCTransport) Build(w at.Writer, m at.GlobalMeta, files []at.File) error { - return errors.Queue( - func() error { return v.buildBaseRPCModel(w, m) }, - //func() error { return v.buildTransport(ctx) }, - //func() error { return v.buildTransportModels(ctx) }, - //func() error { return v.buildTransportHandlers(ctx) }, - ) -} - -func (v JSONRPCTransport) buildBaseRPCModel(w at.Writer, m at.GlobalMeta) error { - baseReq := Comment(jsonGenComment). - Type().ID(modelBaseReq).Struct(). - Block( - ID(util.ToUpperCamelCase(fieldID)).String(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), - ID(util.ToUpperCamelCase(fieldMethod)).String(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldMethod)), - ID(util.ToUpperCamelCase(fieldParams)).Pkg("stdjson").ID("RawMessage"). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldParams)), - ) - - bulkBaseReq := Comment(jsonGenComment). - Type().ID(modelBulkBaseReq).Slice().ID(modelBaseReq) - - errRes := Comment(jsonGenComment). - Type().ID(modelBaseErr).Struct(). - Block( - ID(util.ToUpperCamelCase(fieldMessage)).String(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldMessage)), - ID(util.ToUpperCamelCase(fieldCode)).Int64(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldCode)), - ID(util.ToUpperCamelCase(fieldCtx)).Map(String(), String()). - Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldCtx)), - ) - - errType := Type().ID(errInterface).Interface(). - Block( - ID("GetCode").Bracket().Int64(), - ID("GetMessage").Bracket().String(), - ID("GetContext").Bracket().Map(String(), String()), - ) - - baseRes := Comment(jsonGenComment). - Type().ID(modelBaseRes).Struct(). - Block( - ID(util.ToUpperCamelCase(fieldID)).String(). - Raw(fmt.Sprintf("`json:\"%s\"`", fieldID)), - ID(util.ToUpperCamelCase(fieldResult)).Any(). - Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldResult)), - ID(util.ToUpperCamelCase(fieldError)).Op("*").ID("errResponse"). - Raw(fmt.Sprintf("`json:\"%s,omitempty\"`", fieldError)), - ) - - bulkBaseRes := Comment(jsonGenComment). - Type().ID(modelBulkBaseRes).Slice().ID(modelBaseRes) - - toErr := Func().ID("toJSONRPCError"). - Bracket(ID("e").Error()). - Bracket(Op("*").ID(modelBaseErr)).Block( - If().ID("e").Op("==").Nil().Block(Return().Nil()), - ID("err").Op(":=").Op("&").ID(modelBaseErr).Block(), - List(ID("te"), ID("ok")).Op(":=").ID("e").Op(".").Bracket(ID(errInterface)), - If().ID("ok").Block( - ID("err").Op(".").ID("Code").Op("="). - ID("te").Op(".").ID("GetCode").Bracket(), - ID("err").Op(".").ID("Message").Op("="). - ID("te").Op(".").ID("GetMessage").Bracket(), - ID("err").Op(".").ID("Ctx").Op("="). - ID("te").Op(".").ID("GetContext").Bracket(), - ).Else().Block( - ID("err").Op(".").ID("Message").Op("="). - ID("e").Op(".").ID("Error").Bracket(), - ), - Return().ID("err"), - ) - - t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). - Package(m.PkgName). - Comment("go:generate easyjson"). - Import("stdjson", "encoding/json"). - Join(baseReq, bulkBaseReq, baseRes, bulkBaseRes, errRes, errType, toErr) - - return w.WriteFile(v.FilePrefix+"_model.go", t) -} - -// func (v JSONRPCTransport) buildTransportModels(ctx at.Ctx) error { -// t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). -// Package(ctx.PkgName). -// Comment("go:generate easyjson") -// -// list := syncing.NewMap[string, string](10) -// -// var models []types.Token -// -// for _, method := range ctx.Object.Methods { -// var ( -// argsOut []types.Token -// ) -// for _, p := range method.OutParams { -// switch { -// case p.Type == "error": -// continue -// default: -// } -// -// if link, ok := ctx.File.Imports.Get(p.Pkg); ok { -// list.Set(p.Pkg, link) -// } -// -// argsOut = append(argsOut, -// ID(util.ToUpperCamelCase(p.Name)).Pkg(p.Pkg).ID(p.Type). -// Raw(fmt.Sprintf("`json:\"%s%s\"`", p.Name, -// do.IfElse(p.Omitempty, ",omitempty", ""))), -// ) -// } -// models = append(models, Line().Comment(jsonGenComment). -// Type().ID(fmt.Sprintf(modelNameRes, method.Name)).Struct().Block(argsOut...), -// ) -// -// var ( -// argsIn []types.Token -// ) -// for _, p := range method.InParams { -// switch { -// case p.Pkg == "context" && p.Type == "Context": -// continue -// default: -// } -// -// if link, ok := ctx.File.Imports.Get(p.Pkg); ok { -// list.Set(p.Pkg, link) -// } -// -// argsIn = append(argsIn, -// ID(util.ToUpperCamelCase(p.Name)).Pkg(p.Pkg).ID(p.Type). -// Raw(fmt.Sprintf("`json:\"%s%s\"`", p.Name, -// do.IfElse(p.Omitempty, ",omitempty", ""))), -// ) -// } -// models = append(models, Line().Comment(jsonGenComment). -// Type().ID(fmt.Sprintf(modelNameReq, method.Name)).Struct().Block(argsIn...), -// ) -// } -// -// for alias, link := range list.Yield() { -// t.Import(alias, link) -// } -// -// return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_models.go", t.Join(models...)) -// } -// -// func (v JSONRPCTransport) buildTransportHandlers(ctx at.Ctx) error { -// objectName := fmt.Sprintf(transportName, ctx.Object.Name) -// -// t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). -// Package(ctx.PkgName) -// -// list := syncing.NewMap[string, string](10) -// list.Set("context", "context") -// list.Set("stdjson", "encoding/json") -// -// var models []types.Token -// -// for _, method := range ctx.Object.Methods { -// handle := Func().Bracket(ID("v").Op("*").ID(objectName)). -// ID("method"+method.Name).Bracket( -// ID("ctx").Pkg("context").ID("Context"), -// ID("param").Pkg("stdjson").ID("RawMessage"), -// ).Bracket( -// Pkg("stdjson").ID("Marshaler"), -// Error(), -// ) -// -// var handleSrc []types.Token -// -// handleSrc = append(handleSrc, -// Var().ID("req").ID(fmt.Sprintf(modelNameReq, method.Name)), -// ID("err").Op(":=").Pkg("stdjson").ID("Unmarshal").Bracket( -// ID("param"), Op("&").ID("req"), -// ), -// If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), -// Var().ID("res").ID(fmt.Sprintf(modelNameRes, method.Name)), -// ) -// -// var ( -// handleOut []types.Token -// ) -// for _, p := range method.OutParams { -// if link, ok := ctx.File.Imports.Get(p.Pkg); ok { -// list.Set(p.Pkg, link) -// } -// -// switch { -// case p.Type == "error": -// handleOut = append(handleOut, ID("err")) -// default: -// handleOut = append(handleOut, ID("res").Op(".").ID(util.ToUpperCamelCase(p.Name))) -// } -// } -// -// var ( -// handleIn []types.Token -// ) -// for _, p := range method.InParams { -// if link, ok := ctx.File.Imports.Get(p.Pkg); ok { -// list.Set(p.Pkg, link) -// } -// -// switch { -// case p.Pkg == "context" && p.Type == "Context": -// handleIn = append(handleIn, ID("ctx")) -// default: -// handleIn = append(handleIn, ID("req").Op(".").ID(util.ToUpperCamelCase(p.Name))) -// } -// } -// -// handleSrc = append(handleSrc, -// List(handleOut...).Op("=").ID("v").Op(".").ID("handle").Op(".").ID(method.Name).Call(handleIn...), -// If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), -// Return().List(ID("res"), Nil()), -// ) -// -// models = append(models, handle.Block(handleSrc...)) -// } -// -// for alias, link := range list.Yield() { -// t.Import(alias, link) -// } -// -// return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_handlers.go", t.Join(models...)) -// } -// -// func (v JSONRPCTransport) buildTransport(ctx at.Ctx) error { -// objectName := fmt.Sprintf(transportName, ctx.Object.Name) -// -// t := Comment("Code generated by goppy-cli wsg. DO NOT EDIT."). -// Package(ctx.PkgName) -// -// list := syncing.NewMap[string, string](10) -// list.Set("context", "context") -// list.Set("strings", "strings") -// list.Set("errors", "errors") -// list.Set("stdjson", "encoding/json") -// list.Set("syncing", "go.osspkg.com/syncing") -// list.Set("web", "go.osspkg.com/goppy/v3/web") -// list.Set(ctx.Object.Alias, ctx.Object.Pkg) -// for alias, link := range ctx.File.Imports.Yield() { -// list.Set(alias, link) -// } -// -// for alias, link := range list.Yield() { -// t.Import(alias, link) -// } -// -// t = t.Join( -// Type().ID(objectName).Struct().Block( -// ID("routes").Pkg("web").ID("ServerPool"), -// ID("handle").Pkg(ctx.Object.Alias).ID(ctx.Object.Name), -// ), -// //-- New -// Func().ID("New"+objectName).Bracket( -// ID("routes").Pkg("web").ID("ServerPool"), -// ID("handle").Pkg(ctx.Object.Alias).ID(ctx.Object.Name), -// ).Op("*").ID(objectName).Block( -// Return().Op("&").ID(objectName).Block( -// ID("routes").Op(":").ID("routes").Op(","), -// ID("handle").Op(":").ID("handle").Op(","), -// ), -// ), -// // -- Down -// Func().Bracket(ID("v").Op("*").ID(objectName)). -// ID("Down").Bracket().Error().Block(Return().Nil()), -// // -- Up -// Func().Bracket(ID("v").Op("*").ID(objectName)). -// ID("Up").Bracket(ID("ctx").Pkg("context").ID("Context")).Error(). -// Block(v.buildUp(ctx)), -// ) -// -// return ctx.W.WriteFile(v.FilePrefix+"_"+ctx.Object.Name+"_transport.go", t) -// } -// -// func (v JSONRPCTransport) buildUp(ctx at.Ctx) *Tokens { -// return ID("v").Op(".").ID("routes").Op(".").ID("All").Bracket( -// Func().Bracket(ID("tag").String(), ID("r").Pkg("web").ID("Router")).Block( -// func() *Tokens { -// tags, ok := ctx.Tags[tagWebPool] -// if !ok { -// return Line() -// } -// var list []types.Token -// for _, tag := range tags { -// list = append(list, Text(tag)) -// } -// return Switch().ID("tag").Block( -// Case().List(list...).Op(":").Line().Join( -// ID("r").Op(".").ID("Post").Call( -// Text("/"+strings.ToLower(ctx.Object.Name)), -// Func().Bracket(ID("ctx").Pkg("web").ID("Ctx")).Block( -// Var().ID("req").ID(modelBulkBaseReq), -// If(). -// ID("err").Op(":="). -// ID("ctx").Op(".").ID("BindJSON").Bracket(Op("&").ID("req")).Op(";"). -// ID("err").Op("!=").Nil().Block( -// ID("ctx").Op(".").ID("String").Bracket( -// Raw("400"), ID("err").Op(".").ID("Error").Call(), -// ), -// Return(), -// ), -// ID("res").Op(":="). -// Pkg("syncing").ID("NewSlice").Raw("[").ID(modelBaseRes).Raw("]").Call( -// Uint().Bracket(Raw("len").Bracket(ID("req"))), -// ), -// ID("wg").Op(":=").Pkg("syncing").ID("NewGroup").Bracket( -// ID("ctx").Op(".").ID("Context").Call(), -// ), -// For().List(Raw("_"), ID("item")).Op(":=").Range().ID("req").Block( -// ID("item").Op(":=").ID("item"), -// -// ID("wg").Op(".").ID("Background").Call( -// Text(strings.ToLower(ctx.Object.Name)+".").Op("+"). -// Pkg("strings").ID("ToLower").Bracket( -// ID("item").Op(".").ID(util.ToUpperCamelCase(fieldMethod)), -// ), -// Func().Bracket(ID("ctx").Pkg("context").ID("Context")).Block( -// ID("out").Op(":=").ID(modelBaseRes).Block( -// ID(util.ToUpperCamelCase(fieldID)).Op(":"). -// ID("item").Op(".").ID(util.ToUpperCamelCase(fieldID)).Op(","), -// ), -// Var().ID("err").Error(), -// Var().ID("result").Pkg("stdjson").ID("Marshaler"), -// v.buildMethods(ctx), -// If().ID("err").Op("!=").Nil().Block( -// ID("out").Op(".").ID(util.ToUpperCamelCase(fieldError)).Op("="). -// ID("toJSONRPCError").Bracket(ID("err")), -// ).Else().Block( -// ID("out").Op(".").ID(util.ToUpperCamelCase(fieldResult)).Op("="). -// ID("result"), -// ), -// ID("res").Op(".").ID("Append").Bracket(ID("out")), -// ), -// ), -// ), -// ID("wg").Op(".").ID("Wait").Call(), -// ID("ctx").Op(".").ID("JSON").Bracket( -// Raw("200"), ID(modelBulkBaseRes).Bracket( -// ID("res").Op(".").ID("Extract").Bracket(), -// ), -// ), -// ), -// ), -// ), -// ).Line().Return() -// }(), -// ), -// ). -// Line().Return().Nil() -// -// } -//func (v JSONRPCTransport) buildMethods(ctx at.Ctx) *Tokens { -// var list []types.Token -// -// for _, method := range ctx.Object.Methods { -// list = append(list, -// Case().Text(strings.ToLower(method.Name)).Op(":").Join( -// List(ID("result"), ID("err")).Op("="). -// ID("v").Op(".").ID("method"+method.Name). -// Call( -// ID("ctx"), -// ID("item").Op(".").ID(util.ToUpperCamelCase(fieldParams)), -// ), -// ), -// ) -// } -// -// list = append(list, -// Default().Op(":").Join( -// ID("err").Op("=").Pkg("errors").ID("New").Bracket(Text("unsupported method")), -// ), -// ) -// -// return Switch().Pkg("strings").ID("ToLower").Bracket( -// ID("item").Op(".").ID(util.ToUpperCamelCase(fieldMethod)), -// ).Block(list...) -//} diff --git a/apigen/module/mod-param-cookie/module.go b/apigen/module/mod-param-cookie/module.go new file mode 100644 index 0000000..4ef0000 --- /dev/null +++ b/apigen/module/mod-param-cookie/module.go @@ -0,0 +1,65 @@ +package mod_param_cookie + +import ( + "fmt" + + . "go.osspkg.com/gogen/golang" + at "go.osspkg.com/goppy/v3/apigen/types" +) + +type Module struct{} + +func (Module) Name() string { + return "cookie" +} + +func (v Module) Build(w at.Joiner, m at.ParamMeta, value at.Param) error { + switch m.Type { + case at.ParamIn: + return v.generateIn(w, m, value) + case at.ParamOut: + return v.generateOut(w, m, value) + default: + return fmt.Errorf("unknown type") + } +} + +func (v Module) generateIn(w at.Joiner, m at.ParamMeta, p at.Param) error { + m.Import.Set("web", "go.osspkg.com/goppy/v3/web") + + w.Join( + List(ID(m.CodeName), Raw("_")).Op("="). + ID("web").Op(".").ID("StrTo").Raw("[").Pkg(p.Pkg).ID(p.Type).Raw("]"). + Call( + ID("webCtx").Op(".").ID("Cookie").Call().Op(".").ID("Get").Call(Text(m.Value)), + ), + ) + + return nil +} + +func (v Module) generateOut(w at.Joiner, m at.ParamMeta, _ at.Param) error { + m.Import.Set("nethttp", "net/http") + m.Import.Set("fmt", "fmt") + m.Import.Set("time", "time") + + w.Join( + If().ID("err").Op("!=").Nil().Block(Return()), + ID("webCtx").Op(".").ID("Cookie").Call().Op(".").ID("Set").Bracket( + Op("&").Pkg("nethttp").ID("Cookie").Block( + ID("Name").Op(":").Text(m.Value).Op(","), + ID("Value").Op(":").Pkg("fmt").ID("Sprintf").Bracket( + List(Text("%v"), Raw(m.CodeName)), + ).Op(","), + ID("Path").Op(":").Text(m.Args.Get("path", "/")).Op(","), + ID("Expires").Op(":").Pkg("time").ID("Now").Call().Op(".").ID("Add").Call( + Raw(m.Args.Get("time", "86400")).Op("*").Pkg("time").ID("Second"), + ).Op(","), + ID("Secure").Op(":").Raw(m.Args.Get("secure", "true")).Op(","), + ID("HttpOnly").Op(":").Raw(m.Args.Get("httpOnly", "true")).Op(","), + ID("SameSite").Op(":").Pkg("nethttp").ID(m.Args.Get("sameSite", "SameSiteStrictMode")).Op(","), + ), + ), + ) + return nil +} diff --git a/apigen/module/mod-param-header/module.go b/apigen/module/mod-param-header/module.go new file mode 100644 index 0000000..3eed2d9 --- /dev/null +++ b/apigen/module/mod-param-header/module.go @@ -0,0 +1,54 @@ +package mod_param_header + +import ( + "fmt" + + . "go.osspkg.com/gogen/golang" + at "go.osspkg.com/goppy/v3/apigen/types" +) + +type Module struct{} + +func (Module) Name() string { + return "header" +} + +func (v Module) Build(w at.Joiner, m at.ParamMeta, value at.Param) error { + switch m.Type { + case at.ParamIn: + return v.generateIn(w, m, value) + case at.ParamOut: + return v.generateOut(w, m, value) + default: + return fmt.Errorf("unknown type") + } +} + +func (v Module) generateIn(w at.Joiner, m at.ParamMeta, p at.Param) error { + m.Import.Set("web", "go.osspkg.com/goppy/v3/web") + + w.Join( + List(ID(m.CodeName), Raw("_")).Op("="). + ID("web").Op(".").ID("StrTo").Raw("[").Pkg(p.Pkg).ID(p.Type).Raw("]"). + Call( + ID("webCtx").Op(".").ID("Header").Call().Op(".").ID("Get").Call(Text(m.Value)), + ), + ) + + return nil +} + +func (v Module) generateOut(w at.Joiner, m at.ParamMeta, _ at.Param) error { + m.Import.Set("fmt", "fmt") + + w.Join( + If().ID("err").Op("!=").Nil().Block(Return()), + ID("webCtx").Op(".").ID("Header").Call().Op(".").ID("Set").Bracket( + Text(m.Value), + Pkg("fmt").ID("Sprintf").Bracket( + List(Text("%v"), Raw(m.CodeName)), + ), + ), + ) + return nil +} diff --git a/apigen/parser/visitor.go b/apigen/parser/visitor.go index d6e0c8c..8915130 100644 --- a/apigen/parser/visitor.go +++ b/apigen/parser/visitor.go @@ -33,7 +33,7 @@ type ( PkgPath string GoMod string Imports *syncing.Map[string, string] - Objects []types.Object + Objects []types.Face } Parser interface { @@ -77,7 +77,7 @@ func (v *visitor) ToFile() types.File { PkgPath: v.PkgPath, GoMod: v.GoMod, Imports: v.Imports, - Objects: v.Objects, + Faces: v.Objects, } } @@ -108,14 +108,16 @@ func (v *visitor) DumpStdout() { for _, value := range method.InParams { fmt.Println(" ", "name:", value.Name, ", type:", value.Type, - ", pkg:", value.Pkg, ", omit:", value.Omitempty) + ", pkg:", value.Pkg, ", omit:", value.Omitempty, + ", ptr:", value.Ptr, ", slice:", value.Slice) } fmt.Println(" out:") for _, value := range method.OutParams { fmt.Println(" ", "name:", value.Name, ", type:", value.Type, - ", pkg:", value.Pkg, ", omit:", value.Omitempty) + ", pkg:", value.Pkg, ", omit:", value.Omitempty, + ", ptr:", value.Ptr, ", slice:", value.Slice) } } } @@ -151,6 +153,8 @@ func (v *visitor) astFile(node *ast.File) ast.Visitor { v.PkgName = node.Name.String() v.PkgPath = v.GoMod + filepath.Dir(v.FilePath) + v.Imports.Set(v.PkgName, v.PkgPath) + return v } @@ -186,21 +190,21 @@ func (v *visitor) parseComment(comment string, tags *types.Tags) { buf := bb.FromBytes([]byte(comment)) _, err := buf.Seek(0, io.SeekStart) - panicIfError(err, "parse comment") + util.PanicIfError(err, "parse comment") for { key, err := buf.ReadString('=') if errors.Is(err, io.EOF) { break } - panicIfError(err, "parse comment") + util.PanicIfError(err, "parse comment") key = strings.Trim(strings.TrimSpace(key), "=") r, _, err := buf.ReadRune() if errors.Is(err, io.EOF) { break } - panicIfError(err, "parse comment") + util.PanicIfError(err, "parse comment") var value string switch r { @@ -214,14 +218,14 @@ func (v *visitor) parseComment(comment string, tags *types.Tags) { value, err = buf.ReadString('`') value = strings.Trim(strings.TrimSpace(value), "`") default: - panicIfError(buf.UnreadRune(), "parse comment") + util.PanicIfError(buf.UnreadRune(), "parse comment") value, err = buf.ReadString(' ') } if errors.Is(err, io.EOF) { break } - panicIfError(err, "parse comment") + util.PanicIfError(err, "parse comment") (*tags)[key] = append((*tags)[key], do.Convert[string, string]( strings.Split(value, ","), @@ -255,13 +259,23 @@ func (v *visitor) parseMethods(fields *ast.FieldList) (result []types.Method) { if funcType.Params != nil { for _, param := range funcType.Params.List { - method.InParams = append(method.InParams, getParam(param)) + rp := getParam(param) + if !isBaseType(rp.Type) && len(rp.Pkg) == 0 { + rp.Pkg = v.PkgName + } + + method.InParams = append(method.InParams, rp) } } if funcType.Results != nil { for _, param := range funcType.Results.List { - method.OutParams = append(method.OutParams, getParam(param)) + rp := getParam(param) + if !isBaseType(rp.Type) && len(rp.Pkg) == 0 { + rp.Pkg = v.PkgName + } + + method.OutParams = append(method.OutParams, rp) } } @@ -278,7 +292,7 @@ func (v *visitor) astTypeSpec(node *ast.TypeSpec) { return } - obj := types.Object{ + obj := types.Face{ Pkg: v.PkgPath, Alias: v.PkgName, Name: node.Name.String(), @@ -295,29 +309,25 @@ func (v *visitor) astTypeSpec(node *ast.TypeSpec) { return } -func getParam(param *ast.Field) types.Param { - paramType := getTypeName(param.Type) - paramPkg := func() string { - list := strings.Split(paramType, ".") - switch len(list) { - case 1: - return "" - case 2: - paramType = list[1] - return strings.Trim(list[0], "*[].") - default: - panicIfError(fmt.Errorf("invalid type: %s", paramType), "get param") - return "" - } - }() - - return types.Param{ - Name: param.Names[0].String(), - Type: paramType, - Pkg: paramPkg, - Omitempty: strings.HasPrefix(paramType, "*") || - strings.HasPrefix(paramType, "[]"), +func getParam(param *ast.Field) (p types.Param) { + p.Name = param.Names[0].String() + p.Type = getTypeName(param.Type) + p.Slice = strings.HasPrefix(p.Type, "[]") + p.Ptr = strings.HasPrefix(p.Type, "*") + p.Omitempty = p.Ptr || p.Slice + + list := strings.Split(p.Type, ".") + switch len(list) { + case 1: + p.Type = strings.Trim(list[0], "*[].") + case 2: + p.Type = list[1] + p.Pkg = strings.Trim(list[0], "*[].") + default: + util.PanicIfError(fmt.Errorf("invalid type: %s", p.Type), "get param") } + + return } /* @@ -336,7 +346,7 @@ func exprToString(fset *token.FileSet, expr ast.Expr) string { func getTypeName(expr ast.Expr) string { switch t := expr.(type) { case *ast.Ident: - return t.Name + return fmt.Sprintf("%s", t.Name) case *ast.SelectorExpr: return fmt.Sprintf("%s.%s", getTypeName(t.X), t.Sel.Name) case *ast.StarExpr: @@ -348,10 +358,18 @@ func getTypeName(expr ast.Expr) string { } } -func panicIfError(err error, msg string, args ...interface{}) { - if err == nil { - return +func isBaseType(arg string) bool { + switch arg { + case "bool", "string", "byte", "struct", "struct{}", + "any", "interface", "interface{}", + "complex64", "complex128", + "error", "uintptr", + "float32", "float64", + "int", "int8", "int16", "int32", "int64", + "uint", "uint8", "uint16", "uint32", "uint64", + "map": + return true + default: + return false } - err = errors.Wrapf(err, msg, args...) - panic(err.Error()) } diff --git a/apigen/types/common.go b/apigen/types/common.go new file mode 100644 index 0000000..8e3ff26 --- /dev/null +++ b/apigen/types/common.go @@ -0,0 +1,38 @@ +package types + +import ( + "strings" + + "go.osspkg.com/gogen/golang" + "go.osspkg.com/gogen/types" +) + +func TagSplit(arg string) (string, string, Args) { + val := strings.SplitN(arg, ":", 3) + args := make(Args) + + switch len(val) { + case 0: + return "", "", args + case 1: + return strings.TrimSpace(val[0]), "", args + default: + if len(val) > 2 { + for _, item := range strings.Split(val[2], "|") { + vals := strings.Split(item, "=") + if len(vals) == 2 { + args[strings.TrimSpace(vals[0])] = strings.TrimSpace(vals[1]) + } + } + } + return strings.TrimSpace(val[0]), strings.TrimSpace(val[1]), args + } +} + +type Join struct { + Tok *golang.Tokens +} + +func (j *Join) Join(toks ...types.Token) { + j.Tok = j.Tok.Join(toks...) +} diff --git a/apigen/types/object.go b/apigen/types/object.go index cfe824b..b3ca68a 100644 --- a/apigen/types/object.go +++ b/apigen/types/object.go @@ -15,10 +15,10 @@ type File struct { PkgPath string GoMod string Imports *syncing.Map[string, string] - Objects []Object + Faces []Face } -type Object struct { +type Face struct { Alias string Pkg string Name string @@ -37,6 +37,8 @@ type Param struct { Name string Type string Pkg string + Ptr bool + Slice bool Omitempty bool } @@ -46,3 +48,13 @@ type KV struct { } type Tags map[string][]string + +type Args map[string]string + +func (a Args) Get(key, def string) string { + v, ok := a[key] + if !ok { + return def + } + return v +} diff --git a/apigen/types/storage.go b/apigen/types/storage.go index 6fbd0c1..f92f2c4 100644 --- a/apigen/types/storage.go +++ b/apigen/types/storage.go @@ -7,9 +7,10 @@ import ( ) const ( - globalMod = "gg" - faceMod = "fg" - methodMod = "mg" + globalMod = "g" + faceMod = "f" + methodMod = "m" + paramMod = "p" ) var ( @@ -26,6 +27,8 @@ func Register[T any](module T) { addr = faceMod + "/" + vv.Name() case MethodModule: addr = methodMod + "/" + vv.Name() + case ParamModule: + addr = paramMod + "/" + vv.Name() default: panic("unknown type") } @@ -44,6 +47,8 @@ func Resolve[T any](name string) (T, bool) { addr = faceMod + "/" + name case *MethodModule: addr = methodMod + "/" + name + case *ParamModule: + addr = paramMod + "/" + name default: panic(fmt.Sprintf("unknown type: %T", *nt)) } diff --git a/apigen/types/type.go b/apigen/types/type.go index afb6326..c18f9c4 100644 --- a/apigen/types/type.go +++ b/apigen/types/type.go @@ -9,26 +9,57 @@ type GlobalModule interface { type FaceModule interface { Name() string - Build(w Writer, m FaceMeta, value File) error + Build(w Writer, m FaceMeta, value Face) error } type MethodModule interface { Name() string - Build(w Writer, m MethodMeta, value Method) error + Build(w Joiner, m MethodMeta, value Method) error +} + +type ParamModule interface { + Name() string + Build(w Joiner, m ParamMeta, value Param) error } type Writer interface { WriteFile(fileName string, tok types.Token) error } +type Joiner interface { + Join(toks ...types.Token) +} + +type ImportSetter interface { + Set(string, string) +} + type GlobalMeta struct { PkgName string + Pool []string } type FaceMeta struct { PkgName string + Import ImportSetter } type MethodMeta struct { PkgName string + Import ImportSetter +} + +type ParamType uint8 + +const ( + ParamIn ParamType = 0 + ParamOut ParamType = 1 +) + +type ParamMeta struct { + Type ParamType + CodeName string + Import ImportSetter + Value string + Args Args } diff --git a/apigen/util/util.go b/apigen/util/util.go index 8189157..345b09a 100644 --- a/apigen/util/util.go +++ b/apigen/util/util.go @@ -5,6 +5,8 @@ import ( "hash/crc32" "strings" "unicode" + + "go.osspkg.com/errors" ) func ToKebabCase(s string) string { @@ -57,10 +59,18 @@ func CRC32(s string) string { return "" } - return fmt.Sprintf("%08X", crc32.Checksum([]byte(s), crc32q)) + return fmt.Sprintf("%08x", crc32.Checksum([]byte(s), crc32q)) } func SplitLast(s, sep string) string { result := strings.Split(s, sep) return result[len(result)-1] } + +func PanicIfError(err error, msg string, args ...interface{}) { + if err == nil { + return + } + err = errors.Wrapf(err, msg, args...) + panic(err.Error()) +} diff --git a/internal/commands/wsg.go b/internal/commands/wsg.go index 11513dd..f211a0f 100644 --- a/internal/commands/wsg.go +++ b/internal/commands/wsg.go @@ -25,8 +25,9 @@ func CmdWSG() console.CommandGetter { flagsSetter.String("out", "output file specified") flagsSetter.StringVar("iface", "", "interface names (optional)") flagsSetter.StringVar("mod", "json-rpc", "generation modules (optional)") + flagsSetter.StringVar("pool", "main", "web server pool list (optional)") }) - setter.ExecFunc(func(out, _iface, _mod string) { + setter.ExecFunc(func(out, _iface, _mod, _pool string) { console.ShowDebug(true) console.Infof("--- GENERATE ---") @@ -45,14 +46,30 @@ func CmdWSG() console.CommandGetter { return !strings.HasPrefix(value, out) }) - build := &builder.Builder{ - Out: out, - IFace: do.Entries[string, string, struct{}](strings.Split(_iface, ","), func(s string) (string, struct{}) { + mods := do.Treat[string](strings.Split(_mod, ","), func(value string, index int) string { + return strings.ToLower(strings.TrimSpace(value)) + }) + + pool := do.Treat[string](strings.Split(_pool, ","), func(value string, index int) string { + return strings.ToLower(strings.TrimSpace(value)) + }) + + face := do.Entries[string, string, struct{}]( + do.Filter[string](strings.Split(_iface, ","), + func(value string, index int) bool { + return len(value) > 0 + }, + ), + func(s string) (string, struct{}) { return strings.ToLower(strings.TrimSpace(s)), struct{}{} - }), - Mods: do.Treat[string](strings.Split(_mod, ","), func(value string, index int) string { - return strings.ToLower(strings.TrimSpace(value)) - }), + }, + ) + + build := &builder.Builder{ + Out: out, + IFace: face, + Mods: mods, + Pool: pool, } for _, filePath := range files { diff --git a/web/ctx.go b/web/ctx.go index 48fd779..98be116 100644 --- a/web/ctx.go +++ b/web/ctx.go @@ -9,12 +9,16 @@ package web import ( "context" + "encoding" + "encoding/json" "fmt" "io" "net/http" "net/url" "slices" "strconv" + "strings" + "time" "go.osspkg.com/ioutils" "go.osspkg.com/ioutils/data" @@ -342,3 +346,109 @@ func (v *_ctx) URL() *url.URL { func (v *_ctx) Redirect(uri string) { http.Redirect(v.w, v.r, uri, http.StatusMovedPermanently) } + +/**********************************************************************************************************************/ + +type UnStringer interface { + UnString(string) +} + +func StrTo[T any](s string) (T, error) { + var v T + var err error + + switch p := any(&v).(type) { + case *string: + *p = s + case *int: + var val int64 + val, err = strconv.ParseInt(s, 10, strconv.IntSize) + *p = int(val) + case *int8: + var val int64 + val, err = strconv.ParseInt(s, 10, 8) + *p = int8(val) + case *int16: + var val int64 + val, err = strconv.ParseInt(s, 10, 16) + *p = int16(val) + case *int32: + var val int64 + val, err = strconv.ParseInt(s, 10, 32) + *p = int32(val) + case *int64: + *p, err = strconv.ParseInt(s, 10, 64) + case *uint: + var val uint64 + val, err = strconv.ParseUint(s, 10, strconv.IntSize) + *p = uint(val) + case *uint8: + var val uint64 + val, err = strconv.ParseUint(s, 10, 8) + *p = uint8(val) + case *uint16: + var val uint64 + val, err = strconv.ParseUint(s, 10, 16) + *p = uint16(val) + case *uint32: + var val uint64 + val, err = strconv.ParseUint(s, 10, 32) + *p = uint32(val) + case *uint64: + *p, err = strconv.ParseUint(s, 10, 64) + case *float32: + var val float64 + val, err = strconv.ParseFloat(s, 32) + *p = float32(val) + case *float64: + *p, err = strconv.ParseFloat(s, 64) + case *bool: + *p, err = strconv.ParseBool(s) + case *time.Duration: + *p, err = time.ParseDuration(s) + default: + if us, ok := any(&v).(UnStringer); ok { + us.UnString(s) + } else if bu, ok := any(&v).(encoding.BinaryUnmarshaler); ok { + err = bu.UnmarshalBinary([]byte(s)) + } else if tu, ok := any(&v).(encoding.TextUnmarshaler); ok { + err = tu.UnmarshalText([]byte(s)) + } else if ju, ok := any(&v).(json.Unmarshaler); ok { + err = ju.UnmarshalJSON([]byte(s)) + } else { + err = fmt.Errorf("parser: unsupported type %T", v) + } + } + + return v, err +} + +func StrToSlice[T any](s, sep string) ([]T, error) { + if s == "" { + return nil, nil + } + + count := strings.Count(s, sep) + 1 + result := make([]T, 0, count) + + for { + idx := strings.Index(s, sep) + if idx < 0 { + val, err := StrTo[T](strings.TrimSpace(s)) + if err != nil { + return nil, err + } + result = append(result, val) + break + } + + val, err := StrTo[T](strings.TrimSpace(s[:idx])) + if err != nil { + return nil, err + } + result = append(result, val) + s = s[idx+len(sep):] + } + + return result, nil +} From bfb734e03b6984337adb1e7ef6a47a843e3f810b Mon Sep 17 00:00:00 2001 From: Mikhail Knyazhev Date: Mon, 16 Mar 2026 13:53:14 +0300 Subject: [PATCH 6/6] add web server generator --- _example/web-server-gen/Makefile | 3 +- _example/web-server-gen/main.go | 92 +-- _example/web-server-gen/test.http | 23 + .../transport/jsonrpc_handler.go | 67 +- .../web-server-gen/transport/jsonrpc_model.go | 5 + .../transport/jsonrpc_model_base.go | 5 + .../transport/jsonrpc_model_base_easyjson.go | 471 +++++++++++ .../transport/jsonrpc_model_easyjson.go | 740 ++++++++++++++++++ .../transport/jsonrpc_transport.go | 45 +- _example/web-server-gen/types/custom.go | 16 + _example/web-server-gen/types/interfaces1.go | 3 +- _example/web-server-gen/types/wsg.go | 5 + apigen/builder/builder.go | 10 +- apigen/builder/common.go | 5 + .../module/mod-json-rpc/build_base_model.go | 8 +- apigen/module/mod-json-rpc/build_transport.go | 18 +- .../mod-json-rpc/build_transport_handlers.go | 31 +- .../mod-json-rpc/build_transport_models.go | 10 +- apigen/module/mod-json-rpc/common.go | 7 +- apigen/module/mod-json-rpc/module.go | 6 + apigen/module/mod-param-cookie/module.go | 17 +- apigen/module/mod-param-header/module.go | 17 +- apigen/parser/errors.go | 5 + apigen/parser/visitor.go | 7 +- apigen/types/common.go | 5 + apigen/types/storage.go | 5 + apigen/types/type.go | 5 + apigen/util/util.go | 5 + go.mod | 24 +- go.sum | 42 +- internal/commands/wsg.go | 3 +- internal/global/skip_files.go | 5 + web/ctx.go | 4 + 33 files changed, 1569 insertions(+), 145 deletions(-) create mode 100644 _example/web-server-gen/test.http create mode 100644 _example/web-server-gen/transport/jsonrpc_model_base_easyjson.go create mode 100644 _example/web-server-gen/transport/jsonrpc_model_easyjson.go create mode 100644 _example/web-server-gen/types/custom.go diff --git a/_example/web-server-gen/Makefile b/_example/web-server-gen/Makefile index 23fc67a..166867c 100644 --- a/_example/web-server-gen/Makefile +++ b/_example/web-server-gen/Makefile @@ -1,7 +1,6 @@ SHELL=/bin/bash run: - go generate ./types/wsg.go - go generate ./transport + go generate -run easyjson ./transport go run main.go --config=config.yaml diff --git a/_example/web-server-gen/main.go b/_example/web-server-gen/main.go index 8890645..7338d8d 100644 --- a/_example/web-server-gen/main.go +++ b/_example/web-server-gen/main.go @@ -7,18 +7,11 @@ package main import ( "context" - "encoding/json" "fmt" - "os" - - "go.osspkg.com/logx" - "go.osspkg.com/xc" "go.osspkg.com/goppy/v3" - "go.osspkg.com/goppy/v3/console" - "go.osspkg.com/goppy/v3/dic/broker" - "go.osspkg.com/goppy/v3/metrics" - "go.osspkg.com/goppy/v3/plugins" + "go.osspkg.com/goppy/v3/_example/web-server-gen/transport" + "go.osspkg.com/goppy/v3/_example/web-server-gen/types" "go.osspkg.com/goppy/v3/web" ) @@ -31,48 +24,10 @@ func main() { ) app.Plugins( NewController, - func(routes web.ServerPool, c *Controller) { - router, ok := routes.Main() - if !ok { - return - } - - router.Use(web.ThrottlingMiddleware(100)) - router.Get("/users", c.Users) - - api := router.Collection("/api/v1", web.ThrottlingMiddleware(100)) - api.Get("/user/{id}", c.User) + func(routes web.ServerPool, c *Controller) *transport.JSONRPCHandler { + return transport.NewJSONRPCHandler(routes, c, c, c) }, - broker.WithUniversalBroker[IStatus]( - func(_ xc.Context, status IStatus) error { - fmt.Println(">> UniversalBroker got status", status.GetStatus()) - return nil - }, - func(status IStatus) error { - return nil - }, - ), ) - app.Command(func(ctx context.Context, _ plugins.DIResolver, setter console.CommandSetter) { - setter.Setup("env", "show all envs") - setter.ExecFunc(func() { - fmt.Println(os.Environ()) - }) - }) - app.Command(func(ctx context.Context, r plugins.DIResolver, setter console.CommandSetter) { - setter.Setup("ctrl", "call ctrl") - setter.ExecFunc(func() { - logx.SetLevel(0) - - console.FatalIfErr(r.Resolve(func(c *Controller) { - fmt.Println(c.GetStatus()) - }), "can't find controller") - - console.FatalIfErr(r.Resolve(func(c *Controller) { - fmt.Println(c.GetStatus()) - }), "can't find controller") - }) - }) app.Run() } @@ -82,28 +37,35 @@ func NewController() *Controller { return &Controller{} } -func (v *Controller) Users(ctx web.Ctx) { - metrics.Gauge("users_request").Inc() - data := Model{ - data: []int64{1, 2, 3, 4}, - } - ctx.JSON(200, data) +func (c Controller) Name(ctx context.Context, userID int64) (name string, err error) { + //TODO implement me + panic("implement me") } -func (v *Controller) User(ctx web.Ctx) { - id, _ := ctx.Param("id").Int() // nolint: errcheck - ctx.String(200, "user id: %d", id) - logx.Info("user - %d", id) +func (c Controller) ByID(ctx context.Context, ID int64) (text bool, err error) { + //TODO implement me + panic("implement me") } -func (v *Controller) GetStatus() int { - return 200 +func (c Controller) List(ctx context.Context, userID int64) (text []types.Text, err error) { + //TODO implement me + panic("implement me") } -type Model struct { - data []int64 +func (c Controller) Root(ctx context.Context, userID int64, userName string) (status bool, err error) { + switch userID { + case 0: + return false, fmt.Errorf("userID 0") + default: + return true, nil + } } -func (m Model) MarshalJSON() ([]byte, error) { - return json.Marshal(m.data) +func (c Controller) Auth(ctx context.Context, userID int64, userName string) (status bool, err error) { + switch userID { + case 0: + return false, fmt.Errorf("userID 0") + default: + return true, nil + } } diff --git a/_example/web-server-gen/test.http b/_example/web-server-gen/test.http new file mode 100644 index 0000000..cfd39aa --- /dev/null +++ b/_example/web-server-gen/test.http @@ -0,0 +1,23 @@ +### +POST http://127.0.0.1:10000/rpc +Content-Type: application/json +Cookie: x-user-id=1 +x-user-id: 1 + +[ + { + "id": "1", + "method":"api.root", + "params": { + "userName": "xxxx" + } + }, + { + "id": "2", + "method":"api.auth", + "params": { + "userName": "xxxx" + } + } +] + diff --git a/_example/web-server-gen/transport/jsonrpc_handler.go b/_example/web-server-gen/transport/jsonrpc_handler.go index f18aebe..42f9da7 100644 --- a/_example/web-server-gen/transport/jsonrpc_handler.go +++ b/_example/web-server-gen/transport/jsonrpc_handler.go @@ -1,21 +1,40 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + // Code generated by goppy-cli wsg. DO NOT EDIT. package transport -import context "context" -import stdjson "encoding/json" -import web "go.osspkg.com/goppy/v3/web" -import fmt "fmt" +import ( + nethttp "net/http" + + web "go.osspkg.com/goppy/v3/web" + + time "time" + + context "context" + + stdjson "encoding/json" + + fmt "fmt" +) func (v *JSONRPCHandler) callApiRoot(ctx context.Context, webCtx web.Ctx, param stdjson.RawMessage) (any, error) { var req jsonrpcApiRootModelRequest err := stdjson.Unmarshal(param, &req) if err != nil { + err = fmt.Errorf("invalid request: %w", err) return nil, err } var res jsonrpcApiRootModelResponse var inUserID int64 //Module: cookie inUserID, _ = web.StrTo[int64](webCtx.Cookie().Get("x-user-id")) + if err != nil { + err = fmt.Errorf("invalid request: %w", err) + return nil, err + } res.Status, err = v.handleApi.Root(ctx, inUserID, req.UserName) if err != nil { @@ -27,12 +46,17 @@ func (v *JSONRPCHandler) callApiAuth(ctx context.Context, webCtx web.Ctx, param var req jsonrpcApiAuthModelRequest err := stdjson.Unmarshal(param, &req) if err != nil { + err = fmt.Errorf("invalid request: %w", err) return nil, err } var res jsonrpcApiAuthModelResponse var inUserID int64 //Module: header inUserID, _ = web.StrTo[int64](webCtx.Header().Get("x-user-id")) + if err != nil { + err = fmt.Errorf("invalid request: %w", err) + return nil, err + } var outStatus bool defer func() { @@ -42,6 +66,38 @@ func (v *JSONRPCHandler) callApiAuth(ctx context.Context, webCtx web.Ctx, param } webCtx.Header().Set("x-user-id", fmt.Sprintf("%v", outStatus)) + }() + defer func() { + //Module: cookie + if err != nil { + return + } + webCtx.Cookie().Set(&nethttp.Cookie{ + Name: "uid", + Value: fmt.Sprintf("%v", outStatus), + Path: "/", + Expires: time.Now().Add(86400 * time.Second), + Secure: true, + HttpOnly: true, + SameSite: nethttp.SameSiteStrictMode, + }) + + }() + defer func() { + //Module: cookie + if err != nil { + return + } + webCtx.Cookie().Set(&nethttp.Cookie{ + Name: "uid", + Value: fmt.Sprintf("%v", outStatus), + Path: "/", + Expires: time.Now().Add(86400 * time.Second), + Secure: true, + HttpOnly: true, + SameSite: nethttp.SameSiteStrictMode, + }) + }() outStatus, err = v.handleApi.Auth(ctx, inUserID, req.UserName) if err != nil { @@ -53,6 +109,7 @@ func (v *JSONRPCHandler) callUserName(ctx context.Context, webCtx web.Ctx, param var req jsonrpcUserNameModelRequest err := stdjson.Unmarshal(param, &req) if err != nil { + err = fmt.Errorf("invalid request: %w", err) return nil, err } var res jsonrpcUserNameModelResponse @@ -66,6 +123,7 @@ func (v *JSONRPCHandler) callPostByID(ctx context.Context, webCtx web.Ctx, param var req jsonrpcPostByIDModelRequest err := stdjson.Unmarshal(param, &req) if err != nil { + err = fmt.Errorf("invalid request: %w", err) return nil, err } var res jsonrpcPostByIDModelResponse @@ -79,6 +137,7 @@ func (v *JSONRPCHandler) callPostList(ctx context.Context, webCtx web.Ctx, param var req jsonrpcPostListModelRequest err := stdjson.Unmarshal(param, &req) if err != nil { + err = fmt.Errorf("invalid request: %w", err) return nil, err } var res jsonrpcPostListModelResponse diff --git a/_example/web-server-gen/transport/jsonrpc_model.go b/_example/web-server-gen/transport/jsonrpc_model.go index 6aeb1a1..4d9a8f3 100644 --- a/_example/web-server-gen/transport/jsonrpc_model.go +++ b/_example/web-server-gen/transport/jsonrpc_model.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + // Code generated by goppy-cli wsg. DO NOT EDIT. package transport diff --git a/_example/web-server-gen/transport/jsonrpc_model_base.go b/_example/web-server-gen/transport/jsonrpc_model_base.go index e318353..528d8fa 100644 --- a/_example/web-server-gen/transport/jsonrpc_model_base.go +++ b/_example/web-server-gen/transport/jsonrpc_model_base.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + // Code generated by goppy-cli wsg. DO NOT EDIT. package transport diff --git a/_example/web-server-gen/transport/jsonrpc_model_base_easyjson.go b/_example/web-server-gen/transport/jsonrpc_model_base_easyjson.go new file mode 100644 index 0000000..7e1804e --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_model_base_easyjson.go @@ -0,0 +1,471 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package transport + +import ( + json "encoding/json" + + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(in *jlexer.Lexer, out *errResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "message": + if in.IsNull() { + in.Skip() + } else { + out.Message = string(in.String()) + } + case "code": + if in.IsNull() { + in.Skip() + } else { + out.Code = int64(in.Int64()) + } + case "ctx": + if in.IsNull() { + in.Skip() + } else { + in.Delim('{') + if !in.IsDelim('}') { + out.Ctx = make(map[string]string) + } else { + out.Ctx = nil + } + for !in.IsDelim('}') { + key := string(in.String()) + in.WantColon() + var v1 string + if in.IsNull() { + in.Skip() + } else { + v1 = string(in.String()) + } + (out.Ctx)[key] = v1 + in.WantComma() + } + in.Delim('}') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(out *jwriter.Writer, in errResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"message\":" + out.RawString(prefix[1:]) + out.String(string(in.Message)) + } + { + const prefix string = ",\"code\":" + out.RawString(prefix) + out.Int64(int64(in.Code)) + } + if len(in.Ctx) != 0 { + const prefix string = ",\"ctx\":" + out.RawString(prefix) + { + out.RawByte('{') + v2First := true + for v2Name, v2Value := range in.Ctx { + if v2First { + v2First = false + } else { + out.RawByte(',') + } + out.String(string(v2Name)) + out.RawByte(':') + out.String(string(v2Value)) + } + out.RawByte('}') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v errResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v errResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *errResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *errResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(l, v) +} +func easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(in *jlexer.Lexer, out *bulkResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + in.Skip() + *out = nil + } else { + in.Delim('[') + if *out == nil { + if !in.IsDelim(']') { + *out = make(bulkResponse, 0, 1) + } else { + *out = bulkResponse{} + } + } else { + *out = (*out)[:0] + } + for !in.IsDelim(']') { + var v3 baseResponse + if in.IsNull() { + in.Skip() + } else { + (v3).UnmarshalEasyJSON(in) + } + *out = append(*out, v3) + in.WantComma() + } + in.Delim(']') + } + if isTopLevel { + in.Consumed() + } +} +func easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(out *jwriter.Writer, in bulkResponse) { + if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v4, v5 := range in { + if v4 > 0 { + out.RawByte(',') + } + (v5).MarshalEasyJSON(out) + } + out.RawByte(']') + } +} + +// MarshalJSON supports json.Marshaler interface +func (v bulkResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v bulkResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *bulkResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *bulkResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(l, v) +} +func easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(in *jlexer.Lexer, out *bulkRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + in.Skip() + *out = nil + } else { + in.Delim('[') + if *out == nil { + if !in.IsDelim(']') { + *out = make(bulkRequest, 0, 1) + } else { + *out = bulkRequest{} + } + } else { + *out = (*out)[:0] + } + for !in.IsDelim(']') { + var v6 baseRequest + if in.IsNull() { + in.Skip() + } else { + (v6).UnmarshalEasyJSON(in) + } + *out = append(*out, v6) + in.WantComma() + } + in.Delim(']') + } + if isTopLevel { + in.Consumed() + } +} +func easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(out *jwriter.Writer, in bulkRequest) { + if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v7, v8 := range in { + if v7 > 0 { + out.RawByte(',') + } + (v8).MarshalEasyJSON(out) + } + out.RawByte(']') + } +} + +// MarshalJSON supports json.Marshaler interface +func (v bulkRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v bulkRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *bulkRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *bulkRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(l, v) +} +func easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(in *jlexer.Lexer, out *baseResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "id": + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } + case "result": + if m, ok := out.Result.(easyjson.Unmarshaler); ok { + m.UnmarshalEasyJSON(in) + } else if m, ok := out.Result.(json.Unmarshaler); ok { + _ = m.UnmarshalJSON(in.Raw()) + } else { + out.Result = in.Interface() + } + case "error": + if in.IsNull() { + in.Skip() + out.Error = nil + } else { + if out.Error == nil { + out.Error = new(errResponse) + } + if in.IsNull() { + in.Skip() + } else { + (*out.Error).UnmarshalEasyJSON(in) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(out *jwriter.Writer, in baseResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.String(string(in.Id)) + } + if in.Result != nil { + const prefix string = ",\"result\":" + out.RawString(prefix) + if m, ok := in.Result.(easyjson.Marshaler); ok { + m.MarshalEasyJSON(out) + } else if m, ok := in.Result.(json.Marshaler); ok { + out.Raw(m.MarshalJSON()) + } else { + out.Raw(json.Marshal(in.Result)) + } + } + if in.Error != nil { + const prefix string = ",\"error\":" + out.RawString(prefix) + (*in.Error).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v baseResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v baseResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *baseResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *baseResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(l, v) +} +func easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(in *jlexer.Lexer, out *baseRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "id": + if in.IsNull() { + in.Skip() + } else { + out.Id = string(in.String()) + } + case "method": + if in.IsNull() { + in.Skip() + } else { + out.Method = string(in.String()) + } + case "params": + if in.IsNull() { + in.Skip() + } else { + if data := in.Raw(); in.Ok() { + in.AddError((out.Params).UnmarshalJSON(data)) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(out *jwriter.Writer, in baseRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.String(string(in.Id)) + } + { + const prefix string = ",\"method\":" + out.RawString(prefix) + out.String(string(in.Method)) + } + { + const prefix string = ",\"params\":" + out.RawString(prefix) + out.Raw((in.Params).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v baseRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v baseRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6850ac6fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *baseRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *baseRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6850ac6fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(l, v) +} diff --git a/_example/web-server-gen/transport/jsonrpc_model_easyjson.go b/_example/web-server-gen/transport/jsonrpc_model_easyjson.go new file mode 100644 index 0000000..ab2005a --- /dev/null +++ b/_example/web-server-gen/transport/jsonrpc_model_easyjson.go @@ -0,0 +1,740 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package transport + +import ( + json "encoding/json" + + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" + + types "go.osspkg.com/goppy/v3/_example/web-server-gen/types" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(in *jlexer.Lexer, out *jsonrpcUserNameModelResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "name": + if in.IsNull() { + in.Skip() + } else { + out.Name = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(out *jwriter.Writer, in jsonrpcUserNameModelResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"name\":" + out.RawString(prefix[1:]) + out.String(string(in.Name)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcUserNameModelResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcUserNameModelResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcUserNameModelResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcUserNameModelResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(in *jlexer.Lexer, out *jsonrpcUserNameModelRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "userID": + if in.IsNull() { + in.Skip() + } else { + out.UserID = int64(in.Int64()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(out *jwriter.Writer, in jsonrpcUserNameModelRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"userID\":" + out.RawString(prefix[1:]) + out.Int64(int64(in.UserID)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcUserNameModelRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcUserNameModelRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcUserNameModelRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcUserNameModelRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport1(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(in *jlexer.Lexer, out *jsonrpcPostListModelResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "text": + if in.IsNull() { + in.Skip() + out.Text = nil + } else { + in.Delim('[') + if out.Text == nil { + if !in.IsDelim(']') { + out.Text = make([]types.Text, 0, 2) + } else { + out.Text = []types.Text{} + } + } else { + out.Text = (out.Text)[:0] + } + for !in.IsDelim(']') { + var v1 types.Text + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTypes(in, &v1) + out.Text = append(out.Text, v1) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(out *jwriter.Writer, in jsonrpcPostListModelResponse) { + out.RawByte('{') + first := true + _ = first + if len(in.Text) != 0 { + const prefix string = ",\"text\":" + first = false + out.RawString(prefix[1:]) + { + out.RawByte('[') + for v2, v3 := range in.Text { + if v2 > 0 { + out.RawByte(',') + } + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTypes(out, v3) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcPostListModelResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcPostListModelResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcPostListModelResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcPostListModelResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport2(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTypes(in *jlexer.Lexer, out *types.Text) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "ID": + if in.IsNull() { + in.Skip() + } else { + out.ID = int64(in.Int64()) + } + case "Text": + if in.IsNull() { + in.Skip() + } else { + out.Text = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTypes(out *jwriter.Writer, in types.Text) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"ID\":" + out.RawString(prefix[1:]) + out.Int64(int64(in.ID)) + } + { + const prefix string = ",\"Text\":" + out.RawString(prefix) + out.String(string(in.Text)) + } + out.RawByte('}') +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(in *jlexer.Lexer, out *jsonrpcPostListModelRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "userID": + if in.IsNull() { + in.Skip() + } else { + out.UserID = int64(in.Int64()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(out *jwriter.Writer, in jsonrpcPostListModelRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"userID\":" + out.RawString(prefix[1:]) + out.Int64(int64(in.UserID)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcPostListModelRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcPostListModelRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcPostListModelRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcPostListModelRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport3(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(in *jlexer.Lexer, out *jsonrpcPostByIDModelResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "text": + if in.IsNull() { + in.Skip() + } else { + out.Text = bool(in.Bool()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(out *jwriter.Writer, in jsonrpcPostByIDModelResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"text\":" + out.RawString(prefix[1:]) + out.Bool(bool(in.Text)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcPostByIDModelResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcPostByIDModelResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcPostByIDModelResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcPostByIDModelResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport4(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport5(in *jlexer.Lexer, out *jsonrpcPostByIDModelRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "ID": + if in.IsNull() { + in.Skip() + } else { + out.ID = int64(in.Int64()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport5(out *jwriter.Writer, in jsonrpcPostByIDModelRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"ID\":" + out.RawString(prefix[1:]) + out.Int64(int64(in.ID)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcPostByIDModelRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport5(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcPostByIDModelRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport5(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcPostByIDModelRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport5(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcPostByIDModelRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport5(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport6(in *jlexer.Lexer, out *jsonrpcApiRootModelResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "status": + if in.IsNull() { + in.Skip() + } else { + out.Status = bool(in.Bool()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport6(out *jwriter.Writer, in jsonrpcApiRootModelResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.Bool(bool(in.Status)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcApiRootModelResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport6(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcApiRootModelResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport6(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcApiRootModelResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport6(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcApiRootModelResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport6(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport7(in *jlexer.Lexer, out *jsonrpcApiRootModelRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "userName": + if in.IsNull() { + in.Skip() + } else { + out.UserName = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport7(out *jwriter.Writer, in jsonrpcApiRootModelRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"userName\":" + out.RawString(prefix[1:]) + out.String(string(in.UserName)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcApiRootModelRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport7(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcApiRootModelRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport7(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcApiRootModelRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport7(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcApiRootModelRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport7(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport8(in *jlexer.Lexer, out *jsonrpcApiAuthModelResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport8(out *jwriter.Writer, in jsonrpcApiAuthModelResponse) { + out.RawByte('{') + first := true + _ = first + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcApiAuthModelResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport8(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcApiAuthModelResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport8(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcApiAuthModelResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport8(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcApiAuthModelResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport8(l, v) +} +func easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport9(in *jlexer.Lexer, out *jsonrpcApiAuthModelRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + switch key { + case "userName": + if in.IsNull() { + in.Skip() + } else { + out.UserName = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport9(out *jwriter.Writer, in jsonrpcApiAuthModelRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"userName\":" + out.RawString(prefix[1:]) + out.String(string(in.UserName)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v jsonrpcApiAuthModelRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport9(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonrpcApiAuthModelRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE35df14fEncodeGoOsspkgComGoppyV3ExampleWebServerGenTransport9(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *jsonrpcApiAuthModelRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport9(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonrpcApiAuthModelRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE35df14fDecodeGoOsspkgComGoppyV3ExampleWebServerGenTransport9(l, v) +} diff --git a/_example/web-server-gen/transport/jsonrpc_transport.go b/_example/web-server-gen/transport/jsonrpc_transport.go index 89c9d8f..70c449c 100644 --- a/_example/web-server-gen/transport/jsonrpc_transport.go +++ b/_example/web-server-gen/transport/jsonrpc_transport.go @@ -1,16 +1,29 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + // Code generated by goppy-cli wsg. DO NOT EDIT. package transport -import context "context" -import errors "errors" -import stdjson "encoding/json" -import syncing "go.osspkg.com/syncing" -import time "time" -import types "go.osspkg.com/goppy/v3/_example/web-server-gen/types" -import strings "strings" -import pool "go.osspkg.com/ioutils/pool" -import web "go.osspkg.com/goppy/v3/web" -import logx "go.osspkg.com/logx" +import ( + context "context" + errors "errors" + + web "go.osspkg.com/goppy/v3/web" + + time "time" + + strings "strings" + + syncing "go.osspkg.com/syncing" + + pool "go.osspkg.com/ioutils/pool" + + logx "go.osspkg.com/logx" + + types "go.osspkg.com/goppy/v3/_example/web-server-gen/types" +) var ( poolBaseResponse = pool.New[*syncing.Slice[baseResponse]](func() *syncing.Slice[baseResponse] { @@ -68,7 +81,7 @@ func (v *JSONRPCHandler) Up(ctx context.Context) error { Id: item.Id, } var err error - var result stdjson.Marshaler + var result any ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() result, err = v.callApiRoot(ctx, webCtx, item.Params) @@ -86,7 +99,7 @@ func (v *JSONRPCHandler) Up(ctx context.Context) error { Id: item.Id, } var err error - var result stdjson.Marshaler + var result any ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() result, err = v.callApiAuth(ctx, webCtx, item.Params) @@ -104,7 +117,7 @@ func (v *JSONRPCHandler) Up(ctx context.Context) error { Id: item.Id, } var err error - var result stdjson.Marshaler + var result any ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() result, err = v.callUserName(ctx, webCtx, item.Params) @@ -122,7 +135,7 @@ func (v *JSONRPCHandler) Up(ctx context.Context) error { Id: item.Id, } var err error - var result stdjson.Marshaler + var result any ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() result, err = v.callPostByID(ctx, webCtx, item.Params) @@ -140,7 +153,7 @@ func (v *JSONRPCHandler) Up(ctx context.Context) error { Id: item.Id, } var err error - var result stdjson.Marshaler + var result any ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() result, err = v.callPostList(ctx, webCtx, item.Params) @@ -163,7 +176,7 @@ func (v *JSONRPCHandler) Up(ctx context.Context) error { } } wg.Wait() - ctx.JSON(200, bulkResponse(res.Extract())) + webCtx.JSON(200, bulkResponse(res.Extract())) }) } diff --git a/_example/web-server-gen/types/custom.go b/_example/web-server-gen/types/custom.go new file mode 100644 index 0000000..6db6310 --- /dev/null +++ b/_example/web-server-gen/types/custom.go @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package types + +import ( + "context" + "fmt" +) + +func StdOut(ctx context.Context, arg []Text) error { + fmt.Println(arg) + return nil +} diff --git a/_example/web-server-gen/types/interfaces1.go b/_example/web-server-gen/types/interfaces1.go index 9727213..0e35170 100644 --- a/_example/web-server-gen/types/interfaces1.go +++ b/_example/web-server-gen/types/interfaces1.go @@ -18,7 +18,8 @@ type Api interface { // Auth // @wsg in.userID=header:x-user-id - // @wsg out.status=header:x-user-id + // @wsg out.status=header:x-user-id,cookie:uid + // @wsg out.status=cookie:uid Auth( ctx context.Context, userID int64, diff --git a/_example/web-server-gen/types/wsg.go b/_example/web-server-gen/types/wsg.go index 837f0c7..96ca6c8 100644 --- a/_example/web-server-gen/types/wsg.go +++ b/_example/web-server-gen/types/wsg.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package types //go:generate goppy wsg --mod=json-rpc,rest --pool=main,admin --out=./../transport diff --git a/apigen/builder/builder.go b/apigen/builder/builder.go index 5dc39f9..e5fd289 100644 --- a/apigen/builder/builder.go +++ b/apigen/builder/builder.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package builder import ( @@ -10,6 +15,7 @@ import ( "go.osspkg.com/bb" "go.osspkg.com/gogen/golang" "go.osspkg.com/gogen/types" + at "go.osspkg.com/goppy/v3/apigen/types" "go.osspkg.com/goppy/v3/console" ) @@ -63,7 +69,7 @@ func (b *Builder) WriteFile(fileName string, tok types.Token) error { fullPath := b.Out + "/" + fileName dir := filepath.Dir(fullPath) if err := os.MkdirAll(dir, 0766); err != nil { - return fmt.Errorf("mkdir %q: %v", dir, err) + return fmt.Errorf("mkdir %q: %w", dir, err) } console.Debugf("Writing file %s", fullPath) @@ -84,7 +90,7 @@ func (b *Builder) Build() error { err := mod.Build(b, at.GlobalMeta{PkgName: pkgName, Pool: b.Pool}, files) if err != nil { - return fmt.Errorf("build module %q: %v", name, err) + return fmt.Errorf("build module %q: %w", name, err) } } diff --git a/apigen/builder/common.go b/apigen/builder/common.go index e283ff2..906a25b 100644 --- a/apigen/builder/common.go +++ b/apigen/builder/common.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package builder import ( diff --git a/apigen/module/mod-json-rpc/build_base_model.go b/apigen/module/mod-json-rpc/build_base_model.go index 60df6a0..e88d5c3 100644 --- a/apigen/module/mod-json-rpc/build_base_model.go +++ b/apigen/module/mod-json-rpc/build_base_model.go @@ -1,9 +1,15 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package mod_json_rpc import ( "fmt" - . "go.osspkg.com/gogen/golang" + . "go.osspkg.com/gogen/golang" //nolint:staticcheck + at "go.osspkg.com/goppy/v3/apigen/types" "go.osspkg.com/goppy/v3/apigen/util" ) diff --git a/apigen/module/mod-json-rpc/build_transport.go b/apigen/module/mod-json-rpc/build_transport.go index c8d8a08..42de296 100644 --- a/apigen/module/mod-json-rpc/build_transport.go +++ b/apigen/module/mod-json-rpc/build_transport.go @@ -1,13 +1,19 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package mod_json_rpc import ( "strings" - . "go.osspkg.com/gogen/golang" + . "go.osspkg.com/gogen/golang" //nolint:staticcheck "go.osspkg.com/gogen/types" + "go.osspkg.com/syncing" + at "go.osspkg.com/goppy/v3/apigen/types" "go.osspkg.com/goppy/v3/apigen/util" - "go.osspkg.com/syncing" ) func (v Module) buildTransport(w at.Writer, m at.GlobalMeta, files []at.File) error { @@ -19,7 +25,7 @@ func (v Module) buildTransport(w at.Writer, m at.GlobalMeta, files []at.File) er list.Set("context", "context") list.Set("strings", "strings") list.Set("errors", "errors") - list.Set("stdjson", "encoding/json") + //list.Set("stdjson", "encoding/json") list.Set("syncing", "go.osspkg.com/syncing") list.Set("pool", "go.osspkg.com/ioutils/pool") list.Set("web", "go.osspkg.com/goppy/v3/web") @@ -127,7 +133,7 @@ func (v Module) buildUp(m at.GlobalMeta, files []at.File) types.Token { v.buildMethods(files), ), ID("wg").Op(".").ID("Wait").Call(), - ID("ctx").Op(".").ID("JSON").Bracket( + ID("webCtx").Op(".").ID("JSON").Bracket( Raw("200"), ID(modelBulkBaseRes).Bracket( ID("res").Op(".").ID("Extract").Bracket(), ), @@ -135,7 +141,7 @@ func (v Module) buildUp(m at.GlobalMeta, files []at.File) types.Token { ), ) - var poolTags []types.Token + var poolTags []types.Token //nolint:prealloc for _, s := range m.Pool { poolTags = append(poolTags, Text(s)) } @@ -169,7 +175,7 @@ func (v Module) buildMethods(files []at.File) *Tokens { ), Var().ID("err").Error(), - Var().ID("result").Pkg("stdjson").ID("Marshaler"), + Var().ID("result").Any(), List(ID("ctx"), ID("cancel")).Op(":=").Pkg("context").ID("WithTimeout").Call(ID( "ctx"), Raw("5").Op("*").Pkg("time").ID("Second")), Defer().ID("cancel").Call(), diff --git a/apigen/module/mod-json-rpc/build_transport_handlers.go b/apigen/module/mod-json-rpc/build_transport_handlers.go index 93d0e4c..8d01b14 100644 --- a/apigen/module/mod-json-rpc/build_transport_handlers.go +++ b/apigen/module/mod-json-rpc/build_transport_handlers.go @@ -1,14 +1,20 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package mod_json_rpc import ( "fmt" "go.osspkg.com/do" - . "go.osspkg.com/gogen/golang" + . "go.osspkg.com/gogen/golang" //nolint:staticcheck "go.osspkg.com/gogen/types" + "go.osspkg.com/syncing" + at "go.osspkg.com/goppy/v3/apigen/types" "go.osspkg.com/goppy/v3/apigen/util" - "go.osspkg.com/syncing" ) func (v Module) buildTransportHandlers(w at.Writer, m at.GlobalMeta, files []at.File) error { @@ -58,7 +64,15 @@ func (v Module) buildTransportHandler(imp at.ImportSetter, file at.File) []types ID("err").Op(":=").Pkg("stdjson").ID("Unmarshal").Bracket( ID("param"), Op("&").ID("req"), ), - If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), + If().ID("err").Op("!=").Nil().Block( + ID("err").Op("=").Pkg("fmt").ID("Errorf").Bracket( + Text("invalid request: %w"), + ID("err"), + ), + Return().List( + Nil(), + ID("err"), + )), Var().ID("res").ID(fmt.Sprintf(modelNameRes, object.Name+method.Name)), ) @@ -126,7 +140,6 @@ func (v Module) buildTransportHandler(imp at.ImportSetter, file at.File) []types outParamArgs := make(map[string]string) for _, p := range method.OutParams { vals, ok := method.Tags["out."+p.Name] - fmt.Println(vals, ok, p.Name) if !ok { continue } @@ -167,7 +180,7 @@ func (v Module) buildTransportHandler(imp at.ImportSetter, file at.File) []types ) for _, p := range method.OutParams { - switch { + switch { //nolint:staticcheck case p.Type == "error": handleOut = append(handleOut, ID("err")) default: @@ -187,7 +200,13 @@ func (v Module) buildTransportHandler(imp at.ImportSetter, file at.File) []types handleSrc = append(handleSrc, List(handleOut...).Op("=").ID("v").Op(".").ID("handle"+object.Name).Op(".").ID(method.Name).Call(handleIn...), - If().ID("err").Op("!=").Nil().Block(Return().List(Nil(), ID("err"))), + If().ID("err").Op("!=").Nil().Block( + //ID("err").Op("=").Pkg("fmt").ID("Errorf").Bracket( + // Text("encode request: %w"), + // ID("err"), + //), + Return().List(Nil(), ID("err")), + ), Return().List(ID("res"), Nil()), ) diff --git a/apigen/module/mod-json-rpc/build_transport_models.go b/apigen/module/mod-json-rpc/build_transport_models.go index d1eaaac..3b18c74 100644 --- a/apigen/module/mod-json-rpc/build_transport_models.go +++ b/apigen/module/mod-json-rpc/build_transport_models.go @@ -1,14 +1,20 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package mod_json_rpc import ( "fmt" "go.osspkg.com/do" - . "go.osspkg.com/gogen/golang" + . "go.osspkg.com/gogen/golang" //nolint:staticcheck "go.osspkg.com/gogen/types" + "go.osspkg.com/syncing" + at "go.osspkg.com/goppy/v3/apigen/types" "go.osspkg.com/goppy/v3/apigen/util" - "go.osspkg.com/syncing" ) func (v Module) buildTransportModels(w at.Writer, m at.GlobalMeta, files []at.File) error { diff --git a/apigen/module/mod-json-rpc/common.go b/apigen/module/mod-json-rpc/common.go index f376326..cea0d77 100644 --- a/apigen/module/mod-json-rpc/common.go +++ b/apigen/module/mod-json-rpc/common.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package mod_json_rpc import "strings" @@ -33,7 +38,7 @@ const ( func ignoreModelParam(tmpl, pt, pp string) bool { if tmpl == modelNameRes { - switch { + switch { //nolint:staticcheck case pt == "error": return true default: diff --git a/apigen/module/mod-json-rpc/module.go b/apigen/module/mod-json-rpc/module.go index 1840670..77ced70 100644 --- a/apigen/module/mod-json-rpc/module.go +++ b/apigen/module/mod-json-rpc/module.go @@ -1,7 +1,13 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package mod_json_rpc import ( "go.osspkg.com/errors" + at "go.osspkg.com/goppy/v3/apigen/types" ) diff --git a/apigen/module/mod-param-cookie/module.go b/apigen/module/mod-param-cookie/module.go index 4ef0000..e490ab1 100644 --- a/apigen/module/mod-param-cookie/module.go +++ b/apigen/module/mod-param-cookie/module.go @@ -1,9 +1,15 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package mod_param_cookie import ( "fmt" - . "go.osspkg.com/gogen/golang" + . "go.osspkg.com/gogen/golang" //nolint:staticcheck + at "go.osspkg.com/goppy/v3/apigen/types" ) @@ -33,6 +39,15 @@ func (v Module) generateIn(w at.Joiner, m at.ParamMeta, p at.Param) error { Call( ID("webCtx").Op(".").ID("Cookie").Call().Op(".").ID("Get").Call(Text(m.Value)), ), + If().ID("err").Op("!=").Nil().Block( + ID("err").Op("=").Pkg("fmt").ID("Errorf").Bracket( + Text("invalid request: %w"), + ID("err"), + ), + Return().List( + Nil(), + ID("err"), + )), ) return nil diff --git a/apigen/module/mod-param-header/module.go b/apigen/module/mod-param-header/module.go index 3eed2d9..12d7fdb 100644 --- a/apigen/module/mod-param-header/module.go +++ b/apigen/module/mod-param-header/module.go @@ -1,9 +1,15 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package mod_param_header import ( "fmt" - . "go.osspkg.com/gogen/golang" + . "go.osspkg.com/gogen/golang" //nolint:staticcheck + at "go.osspkg.com/goppy/v3/apigen/types" ) @@ -33,6 +39,15 @@ func (v Module) generateIn(w at.Joiner, m at.ParamMeta, p at.Param) error { Call( ID("webCtx").Op(".").ID("Header").Call().Op(".").ID("Get").Call(Text(m.Value)), ), + If().ID("err").Op("!=").Nil().Block( + ID("err").Op("=").Pkg("fmt").ID("Errorf").Bracket( + Text("invalid request: %w"), + ID("err"), + ), + Return().List( + Nil(), + ID("err"), + )), ) return nil diff --git a/apigen/parser/errors.go b/apigen/parser/errors.go index ba2c9f6..d246a6e 100644 --- a/apigen/parser/errors.go +++ b/apigen/parser/errors.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package parser import "go.osspkg.com/errors" diff --git a/apigen/parser/visitor.go b/apigen/parser/visitor.go index 8915130..d958708 100644 --- a/apigen/parser/visitor.go +++ b/apigen/parser/visitor.go @@ -17,11 +17,12 @@ import ( "go.osspkg.com/bb" "go.osspkg.com/do" "go.osspkg.com/errors" + "go.osspkg.com/ioutils/fs" + "go.osspkg.com/syncing" + "go.osspkg.com/goppy/v3/apigen/types" "go.osspkg.com/goppy/v3/apigen/util" "go.osspkg.com/goppy/v3/internal/global" - "go.osspkg.com/ioutils/fs" - "go.osspkg.com/syncing" ) type ( @@ -346,7 +347,7 @@ func exprToString(fset *token.FileSet, expr ast.Expr) string { func getTypeName(expr ast.Expr) string { switch t := expr.(type) { case *ast.Ident: - return fmt.Sprintf("%s", t.Name) + return t.Name case *ast.SelectorExpr: return fmt.Sprintf("%s.%s", getTypeName(t.X), t.Sel.Name) case *ast.StarExpr: diff --git a/apigen/types/common.go b/apigen/types/common.go index 8e3ff26..737cf49 100644 --- a/apigen/types/common.go +++ b/apigen/types/common.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package types import ( diff --git a/apigen/types/storage.go b/apigen/types/storage.go index f92f2c4..6b82afa 100644 --- a/apigen/types/storage.go +++ b/apigen/types/storage.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package types import ( diff --git a/apigen/types/type.go b/apigen/types/type.go index c18f9c4..a114e56 100644 --- a/apigen/types/type.go +++ b/apigen/types/type.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package types import "go.osspkg.com/gogen/types" diff --git a/apigen/util/util.go b/apigen/util/util.go index 345b09a..d3959aa 100644 --- a/apigen/util/util.go +++ b/apigen/util/util.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package util import ( diff --git a/go.mod b/go.mod index a3e9a0d..2b6550b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/lib/pq v1.11.2 - github.com/mailru/easyjson v0.9.1 + github.com/mailru/easyjson v0.9.2 github.com/mattn/go-sqlite3 v1.14.34 github.com/miekg/dns v1.1.72 github.com/oschwald/geoip2-golang v1.13.0 @@ -21,7 +21,7 @@ require ( go.osspkg.com/do v0.2.1 go.osspkg.com/errors v0.4.0 go.osspkg.com/events v0.3.0 - go.osspkg.com/gogen v0.1.1-0.20260301133223-79de3fce706d + go.osspkg.com/gogen v0.1.1 go.osspkg.com/ioutils v0.7.4 go.osspkg.com/logx v0.6.1 go.osspkg.com/network v0.6.0 @@ -32,10 +32,10 @@ require ( go.osspkg.com/validate v0.1.0 go.osspkg.com/xc v0.4.0 go.uber.org/automaxprocs v1.6.0 - golang.org/x/crypto v0.48.0 - golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa - golang.org/x/mod v0.33.0 - golang.org/x/oauth2 v0.35.0 + golang.org/x/crypto v0.49.0 + golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 + golang.org/x/mod v0.34.0 + golang.org/x/oauth2 v0.36.0 google.golang.org/protobuf v1.36.11 ) @@ -82,12 +82,10 @@ require ( go.etcd.io/bbolt v1.4.3 // indirect go.uber.org/mock v0.5.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.50.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect - golang.org/x/tools v0.42.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + golang.org/x/tools v0.43.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace go.osspkg.com/gogen => ../gogen diff --git a/go.sum b/go.sum index da3333f..ee73342 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= -github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= -github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M= +github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= @@ -142,6 +142,8 @@ go.osspkg.com/errors v0.4.0 h1:E17+WyUzTXEHCTxGm8lOMPOOojzHG1lsOuQtTVGoATQ= go.osspkg.com/errors v0.4.0/go.mod h1:s75ZovPemYtrCtRPVsbQNq9MgMbmLMK1NEypr+uwjXI= go.osspkg.com/events v0.3.0 h1:W2IngTsKs0BKYIglqhrETwtpo6uNSZXWRIt0/l7c6dY= go.osspkg.com/events v0.3.0/go.mod h1:Cjpx+qNM1y2MIAygFyZWYagTuRiYirmKppZQdaZumd4= +go.osspkg.com/gogen v0.1.1 h1:yOixEWv5jpidpq7ZG1yFsoVptVx4YVkfVav5OVBiMZ8= +go.osspkg.com/gogen v0.1.1/go.mod h1:BkoU7OC3LV/kmtjvCnQB1rF9hcK0rF3hZKoaZlP+Uv4= go.osspkg.com/ioutils v0.7.4 h1:Z8Y4jYYmLGWcvHZMLjbai+s48GmHxjMuepsxZcjF5X4= go.osspkg.com/ioutils v0.7.4/go.mod h1:pPIsTL1w1+ESrGTeHDCd6cKsujeWvschxGGP5FqrAqc= go.osspkg.com/logx v0.6.1 h1:JqHk1iFBOKnwO0dZtC7n4sO/0MkUD9c7eulmYEDCnao= @@ -168,27 +170,27 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/commands/wsg.go b/internal/commands/wsg.go index f211a0f..d9fb7c8 100644 --- a/internal/commands/wsg.go +++ b/internal/commands/wsg.go @@ -11,11 +11,12 @@ import ( "go.osspkg.com/do" "go.osspkg.com/errors" + "go.osspkg.com/ioutils/fs" + "go.osspkg.com/goppy/v3/apigen/builder" "go.osspkg.com/goppy/v3/apigen/parser" "go.osspkg.com/goppy/v3/console" "go.osspkg.com/goppy/v3/internal/global" - "go.osspkg.com/ioutils/fs" ) func CmdWSG() console.CommandGetter { diff --git a/internal/global/skip_files.go b/internal/global/skip_files.go index 6554e06..0b829bf 100644 --- a/internal/global/skip_files.go +++ b/internal/global/skip_files.go @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022-2026 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + package global import "strings" diff --git a/web/ctx.go b/web/ctx.go index 98be116..0787901 100644 --- a/web/ctx.go +++ b/web/ctx.go @@ -355,6 +355,10 @@ type UnStringer interface { func StrTo[T any](s string) (T, error) { var v T + if len(s) == 0 { + return v, nil + } + var err error switch p := any(&v).(type) {