├── .codecov.yml ├── .dockerignore ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── go.yml │ ├── issue-translator.yml │ ├── issues.yml │ ├── release.yaml │ └── reviewdog.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── SECURITY.md ├── code-of-conduct.md ├── core ├── bloom │ ├── bloom.go │ ├── bloom_test.go │ ├── setscript.lua │ └── testscript.lua ├── breaker │ ├── breaker.go │ ├── breaker_test.go │ ├── breakers.go │ ├── breakers_test.go │ ├── bucket.go │ ├── bucket_test.go │ ├── googlebreaker.go │ ├── googlebreaker_test.go │ ├── nopbreaker.go │ └── nopbreaker_test.go ├── cmdline │ ├── input.go │ └── input_test.go ├── codec │ ├── aesecb.go │ ├── aesecb_test.go │ ├── dh.go │ ├── dh_test.go │ ├── gzip.go │ ├── gzip_test.go │ ├── hmac.go │ ├── hmac_test.go │ ├── rsa.go │ └── rsa_test.go ├── collection │ ├── cache.go │ ├── cache_test.go │ ├── fifo.go │ ├── fifo_test.go │ ├── ring.go │ ├── ring_test.go │ ├── rollingwindow.go │ ├── rollingwindow_test.go │ ├── safemap.go │ ├── safemap_test.go │ ├── set.go │ ├── set_test.go │ ├── timingwheel.go │ └── timingwheel_test.go ├── color │ ├── color.go │ └── color_test.go ├── conf │ ├── config.go │ ├── config_test.go │ ├── options.go │ ├── properties.go │ ├── properties_test.go │ └── readme.md ├── contextx │ ├── unmarshaler.go │ ├── unmarshaler_test.go │ ├── valueonlycontext.go │ └── valueonlycontext_test.go ├── discov │ ├── accountregistry.go │ ├── accountregistry_test.go │ ├── clients.go │ ├── clients_test.go │ ├── config.go │ ├── config_test.go │ ├── internal │ │ ├── accountmanager.go │ │ ├── accountmanager_test.go │ │ ├── etcdclient.go │ │ ├── etcdclient_mock.go │ │ ├── listener.go │ │ ├── registry.go │ │ ├── registry_test.go │ │ ├── statewatcher.go │ │ ├── statewatcher_mock.go │ │ ├── statewatcher_test.go │ │ ├── updatelistener.go │ │ ├── updatelistener_mock.go │ │ └── vars.go │ ├── kubernetes │ │ ├── discov-namespace.yaml │ │ ├── etcd-statefulset.yaml │ │ └── etcd.yaml │ ├── publisher.go │ ├── publisher_test.go │ ├── subscriber.go │ └── subscriber_test.go ├── errorx │ ├── atomicerror.go │ ├── atomicerror_test.go │ ├── batcherror.go │ ├── batcherror_test.go │ ├── callchain.go │ ├── callchain_test.go │ ├── check.go │ ├── check_test.go │ ├── wrap.go │ └── wrap_test.go ├── executors │ ├── bulkexecutor.go │ ├── bulkexecutor_test.go │ ├── chunkexecutor.go │ ├── chunkexecutor_test.go │ ├── delayexecutor.go │ ├── delayexecutor_test.go │ ├── lessexecutor.go │ ├── lessexecutor_test.go │ ├── periodicalexecutor.go │ ├── periodicalexecutor_test.go │ └── vars.go ├── filex │ ├── file.go │ ├── file_test.go │ ├── lookup.go │ ├── lookup_test.go │ ├── progressscanner.go │ ├── progressscanner_test.go │ ├── rangereader.go │ └── rangereader_test.go ├── fs │ ├── files+polyfill.go │ ├── files.go │ ├── files_test.go │ ├── temps.go │ └── temps_test.go ├── fx │ ├── parallel.go │ ├── parallel_test.go │ ├── retry.go │ ├── retry_test.go │ ├── stream.go │ ├── stream_test.go │ ├── timeout.go │ └── timeout_test.go ├── hash │ ├── consistenthash.go │ ├── consistenthash_test.go │ ├── hash.go │ └── hash_test.go ├── iox │ ├── bufferpool.go │ ├── bufferpool_test.go │ ├── nopcloser.go │ ├── nopcloser_test.go │ ├── pipe.go │ ├── pipe_test.go │ ├── read.go │ ├── read_test.go │ ├── tee.go │ ├── tee_test.go │ ├── textfile.go │ ├── textfile_test.go │ ├── textlinescanner.go │ └── textlinescanner_test.go ├── jsonx │ ├── json.go │ └── json_test.go ├── lang │ ├── lang.go │ └── lang_test.go ├── limit │ ├── periodlimit.go │ ├── periodlimit_test.go │ ├── periodscript.lua │ ├── tokenlimit.go │ ├── tokenlimit_test.go │ └── tokenscript.lua ├── load │ ├── adaptiveshedder.go │ ├── adaptiveshedder_test.go │ ├── nopshedder.go │ ├── nopshedder_test.go │ ├── sheddergroup.go │ ├── sheddergroup_test.go │ ├── sheddingstat.go │ └── sheddingstat_test.go ├── logc │ ├── logs.go │ └── logs_test.go ├── logx │ ├── color.go │ ├── color_test.go │ ├── config.go │ ├── fields.go │ ├── fields_test.go │ ├── fs.go │ ├── lesslogger.go │ ├── lesslogger_test.go │ ├── lesswriter.go │ ├── lesswriter_test.go │ ├── limitedexecutor.go │ ├── limitedexecutor_test.go │ ├── logger.go │ ├── logs.go │ ├── logs_test.go │ ├── logtest │ │ ├── logtest.go │ │ └── logtest_test.go │ ├── logwriter.go │ ├── readme-cn.md │ ├── readme.md │ ├── richlogger.go │ ├── richlogger_test.go │ ├── rotatelogger.go │ ├── rotatelogger_test.go │ ├── syslog.go │ ├── syslog_test.go │ ├── util.go │ ├── util_test.go │ ├── vars.go │ ├── writer.go │ └── writer_test.go ├── mapping │ ├── fieldoptions.go │ ├── fieldoptions_test.go │ ├── jsonunmarshaler.go │ ├── jsonunmarshaler_test.go │ ├── marshaler.go │ ├── marshaler_test.go │ ├── tomlunmarshaler.go │ ├── tomlunmarshaler_test.go │ ├── unmarshaler.go │ ├── unmarshaler_test.go │ ├── utils.go │ ├── utils_test.go │ ├── valuer.go │ ├── valuer_test.go │ ├── yamlunmarshaler.go │ └── yamlunmarshaler_test.go ├── mathx │ ├── entropy.go │ ├── entropy_test.go │ ├── int.go │ ├── int_test.go │ ├── proba.go │ ├── proba_test.go │ ├── range.go │ ├── range_test.go │ ├── unstable.go │ └── unstable_test.go ├── metric │ ├── counter.go │ ├── counter_test.go │ ├── gauge.go │ ├── gauge_test.go │ ├── histogram.go │ ├── histogram_test.go │ ├── metric.go │ ├── summary.go │ └── summary_test.go ├── mr │ ├── mapreduce.go │ ├── mapreduce_fuzz_test.go │ ├── mapreduce_fuzzcase_test.go │ ├── mapreduce_test.go │ ├── readme-cn.md │ └── readme.md ├── naming │ └── namer.go ├── netx │ ├── ip.go │ └── ip_test.go ├── proc │ ├── env.go │ ├── env_test.go │ ├── goroutines.go │ ├── goroutines_test.go │ ├── process.go │ ├── process_test.go │ ├── profile+polyfill.go │ ├── profile.go │ ├── profile_test.go │ ├── shutdown+polyfill.go │ ├── shutdown.go │ ├── shutdown_test.go │ ├── signals+polyfill.go │ ├── signals.go │ ├── signals_test.go │ ├── stopper.go │ └── stopper_test.go ├── prof │ ├── profilecenter.go │ ├── profilecenter_test.go │ ├── profiler.go │ ├── profiler_test.go │ ├── runtime.go │ └── runtime_test.go ├── prometheus │ ├── agent.go │ └── config.go ├── queue │ ├── balancedpusher.go │ ├── balancedpusher_test.go │ ├── consumer.go │ ├── messagequeue.go │ ├── multipusher.go │ ├── multipusher_test.go │ ├── producer.go │ ├── queue.go │ ├── queue_test.go │ ├── util.go │ └── util_test.go ├── rescue │ ├── recover.go │ └── recover_test.go ├── search │ ├── tree.go │ ├── tree_debug.go │ └── tree_test.go ├── service │ ├── serviceconf.go │ ├── serviceconf_test.go │ ├── servicegroup.go │ └── servicegroup_test.go ├── stat │ ├── alert+polyfill.go │ ├── alert.go │ ├── alert_test.go │ ├── internal │ │ ├── cgroup_linux.go │ │ ├── cgroup_linux_test.go │ │ ├── cpu_linux.go │ │ ├── cpu_linux_test.go │ │ └── cpu_other.go │ ├── metrics.go │ ├── metrics_test.go │ ├── remotewriter.go │ ├── remotewriter_test.go │ ├── task.go │ ├── topk.go │ ├── topk_test.go │ ├── usage.go │ └── usage_test.go ├── stores │ ├── builder │ │ ├── builder.go │ │ └── builder_test.go │ ├── cache │ │ ├── cache.go │ │ ├── cache_test.go │ │ ├── cacheconf.go │ │ ├── cachenode.go │ │ ├── cachenode_test.go │ │ ├── cacheopt.go │ │ ├── cacheopt_test.go │ │ ├── cachestat.go │ │ ├── cachestat_test.go │ │ ├── cleaner.go │ │ ├── cleaner_test.go │ │ ├── config.go │ │ ├── util.go │ │ └── util_test.go │ ├── dbtest │ │ └── sql.go │ ├── kv │ │ ├── config.go │ │ ├── store.go │ │ └── store_test.go │ ├── mon │ │ ├── bulkinserter.go │ │ ├── bulkinserter_test.go │ │ ├── clientmanager.go │ │ ├── clientmanager_test.go │ │ ├── collection.go │ │ ├── collection_test.go │ │ ├── model.go │ │ ├── model_test.go │ │ ├── options.go │ │ ├── options_test.go │ │ ├── trace.go │ │ ├── util.go │ │ └── util_test.go │ ├── monc │ │ ├── cachedmodel.go │ │ └── cachedmodel_test.go │ ├── postgres │ │ ├── postgresql.go │ │ └── postgresql_test.go │ ├── redis │ │ ├── breakerhook.go │ │ ├── breakerhook_test.go │ │ ├── conf.go │ │ ├── conf_test.go │ │ ├── delscript.lua │ │ ├── durationhook.go │ │ ├── durationhook_test.go │ │ ├── lockscript.lua │ │ ├── metrics.go │ │ ├── metrics_test.go │ │ ├── redis.go │ │ ├── redis_test.go │ │ ├── redisblockingnode.go │ │ ├── redisblockingnode_test.go │ │ ├── redisclientmanager.go │ │ ├── redisclustermanager.go │ │ ├── redisclustermanager_test.go │ │ ├── redislock.go │ │ ├── redislock_test.go │ │ ├── redistest │ │ │ └── redistest.go │ │ ├── scriptcache.go │ │ └── scriptcache_test.go │ ├── sqlc │ │ ├── cachedsql.go │ │ └── cachedsql_test.go │ └── sqlx │ │ ├── bulkinserter.go │ │ ├── bulkinserter_test.go │ │ ├── errors.go │ │ ├── metrics.go │ │ ├── metrics_test.go │ │ ├── mysql.go │ │ ├── mysql_test.go │ │ ├── orm.go │ │ ├── orm_test.go │ │ ├── sqlconn.go │ │ ├── sqlconn_test.go │ │ ├── sqlmanager.go │ │ ├── stmt.go │ │ ├── stmt_test.go │ │ ├── trace.go │ │ ├── tx.go │ │ ├── tx_test.go │ │ ├── utils.go │ │ └── utils_test.go ├── stringx │ ├── node.go │ ├── node_fuzz_test.go │ ├── node_test.go │ ├── random.go │ ├── random_test.go │ ├── replacer.go │ ├── replacer_fuzz_test.go │ ├── replacer_test.go │ ├── strings.go │ ├── strings_test.go │ ├── trie.go │ └── trie_test.go ├── syncx │ ├── atomicbool.go │ ├── atomicbool_test.go │ ├── atomicduration.go │ ├── atomicduration_test.go │ ├── atomicfloat64.go │ ├── atomicfloat64_test.go │ ├── barrier.go │ ├── barrier_test.go │ ├── cond.go │ ├── cond_test.go │ ├── donechan.go │ ├── donechan_test.go │ ├── immutableresource.go │ ├── immutableresource_test.go │ ├── limit.go │ ├── limit_test.go │ ├── lockedcalls.go │ ├── lockedcalls_test.go │ ├── managedresource.go │ ├── managedresource_test.go │ ├── once.go │ ├── once_test.go │ ├── onceguard.go │ ├── onceguard_test.go │ ├── pool.go │ ├── pool_test.go │ ├── refresource.go │ ├── refresource_test.go │ ├── resourcemanager.go │ ├── resourcemanager_test.go │ ├── singleflight.go │ ├── singleflight_test.go │ ├── spinlock.go │ ├── spinlock_test.go │ ├── timeoutlimit.go │ └── timeoutlimit_test.go ├── sysx │ ├── automaxprocs.go │ ├── host.go │ └── host_test.go ├── threading │ ├── routinegroup.go │ ├── routinegroup_test.go │ ├── routines.go │ ├── routines_test.go │ ├── stablerunner.go │ ├── stablerunner_test.go │ ├── taskrunner.go │ ├── taskrunner_test.go │ ├── workergroup.go │ └── workergroup_test.go ├── timex │ ├── relativetime.go │ ├── relativetime_test.go │ ├── repr.go │ ├── repr_test.go │ ├── ticker.go │ └── ticker_test.go ├── trace │ ├── agent.go │ ├── agent_test.go │ ├── attributes.go │ ├── attributes_test.go │ ├── config.go │ ├── message.go │ ├── message_test.go │ ├── propagation.go │ ├── resource.go │ ├── tracer.go │ ├── tracer_test.go │ ├── tracetest │ │ └── tracetest.go │ ├── utils.go │ ├── utils_test.go │ └── vars.go ├── utils │ ├── times.go │ ├── times_test.go │ ├── uuid.go │ ├── uuid_test.go │ ├── version.go │ └── version_test.go └── validation │ └── validator.go ├── gateway ├── config.go ├── internal │ ├── descriptorsource.go │ ├── descriptorsource_test.go │ ├── eventhandler.go │ ├── eventhandler_test.go │ ├── headerprocessor.go │ ├── headerprocessor_test.go │ ├── requestparser.go │ ├── requestparser_test.go │ ├── timeout.go │ └── timeout_test.go ├── readme.md ├── server.go └── server_test.go ├── go.mod ├── go.sum ├── internal ├── devserver │ ├── config.go │ └── server.go ├── encoding │ ├── encoding.go │ └── encoding_test.go ├── health │ ├── health.go │ └── health_test.go ├── mock │ ├── deposit.pb.go │ ├── deposit.proto │ └── depositserver.go └── trace │ ├── trace.go │ └── trace_test.go ├── readme-cn.md ├── readme.md ├── rest ├── chain │ ├── chain.go │ └── chain_test.go ├── config.go ├── engine.go ├── engine_test.go ├── handler │ ├── authhandler.go │ ├── authhandler_test.go │ ├── breakerhandler.go │ ├── breakerhandler_test.go │ ├── contentsecurityhandler.go │ ├── contentsecurityhandler_test.go │ ├── cryptionhandler.go │ ├── cryptionhandler_test.go │ ├── gunziphandler.go │ ├── gunziphandler_test.go │ ├── loghandler.go │ ├── loghandler_test.go │ ├── maxbyteshandler.go │ ├── maxbyteshandler_test.go │ ├── maxconnshandler.go │ ├── maxconnshandler_test.go │ ├── metrichandler.go │ ├── metrichandler_test.go │ ├── prometheushandler.go │ ├── prometheushandler_test.go │ ├── recoverhandler.go │ ├── recoverhandler_test.go │ ├── sheddinghandler.go │ ├── sheddinghandler_test.go │ ├── timeouthandler.go │ ├── timeouthandler_test.go │ ├── tracehandler.go │ └── tracehandler_test.go ├── httpc │ ├── internal │ │ ├── interceptor.go │ │ ├── loginterceptor.go │ │ ├── loginterceptor_test.go │ │ ├── metricsinterceptor.go │ │ └── metricsinterceptor_test.go │ ├── requests.go │ ├── requests_test.go │ ├── responses.go │ ├── responses_test.go │ ├── service.go │ ├── service_test.go │ └── vars.go ├── httpx │ ├── requests.go │ ├── requests_test.go │ ├── responses.go │ ├── responses_test.go │ ├── router.go │ ├── util.go │ ├── util_test.go │ └── vars.go ├── internal │ ├── cors │ │ ├── handlers.go │ │ └── handlers_test.go │ ├── encoding │ │ ├── parser.go │ │ └── parser_test.go │ ├── errcode │ │ ├── grpc.go │ │ └── grpc_test.go │ ├── header │ │ └── headers.go │ ├── log.go │ ├── log_test.go │ ├── response │ │ ├── headeronceresponsewriter.go │ │ ├── headeronceresponsewriter_test.go │ │ ├── withcoderesponsewriter.go │ │ └── withcoderesponsewriter_test.go │ ├── security │ │ ├── contentsecurity.go │ │ └── contentsecurity_test.go │ ├── starter.go │ └── starter_test.go ├── pathvar │ ├── params.go │ └── params_test.go ├── router │ ├── patrouter.go │ └── patrouter_test.go ├── server.go ├── server_test.go ├── token │ ├── tokenparser.go │ └── tokenparser_test.go └── types.go ├── tools └── goctl │ ├── .dockerignore │ ├── Dockerfile │ ├── Makefile │ ├── api │ ├── apigen │ │ ├── api.tpl │ │ ├── gen.go │ │ ├── template.go │ │ └── util.go │ ├── cmd.go │ ├── dartgen │ │ ├── format.go │ │ ├── gen.go │ │ ├── genapi.go │ │ ├── gendata.go │ │ ├── genvars.go │ │ ├── util.go │ │ ├── util_test.go │ │ └── vars.go │ ├── docgen │ │ ├── doc.go │ │ ├── gen.go │ │ └── markdown.tpl │ ├── format │ │ ├── format.go │ │ └── format_test.go │ ├── gogen │ │ ├── config.tpl │ │ ├── etc.tpl │ │ ├── gen.go │ │ ├── gen_test.go │ │ ├── genconfig.go │ │ ├── genetc.go │ │ ├── genhandlers.go │ │ ├── genlogic.go │ │ ├── genmain.go │ │ ├── genmiddleware.go │ │ ├── genroutes.go │ │ ├── gensvc.go │ │ ├── gentypes.go │ │ ├── handler.tpl │ │ ├── logic.tpl │ │ ├── main.tpl │ │ ├── middleware.tpl │ │ ├── svc.tpl │ │ ├── template.go │ │ ├── template_test.go │ │ ├── testdata │ │ │ ├── anonymous_annotation.api │ │ │ ├── another_import_api.api │ │ │ ├── ap_ino_info.api │ │ │ ├── api_has_middleware.api │ │ │ ├── api_has_no_request.api │ │ │ ├── api_jwt.api │ │ │ ├── api_jwt_with_middleware.api │ │ │ ├── api_route_test.api │ │ │ ├── has_comment_api_test.api │ │ │ ├── has_import_api.api │ │ │ ├── has_inline_no_exist_test.api │ │ │ ├── import_api.api │ │ │ ├── import_twice.api │ │ │ ├── invalid_api_file.api │ │ │ ├── nest_type_api.api │ │ │ ├── no_struct_tag_api.api │ │ │ ├── test_api_template.api │ │ │ └── test_multi_service_template.api │ │ ├── types.tpl │ │ ├── util.go │ │ └── vars.go │ ├── javagen │ │ ├── bool.tpl │ │ ├── component.tpl │ │ ├── gen.go │ │ ├── gencomponents.go │ │ ├── genpacket.go │ │ ├── getset.tpl │ │ ├── packet.tpl │ │ ├── util.go │ │ └── vars.go │ ├── ktgen │ │ ├── api.tpl │ │ ├── apibase.tpl │ │ ├── cmd.go │ │ ├── funcs.go │ │ └── gen.go │ ├── new │ │ ├── api.tpl │ │ ├── newservice.go │ │ └── template.go │ ├── parser │ │ ├── g4 │ │ │ ├── ApiLexer.g4 │ │ │ ├── ApiParser.g4 │ │ │ ├── ast │ │ │ │ ├── api.go │ │ │ │ ├── apiparser.go │ │ │ │ ├── apiparser_test.go │ │ │ │ ├── ast.go │ │ │ │ ├── import.go │ │ │ │ ├── importstack.go │ │ │ │ ├── info.go │ │ │ │ ├── kv.go │ │ │ │ ├── placeholder.go │ │ │ │ ├── service.go │ │ │ │ ├── syntax.go │ │ │ │ └── type.go │ │ │ ├── gen │ │ │ │ └── api │ │ │ │ │ ├── apiparser_base_visitor.go │ │ │ │ │ ├── apiparser_lexer.go │ │ │ │ │ ├── apiparser_parser.go │ │ │ │ │ ├── apiparser_parser1.go │ │ │ │ │ ├── apiparser_parser2.go │ │ │ │ │ ├── apiparser_parser3.go │ │ │ │ │ ├── apiparser_parser4.go │ │ │ │ │ ├── apiparser_parser5.go │ │ │ │ │ ├── apiparser_parser6.go │ │ │ │ │ ├── apiparser_parser7.go │ │ │ │ │ ├── apiparser_parser8.go │ │ │ │ │ ├── apiparser_visitor.go │ │ │ │ │ ├── baseparser.go │ │ │ │ │ ├── baseparser_test.go │ │ │ │ │ └── file_splitor_test.go │ │ │ ├── test.api │ │ │ └── test │ │ │ │ ├── apiparser_test.go │ │ │ │ ├── apis │ │ │ │ ├── empty.api │ │ │ │ ├── example.api │ │ │ │ ├── info.api │ │ │ │ ├── service.api │ │ │ │ ├── syntax.api │ │ │ │ ├── test.api │ │ │ │ └── types.api │ │ │ │ ├── ast_test.go │ │ │ │ ├── grammar_test.go │ │ │ │ ├── import_test.go │ │ │ │ ├── info_test.go │ │ │ │ ├── service_test.go │ │ │ │ ├── syntax_test.go │ │ │ │ └── type_test.go │ │ ├── parser.go │ │ ├── parser_test.go │ │ ├── readme.md │ │ └── testdata │ │ │ └── test.api │ ├── spec │ │ ├── example_test.go │ │ ├── fn.go │ │ ├── name.go │ │ ├── spec.go │ │ ├── tags.go │ │ └── validate.go │ ├── tsgen │ │ ├── components.tpl │ │ ├── gen.go │ │ ├── gencomponents.go │ │ ├── genpacket.go │ │ ├── genrequest.go │ │ ├── handler.tpl │ │ ├── request.ts │ │ ├── util.go │ │ ├── util_test.go │ │ └── vars.go │ ├── util │ │ ├── case.go │ │ └── util.go │ └── validate │ │ └── validate.go │ ├── bug │ ├── bug.go │ ├── cmd.go │ ├── env.go │ └── issue.go │ ├── cmd │ ├── root.go │ ├── usage.go │ └── usage.tpl │ ├── compare │ ├── cmd │ │ └── cmd.go │ ├── compare.go │ ├── make.sh │ └── testdata │ │ ├── kotlin.api │ │ ├── testcase.go │ │ ├── testdata.go │ │ ├── unformat.api │ │ └── user.sql │ ├── config │ ├── cmd.go │ ├── config.go │ ├── default.yaml │ └── readme.md │ ├── docker │ ├── cmd.go │ ├── docker.go │ ├── docker.tpl │ └── template.go │ ├── env │ ├── check.go │ ├── cmd.go │ ├── env.go │ └── install.go │ ├── example │ └── rpc │ │ ├── hello.proto │ │ ├── hello │ │ ├── client │ │ │ └── greet │ │ │ │ └── greet.go │ │ ├── etc │ │ │ └── hello.yaml │ │ ├── hello.go │ │ ├── internal │ │ │ ├── config │ │ │ │ └── config.go │ │ │ ├── logic │ │ │ │ └── greet │ │ │ │ │ └── sayhellologic.go │ │ │ ├── server │ │ │ │ └── greet │ │ │ │ │ └── greetserver.go │ │ │ └── svc │ │ │ │ └── servicecontext.go │ │ └── pb │ │ │ └── hello │ │ │ ├── hello.pb.go │ │ │ └── hello_grpc.pb.go │ │ ├── hi.proto │ │ ├── hi │ │ ├── client │ │ │ ├── event │ │ │ │ └── event.go │ │ │ └── greet │ │ │ │ └── greet.go │ │ ├── etc │ │ │ └── hi.yaml │ │ ├── hi.go │ │ ├── internal │ │ │ ├── config │ │ │ │ └── config.go │ │ │ ├── logic │ │ │ │ ├── event │ │ │ │ │ └── askquestionlogic.go │ │ │ │ └── greet │ │ │ │ │ ├── sayhellologic.go │ │ │ │ │ └── sayhilogic.go │ │ │ ├── server │ │ │ │ ├── event │ │ │ │ │ └── eventserver.go │ │ │ │ └── greet │ │ │ │ │ └── greetserver.go │ │ │ └── svc │ │ │ │ └── servicecontext.go │ │ └── pb │ │ │ └── hi │ │ │ ├── hi.pb.go │ │ │ └── hi_grpc.pb.go │ │ ├── multiple_rpc_service_generate.sh │ │ └── single_rpc_service_generate.sh │ ├── gateway │ ├── cmd.go │ ├── conf.yml │ ├── gateway.tpl │ └── template.go │ ├── go.mod │ ├── go.sum │ ├── goctl.go │ ├── internal │ ├── cobrax │ │ └── cobrax.go │ ├── errorx │ │ ├── errorx._test.go │ │ └── errorx.go │ ├── flags │ │ ├── default_en.json │ │ ├── flags.go │ │ └── flags_test.go │ └── version │ │ ├── version.go │ │ └── version_test.go │ ├── kube │ ├── cmd.go │ ├── deployment.tpl │ ├── job.tpl │ └── kube.go │ ├── migrate │ ├── cancel+polyfill.go │ ├── cancel.go │ ├── cmd.go │ ├── migrate.go │ ├── mod.go │ ├── proxy.go │ └── version.go │ ├── model │ ├── cmd.go │ ├── mongo │ │ ├── generate │ │ │ ├── generate.go │ │ │ ├── generate_test.go │ │ │ ├── template.go │ │ │ └── template_test.go │ │ ├── mongo.go │ │ ├── readme.md │ │ └── template │ │ │ ├── error.tpl │ │ │ ├── model.tpl │ │ │ ├── model_custom.tpl │ │ │ ├── template.go │ │ │ └── types.tpl │ └── sql │ │ ├── CHANGELOG.md │ │ ├── README.MD │ │ ├── builderx │ │ └── builder.go │ │ ├── command │ │ ├── command.go │ │ ├── command_test.go │ │ ├── migrationnotes │ │ │ ├── migrationnotes.go │ │ │ ├── v1.3.4.go │ │ │ └── v1.3.4_test.go │ │ └── testdata │ │ │ └── user.sql │ │ ├── converter │ │ ├── types.go │ │ └── types_test.go │ │ ├── example │ │ ├── makefile │ │ └── sql │ │ │ └── user.sql │ │ ├── gen │ │ ├── customized.go │ │ ├── delete.go │ │ ├── field.go │ │ ├── findone.go │ │ ├── findonebyfield.go │ │ ├── gen.go │ │ ├── gen_test.go │ │ ├── imports.go │ │ ├── insert.go │ │ ├── keys.go │ │ ├── keys_test.go │ │ ├── new.go │ │ ├── tablename.go │ │ ├── tag.go │ │ ├── template.go │ │ ├── template_test.go │ │ ├── testdata │ │ │ └── user.sql │ │ ├── types.go │ │ ├── update.go │ │ └── vars.go │ │ ├── model │ │ ├── infoschemamodel.go │ │ └── postgresqlmodel.go │ │ ├── parser │ │ ├── parser.go │ │ ├── parser_test.go │ │ └── testdata │ │ │ └── user.sql │ │ ├── template │ │ ├── template.go │ │ └── tpl │ │ │ ├── customized.tpl │ │ │ ├── delete.tpl │ │ │ ├── err.tpl │ │ │ ├── field.tpl │ │ │ ├── find-one-by-field-extra-method.tpl │ │ │ ├── find-one-by-field.tpl │ │ │ ├── find-one.tpl │ │ │ ├── import-no-cache.tpl │ │ │ ├── import.tpl │ │ │ ├── insert.tpl │ │ │ ├── interface-delete.tpl │ │ │ ├── interface-find-one-by-field.tpl │ │ │ ├── interface-find-one.tpl │ │ │ ├── interface-insert.tpl │ │ │ ├── interface-update.tpl │ │ │ ├── model-new.tpl │ │ │ ├── model.tpl │ │ │ ├── table-name.tpl │ │ │ ├── tag.tpl │ │ │ ├── types.tpl │ │ │ ├── update.tpl │ │ │ └── var.tpl │ │ ├── test │ │ ├── model │ │ │ ├── model_test.go │ │ │ ├── studentmodel.go │ │ │ ├── usermodel.go │ │ │ └── vars.go │ │ ├── orm.go │ │ ├── sqlconn.go │ │ ├── stmt.go │ │ └── utils.go │ │ └── util │ │ ├── match_test.go │ │ ├── matcher.go │ │ ├── newline.go │ │ ├── slice.go │ │ ├── studeat.sql │ │ ├── student.sql │ │ ├── sub │ │ └── sub.sql │ │ ├── xx.sql │ │ └── xx.sql1 │ ├── pkg │ ├── collection │ │ ├── sortedmap.go │ │ └── sortedmap_test.go │ ├── downloader │ │ └── downloader.go │ ├── env │ │ └── env.go │ ├── goctl │ │ └── goctl.go │ ├── golang │ │ ├── bin.go │ │ ├── format.go │ │ ├── install.go │ │ └── path.go │ ├── parser │ │ └── api │ │ │ ├── assertx │ │ │ └── error.go │ │ │ ├── ast │ │ │ ├── ast.go │ │ │ ├── comment.go │ │ │ ├── importstatement.go │ │ │ ├── infostatement.go │ │ │ ├── kvexpression.go │ │ │ ├── print.go │ │ │ ├── servicestatement.go │ │ │ ├── syntaxstatement.go │ │ │ ├── typestatement.go │ │ │ └── writer.go │ │ │ ├── format │ │ │ ├── format.go │ │ │ ├── format_test.go │ │ │ └── testdata │ │ │ │ ├── expected_service.api │ │ │ │ ├── expected_type_struct_group.api │ │ │ │ ├── expected_type_struct_lit.api │ │ │ │ ├── test_format.api │ │ │ │ ├── test_service.api │ │ │ │ ├── test_type_struct_group.api │ │ │ │ └── test_type_struct_lit.api │ │ │ ├── importstack │ │ │ └── importstack.go │ │ │ ├── parser │ │ │ ├── analyzer.go │ │ │ ├── analyzer_test.go │ │ │ ├── api.go │ │ │ ├── error.go │ │ │ ├── filter.go │ │ │ ├── parser.go │ │ │ ├── parser_test.go │ │ │ └── testdata │ │ │ │ ├── atdoc_group_test.api │ │ │ │ ├── atdoc_literal_test.api │ │ │ │ ├── athandler_test.api │ │ │ │ ├── atserver_test.api │ │ │ │ ├── base.api │ │ │ │ ├── base │ │ │ │ ├── request.api │ │ │ │ └── response.api │ │ │ │ ├── base1.api │ │ │ │ ├── base2.api │ │ │ │ ├── comment_test.api │ │ │ │ ├── duplicate_type.api │ │ │ │ ├── example.api │ │ │ │ ├── example_base.api │ │ │ │ ├── example_base1.api │ │ │ │ ├── example_base2.api │ │ │ │ ├── import_group_test.api │ │ │ │ ├── import_literal_test.api │ │ │ │ ├── info_test.api │ │ │ │ ├── invalid.api │ │ │ │ ├── link_import.api │ │ │ │ ├── service_test.api │ │ │ │ ├── test.api │ │ │ │ └── test_format.api │ │ │ ├── placeholder │ │ │ └── placeholder.go │ │ │ ├── scanner │ │ │ ├── scanner.go │ │ │ ├── scanner_test.go │ │ │ └── test.api │ │ │ └── token │ │ │ ├── position.go │ │ │ └── token.go │ ├── protoc │ │ └── protoc.go │ ├── protocgengo │ │ └── protocgengo.go │ └── protocgengogrpc │ │ └── protocgengogrpc.go │ ├── plugin │ ├── demo │ │ └── goctlplugin.go │ ├── plugin.go │ └── plugin_test.go │ ├── quickstart │ ├── cmd.go │ ├── idl │ │ ├── api.yaml │ │ ├── apilogic.tpl │ │ ├── greet.api │ │ ├── greet.proto │ │ ├── rpc.yaml │ │ └── svc.tpl │ ├── micro.go │ ├── mono.go │ ├── quickstart.go │ └── run.go │ ├── readme-cn.md │ ├── readme.md │ ├── rpc │ ├── README.md │ ├── cli │ │ ├── cli.go │ │ └── zrpc.go │ ├── cmd.go │ ├── execx │ │ └── execx.go │ ├── generator │ │ ├── base │ │ │ └── common.proto │ │ ├── call.tpl │ │ ├── config.tpl │ │ ├── etc.tpl │ │ ├── gen.go │ │ ├── gen_test.go │ │ ├── gencall.go │ │ ├── genconfig.go │ │ ├── generator.go │ │ ├── genetc.go │ │ ├── genlogic.go │ │ ├── genmain.go │ │ ├── genpb.go │ │ ├── genpb_test.go │ │ ├── genserver.go │ │ ├── gensvc.go │ │ ├── logic.tpl │ │ ├── main.tpl │ │ ├── mkdir.go │ │ ├── prototmpl.go │ │ ├── prototmpl_test.go │ │ ├── rpc.tpl │ │ ├── server.tpl │ │ ├── svc.tpl │ │ ├── template.go │ │ ├── template_test.go │ │ └── test.proto │ └── parser │ │ ├── comment.go │ │ ├── import.go │ │ ├── message.go │ │ ├── option.go │ │ ├── package.go │ │ ├── parser.go │ │ ├── parser_test.go │ │ ├── proto.go │ │ ├── rpc.go │ │ ├── service.go │ │ ├── stream.proto │ │ ├── test.proto │ │ ├── test_invalid_request.proto │ │ ├── test_invalid_response.proto │ │ ├── test_option.proto │ │ └── test_option2.proto │ ├── test │ ├── common │ │ └── echo.sh │ ├── integration │ │ └── model │ │ │ └── mongo │ │ │ ├── Dockerfile │ │ │ ├── cmd.sh │ │ │ └── mongo.sh │ ├── main.sh │ ├── test.go │ └── test_test.go │ ├── tpl │ ├── cmd.go │ └── templates.go │ ├── update │ ├── config │ │ └── config.go │ ├── etc │ │ └── update-api.json │ └── update.go │ ├── upgrade │ ├── cmd.go │ └── upgrade.go │ ├── util │ ├── console │ │ └── console.go │ ├── ctx │ │ ├── context.go │ │ ├── context_test.go │ │ ├── gomod.go │ │ ├── gomod_test.go │ │ ├── gopath.go │ │ ├── gopath_test.go │ │ ├── modcheck.go │ │ └── modcheck_test.go │ ├── env │ │ ├── env.go │ │ └── env_test.go │ ├── format │ │ ├── format.go │ │ └── format_test.go │ ├── git.go │ ├── head.go │ ├── name │ │ ├── naming.go │ │ └── naming_test.go │ ├── pathx │ │ ├── file.go │ │ ├── file_test.go │ │ ├── path.go │ │ ├── path_test.go │ │ ├── readlink+polyfill.go │ │ └── readlink.go │ ├── string.go │ ├── string_test.go │ ├── stringx │ │ ├── string.go │ │ └── string_test.go │ ├── templatex.go │ ├── templatex_test.go │ └── zipx │ │ └── zipx.go │ └── vars │ └── settings.go └── zrpc ├── client.go ├── client_test.go ├── config.go ├── config_test.go ├── internal ├── auth │ ├── auth.go │ ├── auth_test.go │ ├── credential.go │ ├── credential_test.go │ └── vars.go ├── balancer │ └── p2c │ │ ├── p2c.go │ │ └── p2c_test.go ├── client.go ├── client_test.go ├── clientinterceptors │ ├── breakerinterceptor.go │ ├── breakerinterceptor_test.go │ ├── durationinterceptor.go │ ├── durationinterceptor_test.go │ ├── prometheusinterceptor.go │ ├── prometheusinterceptor_test.go │ ├── timeoutinterceptor.go │ ├── timeoutinterceptor_test.go │ ├── tracinginterceptor.go │ └── tracinginterceptor_test.go ├── codes │ ├── accept.go │ └── accept_test.go ├── config.go ├── rpclogger.go ├── rpclogger_test.go ├── rpcpubserver.go ├── rpcpubserver_test.go ├── rpcserver.go ├── rpcserver_test.go ├── server.go ├── server_test.go └── serverinterceptors │ ├── authinterceptor.go │ ├── authinterceptor_test.go │ ├── breakerinterceptor.go │ ├── breakerinterceptor_test.go │ ├── prometheusinterceptor.go │ ├── prometheusinterceptor_test.go │ ├── recoverinterceptor.go │ ├── recoverinterceptor_test.go │ ├── sheddinginterceptor.go │ ├── sheddinginterceptor_test.go │ ├── statinterceptor.go │ ├── statinterceptor_test.go │ ├── timeoutinterceptor.go │ ├── timeoutinterceptor_test.go │ ├── tracinginterceptor.go │ └── tracinginterceptor_test.go ├── proxy.go ├── proxy_test.go ├── resolver ├── internal │ ├── directbuilder.go │ ├── directbuilder_test.go │ ├── discovbuilder.go │ ├── discovbuilder_test.go │ ├── etcdbuilder.go │ ├── etcdbuilder_test.go │ ├── kube │ │ ├── eventhandler.go │ │ ├── eventhandler_test.go │ │ ├── targetparser.go │ │ └── targetparser_test.go │ ├── kubebuilder.go │ ├── kubebuilder_test.go │ ├── resolver.go │ ├── resolver_test.go │ ├── subset.go │ ├── subset_test.go │ └── targets │ │ ├── endpoints.go │ │ └── endpoints_test.go ├── register.go ├── register_test.go ├── target.go └── target_test.go ├── server.go └── server_test.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: true 4 | project: false # disabled because project coverage is not stable 5 | comment: 6 | layout: "flags, files" 7 | behavior: once 8 | require_changes: true 9 | ignore: 10 | - "tools" 11 | - "**/mock" 12 | - "**/*_mock.go" 13 | - "**/*test" 14 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | .dockerignore 3 | Dockerfile 4 | goctl 5 | Makefile 6 | readme.md 7 | readme-cn.md 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [zeromicro] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior, if applicable: 15 | 16 | 1. The code is 17 | 18 | ```go 19 | 20 | ``` 21 | 22 | 2. The error is 23 | 24 | ``` 25 | 26 | ``` 27 | 28 | **Expected behavior** 29 | A clear and concise description of what you expected to happen. 30 | 31 | **Screenshots** 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | **Environments (please complete the following information):** 35 | - OS: [e.g. Linux] 36 | - go-zero version [e.g. 1.2.1] 37 | - goctl version [e.g. 1.2.1, optional] 38 | 39 | **More description** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question on using go-zero or goctl 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/issue-translator.yml: -------------------------------------------------------------------------------- 1 | name: 'issue-translator' 2 | on: 3 | issue_comment: 4 | types: [created] 5 | issues: 6 | types: [opened] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: usthe/issues-translate-action@v2.7 13 | with: 14 | IS_MODIFY_TITLE: true 15 | # not require, default false, . Decide whether to modify the issue title 16 | # if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot. 17 | CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿 18 | # not require. Customize the translation robot prefix message. 19 | -------------------------------------------------------------------------------- /.github/workflows/issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v9 11 | with: 12 | days-before-issue-stale: 365 13 | days-before-issue-close: 90 14 | stale-issue-label: "stale" 15 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 16 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 17 | days-before-pr-stale: -1 18 | days-before-pr-close: -1 19 | repo-token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/reviewdog.yml: -------------------------------------------------------------------------------- 1 | name: reviewdog 2 | on: [pull_request] 3 | jobs: 4 | staticcheck: 5 | name: runner / staticcheck 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: reviewdog/action-staticcheck@v1 10 | with: 11 | github_token: ${{ secrets.github_token }} 12 | # Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review]. 13 | reporter: github-pr-review 14 | # Report all results. 15 | filter_mode: nofilter 16 | # Exit with 1 when it find at least one finding. 17 | fail_on_error: true 18 | # Set staticcheck flags 19 | staticcheck_flags: -checks=inherit,-SA1019,-SA1029,-SA5008 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | !**/Dockerfile 7 | !**/Makefile 8 | 9 | # Unignore all dirs 10 | !*/ 11 | !api 12 | 13 | # ignore 14 | **/.idea 15 | **/.vscode 16 | **/.DS_Store 17 | **/logs 18 | **/adhoc 19 | **/coverage.txt 20 | 21 | # for test purpose 22 | go.work 23 | go.work.sum 24 | 25 | # gitlab ci 26 | .cache 27 | .golangci.yml 28 | 29 | # vim auto backup file 30 | *~ 31 | !OWNERS 32 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We publish releases monthly. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | >= 1.4.4 | :white_check_mark: | 10 | | < 1.4.4 | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | https://github.com/zeromicro/go-zero/security/advisories 15 | 16 | Accepted vulnerabilities are expected to be fixed within a month. 17 | -------------------------------------------------------------------------------- /core/bloom/setscript.lua: -------------------------------------------------------------------------------- 1 | for _, offset in ipairs(ARGV) do 2 | redis.call("setbit", KEYS[1], offset, 1) 3 | end -------------------------------------------------------------------------------- /core/bloom/testscript.lua: -------------------------------------------------------------------------------- 1 | for _, offset in ipairs(ARGV) do 2 | if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then 3 | return false 4 | end 5 | end 6 | return true -------------------------------------------------------------------------------- /core/breaker/bucket.go: -------------------------------------------------------------------------------- 1 | package breaker 2 | 3 | const ( 4 | success = iota 5 | fail 6 | drop 7 | ) 8 | 9 | // bucket defines the bucket that holds sum and num of additions. 10 | type bucket struct { 11 | Sum int64 12 | Success int64 13 | Failure int64 14 | Drop int64 15 | } 16 | 17 | func (b *bucket) Add(v int64) { 18 | switch v { 19 | case fail: 20 | b.fail() 21 | case drop: 22 | b.drop() 23 | default: 24 | b.succeed() 25 | } 26 | } 27 | 28 | func (b *bucket) Reset() { 29 | b.Sum = 0 30 | b.Success = 0 31 | b.Failure = 0 32 | b.Drop = 0 33 | } 34 | 35 | func (b *bucket) drop() { 36 | b.Sum++ 37 | b.Drop++ 38 | } 39 | 40 | func (b *bucket) fail() { 41 | b.Sum++ 42 | b.Failure++ 43 | } 44 | 45 | func (b *bucket) succeed() { 46 | b.Sum++ 47 | b.Success++ 48 | } 49 | -------------------------------------------------------------------------------- /core/cmdline/input.go: -------------------------------------------------------------------------------- 1 | package cmdline 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // EnterToContinue let stdin waiting for an enter key to continue. 11 | func EnterToContinue() { 12 | fmt.Print("Press 'Enter' to continue...") 13 | bufio.NewReader(os.Stdin).ReadBytes('\n') 14 | } 15 | 16 | // ReadLine shows prompt to stdout and read a line from stdin. 17 | func ReadLine(prompt string) string { 18 | fmt.Print(prompt) 19 | input, _ := bufio.NewReader(os.Stdin).ReadString('\n') 20 | return strings.TrimSpace(input) 21 | } 22 | -------------------------------------------------------------------------------- /core/codec/gzip.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "io" 7 | ) 8 | 9 | const unzipLimit = 100 * 1024 * 1024 // 100MB 10 | 11 | // Gzip compresses bs. 12 | func Gzip(bs []byte) []byte { 13 | var b bytes.Buffer 14 | 15 | w := gzip.NewWriter(&b) 16 | w.Write(bs) 17 | w.Close() 18 | 19 | return b.Bytes() 20 | } 21 | 22 | // Gunzip uncompresses bs. 23 | func Gunzip(bs []byte) ([]byte, error) { 24 | r, err := gzip.NewReader(bytes.NewBuffer(bs)) 25 | if err != nil { 26 | return nil, err 27 | } 28 | defer r.Close() 29 | 30 | var c bytes.Buffer 31 | if _, err = io.Copy(&c, io.LimitReader(r, unzipLimit)); err != nil { 32 | return nil, err 33 | } 34 | 35 | return c.Bytes(), nil 36 | } 37 | -------------------------------------------------------------------------------- /core/codec/hmac.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "io" 8 | ) 9 | 10 | // Hmac returns HMAC bytes for body with the given key. 11 | func Hmac(key []byte, body string) []byte { 12 | h := hmac.New(sha256.New, key) 13 | io.WriteString(h, body) 14 | return h.Sum(nil) 15 | } 16 | 17 | // HmacBase64 returns the base64 encoded string of HMAC for body with the given key. 18 | func HmacBase64(key []byte, body string) string { 19 | return base64.StdEncoding.EncodeToString(Hmac(key, body)) 20 | } 21 | -------------------------------------------------------------------------------- /core/codec/hmac_test.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestHmac(t *testing.T) { 11 | ret := Hmac([]byte("foo"), "bar") 12 | assert.Equal(t, "f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", 13 | fmt.Sprintf("%x", ret)) 14 | } 15 | 16 | func TestHmacBase64(t *testing.T) { 17 | ret := HmacBase64([]byte("foo"), "bar") 18 | assert.Equal(t, "+TILrwJJFp5zhQzWFW3tAQbiu2rYyrAbe7vr5tEGUxc=", ret) 19 | } 20 | -------------------------------------------------------------------------------- /core/color/color_test.go: -------------------------------------------------------------------------------- 1 | package color 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestWithColor(t *testing.T) { 10 | output := WithColor("Hello", BgRed) 11 | assert.Equal(t, "Hello", output) 12 | } 13 | 14 | func TestWithColorPadding(t *testing.T) { 15 | output := WithColorPadding("Hello", BgRed) 16 | assert.Equal(t, " Hello ", output) 17 | } 18 | -------------------------------------------------------------------------------- /core/conf/options.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | type ( 4 | // Option defines the method to customize the config options. 5 | Option func(opt *options) 6 | 7 | options struct { 8 | env bool 9 | } 10 | ) 11 | 12 | // UseEnv customizes the config to use environment variables. 13 | func UseEnv() Option { 14 | return func(opt *options) { 15 | opt.env = true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/contextx/unmarshaler.go: -------------------------------------------------------------------------------- 1 | package contextx 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/zeromicro/go-zero/core/mapping" 7 | ) 8 | 9 | const contextTagKey = "ctx" 10 | 11 | var unmarshaler = mapping.NewUnmarshaler(contextTagKey) 12 | 13 | type contextValuer struct { 14 | context.Context 15 | } 16 | 17 | func (cv contextValuer) Value(key string) (any, bool) { 18 | v := cv.Context.Value(key) 19 | return v, v != nil 20 | } 21 | 22 | // For unmarshals ctx into v. 23 | func For(ctx context.Context, v any) error { 24 | return unmarshaler.UnmarshalValuer(contextValuer{ 25 | Context: ctx, 26 | }, v) 27 | } 28 | -------------------------------------------------------------------------------- /core/contextx/valueonlycontext.go: -------------------------------------------------------------------------------- 1 | package contextx 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | type valueOnlyContext struct { 9 | context.Context 10 | } 11 | 12 | func (valueOnlyContext) Deadline() (deadline time.Time, ok bool) { 13 | return 14 | } 15 | 16 | func (valueOnlyContext) Done() <-chan struct{} { 17 | return nil 18 | } 19 | 20 | func (valueOnlyContext) Err() error { 21 | return nil 22 | } 23 | 24 | // ValueOnlyFrom takes all values from the given ctx, without deadline and error control. 25 | func ValueOnlyFrom(ctx context.Context) context.Context { 26 | return valueOnlyContext{ 27 | Context: ctx, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/discov/accountregistry.go: -------------------------------------------------------------------------------- 1 | package discov 2 | 3 | import "github.com/zeromicro/go-zero/core/discov/internal" 4 | 5 | // RegisterAccount registers the username/password to the given etcd cluster. 6 | func RegisterAccount(endpoints []string, user, pass string) { 7 | internal.AddAccount(endpoints, user, pass) 8 | } 9 | 10 | // RegisterTLS registers the CertFile/CertKeyFile/CACertFile to the given etcd. 11 | func RegisterTLS(endpoints []string, certFile, certKeyFile, caFile string, 12 | insecureSkipVerify bool) error { 13 | return internal.AddTLS(endpoints, certFile, certKeyFile, caFile, insecureSkipVerify) 14 | } 15 | -------------------------------------------------------------------------------- /core/discov/accountregistry_test.go: -------------------------------------------------------------------------------- 1 | package discov 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/zeromicro/go-zero/core/discov/internal" 8 | "github.com/zeromicro/go-zero/core/stringx" 9 | ) 10 | 11 | func TestRegisterAccount(t *testing.T) { 12 | endpoints := []string{ 13 | "localhost:2379", 14 | } 15 | user := "foo" + stringx.Rand() 16 | RegisterAccount(endpoints, user, "bar") 17 | account, ok := internal.GetAccount(endpoints) 18 | assert.True(t, ok) 19 | assert.Equal(t, user, account.User) 20 | assert.Equal(t, "bar", account.Pass) 21 | } 22 | -------------------------------------------------------------------------------- /core/discov/clients.go: -------------------------------------------------------------------------------- 1 | package discov 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/zeromicro/go-zero/core/discov/internal" 8 | ) 9 | 10 | const ( 11 | _ = iota 12 | indexOfId 13 | ) 14 | 15 | const timeToLive int64 = 10 16 | 17 | // TimeToLive is seconds to live in etcd. 18 | var TimeToLive = timeToLive 19 | 20 | func extract(etcdKey string, index int) (string, bool) { 21 | if index < 0 { 22 | return "", false 23 | } 24 | 25 | fields := strings.FieldsFunc(etcdKey, func(ch rune) bool { 26 | return ch == internal.Delimiter 27 | }) 28 | if index >= len(fields) { 29 | return "", false 30 | } 31 | 32 | return fields[index], true 33 | } 34 | 35 | func extractId(etcdKey string) (string, bool) { 36 | return extract(etcdKey, indexOfId) 37 | } 38 | 39 | func makeEtcdKey(key string, id int64) string { 40 | return fmt.Sprintf("%s%c%d", key, internal.Delimiter, id) 41 | } 42 | -------------------------------------------------------------------------------- /core/discov/clients_test.go: -------------------------------------------------------------------------------- 1 | package discov 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/zeromicro/go-zero/core/discov/internal" 9 | ) 10 | 11 | var mockLock sync.Mutex 12 | 13 | func setMockClient(cli internal.EtcdClient) func() { 14 | mockLock.Lock() 15 | internal.NewClient = func([]string) (internal.EtcdClient, error) { 16 | return cli, nil 17 | } 18 | return func() { 19 | internal.NewClient = internal.DialClient 20 | mockLock.Unlock() 21 | } 22 | } 23 | 24 | func TestExtract(t *testing.T) { 25 | id, ok := extractId("key/123/val") 26 | assert.True(t, ok) 27 | assert.Equal(t, "123", id) 28 | 29 | _, ok = extract("any", -1) 30 | assert.False(t, ok) 31 | 32 | _, ok = extract("any", 10) 33 | assert.False(t, ok) 34 | } 35 | 36 | func TestMakeKey(t *testing.T) { 37 | assert.Equal(t, "key/123", makeEtcdKey("key", 123)) 38 | } 39 | -------------------------------------------------------------------------------- /core/discov/internal/listener.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | // Listener interface wraps the OnUpdate method. 4 | type Listener interface { 5 | OnUpdate(keys, values []string, newKey string) 6 | } 7 | -------------------------------------------------------------------------------- /core/discov/internal/statewatcher_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/golang/mock/gomock" 8 | "google.golang.org/grpc/connectivity" 9 | ) 10 | 11 | func TestStateWatcher_watch(t *testing.T) { 12 | ctrl := gomock.NewController(t) 13 | defer ctrl.Finish() 14 | watcher := newStateWatcher() 15 | var wg sync.WaitGroup 16 | wg.Add(1) 17 | watcher.addListener(func() { 18 | wg.Done() 19 | }) 20 | conn := NewMocketcdConn(ctrl) 21 | conn.EXPECT().GetState().Return(connectivity.Ready) 22 | conn.EXPECT().GetState().Return(connectivity.TransientFailure) 23 | conn.EXPECT().GetState().Return(connectivity.Ready).AnyTimes() 24 | conn.EXPECT().WaitForStateChange(gomock.Any(), gomock.Any()).Return(true).AnyTimes() 25 | go watcher.watch(conn) 26 | wg.Wait() 27 | } 28 | -------------------------------------------------------------------------------- /core/discov/internal/updatelistener.go: -------------------------------------------------------------------------------- 1 | //go:generate mockgen -package internal -destination updatelistener_mock.go -source updatelistener.go UpdateListener 2 | 3 | package internal 4 | 5 | type ( 6 | // A KV is used to store an etcd entry with key and value. 7 | KV struct { 8 | Key string 9 | Val string 10 | } 11 | 12 | // UpdateListener wraps the OnAdd and OnDelete methods. 13 | UpdateListener interface { 14 | OnAdd(kv KV) 15 | OnDelete(kv KV) 16 | } 17 | ) 18 | -------------------------------------------------------------------------------- /core/discov/internal/vars.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "time" 4 | 5 | const ( 6 | // Delimiter is a separator that separates the etcd path. 7 | Delimiter = '/' 8 | 9 | autoSyncInterval = time.Minute 10 | coolDownInterval = time.Second 11 | dialTimeout = 5 * time.Second 12 | requestTimeout = 3 * time.Second 13 | endpointsSeparator = "," 14 | ) 15 | 16 | var ( 17 | // DialTimeout is the dial timeout. 18 | DialTimeout = dialTimeout 19 | // RequestTimeout is the request timeout. 20 | RequestTimeout = requestTimeout 21 | // NewClient is used to create etcd clients. 22 | NewClient = DialClient 23 | ) 24 | -------------------------------------------------------------------------------- /core/discov/kubernetes/discov-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: discov 5 | -------------------------------------------------------------------------------- /core/errorx/atomicerror.go: -------------------------------------------------------------------------------- 1 | package errorx 2 | 3 | import "sync/atomic" 4 | 5 | // AtomicError defines an atomic error. 6 | type AtomicError struct { 7 | err atomic.Value // error 8 | } 9 | 10 | // Set sets the error. 11 | func (ae *AtomicError) Set(err error) { 12 | if err != nil { 13 | ae.err.Store(err) 14 | } 15 | } 16 | 17 | // Load returns the error. 18 | func (ae *AtomicError) Load() error { 19 | if v := ae.err.Load(); v != nil { 20 | return v.(error) 21 | } 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /core/errorx/callchain.go: -------------------------------------------------------------------------------- 1 | package errorx 2 | 3 | // Chain runs funs one by one until an error occurred. 4 | func Chain(fns ...func() error) error { 5 | for _, fn := range fns { 6 | if err := fn(); err != nil { 7 | return err 8 | } 9 | } 10 | 11 | return nil 12 | } 13 | -------------------------------------------------------------------------------- /core/errorx/callchain_test.go: -------------------------------------------------------------------------------- 1 | package errorx 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestChain(t *testing.T) { 11 | errDummy := errors.New("dummy") 12 | assert.Nil(t, Chain(func() error { 13 | return nil 14 | }, func() error { 15 | return nil 16 | })) 17 | assert.Equal(t, errDummy, Chain(func() error { 18 | return errDummy 19 | }, func() error { 20 | return nil 21 | })) 22 | assert.Equal(t, errDummy, Chain(func() error { 23 | return nil 24 | }, func() error { 25 | return errDummy 26 | })) 27 | } 28 | -------------------------------------------------------------------------------- /core/errorx/check.go: -------------------------------------------------------------------------------- 1 | package errorx 2 | 3 | import "errors" 4 | 5 | // In checks if the given err is one of errs. 6 | func In(err error, errs ...error) bool { 7 | for _, each := range errs { 8 | if errors.Is(err, each) { 9 | return true 10 | } 11 | } 12 | 13 | return false 14 | } 15 | -------------------------------------------------------------------------------- /core/errorx/wrap.go: -------------------------------------------------------------------------------- 1 | package errorx 2 | 3 | import "fmt" 4 | 5 | // Wrap returns an error that wraps err with given message. 6 | func Wrap(err error, message string) error { 7 | if err == nil { 8 | return nil 9 | } 10 | 11 | return fmt.Errorf("%s: %w", message, err) 12 | } 13 | 14 | // Wrapf returns an error that wraps err with given format and args. 15 | func Wrapf(err error, format string, args ...any) error { 16 | if err == nil { 17 | return nil 18 | } 19 | 20 | return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err) 21 | } 22 | -------------------------------------------------------------------------------- /core/errorx/wrap_test.go: -------------------------------------------------------------------------------- 1 | package errorx 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestWrap(t *testing.T) { 11 | assert.Nil(t, Wrap(nil, "test")) 12 | assert.Equal(t, "foo: bar", Wrap(errors.New("bar"), "foo").Error()) 13 | 14 | err := errors.New("foo") 15 | assert.True(t, errors.Is(Wrap(err, "bar"), err)) 16 | } 17 | 18 | func TestWrapf(t *testing.T) { 19 | assert.Nil(t, Wrapf(nil, "%s", "test")) 20 | assert.Equal(t, "foo bar: quz", Wrapf(errors.New("quz"), "foo %s", "bar").Error()) 21 | 22 | err := errors.New("foo") 23 | assert.True(t, errors.Is(Wrapf(err, "foo %s", "bar"), err)) 24 | } 25 | -------------------------------------------------------------------------------- /core/executors/delayexecutor_test.go: -------------------------------------------------------------------------------- 1 | package executors 2 | 3 | import ( 4 | "sync/atomic" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestDelayExecutor(t *testing.T) { 12 | var count int32 13 | ex := NewDelayExecutor(func() { 14 | atomic.AddInt32(&count, 1) 15 | }, time.Millisecond*10) 16 | for i := 0; i < 100; i++ { 17 | ex.Trigger() 18 | } 19 | time.Sleep(time.Millisecond * 100) 20 | assert.Equal(t, int32(1), atomic.LoadInt32(&count)) 21 | } 22 | -------------------------------------------------------------------------------- /core/executors/lessexecutor_test.go: -------------------------------------------------------------------------------- 1 | package executors 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/zeromicro/go-zero/core/timex" 9 | ) 10 | 11 | func TestLessExecutor_DoOrDiscard(t *testing.T) { 12 | executor := NewLessExecutor(time.Minute) 13 | assert.True(t, executor.DoOrDiscard(func() {})) 14 | assert.False(t, executor.DoOrDiscard(func() {})) 15 | executor.lastTime.Set(timex.Now() - time.Minute - time.Second*30) 16 | assert.True(t, executor.DoOrDiscard(func() {})) 17 | assert.False(t, executor.DoOrDiscard(func() {})) 18 | } 19 | 20 | func BenchmarkLessExecutor(b *testing.B) { 21 | exec := NewLessExecutor(time.Millisecond) 22 | for i := 0; i < b.N; i++ { 23 | exec.DoOrDiscard(func() { 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/executors/vars.go: -------------------------------------------------------------------------------- 1 | package executors 2 | 3 | import "time" 4 | 5 | const defaultFlushInterval = time.Second 6 | 7 | // Execute defines the method to execute tasks. 8 | type Execute func(tasks []any) 9 | -------------------------------------------------------------------------------- /core/filex/progressscanner.go: -------------------------------------------------------------------------------- 1 | package filex 2 | 3 | import "gopkg.in/cheggaaa/pb.v1" 4 | 5 | type ( 6 | // A Scanner is used to read lines. 7 | Scanner interface { 8 | // Scan checks if it has remaining to read. 9 | Scan() bool 10 | // Text returns next line. 11 | Text() string 12 | } 13 | 14 | progressScanner struct { 15 | Scanner 16 | bar *pb.ProgressBar 17 | } 18 | ) 19 | 20 | // NewProgressScanner returns a Scanner with progress indicator. 21 | func NewProgressScanner(scanner Scanner, bar *pb.ProgressBar) Scanner { 22 | return &progressScanner{ 23 | Scanner: scanner, 24 | bar: bar, 25 | } 26 | } 27 | 28 | func (ps *progressScanner) Text() string { 29 | s := ps.Scanner.Text() 30 | ps.bar.Add64(int64(len(s)) + 1) // take newlines into account 31 | return s 32 | } 33 | -------------------------------------------------------------------------------- /core/filex/progressscanner_test.go: -------------------------------------------------------------------------------- 1 | package filex 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "gopkg.in/cheggaaa/pb.v1" 9 | ) 10 | 11 | func TestProgressScanner(t *testing.T) { 12 | const text = "hello, world" 13 | bar := pb.New(100) 14 | var builder strings.Builder 15 | builder.WriteString(text) 16 | scanner := NewProgressScanner(&mockedScanner{builder: &builder}, bar) 17 | assert.True(t, scanner.Scan()) 18 | assert.Equal(t, text, scanner.Text()) 19 | } 20 | 21 | type mockedScanner struct { 22 | builder *strings.Builder 23 | } 24 | 25 | func (s *mockedScanner) Scan() bool { 26 | return s.builder.Len() > 0 27 | } 28 | 29 | func (s *mockedScanner) Text() string { 30 | return s.builder.String() 31 | } 32 | -------------------------------------------------------------------------------- /core/fs/files+polyfill.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package fs 4 | 5 | import "os" 6 | 7 | func CloseOnExec(*os.File) { 8 | } 9 | -------------------------------------------------------------------------------- /core/fs/files.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package fs 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | ) 9 | 10 | // CloseOnExec makes sure closing the file on process forking. 11 | func CloseOnExec(file *os.File) { 12 | if file != nil { 13 | syscall.CloseOnExec(int(file.Fd())) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/fs/files_test.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCloseOnExec(t *testing.T) { 11 | file := os.NewFile(0, os.DevNull) 12 | assert.NotPanics(t, func() { 13 | CloseOnExec(file) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /core/fx/parallel.go: -------------------------------------------------------------------------------- 1 | package fx 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/core/errorx" 5 | "github.com/zeromicro/go-zero/core/threading" 6 | ) 7 | 8 | // Parallel runs fns parallelly and waits for done. 9 | func Parallel(fns ...func()) { 10 | group := threading.NewRoutineGroup() 11 | for _, fn := range fns { 12 | group.RunSafe(fn) 13 | } 14 | group.Wait() 15 | } 16 | 17 | func ParallelErr(fns ...func() error) error { 18 | var be errorx.BatchError 19 | 20 | group := threading.NewRoutineGroup() 21 | for _, fn := range fns { 22 | f := fn 23 | group.RunSafe(func() { 24 | if err := f(); err != nil { 25 | be.Add(err) 26 | } 27 | }) 28 | } 29 | group.Wait() 30 | 31 | return be.Err() 32 | } 33 | -------------------------------------------------------------------------------- /core/hash/hash.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | 7 | "github.com/spaolacci/murmur3" 8 | ) 9 | 10 | // Hash returns the hash value of data. 11 | func Hash(data []byte) uint64 { 12 | return murmur3.Sum64(data) 13 | } 14 | 15 | // Md5 returns the md5 bytes of data. 16 | func Md5(data []byte) []byte { 17 | digest := md5.New() 18 | digest.Write(data) 19 | return digest.Sum(nil) 20 | } 21 | 22 | // Md5Hex returns the md5 hex string of data. 23 | func Md5Hex(data []byte) string { 24 | return fmt.Sprintf("%x", Md5(data)) 25 | } 26 | -------------------------------------------------------------------------------- /core/iox/bufferpool.go: -------------------------------------------------------------------------------- 1 | package iox 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | ) 7 | 8 | // A BufferPool is a pool to buffer bytes.Buffer objects. 9 | type BufferPool struct { 10 | capability int 11 | pool *sync.Pool 12 | } 13 | 14 | // NewBufferPool returns a BufferPool. 15 | func NewBufferPool(capability int) *BufferPool { 16 | return &BufferPool{ 17 | capability: capability, 18 | pool: &sync.Pool{ 19 | New: func() any { 20 | return new(bytes.Buffer) 21 | }, 22 | }, 23 | } 24 | } 25 | 26 | // Get returns a bytes.Buffer object from bp. 27 | func (bp *BufferPool) Get() *bytes.Buffer { 28 | buf := bp.pool.Get().(*bytes.Buffer) 29 | buf.Reset() 30 | return buf 31 | } 32 | 33 | // Put returns buf into bp. 34 | func (bp *BufferPool) Put(buf *bytes.Buffer) { 35 | if buf == nil { 36 | return 37 | } 38 | 39 | if buf.Cap() < bp.capability { 40 | bp.pool.Put(buf) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/iox/nopcloser.go: -------------------------------------------------------------------------------- 1 | package iox 2 | 3 | import "io" 4 | 5 | type nopCloser struct { 6 | io.Writer 7 | } 8 | 9 | func (nopCloser) Close() error { 10 | return nil 11 | } 12 | 13 | // NopCloser returns an io.WriteCloser that does nothing on calling Close. 14 | func NopCloser(w io.Writer) io.WriteCloser { 15 | return nopCloser{w} 16 | } 17 | -------------------------------------------------------------------------------- /core/iox/nopcloser_test.go: -------------------------------------------------------------------------------- 1 | package iox 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNopCloser(t *testing.T) { 10 | closer := NopCloser(nil) 11 | assert.NoError(t, closer.Close()) 12 | } 13 | -------------------------------------------------------------------------------- /core/iox/pipe.go: -------------------------------------------------------------------------------- 1 | package iox 2 | 3 | import "os" 4 | 5 | // RedirectInOut redirects stdin to r, stdout to w, and callers need to call restore afterward. 6 | func RedirectInOut() (restore func(), err error) { 7 | var r, w *os.File 8 | r, w, err = os.Pipe() 9 | if err != nil { 10 | return 11 | } 12 | 13 | ow := os.Stdout 14 | os.Stdout = w 15 | or := os.Stdin 16 | os.Stdin = r 17 | restore = func() { 18 | os.Stdin = or 19 | os.Stdout = ow 20 | } 21 | 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /core/iox/pipe_test.go: -------------------------------------------------------------------------------- 1 | package iox 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRedirectInOut(t *testing.T) { 10 | restore, err := RedirectInOut() 11 | assert.Nil(t, err) 12 | defer restore() 13 | } 14 | -------------------------------------------------------------------------------- /core/iox/textfile.go: -------------------------------------------------------------------------------- 1 | package iox 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "os" 8 | ) 9 | 10 | const bufSize = 32 * 1024 11 | 12 | // CountLines returns the number of lines in the file. 13 | func CountLines(file string) (int, error) { 14 | f, err := os.Open(file) 15 | if err != nil { 16 | return 0, err 17 | } 18 | defer f.Close() 19 | 20 | var noEol bool 21 | buf := make([]byte, bufSize) 22 | count := 0 23 | lineSep := []byte{'\n'} 24 | 25 | for { 26 | c, err := f.Read(buf) 27 | count += bytes.Count(buf[:c], lineSep) 28 | 29 | switch { 30 | case errors.Is(err, io.EOF): 31 | if noEol { 32 | count++ 33 | } 34 | return count, nil 35 | case err != nil: 36 | return count, err 37 | } 38 | 39 | noEol = c > 0 && buf[c-1] != '\n' 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/iox/textfile_test.go: -------------------------------------------------------------------------------- 1 | package iox 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCountLines(t *testing.T) { 11 | const val = `1 12 | 2 13 | 3 14 | 4` 15 | file, err := os.CreateTemp(os.TempDir(), "test-") 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | defer os.Remove(file.Name()) 20 | 21 | file.WriteString(val) 22 | file.Close() 23 | lines, err := CountLines(file.Name()) 24 | assert.Nil(t, err) 25 | assert.Equal(t, 4, lines) 26 | } 27 | 28 | func TestCountLinesError(t *testing.T) { 29 | _, err := CountLines("not-exist") 30 | assert.NotNil(t, err) 31 | } 32 | -------------------------------------------------------------------------------- /core/iox/textlinescanner_test.go: -------------------------------------------------------------------------------- 1 | package iox 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "testing/iotest" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestScanner(t *testing.T) { 12 | const val = `1 13 | 2 14 | 3 15 | 4` 16 | reader := strings.NewReader(val) 17 | scanner := NewTextLineScanner(reader) 18 | var lines []string 19 | for scanner.Scan() { 20 | line, err := scanner.Line() 21 | assert.Nil(t, err) 22 | lines = append(lines, line) 23 | } 24 | assert.EqualValues(t, []string{"1", "2", "3", "4"}, lines) 25 | } 26 | 27 | func TestBadScanner(t *testing.T) { 28 | scanner := NewTextLineScanner(iotest.ErrReader(iotest.ErrTimeout)) 29 | assert.False(t, scanner.Scan()) 30 | _, err := scanner.Line() 31 | assert.ErrorIs(t, err, iotest.ErrTimeout) 32 | } 33 | -------------------------------------------------------------------------------- /core/limit/periodscript.lua: -------------------------------------------------------------------------------- 1 | -- to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key 2 | local limit = tonumber(ARGV[1]) 3 | local window = tonumber(ARGV[2]) 4 | local current = redis.call("INCRBY", KEYS[1], 1) 5 | if current == 1 then 6 | redis.call("expire", KEYS[1], window) 7 | end 8 | if current < limit then 9 | return 1 10 | elseif current == limit then 11 | return 2 12 | else 13 | return 0 14 | end -------------------------------------------------------------------------------- /core/load/nopshedder.go: -------------------------------------------------------------------------------- 1 | package load 2 | 3 | type nopShedder struct{} 4 | 5 | func newNopShedder() Shedder { 6 | return nopShedder{} 7 | } 8 | 9 | func (s nopShedder) Allow() (Promise, error) { 10 | return nopPromise{}, nil 11 | } 12 | 13 | type nopPromise struct{} 14 | 15 | func (p nopPromise) Pass() { 16 | } 17 | 18 | func (p nopPromise) Fail() { 19 | } 20 | -------------------------------------------------------------------------------- /core/load/nopshedder_test.go: -------------------------------------------------------------------------------- 1 | package load 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNopShedder(t *testing.T) { 10 | Disable() 11 | shedder := NewAdaptiveShedder() 12 | for i := 0; i < 1000; i++ { 13 | p, err := shedder.Allow() 14 | assert.Nil(t, err) 15 | p.Fail() 16 | } 17 | 18 | p, err := shedder.Allow() 19 | assert.Nil(t, err) 20 | p.Pass() 21 | } 22 | -------------------------------------------------------------------------------- /core/load/sheddergroup_test.go: -------------------------------------------------------------------------------- 1 | package load 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGroup(t *testing.T) { 10 | group := NewShedderGroup() 11 | t.Run("get", func(t *testing.T) { 12 | limiter := group.GetShedder("test") 13 | assert.NotNil(t, limiter) 14 | }) 15 | } 16 | 17 | func TestShedderClose(t *testing.T) { 18 | var nop nopCloser 19 | assert.Nil(t, nop.Close()) 20 | } 21 | -------------------------------------------------------------------------------- /core/logx/color.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "sync/atomic" 5 | 6 | "github.com/zeromicro/go-zero/core/color" 7 | ) 8 | 9 | // WithColor is a helper function to add color to a string, only in plain encoding. 10 | func WithColor(text string, colour color.Color) string { 11 | if atomic.LoadUint32(&encoding) == plainEncodingType { 12 | return color.WithColor(text, colour) 13 | } 14 | 15 | return text 16 | } 17 | 18 | // WithColorPadding is a helper function to add color to a string with leading and trailing spaces, 19 | // only in plain encoding. 20 | func WithColorPadding(text string, colour color.Color) string { 21 | if atomic.LoadUint32(&encoding) == plainEncodingType { 22 | return color.WithColorPadding(text, colour) 23 | } 24 | 25 | return text 26 | } 27 | -------------------------------------------------------------------------------- /core/logx/lesslogger.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | // A LessLogger is a logger that controls to log once during the given duration. 4 | type LessLogger struct { 5 | *limitedExecutor 6 | } 7 | 8 | // NewLessLogger returns a LessLogger. 9 | func NewLessLogger(milliseconds int) *LessLogger { 10 | return &LessLogger{ 11 | limitedExecutor: newLimitedExecutor(milliseconds), 12 | } 13 | } 14 | 15 | // Error logs v into error log or discard it if more than once in the given duration. 16 | func (logger *LessLogger) Error(v ...any) { 17 | logger.logOrDiscard(func() { 18 | Error(v...) 19 | }) 20 | } 21 | 22 | // Errorf logs v with format into error log or discard it if more than once in the given duration. 23 | func (logger *LessLogger) Errorf(format string, v ...any) { 24 | logger.logOrDiscard(func() { 25 | Errorf(format, v...) 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /core/logx/lesslogger_test.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestLessLogger_Error(t *testing.T) { 11 | w := new(mockWriter) 12 | old := writer.Swap(w) 13 | defer writer.Store(old) 14 | 15 | l := NewLessLogger(500) 16 | for i := 0; i < 100; i++ { 17 | l.Error("hello") 18 | } 19 | 20 | assert.Equal(t, 1, strings.Count(w.String(), "\n")) 21 | } 22 | 23 | func TestLessLogger_Errorf(t *testing.T) { 24 | w := new(mockWriter) 25 | old := writer.Swap(w) 26 | defer writer.Store(old) 27 | 28 | l := NewLessLogger(500) 29 | for i := 0; i < 100; i++ { 30 | l.Errorf("hello") 31 | } 32 | 33 | assert.Equal(t, 1, strings.Count(w.String(), "\n")) 34 | } 35 | -------------------------------------------------------------------------------- /core/logx/lesswriter.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import "io" 4 | 5 | type lessWriter struct { 6 | *limitedExecutor 7 | writer io.Writer 8 | } 9 | 10 | func newLessWriter(writer io.Writer, milliseconds int) *lessWriter { 11 | return &lessWriter{ 12 | limitedExecutor: newLimitedExecutor(milliseconds), 13 | writer: writer, 14 | } 15 | } 16 | 17 | func (w *lessWriter) Write(p []byte) (n int, err error) { 18 | w.logOrDiscard(func() { 19 | w.writer.Write(p) 20 | }) 21 | return len(p), nil 22 | } 23 | -------------------------------------------------------------------------------- /core/logx/lesswriter_test.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestLessWriter(t *testing.T) { 11 | var builder strings.Builder 12 | w := newLessWriter(&builder, 500) 13 | for i := 0; i < 100; i++ { 14 | _, err := w.Write([]byte("hello")) 15 | assert.Nil(t, err) 16 | } 17 | 18 | assert.Equal(t, "hello", builder.String()) 19 | } 20 | -------------------------------------------------------------------------------- /core/logx/logwriter.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import "log" 4 | 5 | type logWriter struct { 6 | logger *log.Logger 7 | } 8 | 9 | func newLogWriter(logger *log.Logger) logWriter { 10 | return logWriter{ 11 | logger: logger, 12 | } 13 | } 14 | 15 | func (lw logWriter) Close() error { 16 | return nil 17 | } 18 | 19 | func (lw logWriter) Write(data []byte) (int, error) { 20 | lw.logger.Print(string(data)) 21 | return len(data), nil 22 | } 23 | -------------------------------------------------------------------------------- /core/logx/syslog.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import "log" 4 | 5 | type redirector struct{} 6 | 7 | // CollectSysLog redirects system log into logx info 8 | func CollectSysLog() { 9 | log.SetOutput(new(redirector)) 10 | } 11 | 12 | func (r *redirector) Write(p []byte) (n int, err error) { 13 | Info(string(p)) 14 | return len(p), nil 15 | } 16 | -------------------------------------------------------------------------------- /core/logx/util.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | func getCaller(callDepth int) string { 11 | _, file, line, ok := runtime.Caller(callDepth) 12 | if !ok { 13 | return "" 14 | } 15 | 16 | return prettyCaller(file, line) 17 | } 18 | 19 | func getTimestamp() string { 20 | return time.Now().Format(timeFormat) 21 | } 22 | 23 | func prettyCaller(file string, line int) string { 24 | idx := strings.LastIndexByte(file, '/') 25 | if idx < 0 { 26 | return fmt.Sprintf("%s:%d", file, line) 27 | } 28 | 29 | idx = strings.LastIndexByte(file[:idx], '/') 30 | if idx < 0 { 31 | return fmt.Sprintf("%s:%d", file, line) 32 | } 33 | 34 | return fmt.Sprintf("%s:%d", file[idx+1:], line) 35 | } 36 | -------------------------------------------------------------------------------- /core/mapping/fieldoptions_test.go: -------------------------------------------------------------------------------- 1 | package mapping 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type Bar struct { 11 | Val string `json:"val"` 12 | } 13 | 14 | func TestFieldOptionOptionalDep(t *testing.T) { 15 | var bar Bar 16 | rt := reflect.TypeOf(bar) 17 | for i := 0; i < rt.NumField(); i++ { 18 | field := rt.Field(i) 19 | val, opt, err := parseKeyAndOptions(jsonTagKey, field) 20 | assert.Equal(t, "val", val) 21 | assert.Nil(t, opt) 22 | assert.Nil(t, err) 23 | } 24 | 25 | // check nil working 26 | var o *fieldOptions 27 | check := func(o *fieldOptions) { 28 | assert.Equal(t, 0, len(o.optionalDep())) 29 | } 30 | check(o) 31 | } 32 | -------------------------------------------------------------------------------- /core/mapping/tomlunmarshaler.go: -------------------------------------------------------------------------------- 1 | package mapping 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/zeromicro/go-zero/internal/encoding" 7 | ) 8 | 9 | // UnmarshalTomlBytes unmarshals TOML bytes into the given v. 10 | func UnmarshalTomlBytes(content []byte, v any, opts ...UnmarshalOption) error { 11 | b, err := encoding.TomlToJson(content) 12 | if err != nil { 13 | return err 14 | } 15 | 16 | return UnmarshalJsonBytes(b, v, opts...) 17 | } 18 | 19 | // UnmarshalTomlReader unmarshals TOML from the given io.Reader into the given v. 20 | func UnmarshalTomlReader(r io.Reader, v any, opts ...UnmarshalOption) error { 21 | b, err := io.ReadAll(r) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | return UnmarshalTomlBytes(b, v, opts...) 27 | } 28 | -------------------------------------------------------------------------------- /core/mapping/yamlunmarshaler.go: -------------------------------------------------------------------------------- 1 | package mapping 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/zeromicro/go-zero/internal/encoding" 7 | ) 8 | 9 | // UnmarshalYamlBytes unmarshals content into v. 10 | func UnmarshalYamlBytes(content []byte, v any, opts ...UnmarshalOption) error { 11 | b, err := encoding.YamlToJson(content) 12 | if err != nil { 13 | return err 14 | } 15 | 16 | return UnmarshalJsonBytes(b, v, opts...) 17 | } 18 | 19 | // UnmarshalYamlReader unmarshals content from reader into v. 20 | func UnmarshalYamlReader(reader io.Reader, v any, opts ...UnmarshalOption) error { 21 | b, err := io.ReadAll(reader) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | return UnmarshalYamlBytes(b, v, opts...) 27 | } 28 | -------------------------------------------------------------------------------- /core/mathx/entropy.go: -------------------------------------------------------------------------------- 1 | package mathx 2 | 3 | import "math" 4 | 5 | const epsilon = 1e-6 6 | 7 | // CalcEntropy calculates the entropy of m. 8 | func CalcEntropy(m map[any]int) float64 { 9 | if len(m) == 0 || len(m) == 1 { 10 | return 1 11 | } 12 | 13 | var entropy float64 14 | var total int 15 | for _, v := range m { 16 | total += v 17 | } 18 | 19 | for _, v := range m { 20 | proba := float64(v) / float64(total) 21 | if proba < epsilon { 22 | proba = epsilon 23 | } 24 | entropy -= proba * math.Log2(proba) 25 | } 26 | 27 | return entropy / math.Log2(float64(len(m))) 28 | } 29 | -------------------------------------------------------------------------------- /core/mathx/entropy_test.go: -------------------------------------------------------------------------------- 1 | package mathx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCalcEntropy(t *testing.T) { 10 | const total = 1000 11 | const count = 100 12 | m := make(map[any]int, total) 13 | for i := 0; i < total; i++ { 14 | m[i] = count 15 | } 16 | assert.True(t, CalcEntropy(m) > .99) 17 | } 18 | 19 | func TestCalcEmptyEntropy(t *testing.T) { 20 | m := make(map[any]int) 21 | assert.Equal(t, float64(1), CalcEntropy(m)) 22 | } 23 | 24 | func TestCalcDiffEntropy(t *testing.T) { 25 | const total = 1000 26 | m := make(map[any]int, total) 27 | for i := 0; i < total; i++ { 28 | m[i] = i 29 | } 30 | assert.True(t, CalcEntropy(m) < .99) 31 | } 32 | -------------------------------------------------------------------------------- /core/mathx/int.go: -------------------------------------------------------------------------------- 1 | package mathx 2 | 3 | // MaxInt returns the larger one of a and b. 4 | func MaxInt(a, b int) int { 5 | if a > b { 6 | return a 7 | } 8 | 9 | return b 10 | } 11 | 12 | // MinInt returns the smaller one of a and b. 13 | func MinInt(a, b int) int { 14 | if a < b { 15 | return a 16 | } 17 | 18 | return b 19 | } 20 | -------------------------------------------------------------------------------- /core/mathx/proba.go: -------------------------------------------------------------------------------- 1 | package mathx 2 | 3 | import ( 4 | "math/rand" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // A Proba is used to test if true on given probability. 10 | type Proba struct { 11 | // rand.New(...) returns a non thread safe object 12 | r *rand.Rand 13 | lock sync.Mutex 14 | } 15 | 16 | // NewProba returns a Proba. 17 | func NewProba() *Proba { 18 | return &Proba{ 19 | r: rand.New(rand.NewSource(time.Now().UnixNano())), 20 | } 21 | } 22 | 23 | // TrueOnProba checks if true on given probability. 24 | func (p *Proba) TrueOnProba(proba float64) (truth bool) { 25 | p.lock.Lock() 26 | truth = p.r.Float64() < proba 27 | p.lock.Unlock() 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /core/mathx/proba_test.go: -------------------------------------------------------------------------------- 1 | package mathx 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestTrueOnProba(t *testing.T) { 11 | const proba = math.Pi / 10 12 | const total = 100000 13 | const epsilon = 0.05 14 | var count int 15 | p := NewProba() 16 | for i := 0; i < total; i++ { 17 | if p.TrueOnProba(proba) { 18 | count++ 19 | } 20 | } 21 | 22 | ratio := float64(count) / float64(total) 23 | assert.InEpsilon(t, proba, ratio, epsilon) 24 | } 25 | -------------------------------------------------------------------------------- /core/mathx/range.go: -------------------------------------------------------------------------------- 1 | package mathx 2 | 3 | type Numerical interface { 4 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | 5 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | 6 | ~float32 | ~float64 7 | } 8 | 9 | // AtLeast returns the greater of x or lower. 10 | func AtLeast[T Numerical](x, lower T) T { 11 | if x < lower { 12 | return lower 13 | } 14 | return x 15 | } 16 | 17 | // AtMost returns the smaller of x or upper. 18 | func AtMost[T Numerical](x, upper T) T { 19 | if x > upper { 20 | return upper 21 | } 22 | return x 23 | } 24 | 25 | // Between returns the value of x clamped to the range [lower, upper]. 26 | func Between[T Numerical](x, lower, upper T) T { 27 | if x < lower { 28 | return lower 29 | } 30 | if x > upper { 31 | return upper 32 | } 33 | return x 34 | } 35 | -------------------------------------------------------------------------------- /core/metric/metric.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import "github.com/zeromicro/go-zero/core/prometheus" 4 | 5 | // A VectorOpts is a general configuration. 6 | type VectorOpts struct { 7 | Namespace string 8 | Subsystem string 9 | Name string 10 | Help string 11 | Labels []string 12 | } 13 | 14 | func update(fn func()) { 15 | if !prometheus.Enabled() { 16 | return 17 | } 18 | 19 | fn() 20 | } 21 | -------------------------------------------------------------------------------- /core/naming/namer.go: -------------------------------------------------------------------------------- 1 | package naming 2 | 3 | // Namer interface wraps the method Name. 4 | type Namer interface { 5 | Name() string 6 | } 7 | -------------------------------------------------------------------------------- /core/netx/ip.go: -------------------------------------------------------------------------------- 1 | package netx 2 | 3 | import "net" 4 | 5 | // InternalIp returns an internal ip. 6 | func InternalIp() string { 7 | infs, err := net.Interfaces() 8 | if err != nil { 9 | return "" 10 | } 11 | 12 | for _, inf := range infs { 13 | if isEthDown(inf.Flags) || isLoopback(inf.Flags) { 14 | continue 15 | } 16 | 17 | addrs, err := inf.Addrs() 18 | if err != nil { 19 | continue 20 | } 21 | 22 | for _, addr := range addrs { 23 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 24 | if ipnet.IP.To4() != nil { 25 | return ipnet.IP.String() 26 | } 27 | } 28 | } 29 | } 30 | 31 | return "" 32 | } 33 | 34 | func isEthDown(f net.Flags) bool { 35 | return f&net.FlagUp != net.FlagUp 36 | } 37 | 38 | func isLoopback(f net.Flags) bool { 39 | return f&net.FlagLoopback == net.FlagLoopback 40 | } 41 | -------------------------------------------------------------------------------- /core/netx/ip_test.go: -------------------------------------------------------------------------------- 1 | package netx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestInternalIp(t *testing.T) { 10 | assert.True(t, len(InternalIp()) > 0) 11 | } 12 | -------------------------------------------------------------------------------- /core/proc/env.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | envs = make(map[string]string) 11 | envLock sync.RWMutex 12 | ) 13 | 14 | // Env returns the value of the given environment variable. 15 | func Env(name string) string { 16 | envLock.RLock() 17 | val, ok := envs[name] 18 | envLock.RUnlock() 19 | 20 | if ok { 21 | return val 22 | } 23 | 24 | val = os.Getenv(name) 25 | envLock.Lock() 26 | envs[name] = val 27 | envLock.Unlock() 28 | 29 | return val 30 | } 31 | 32 | // EnvInt returns an int value of the given environment variable. 33 | func EnvInt(name string) (int, bool) { 34 | val := Env(name) 35 | if len(val) == 0 { 36 | return 0, false 37 | } 38 | 39 | n, err := strconv.Atoi(val) 40 | if err != nil { 41 | return 0, false 42 | } 43 | 44 | return n, true 45 | } 46 | -------------------------------------------------------------------------------- /core/proc/env_test.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestEnv(t *testing.T) { 10 | assert.True(t, len(Env("any")) == 0) 11 | envLock.RLock() 12 | val, ok := envs["any"] 13 | envLock.RUnlock() 14 | assert.True(t, len(val) == 0) 15 | assert.True(t, ok) 16 | assert.True(t, len(Env("any")) == 0) 17 | } 18 | 19 | func TestEnvInt(t *testing.T) { 20 | val, ok := EnvInt("any") 21 | assert.Equal(t, 0, val) 22 | assert.False(t, ok) 23 | t.Setenv("anyInt", "10") 24 | val, ok = EnvInt("anyInt") 25 | assert.Equal(t, 10, val) 26 | assert.True(t, ok) 27 | t.Setenv("anyString", "a") 28 | val, ok = EnvInt("anyString") 29 | assert.Equal(t, 0, val) 30 | assert.False(t, ok) 31 | } 32 | -------------------------------------------------------------------------------- /core/proc/process.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | procName string 10 | pid int 11 | ) 12 | 13 | func init() { 14 | procName = filepath.Base(os.Args[0]) 15 | pid = os.Getpid() 16 | } 17 | 18 | // Pid returns pid of current process. 19 | func Pid() int { 20 | return pid 21 | } 22 | 23 | // ProcessName returns the processname, same as the command name. 24 | func ProcessName() string { 25 | return procName 26 | } 27 | -------------------------------------------------------------------------------- /core/proc/process_test.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestProcessName(t *testing.T) { 10 | assert.True(t, len(ProcessName()) > 0) 11 | } 12 | 13 | func TestPid(t *testing.T) { 14 | assert.True(t, Pid() > 0) 15 | } 16 | -------------------------------------------------------------------------------- /core/proc/profile+polyfill.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package proc 4 | 5 | func StartProfile() Stopper { 6 | return noopStopper 7 | } 8 | -------------------------------------------------------------------------------- /core/proc/profile_test.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/zeromicro/go-zero/core/logx/logtest" 9 | ) 10 | 11 | func TestProfile(t *testing.T) { 12 | c := logtest.NewCollector(t) 13 | profiler := StartProfile() 14 | // start again should not work 15 | assert.NotNil(t, StartProfile()) 16 | profiler.Stop() 17 | // stop twice 18 | profiler.Stop() 19 | assert.True(t, strings.Contains(c.String(), ".pprof")) 20 | } 21 | -------------------------------------------------------------------------------- /core/proc/shutdown+polyfill.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package proc 4 | 5 | import "time" 6 | 7 | // AddShutdownListener returns fn itself on windows, lets callers call fn on their own. 8 | func AddShutdownListener(fn func()) func() { 9 | return fn 10 | } 11 | 12 | // AddWrapUpListener returns fn itself on windows, lets callers call fn on their own. 13 | func AddWrapUpListener(fn func()) func() { 14 | return fn 15 | } 16 | 17 | // SetTimeToForceQuit does nothing on windows. 18 | func SetTimeToForceQuit(duration time.Duration) { 19 | } 20 | 21 | // Shutdown does nothing on windows. 22 | func Shutdown() { 23 | } 24 | 25 | // WrapUp does nothing on windows. 26 | func WrapUp() { 27 | } 28 | -------------------------------------------------------------------------------- /core/proc/signals+polyfill.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package proc 4 | 5 | import "context" 6 | 7 | func Done() <-chan struct{} { 8 | return context.Background().Done() 9 | } 10 | -------------------------------------------------------------------------------- /core/proc/signals_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package proc 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestDone(t *testing.T) { 12 | select { 13 | case <-Done(): 14 | assert.Fail(t, "should run") 15 | default: 16 | } 17 | assert.NotNil(t, Done()) 18 | } 19 | -------------------------------------------------------------------------------- /core/proc/stopper.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | var noopStopper nilStopper 4 | 5 | type ( 6 | // Stopper interface wraps the method Stop. 7 | Stopper interface { 8 | Stop() 9 | } 10 | 11 | nilStopper struct{} 12 | ) 13 | 14 | func (ns nilStopper) Stop() { 15 | } 16 | -------------------------------------------------------------------------------- /core/proc/stopper_test.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import "testing" 4 | 5 | func TestNopStopper(t *testing.T) { 6 | // no panic 7 | noopStopper.Stop() 8 | } 9 | -------------------------------------------------------------------------------- /core/prof/profilecenter_test.go: -------------------------------------------------------------------------------- 1 | package prof 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestReport(t *testing.T) { 11 | once.Do(func() {}) 12 | assert.NotContains(t, generateReport(), "foo") 13 | report("foo", time.Second) 14 | assert.Contains(t, generateReport(), "foo") 15 | report("foo", time.Second) 16 | } 17 | -------------------------------------------------------------------------------- /core/prof/profiler_test.go: -------------------------------------------------------------------------------- 1 | package prof 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/zeromicro/go-zero/core/utils" 7 | ) 8 | 9 | func TestProfiler(t *testing.T) { 10 | EnableProfiling() 11 | Start() 12 | Report("foo", ProfilePoint{ 13 | ElapsedTimer: utils.NewElapsedTimer(), 14 | }) 15 | } 16 | 17 | func TestNullProfiler(t *testing.T) { 18 | p := newNullProfiler() 19 | p.Start() 20 | p.Report("foo", ProfilePoint{ 21 | ElapsedTimer: utils.NewElapsedTimer(), 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /core/prof/runtime_test.go: -------------------------------------------------------------------------------- 1 | package prof 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestDisplayStats(t *testing.T) { 13 | writer := &threadSafeBuffer{ 14 | buf: strings.Builder{}, 15 | } 16 | displayStatsWithWriter(writer, time.Millisecond*10) 17 | time.Sleep(time.Millisecond * 50) 18 | assert.Contains(t, writer.String(), "Goroutines: ") 19 | } 20 | 21 | type threadSafeBuffer struct { 22 | buf strings.Builder 23 | lock sync.Mutex 24 | } 25 | 26 | func (b *threadSafeBuffer) String() string { 27 | b.lock.Lock() 28 | defer b.lock.Unlock() 29 | return b.buf.String() 30 | } 31 | 32 | func (b *threadSafeBuffer) Write(p []byte) (n int, err error) { 33 | b.lock.Lock() 34 | defer b.lock.Unlock() 35 | return b.buf.Write(p) 36 | } 37 | -------------------------------------------------------------------------------- /core/prometheus/config.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | // A Config is a prometheus config. 4 | type Config struct { 5 | Host string `json:",optional"` 6 | Port int `json:",default=9101"` 7 | Path string `json:",default=/metrics"` 8 | } 9 | -------------------------------------------------------------------------------- /core/queue/consumer.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | type ( 4 | // A Consumer interface represents a consumer that can consume string messages. 5 | Consumer interface { 6 | Consume(string) error 7 | OnEvent(event any) 8 | } 9 | 10 | // ConsumerFactory defines the factory to generate consumers. 11 | ConsumerFactory func() (Consumer, error) 12 | ) 13 | -------------------------------------------------------------------------------- /core/queue/messagequeue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | // A MessageQueue interface represents a message queue. 4 | type MessageQueue interface { 5 | Start() 6 | Stop() 7 | } 8 | -------------------------------------------------------------------------------- /core/queue/producer.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | type ( 4 | // A Producer interface represents a producer that produces messages. 5 | Producer interface { 6 | AddListener(listener ProduceListener) 7 | Produce() (string, bool) 8 | } 9 | 10 | // A ProduceListener interface represents a produce listener. 11 | ProduceListener interface { 12 | OnProducerPause() 13 | OnProducerResume() 14 | } 15 | 16 | // ProducerFactory defines the method to generate a Producer. 17 | ProducerFactory func() (Producer, error) 18 | ) 19 | -------------------------------------------------------------------------------- /core/queue/util.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import "strings" 4 | 5 | func generateName(pushers []Pusher) string { 6 | names := make([]string, len(pushers)) 7 | for i, pusher := range pushers { 8 | names[i] = pusher.Name() 9 | } 10 | 11 | return strings.Join(names, ",") 12 | } 13 | -------------------------------------------------------------------------------- /core/rescue/recover.go: -------------------------------------------------------------------------------- 1 | package rescue 2 | 3 | import ( 4 | "context" 5 | "runtime/debug" 6 | 7 | "github.com/zeromicro/go-zero/core/logx" 8 | ) 9 | 10 | // Recover is used with defer to do cleanup on panics. 11 | // Use it like: 12 | // 13 | // defer Recover(func() {}) 14 | func Recover(cleanups ...func()) { 15 | for _, cleanup := range cleanups { 16 | cleanup() 17 | } 18 | 19 | if p := recover(); p != nil { 20 | logx.ErrorStack(p) 21 | } 22 | } 23 | 24 | // RecoverCtx is used with defer to do cleanup on panics. 25 | func RecoverCtx(ctx context.Context, cleanups ...func()) { 26 | for _, cleanup := range cleanups { 27 | cleanup() 28 | } 29 | 30 | if p := recover(); p != nil { 31 | logx.WithContext(ctx).Errorf("%+v\n%s", p, debug.Stack()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/search/tree_debug.go: -------------------------------------------------------------------------------- 1 | //go:build debug 2 | 3 | package search 4 | 5 | import "fmt" 6 | 7 | func (t *Tree) Print() { 8 | if t.root.item == nil { 9 | fmt.Println("/") 10 | } else { 11 | fmt.Printf("/:%#v\n", t.root.item) 12 | } 13 | printNode(t.root, 1) 14 | } 15 | 16 | func printNode(n *node, depth int) { 17 | indent := make([]byte, depth) 18 | for i := 0; i < len(indent); i++ { 19 | indent[i] = '\t' 20 | } 21 | 22 | for _, children := range n.children { 23 | for k, v := range children { 24 | if v.item == nil { 25 | fmt.Printf("%s%s\n", string(indent), k) 26 | } else { 27 | fmt.Printf("%s%s:%#v\n", string(indent), k, v.item) 28 | } 29 | printNode(v, depth+1) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/service/serviceconf_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/zeromicro/go-zero/core/logx" 8 | "github.com/zeromicro/go-zero/internal/devserver" 9 | ) 10 | 11 | func TestServiceConf(t *testing.T) { 12 | c := ServiceConf{ 13 | Name: "foo", 14 | Log: logx.LogConf{ 15 | Mode: "console", 16 | }, 17 | Mode: "dev", 18 | DevServer: devserver.Config{ 19 | Port: 6470, 20 | HealthPath: "/healthz", 21 | }, 22 | } 23 | c.MustSetUp() 24 | } 25 | 26 | func TestServiceConfWithMetricsUrl(t *testing.T) { 27 | c := ServiceConf{ 28 | Name: "foo", 29 | Log: logx.LogConf{ 30 | Mode: "volume", 31 | }, 32 | Mode: "dev", 33 | MetricsUrl: "http://localhost:8080", 34 | } 35 | assert.NoError(t, c.SetUp()) 36 | } 37 | -------------------------------------------------------------------------------- /core/stat/alert+polyfill.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package stat 4 | 5 | // Report reports given message. 6 | func Report(string) { 7 | } 8 | 9 | // SetReporter sets the given reporter. 10 | func SetReporter(func(string)) { 11 | } 12 | -------------------------------------------------------------------------------- /core/stat/alert_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package stat 4 | 5 | import ( 6 | "strconv" 7 | "sync/atomic" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestReport(t *testing.T) { 14 | t.Setenv(clusterNameKey, "test-cluster") 15 | 16 | var count int32 17 | SetReporter(func(s string) { 18 | atomic.AddInt32(&count, 1) 19 | }) 20 | for i := 0; i < 10; i++ { 21 | Report(strconv.Itoa(i)) 22 | } 23 | assert.Equal(t, int32(1), count) 24 | } 25 | -------------------------------------------------------------------------------- /core/stat/internal/cpu_linux_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRefreshCpu(t *testing.T) { 10 | assert.NotPanics(t, func() { 11 | RefreshCpu() 12 | }) 13 | } 14 | 15 | func BenchmarkRefreshCpu(b *testing.B) { 16 | for i := 0; i < b.N; i++ { 17 | RefreshCpu() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/stat/internal/cpu_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package internal 4 | 5 | // RefreshCpu returns cpu usage, always returns 0 on systems other than linux. 6 | func RefreshCpu() uint64 { 7 | return 0 8 | } 9 | -------------------------------------------------------------------------------- /core/stat/task.go: -------------------------------------------------------------------------------- 1 | package stat 2 | 3 | import "time" 4 | 5 | // A Task is a task reported to Metrics. 6 | type Task struct { 7 | Drop bool 8 | Duration time.Duration 9 | Description string 10 | } 11 | -------------------------------------------------------------------------------- /core/stat/topk.go: -------------------------------------------------------------------------------- 1 | package stat 2 | 3 | import "container/heap" 4 | 5 | type taskHeap []Task 6 | 7 | func (h *taskHeap) Len() int { 8 | return len(*h) 9 | } 10 | 11 | func (h *taskHeap) Less(i, j int) bool { 12 | return (*h)[i].Duration < (*h)[j].Duration 13 | } 14 | 15 | func (h *taskHeap) Swap(i, j int) { 16 | (*h)[i], (*h)[j] = (*h)[j], (*h)[i] 17 | } 18 | 19 | func (h *taskHeap) Push(x any) { 20 | *h = append(*h, x.(Task)) 21 | } 22 | 23 | func (h *taskHeap) Pop() any { 24 | old := *h 25 | n := len(old) 26 | x := old[n-1] 27 | *h = old[0 : n-1] 28 | return x 29 | } 30 | 31 | func topK(all []Task, k int) []Task { 32 | h := new(taskHeap) 33 | heap.Init(h) 34 | 35 | for _, each := range all { 36 | if h.Len() < k { 37 | heap.Push(h, each) 38 | } else if (*h)[0].Duration < each.Duration { 39 | heap.Pop(h) 40 | heap.Push(h, each) 41 | } 42 | } 43 | 44 | return *h 45 | } 46 | -------------------------------------------------------------------------------- /core/stores/cache/cacheconf.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | // CacheConf is an alias of ClusterConf. 4 | type CacheConf = ClusterConf 5 | -------------------------------------------------------------------------------- /core/stores/cache/cacheopt_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCacheOptions(t *testing.T) { 11 | t.Run("default options", func(t *testing.T) { 12 | o := newOptions() 13 | assert.Equal(t, defaultExpiry, o.Expiry) 14 | assert.Equal(t, defaultNotFoundExpiry, o.NotFoundExpiry) 15 | }) 16 | 17 | t.Run("with expiry", func(t *testing.T) { 18 | o := newOptions(WithExpiry(time.Second)) 19 | assert.Equal(t, time.Second, o.Expiry) 20 | assert.Equal(t, defaultNotFoundExpiry, o.NotFoundExpiry) 21 | }) 22 | 23 | t.Run("with not found expiry", func(t *testing.T) { 24 | o := newOptions(WithNotFoundExpiry(time.Second)) 25 | assert.Equal(t, defaultExpiry, o.Expiry) 26 | assert.Equal(t, time.Second, o.NotFoundExpiry) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /core/stores/cache/cachestat_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/zeromicro/go-zero/core/timex" 7 | ) 8 | 9 | func TestCacheStat_statLoop(t *testing.T) { 10 | t.Run("stat loop total 0", func(t *testing.T) { 11 | var stat Stat 12 | ticker := timex.NewFakeTicker() 13 | go stat.statLoop(ticker) 14 | ticker.Tick() 15 | ticker.Tick() 16 | ticker.Stop() 17 | }) 18 | 19 | t.Run("stat loop total not 0", func(t *testing.T) { 20 | var stat Stat 21 | stat.IncrementTotal() 22 | ticker := timex.NewFakeTicker() 23 | go stat.statLoop(ticker) 24 | ticker.Tick() 25 | ticker.Tick() 26 | ticker.Stop() 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /core/stores/cache/config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "github.com/zeromicro/go-zero/core/stores/redis" 4 | 5 | type ( 6 | // A ClusterConf is the config of a redis cluster that used as cache. 7 | ClusterConf []NodeConf 8 | 9 | // A NodeConf is the config of a redis node that used as cache. 10 | NodeConf struct { 11 | redis.RedisConf 12 | Weight int `json:",default=100"` 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /core/stores/cache/util.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "strings" 4 | 5 | const keySeparator = "," 6 | 7 | // TotalWeights returns the total weights of given nodes. 8 | func TotalWeights(c []NodeConf) int { 9 | var weights int 10 | 11 | for _, node := range c { 12 | if node.Weight < 0 { 13 | node.Weight = 0 14 | } 15 | weights += node.Weight 16 | } 17 | 18 | return weights 19 | } 20 | 21 | func formatKeys(keys []string) string { 22 | return strings.Join(keys, keySeparator) 23 | } 24 | -------------------------------------------------------------------------------- /core/stores/cache/util_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFormatKeys(t *testing.T) { 10 | assert.Equal(t, "a,b", formatKeys([]string{"a", "b"})) 11 | } 12 | 13 | func TestTotalWeights(t *testing.T) { 14 | val := TotalWeights([]NodeConf{ 15 | { 16 | Weight: -1, 17 | }, 18 | { 19 | Weight: 0, 20 | }, 21 | { 22 | Weight: 1, 23 | }, 24 | }) 25 | assert.Equal(t, 1, val) 26 | } 27 | -------------------------------------------------------------------------------- /core/stores/kv/config.go: -------------------------------------------------------------------------------- 1 | package kv 2 | 3 | import "github.com/zeromicro/go-zero/core/stores/cache" 4 | 5 | // KvConf is an alias of cache.ClusterConf. 6 | type KvConf = cache.ClusterConf 7 | -------------------------------------------------------------------------------- /core/stores/mon/clientmanager_test.go: -------------------------------------------------------------------------------- 1 | package mon 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "go.mongodb.org/mongo-driver/mongo/integration/mtest" 8 | ) 9 | 10 | func TestClientManger_getClient(t *testing.T) { 11 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 12 | mt.Run("test", func(mt *mtest.T) { 13 | Inject(mtest.ClusterURI(), mt.Client) 14 | cli, err := getClient(mtest.ClusterURI()) 15 | assert.Nil(t, err) 16 | assert.Equal(t, mt.Client, cli) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /core/stores/postgres/postgresql.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | // imports the driver, don't remove this comment, golint requires. 5 | _ "github.com/jackc/pgx/v5/stdlib" 6 | 7 | "github.com/zeromicro/go-zero/core/stores/sqlx" 8 | ) 9 | 10 | const postgresDriverName = "pgx" 11 | 12 | // New returns a postgres connection. 13 | func New(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn { 14 | return sqlx.NewSqlConn(postgresDriverName, datasource, opts...) 15 | } 16 | -------------------------------------------------------------------------------- /core/stores/postgres/postgresql_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPostgreSql(t *testing.T) { 10 | assert.NotNil(t, New("postgre")) 11 | } 12 | -------------------------------------------------------------------------------- /core/stores/redis/delscript.lua: -------------------------------------------------------------------------------- 1 | if redis.call("GET", KEYS[1]) == ARGV[1] then 2 | return redis.call("DEL", KEYS[1]) 3 | else 4 | return 0 5 | end -------------------------------------------------------------------------------- /core/stores/redis/lockscript.lua: -------------------------------------------------------------------------------- 1 | if redis.call("GET", KEYS[1]) == ARGV[1] then 2 | redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2]) 3 | return "OK" 4 | else 5 | return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) 6 | end -------------------------------------------------------------------------------- /core/stores/redis/redistest/redistest.go: -------------------------------------------------------------------------------- 1 | package redistest 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/alicebob/miniredis/v2" 7 | "github.com/zeromicro/go-zero/core/stores/redis" 8 | ) 9 | 10 | // CreateRedis returns an in process redis.Redis. 11 | func CreateRedis(t *testing.T) *redis.Redis { 12 | r, _ := CreateRedisWithClean(t) 13 | return r 14 | } 15 | 16 | // CreateRedisWithClean returns an in process redis.Redis and a clean function. 17 | func CreateRedisWithClean(t *testing.T) (r *redis.Redis, clean func()) { 18 | mr := miniredis.RunT(t) 19 | return redis.New(mr.Addr()), mr.Close 20 | } 21 | -------------------------------------------------------------------------------- /core/stores/redis/scriptcache_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/zeromicro/go-zero/core/logx" 8 | ) 9 | 10 | func TestScriptCache(t *testing.T) { 11 | logx.Disable() 12 | 13 | cache := GetScriptCache() 14 | cache.SetSha("foo", "bar") 15 | cache.SetSha("bla", "blabla") 16 | bar, ok := cache.GetSha("foo") 17 | assert.True(t, ok) 18 | assert.Equal(t, "bar", bar) 19 | } 20 | -------------------------------------------------------------------------------- /core/stores/sqlx/errors.go: -------------------------------------------------------------------------------- 1 | package sqlx 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | // ErrNotFound is an alias of sql.ErrNoRows 10 | ErrNotFound = sql.ErrNoRows 11 | 12 | errCantNestTx = errors.New("cannot nest transactions") 13 | errNoRawDBFromTx = errors.New("cannot get raw db from transaction") 14 | ) 15 | -------------------------------------------------------------------------------- /core/stringx/random_test.go: -------------------------------------------------------------------------------- 1 | package stringx 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRand(t *testing.T) { 11 | Seed(time.Now().UnixNano()) 12 | assert.True(t, len(Rand()) > 0) 13 | assert.True(t, len(RandId()) > 0) 14 | 15 | const size = 10 16 | assert.True(t, len(Randn(size)) == size) 17 | } 18 | 19 | func BenchmarkRandString(b *testing.B) { 20 | for i := 0; i < b.N; i++ { 21 | _ = Randn(10) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/syncx/atomicbool_test.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestAtomicBool(t *testing.T) { 10 | val := ForAtomicBool(true) 11 | assert.True(t, val.True()) 12 | val.Set(false) 13 | assert.False(t, val.True()) 14 | val.Set(true) 15 | assert.True(t, val.True()) 16 | val.Set(false) 17 | assert.False(t, val.True()) 18 | ok := val.CompareAndSwap(false, true) 19 | assert.True(t, ok) 20 | assert.True(t, val.True()) 21 | ok = val.CompareAndSwap(true, false) 22 | assert.True(t, ok) 23 | assert.False(t, val.True()) 24 | ok = val.CompareAndSwap(true, false) 25 | assert.False(t, ok) 26 | assert.False(t, val.True()) 27 | } 28 | -------------------------------------------------------------------------------- /core/syncx/atomicduration_test.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAtomicDuration(t *testing.T) { 11 | d := ForAtomicDuration(time.Duration(100)) 12 | assert.Equal(t, time.Duration(100), d.Load()) 13 | d.Set(time.Duration(200)) 14 | assert.Equal(t, time.Duration(200), d.Load()) 15 | assert.True(t, d.CompareAndSwap(time.Duration(200), time.Duration(300))) 16 | assert.Equal(t, time.Duration(300), d.Load()) 17 | assert.False(t, d.CompareAndSwap(time.Duration(200), time.Duration(400))) 18 | assert.Equal(t, time.Duration(300), d.Load()) 19 | } 20 | -------------------------------------------------------------------------------- /core/syncx/atomicfloat64_test.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAtomicFloat64(t *testing.T) { 11 | f := ForAtomicFloat64(100) 12 | var wg sync.WaitGroup 13 | for i := 0; i < 5; i++ { 14 | wg.Add(1) 15 | go func() { 16 | for i := 0; i < 100; i++ { 17 | f.Add(1) 18 | } 19 | wg.Done() 20 | }() 21 | } 22 | wg.Wait() 23 | assert.Equal(t, float64(600), f.Load()) 24 | } 25 | -------------------------------------------------------------------------------- /core/syncx/barrier.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import "sync" 4 | 5 | // A Barrier is used to facility the barrier on a resource. 6 | type Barrier struct { 7 | lock sync.Mutex 8 | } 9 | 10 | // Guard guards the given fn on the resource. 11 | func (b *Barrier) Guard(fn func()) { 12 | Guard(&b.lock, fn) 13 | } 14 | 15 | // Guard guards the given fn with lock. 16 | func Guard(lock sync.Locker, fn func()) { 17 | lock.Lock() 18 | defer lock.Unlock() 19 | fn() 20 | } 21 | -------------------------------------------------------------------------------- /core/syncx/donechan.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/zeromicro/go-zero/core/lang" 7 | ) 8 | 9 | // A DoneChan is used as a channel that can be closed multiple times and wait for done. 10 | type DoneChan struct { 11 | done chan lang.PlaceholderType 12 | once sync.Once 13 | } 14 | 15 | // NewDoneChan returns a DoneChan. 16 | func NewDoneChan() *DoneChan { 17 | return &DoneChan{ 18 | done: make(chan lang.PlaceholderType), 19 | } 20 | } 21 | 22 | // Close closes dc, it's safe to close more than once. 23 | func (dc *DoneChan) Close() { 24 | dc.once.Do(func() { 25 | close(dc.done) 26 | }) 27 | } 28 | 29 | // Done returns a channel that can be notified on dc closed. 30 | func (dc *DoneChan) Done() chan lang.PlaceholderType { 31 | return dc.done 32 | } 33 | -------------------------------------------------------------------------------- /core/syncx/donechan_test.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func TestDoneChanClose(t *testing.T) { 9 | doneChan := NewDoneChan() 10 | 11 | for i := 0; i < 5; i++ { 12 | doneChan.Close() 13 | } 14 | } 15 | 16 | func TestDoneChanDone(t *testing.T) { 17 | var waitGroup sync.WaitGroup 18 | doneChan := NewDoneChan() 19 | 20 | waitGroup.Add(1) 21 | go func() { 22 | <-doneChan.Done() 23 | waitGroup.Done() 24 | }() 25 | 26 | for i := 0; i < 5; i++ { 27 | doneChan.Close() 28 | } 29 | 30 | waitGroup.Wait() 31 | } 32 | -------------------------------------------------------------------------------- /core/syncx/limit_test.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestLimit(t *testing.T) { 10 | limit := NewLimit(2) 11 | limit.Borrow() 12 | assert.True(t, limit.TryBorrow()) 13 | assert.False(t, limit.TryBorrow()) 14 | assert.Nil(t, limit.Return()) 15 | assert.Nil(t, limit.Return()) 16 | assert.Equal(t, ErrLimitReturn, limit.Return()) 17 | } 18 | -------------------------------------------------------------------------------- /core/syncx/managedresource_test.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "sync/atomic" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestManagedResource(t *testing.T) { 11 | var count int32 12 | resource := NewManagedResource(func() any { 13 | return atomic.AddInt32(&count, 1) 14 | }, func(a, b any) bool { 15 | return a == b 16 | }) 17 | 18 | assert.Equal(t, resource.Take(), resource.Take()) 19 | old := resource.Take() 20 | resource.MarkBroken(old) 21 | assert.NotEqual(t, old, resource.Take()) 22 | } 23 | -------------------------------------------------------------------------------- /core/syncx/once.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import "sync" 4 | 5 | // Once returns a func that guarantees fn can only called once. 6 | func Once(fn func()) func() { 7 | once := new(sync.Once) 8 | return func() { 9 | once.Do(fn) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /core/syncx/once_test.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestOnce(t *testing.T) { 10 | var v int 11 | add := Once(func() { 12 | v++ 13 | }) 14 | 15 | for i := 0; i < 5; i++ { 16 | add() 17 | } 18 | 19 | assert.Equal(t, 1, v) 20 | } 21 | 22 | func BenchmarkOnce(b *testing.B) { 23 | var v int 24 | add := Once(func() { 25 | v++ 26 | }) 27 | 28 | b.ResetTimer() 29 | for i := 0; i < b.N; i++ { 30 | add() 31 | } 32 | assert.Equal(b, 1, v) 33 | } 34 | -------------------------------------------------------------------------------- /core/syncx/onceguard.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import "sync/atomic" 4 | 5 | // An OnceGuard is used to make sure a resource can be taken once. 6 | type OnceGuard struct { 7 | done uint32 8 | } 9 | 10 | // Taken checks if the resource is taken. 11 | func (og *OnceGuard) Taken() bool { 12 | return atomic.LoadUint32(&og.done) == 1 13 | } 14 | 15 | // Take takes the resource, returns true on success, false for otherwise. 16 | func (og *OnceGuard) Take() bool { 17 | return atomic.CompareAndSwapUint32(&og.done, 0, 1) 18 | } 19 | -------------------------------------------------------------------------------- /core/syncx/onceguard_test.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestOnceGuard(t *testing.T) { 10 | var guard OnceGuard 11 | 12 | assert.False(t, guard.Taken()) 13 | assert.True(t, guard.Take()) 14 | assert.True(t, guard.Taken()) 15 | assert.False(t, guard.Take()) 16 | assert.True(t, guard.Taken()) 17 | } 18 | -------------------------------------------------------------------------------- /core/syncx/refresource_test.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRefCleaner(t *testing.T) { 10 | var count int 11 | clean := func() { 12 | count += 1 13 | } 14 | 15 | cleaner := NewRefResource(clean) 16 | err := cleaner.Use() 17 | assert.Nil(t, err) 18 | err = cleaner.Use() 19 | assert.Nil(t, err) 20 | cleaner.Clean() 21 | cleaner.Clean() 22 | assert.Equal(t, 1, count) 23 | cleaner.Clean() 24 | cleaner.Clean() 25 | assert.Equal(t, 1, count) 26 | assert.Equal(t, ErrUseOfCleaned, cleaner.Use()) 27 | } 28 | -------------------------------------------------------------------------------- /core/syncx/spinlock.go: -------------------------------------------------------------------------------- 1 | package syncx 2 | 3 | import ( 4 | "runtime" 5 | "sync/atomic" 6 | ) 7 | 8 | // A SpinLock is used as a lock a fast execution. 9 | type SpinLock struct { 10 | lock uint32 11 | } 12 | 13 | // Lock locks the SpinLock. 14 | func (sl *SpinLock) Lock() { 15 | for !sl.TryLock() { 16 | runtime.Gosched() 17 | } 18 | } 19 | 20 | // TryLock tries to lock the SpinLock. 21 | func (sl *SpinLock) TryLock() bool { 22 | return atomic.CompareAndSwapUint32(&sl.lock, 0, 1) 23 | } 24 | 25 | // Unlock unlocks the SpinLock. 26 | func (sl *SpinLock) Unlock() { 27 | atomic.StoreUint32(&sl.lock, 0) 28 | } 29 | -------------------------------------------------------------------------------- /core/sysx/automaxprocs.go: -------------------------------------------------------------------------------- 1 | package sysx 2 | 3 | import "go.uber.org/automaxprocs/maxprocs" 4 | 5 | // Automatically set GOMAXPROCS to match Linux container CPU quota. 6 | func init() { 7 | maxprocs.Set(maxprocs.Logger(nil)) 8 | } 9 | -------------------------------------------------------------------------------- /core/sysx/host.go: -------------------------------------------------------------------------------- 1 | package sysx 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/zeromicro/go-zero/core/stringx" 7 | ) 8 | 9 | var hostname string 10 | 11 | func init() { 12 | var err error 13 | hostname, err = os.Hostname() 14 | if err != nil { 15 | hostname = stringx.RandId() 16 | } 17 | } 18 | 19 | // Hostname returns the name of the host, if no hostname, a random id is returned. 20 | func Hostname() string { 21 | return hostname 22 | } 23 | -------------------------------------------------------------------------------- /core/sysx/host_test.go: -------------------------------------------------------------------------------- 1 | package sysx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestHostname(t *testing.T) { 10 | assert.True(t, len(Hostname()) > 0) 11 | } 12 | -------------------------------------------------------------------------------- /core/threading/routinegroup_test.go: -------------------------------------------------------------------------------- 1 | package threading 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "sync" 7 | "sync/atomic" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestRoutineGroupRun(t *testing.T) { 14 | var count int32 15 | group := NewRoutineGroup() 16 | for i := 0; i < 3; i++ { 17 | group.Run(func() { 18 | atomic.AddInt32(&count, 1) 19 | }) 20 | } 21 | 22 | group.Wait() 23 | 24 | assert.Equal(t, int32(3), count) 25 | } 26 | 27 | func TestRoutingGroupRunSafe(t *testing.T) { 28 | log.SetOutput(io.Discard) 29 | 30 | var count int32 31 | group := NewRoutineGroup() 32 | var once sync.Once 33 | for i := 0; i < 3; i++ { 34 | group.RunSafe(func() { 35 | once.Do(func() { 36 | panic("") 37 | }) 38 | atomic.AddInt32(&count, 1) 39 | }) 40 | } 41 | 42 | group.Wait() 43 | 44 | assert.Equal(t, int32(2), count) 45 | } 46 | -------------------------------------------------------------------------------- /core/threading/workergroup.go: -------------------------------------------------------------------------------- 1 | package threading 2 | 3 | // A WorkerGroup is used to run given number of workers to process jobs. 4 | type WorkerGroup struct { 5 | job func() 6 | workers int 7 | } 8 | 9 | // NewWorkerGroup returns a WorkerGroup with given job and workers. 10 | func NewWorkerGroup(job func(), workers int) WorkerGroup { 11 | return WorkerGroup{ 12 | job: job, 13 | workers: workers, 14 | } 15 | } 16 | 17 | // Start starts a WorkerGroup. 18 | func (wg WorkerGroup) Start() { 19 | group := NewRoutineGroup() 20 | for i := 0; i < wg.workers; i++ { 21 | group.RunSafe(wg.job) 22 | } 23 | group.Wait() 24 | } 25 | -------------------------------------------------------------------------------- /core/threading/workergroup_test.go: -------------------------------------------------------------------------------- 1 | package threading 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/zeromicro/go-zero/core/lang" 11 | ) 12 | 13 | func TestWorkerGroup(t *testing.T) { 14 | m := make(map[string]lang.PlaceholderType) 15 | var lock sync.Mutex 16 | var wg sync.WaitGroup 17 | wg.Add(runtime.NumCPU()) 18 | group := NewWorkerGroup(func() { 19 | lock.Lock() 20 | m[fmt.Sprint(RoutineId())] = lang.Placeholder 21 | lock.Unlock() 22 | wg.Done() 23 | }, runtime.NumCPU()) 24 | go group.Start() 25 | wg.Wait() 26 | assert.Equal(t, runtime.NumCPU(), len(m)) 27 | } 28 | -------------------------------------------------------------------------------- /core/timex/relativetime.go: -------------------------------------------------------------------------------- 1 | package timex 2 | 3 | import "time" 4 | 5 | // Use the long enough past time as start time, in case timex.Now() - lastTime equals 0. 6 | var initTime = time.Now().AddDate(-1, -1, -1) 7 | 8 | // Now returns a relative time duration since initTime, which is not important. 9 | // The caller only needs to care about the relative value. 10 | func Now() time.Duration { 11 | return time.Since(initTime) 12 | } 13 | 14 | // Since returns a diff since given d. 15 | func Since(d time.Duration) time.Duration { 16 | return time.Since(initTime) - d 17 | } 18 | -------------------------------------------------------------------------------- /core/timex/relativetime_test.go: -------------------------------------------------------------------------------- 1 | package timex 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRelativeTime(t *testing.T) { 11 | time.Sleep(time.Millisecond) 12 | now := Now() 13 | assert.True(t, now > 0) 14 | time.Sleep(time.Millisecond) 15 | assert.True(t, Since(now) > 0) 16 | } 17 | 18 | func BenchmarkTimeSince(b *testing.B) { 19 | b.ReportAllocs() 20 | 21 | for i := 0; i < b.N; i++ { 22 | _ = time.Since(time.Now()) 23 | } 24 | } 25 | 26 | func BenchmarkTimexSince(b *testing.B) { 27 | b.ReportAllocs() 28 | 29 | for i := 0; i < b.N; i++ { 30 | _ = Since(Now()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/timex/repr.go: -------------------------------------------------------------------------------- 1 | package timex 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // ReprOfDuration returns the string representation of given duration in ms. 9 | func ReprOfDuration(duration time.Duration) string { 10 | return fmt.Sprintf("%.1fms", float32(duration)/float32(time.Millisecond)) 11 | } 12 | -------------------------------------------------------------------------------- /core/timex/repr_test.go: -------------------------------------------------------------------------------- 1 | package timex 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestReprOfDuration(t *testing.T) { 11 | assert.Equal(t, "1000.0ms", ReprOfDuration(time.Second)) 12 | assert.Equal(t, "1111.6ms", ReprOfDuration( 13 | time.Second+time.Millisecond*111+time.Microsecond*555)) 14 | } 15 | -------------------------------------------------------------------------------- /core/trace/attributes_test.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | gcodes "google.golang.org/grpc/codes" 8 | ) 9 | 10 | func TestStatusCodeAttr(t *testing.T) { 11 | assert.Equal(t, GRPCStatusCodeKey.Int(int(gcodes.DataLoss)), StatusCodeAttr(gcodes.DataLoss)) 12 | } 13 | -------------------------------------------------------------------------------- /core/trace/config.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | // TraceName represents the tracing name. 4 | const TraceName = "go-zero" 5 | 6 | // A Config is an opentelemetry config. 7 | type Config struct { 8 | Name string `json:",optional"` 9 | Endpoint string `json:",optional"` 10 | Sampler float64 `json:",default=1.0"` 11 | Batcher string `json:",default=jaeger,options=jaeger|zipkin|otlpgrpc|otlphttp|file"` 12 | // OtlpHeaders represents the headers for OTLP gRPC or HTTP transport. 13 | // For example: 14 | // uptrace-dsn: 'http://project2_secret_token@localhost:14317/2' 15 | OtlpHeaders map[string]string `json:",optional"` 16 | // OtlpHttpPath represents the path for OTLP HTTP transport. 17 | // For example 18 | // /v1/traces 19 | OtlpHttpPath string `json:",optional"` 20 | // Disabled indicates whether StartAgent starts the agent. 21 | Disabled bool `json:",optional"` 22 | } 23 | -------------------------------------------------------------------------------- /core/trace/propagation.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "go.opentelemetry.io/otel" 5 | "go.opentelemetry.io/otel/propagation" 6 | ) 7 | 8 | func init() { 9 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( 10 | propagation.TraceContext{}, propagation.Baggage{})) 11 | } 12 | -------------------------------------------------------------------------------- /core/trace/resource.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import "go.opentelemetry.io/otel/attribute" 4 | 5 | var attrResources = make([]attribute.KeyValue, 0) 6 | 7 | // AddResources add more resources in addition to configured trace name. 8 | func AddResources(attrs ...attribute.KeyValue) { 9 | attrResources = append(attrResources, attrs...) 10 | } 11 | -------------------------------------------------------------------------------- /core/trace/tracetest/tracetest.go: -------------------------------------------------------------------------------- 1 | package tracetest 2 | 3 | import ( 4 | "testing" 5 | 6 | "go.opentelemetry.io/otel" 7 | "go.opentelemetry.io/otel/sdk/trace" 8 | "go.opentelemetry.io/otel/sdk/trace/tracetest" 9 | ) 10 | 11 | // NewInMemoryExporter returns a new InMemoryExporter 12 | // and sets it as the global for tests. 13 | func NewInMemoryExporter(t *testing.T) *tracetest.InMemoryExporter { 14 | me := tracetest.NewInMemoryExporter() 15 | t.Cleanup(func() { 16 | me.Reset() 17 | }) 18 | otel.SetTracerProvider(trace.NewTracerProvider(trace.WithSyncer(me))) 19 | 20 | return me 21 | } 22 | -------------------------------------------------------------------------------- /core/trace/vars.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import "net/http" 4 | 5 | // TraceIdKey is the trace id header. 6 | // https://www.w3.org/TR/trace-context/#trace-id 7 | // May change it to trace-id afterward. 8 | var TraceIdKey = http.CanonicalHeaderKey("x-trace-id") 9 | -------------------------------------------------------------------------------- /core/utils/uuid.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/google/uuid" 4 | 5 | // NewUuid returns an uuid string. 6 | func NewUuid() string { 7 | return uuid.New().String() 8 | } 9 | -------------------------------------------------------------------------------- /core/utils/uuid_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUuid(t *testing.T) { 10 | assert.Equal(t, 36, len(NewUuid())) 11 | } 12 | -------------------------------------------------------------------------------- /core/validation/validator.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | // Validator represents a validator. 4 | type Validator interface { 5 | // Validate validates the value. 6 | Validate() error 7 | } 8 | -------------------------------------------------------------------------------- /gateway/internal/eventhandler_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | func TestEventHandler(t *testing.T) { 13 | h := NewEventHandler(io.Discard, nil) 14 | h.OnResolveMethod(nil) 15 | h.OnSendHeaders(nil) 16 | h.OnReceiveHeaders(nil) 17 | h.OnReceiveTrailers(status.New(codes.OK, ""), nil) 18 | assert.Equal(t, codes.OK, h.Status.Code()) 19 | h.OnReceiveResponse(nil) 20 | } 21 | -------------------------------------------------------------------------------- /gateway/internal/headerprocessor.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | metadataHeaderPrefix = "Grpc-Metadata-" 11 | metadataPrefix = "gateway-" 12 | ) 13 | 14 | // ProcessHeaders builds the headers for the gateway from HTTP headers. 15 | func ProcessHeaders(header http.Header) []string { 16 | var headers []string 17 | 18 | for k, v := range header { 19 | if !strings.HasPrefix(k, metadataHeaderPrefix) { 20 | continue 21 | } 22 | 23 | key := fmt.Sprintf("%s%s", metadataPrefix, strings.TrimPrefix(k, metadataHeaderPrefix)) 24 | for _, vv := range v { 25 | headers = append(headers, key+":"+vv) 26 | } 27 | } 28 | 29 | return headers 30 | } 31 | -------------------------------------------------------------------------------- /gateway/internal/headerprocessor_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestBuildHeadersNoValue(t *testing.T) { 12 | req := httptest.NewRequest("GET", "/", http.NoBody) 13 | req.Header.Add("a", "b") 14 | assert.Nil(t, ProcessHeaders(req.Header)) 15 | } 16 | 17 | func TestBuildHeadersWithValues(t *testing.T) { 18 | req := httptest.NewRequest("GET", "/", http.NoBody) 19 | req.Header.Add("grpc-metadata-a", "b") 20 | req.Header.Add("grpc-metadata-b", "b") 21 | assert.ElementsMatch(t, []string{"gateway-A:b", "gateway-B:b"}, ProcessHeaders(req.Header)) 22 | } 23 | -------------------------------------------------------------------------------- /gateway/internal/timeout.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | const grpcTimeoutHeader = "Grpc-Timeout" 9 | 10 | // GetTimeout returns the timeout from the header, if not set, returns the default timeout. 11 | func GetTimeout(header http.Header, defaultTimeout time.Duration) time.Duration { 12 | if timeout := header.Get(grpcTimeoutHeader); len(timeout) > 0 { 13 | if t, err := time.ParseDuration(timeout); err == nil { 14 | return t 15 | } 16 | } 17 | 18 | return defaultTimeout 19 | } 20 | -------------------------------------------------------------------------------- /gateway/internal/timeout_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestGetTimeout(t *testing.T) { 13 | req := httptest.NewRequest("GET", "/", http.NoBody) 14 | req.Header.Set(grpcTimeoutHeader, "1s") 15 | timeout := GetTimeout(req.Header, time.Second*5) 16 | assert.Equal(t, time.Second, timeout) 17 | } 18 | 19 | func TestGetTimeoutDefault(t *testing.T) { 20 | req := httptest.NewRequest("GET", "/", http.NoBody) 21 | timeout := GetTimeout(req.Header, time.Second*5) 22 | assert.Equal(t, time.Second*5, timeout) 23 | } 24 | -------------------------------------------------------------------------------- /internal/devserver/config.go: -------------------------------------------------------------------------------- 1 | package devserver 2 | 3 | // Config is config for inner http server. 4 | type Config struct { 5 | Enabled bool `json:",default=true"` 6 | Host string `json:",optional"` 7 | Port int `json:",default=6060"` 8 | MetricsPath string `json:",default=/metrics"` 9 | HealthPath string `json:",default=/healthz"` 10 | EnableMetrics bool `json:",default=true"` 11 | EnablePprof bool `json:",default=true"` 12 | HealthResponse string `json:",default=OK"` 13 | } 14 | -------------------------------------------------------------------------------- /internal/mock/deposit.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package mock; 4 | 5 | option go_package = ".;mock"; 6 | 7 | message DepositRequest { 8 | float amount = 1; 9 | } 10 | 11 | message DepositResponse { 12 | bool ok = 1; 13 | } 14 | 15 | service DepositService { 16 | rpc Deposit(DepositRequest) returns (DepositResponse); 17 | } 18 | -------------------------------------------------------------------------------- /internal/mock/depositserver.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | // DepositServer is used for mocking. 12 | type DepositServer struct{} 13 | 14 | // Deposit handles the deposit requests. 15 | func (*DepositServer) Deposit(_ context.Context, req *DepositRequest) (*DepositResponse, error) { 16 | if req.GetAmount() < 0 { 17 | return nil, status.Errorf(codes.InvalidArgument, "cannot deposit %v", req.GetAmount()) 18 | } 19 | 20 | time.Sleep(time.Duration(req.GetAmount()) * time.Millisecond) 21 | return &DepositResponse{Ok: true}, nil 22 | } 23 | -------------------------------------------------------------------------------- /internal/trace/trace.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "context" 5 | 6 | "go.opentelemetry.io/otel/trace" 7 | ) 8 | 9 | func SpanIDFromContext(ctx context.Context) string { 10 | spanCtx := trace.SpanContextFromContext(ctx) 11 | if spanCtx.HasSpanID() { 12 | return spanCtx.SpanID().String() 13 | } 14 | 15 | return "" 16 | } 17 | 18 | func TraceIDFromContext(ctx context.Context) string { 19 | spanCtx := trace.SpanContextFromContext(ctx) 20 | if spanCtx.HasTraceID() { 21 | return spanCtx.TraceID().String() 22 | } 23 | 24 | return "" 25 | } 26 | -------------------------------------------------------------------------------- /rest/handler/gunziphandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "compress/gzip" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/zeromicro/go-zero/rest/httpx" 9 | ) 10 | 11 | const gzipEncoding = "gzip" 12 | 13 | // GunzipHandler returns a middleware to gunzip http request body. 14 | func GunzipHandler(next http.Handler) http.Handler { 15 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | if strings.Contains(r.Header.Get(httpx.ContentEncoding), gzipEncoding) { 17 | reader, err := gzip.NewReader(r.Body) 18 | if err != nil { 19 | w.WriteHeader(http.StatusBadRequest) 20 | return 21 | } 22 | 23 | r.Body = reader 24 | } 25 | 26 | next.ServeHTTP(w, r) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /rest/handler/maxbyteshandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/zeromicro/go-zero/rest/internal" 7 | ) 8 | 9 | // MaxBytesHandler returns a middleware that limit reading of http request body. 10 | func MaxBytesHandler(n int64) func(http.Handler) http.Handler { 11 | if n <= 0 { 12 | return func(next http.Handler) http.Handler { 13 | return next 14 | } 15 | } 16 | 17 | return func(next http.Handler) http.Handler { 18 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 19 | if r.ContentLength > n { 20 | internal.Errorf(r, "request entity too large, limit is %d, but got %d, rejected with code %d", 21 | n, r.ContentLength, http.StatusRequestEntityTooLarge) 22 | w.WriteHeader(http.StatusRequestEntityTooLarge) 23 | } else { 24 | next.ServeHTTP(w, r) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rest/handler/metrichandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/zeromicro/go-zero/core/stat" 7 | "github.com/zeromicro/go-zero/core/timex" 8 | ) 9 | 10 | // MetricHandler returns a middleware that stat the metrics. 11 | func MetricHandler(metrics *stat.Metrics) func(http.Handler) http.Handler { 12 | return func(next http.Handler) http.Handler { 13 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | startTime := timex.Now() 15 | defer func() { 16 | metrics.Add(stat.Task{ 17 | Duration: timex.Since(startTime), 18 | }) 19 | }() 20 | 21 | next.ServeHTTP(w, r) 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rest/handler/metrichandler_test.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/zeromicro/go-zero/core/stat" 10 | ) 11 | 12 | func TestMetricHandler(t *testing.T) { 13 | metrics := stat.NewMetrics("unit-test") 14 | metricHandler := MetricHandler(metrics) 15 | handler := metricHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | w.WriteHeader(http.StatusOK) 17 | })) 18 | 19 | req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody) 20 | resp := httptest.NewRecorder() 21 | handler.ServeHTTP(resp, req) 22 | assert.Equal(t, http.StatusOK, resp.Code) 23 | } 24 | -------------------------------------------------------------------------------- /rest/handler/recoverhandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "runtime/debug" 7 | 8 | "github.com/zeromicro/go-zero/rest/internal" 9 | ) 10 | 11 | // RecoverHandler returns a middleware that recovers if panic happens. 12 | func RecoverHandler(next http.Handler) http.Handler { 13 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | defer func() { 15 | if result := recover(); result != nil { 16 | internal.Error(r, fmt.Sprintf("%v\n%s", result, debug.Stack())) 17 | w.WriteHeader(http.StatusInternalServerError) 18 | } 19 | }() 20 | 21 | next.ServeHTTP(w, r) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /rest/httpc/internal/interceptor.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "net/http" 4 | 5 | type ( 6 | Interceptor func(r *http.Request) (*http.Request, ResponseHandler) 7 | ResponseHandler func(resp *http.Response, err error) 8 | ) 9 | -------------------------------------------------------------------------------- /rest/httpc/vars.go: -------------------------------------------------------------------------------- 1 | package httpc 2 | 3 | import "errors" 4 | 5 | const ( 6 | pathKey = "path" 7 | formKey = "form" 8 | headerKey = "header" 9 | jsonKey = "json" 10 | slash = "/" 11 | colon = ':' 12 | ) 13 | 14 | // ErrGetWithBody indicates that GET request with body. 15 | var ErrGetWithBody = errors.New("HTTP GET should not have body") 16 | -------------------------------------------------------------------------------- /rest/httpx/router.go: -------------------------------------------------------------------------------- 1 | package httpx 2 | 3 | import "net/http" 4 | 5 | // Router interface represents a http router that handles http requests. 6 | type Router interface { 7 | http.Handler 8 | Handle(method, path string, handler http.Handler) error 9 | SetNotFoundHandler(handler http.Handler) 10 | SetNotAllowedHandler(handler http.Handler) 11 | } 12 | -------------------------------------------------------------------------------- /rest/httpx/util_test.go: -------------------------------------------------------------------------------- 1 | package httpx 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGetRemoteAddr(t *testing.T) { 12 | host := "8.8.8.8" 13 | r, err := http.NewRequest(http.MethodGet, "/", strings.NewReader("")) 14 | assert.Nil(t, err) 15 | 16 | r.Header.Set(xForwardedFor, host) 17 | assert.Equal(t, host, GetRemoteAddr(r)) 18 | } 19 | 20 | func TestGetRemoteAddrNoHeader(t *testing.T) { 21 | r, err := http.NewRequest(http.MethodGet, "/", strings.NewReader("")) 22 | assert.Nil(t, err) 23 | 24 | assert.True(t, len(GetRemoteAddr(r)) == 0) 25 | } 26 | -------------------------------------------------------------------------------- /rest/internal/encoding/parser.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "net/http" 5 | "net/textproto" 6 | 7 | "github.com/zeromicro/go-zero/core/mapping" 8 | ) 9 | 10 | const headerKey = "header" 11 | 12 | var headerUnmarshaler = mapping.NewUnmarshaler(headerKey, mapping.WithStringValues(), 13 | mapping.WithCanonicalKeyFunc(textproto.CanonicalMIMEHeaderKey)) 14 | 15 | // ParseHeaders parses the headers request. 16 | func ParseHeaders(header http.Header, v any) error { 17 | m := map[string]any{} 18 | for k, v := range header { 19 | if len(v) == 1 { 20 | m[k] = v[0] 21 | } else { 22 | m[k] = v 23 | } 24 | } 25 | 26 | return headerUnmarshaler.Unmarshal(m, v) 27 | } 28 | -------------------------------------------------------------------------------- /rest/internal/header/headers.go: -------------------------------------------------------------------------------- 1 | package header 2 | 3 | const ( 4 | // ApplicationJson stands for application/json. 5 | ApplicationJson = "application/json" 6 | // ContentType is the header key for Content-Type. 7 | ContentType = "Content-Type" 8 | // JsonContentType is the content type for JSON. 9 | JsonContentType = "application/json; charset=utf-8" 10 | ) 11 | -------------------------------------------------------------------------------- /rest/pathvar/params.go: -------------------------------------------------------------------------------- 1 | package pathvar 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | var pathVars = contextKey("pathVars") 9 | 10 | // Vars parses path variables and returns a map. 11 | func Vars(r *http.Request) map[string]string { 12 | vars, ok := r.Context().Value(pathVars).(map[string]string) 13 | if ok { 14 | return vars 15 | } 16 | 17 | return nil 18 | } 19 | 20 | // WithVars writes params into given r and returns a new http.Request. 21 | func WithVars(r *http.Request, params map[string]string) *http.Request { 22 | return r.WithContext(context.WithValue(r.Context(), pathVars, params)) 23 | } 24 | 25 | type contextKey string 26 | 27 | func (c contextKey) String() string { 28 | return "rest/pathvar/context key: " + string(c) 29 | } 30 | -------------------------------------------------------------------------------- /rest/pathvar/params_test.go: -------------------------------------------------------------------------------- 1 | package pathvar 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestVars(t *testing.T) { 12 | expect := map[string]string{ 13 | "a": "1", 14 | "b": "2", 15 | } 16 | r, err := http.NewRequest(http.MethodGet, "/", nil) 17 | assert.Nil(t, err) 18 | r = WithVars(r, expect) 19 | assert.EqualValues(t, expect, Vars(r)) 20 | } 21 | 22 | func TestVarsNil(t *testing.T) { 23 | r, err := http.NewRequest(http.MethodGet, "/", nil) 24 | assert.Nil(t, err) 25 | assert.Nil(t, Vars(r)) 26 | } 27 | 28 | func TestContextKey(t *testing.T) { 29 | ck := contextKey("hello") 30 | assert.True(t, strings.Contains(ck.String(), "hello")) 31 | } 32 | -------------------------------------------------------------------------------- /rest/types.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | type ( 9 | // Middleware defines the middleware method. 10 | Middleware func(next http.HandlerFunc) http.HandlerFunc 11 | 12 | // A Route is a http route. 13 | Route struct { 14 | Method string 15 | Path string 16 | Handler http.HandlerFunc 17 | } 18 | 19 | // RouteOption defines the method to customize a featured route. 20 | RouteOption func(r *featuredRoutes) 21 | 22 | jwtSetting struct { 23 | enabled bool 24 | secret string 25 | prevSecret string 26 | } 27 | 28 | signatureSetting struct { 29 | SignatureConf 30 | enabled bool 31 | } 32 | 33 | featuredRoutes struct { 34 | timeout time.Duration 35 | priority bool 36 | jwt jwtSetting 37 | signature signatureSetting 38 | routes []Route 39 | maxBytes int64 40 | } 41 | ) 42 | -------------------------------------------------------------------------------- /tools/goctl/.dockerignore: -------------------------------------------------------------------------------- 1 | test/ 2 | .dockerignore 3 | .go-version 4 | Dockerfile 5 | goctl 6 | Makefile 7 | readme.md 8 | readme-cn.md 9 | -------------------------------------------------------------------------------- /tools/goctl/api/apigen/api.tpl: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | info ( 4 | title: // TODO: add title 5 | desc: // TODO: add description 6 | author: "{{.gitUser}}" 7 | email: "{{.gitEmail}}" 8 | ) 9 | 10 | type request { 11 | // TODO: add members here and delete this comment 12 | } 13 | 14 | type response { 15 | // TODO: add members here and delete this comment 16 | } 17 | 18 | service {{.serviceName}} { 19 | @handler GetUser // TODO: set handler name and delete this comment 20 | get /users/id/:userId(request) returns(response) 21 | 22 | @handler CreateUser // TODO: set handler name and delete this comment 23 | post /users/create(request) 24 | } 25 | -------------------------------------------------------------------------------- /tools/goctl/api/apigen/util.go: -------------------------------------------------------------------------------- 1 | package apigen 2 | 3 | import ( 4 | "os/exec" 5 | "strings" 6 | ) 7 | 8 | func getGitName() string { 9 | cmd := exec.Command("git", "config", "user.name") 10 | out, err := cmd.CombinedOutput() 11 | if err != nil { 12 | return "" 13 | } 14 | 15 | return strings.TrimSpace(string(out)) 16 | } 17 | 18 | func getGitEmail() string { 19 | cmd := exec.Command("git", "config", "user.email") 20 | out, err := cmd.CombinedOutput() 21 | if err != nil { 22 | return "" 23 | } 24 | 25 | return strings.TrimSpace(string(out)) 26 | } 27 | -------------------------------------------------------------------------------- /tools/goctl/api/dartgen/format.go: -------------------------------------------------------------------------------- 1 | package dartgen 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | const dartExec = "dart" 10 | 11 | func formatDir(dir string) error { 12 | ok, err := dirctoryExists(dir) 13 | if err != nil { 14 | return err 15 | } 16 | if !ok { 17 | return fmt.Errorf("format failed, directory %q does not exist", dir) 18 | } 19 | 20 | _, err = exec.LookPath(dartExec) 21 | if err != nil { 22 | return err 23 | } 24 | cmd := exec.Command(dartExec, "format", dir) 25 | cmd.Env = os.Environ() 26 | cmd.Stderr = os.Stderr 27 | 28 | return cmd.Run() 29 | } 30 | 31 | func dirctoryExists(dir string) (bool, error) { 32 | _, err := os.Stat(dir) 33 | if err == nil { 34 | return true, nil 35 | } 36 | if os.IsNotExist(err) { 37 | return false, nil 38 | } 39 | return false, err 40 | } 41 | -------------------------------------------------------------------------------- /tools/goctl/api/docgen/markdown.tpl: -------------------------------------------------------------------------------- 1 | ### {{.index}}. {{.routeComment}} 2 | 3 | 1. route definition 4 | 5 | - Url: {{.uri}} 6 | - Method: {{.method}} 7 | - Request: {{.requestType}} 8 | - Response: {{.responseType}} 9 | 10 | 2. request definition 11 | 12 | {{.requestContent}} 13 | 14 | 3. response definition 15 | 16 | {{.responseContent}} 17 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/config.tpl: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import {{.authImport}} 4 | 5 | type Config struct { 6 | rest.RestConf 7 | {{.auth}} 8 | {{.jwtTrans}} 9 | } 10 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/etc.tpl: -------------------------------------------------------------------------------- 1 | Name: {{.serviceName}} 2 | Host: {{.host}} 3 | Port: {{.port}} 4 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/handler.tpl: -------------------------------------------------------------------------------- 1 | package {{.PkgName}} 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/zeromicro/go-zero/rest/httpx" 7 | {{.ImportPackages}} 8 | ) 9 | 10 | {{if .HasDoc}}{{.Doc}}{{end}} 11 | func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc { 12 | return func(w http.ResponseWriter, r *http.Request) { 13 | {{if .HasRequest}}var req types.{{.RequestType}} 14 | if err := httpx.Parse(r, &req); err != nil { 15 | httpx.ErrorCtx(r.Context(), w, err) 16 | return 17 | } 18 | 19 | {{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx) 20 | {{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}}) 21 | if err != nil { 22 | httpx.ErrorCtx(r.Context(), w, err) 23 | } else { 24 | {{if .HasResp}}httpx.OkJsonCtx(r.Context(), w, resp){{else}}httpx.Ok(w){{end}} 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/logic.tpl: -------------------------------------------------------------------------------- 1 | package {{.pkgName}} 2 | 3 | import ( 4 | {{.imports}} 5 | ) 6 | 7 | type {{.logic}} struct { 8 | logx.Logger 9 | ctx context.Context 10 | svcCtx *svc.ServiceContext 11 | } 12 | 13 | {{if .hasDoc}}{{.doc}}{{end}} 14 | func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} { 15 | return &{{.logic}}{ 16 | Logger: logx.WithContext(ctx), 17 | ctx: ctx, 18 | svcCtx: svcCtx, 19 | } 20 | } 21 | 22 | func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} { 23 | // todo: add your logic here and delete this line 24 | 25 | {{.returnString}} 26 | } 27 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/main.tpl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | {{.importPackages}} 8 | ) 9 | 10 | var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file") 11 | 12 | func main() { 13 | flag.Parse() 14 | 15 | var c config.Config 16 | conf.MustLoad(*configFile, &c) 17 | 18 | server := rest.MustNewServer(c.RestConf) 19 | defer server.Stop() 20 | 21 | ctx := svc.NewServiceContext(c) 22 | handler.RegisterHandlers(server, ctx) 23 | 24 | fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) 25 | server.Start() 26 | } 27 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/middleware.tpl: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import "net/http" 4 | 5 | type {{.name}} struct { 6 | } 7 | 8 | func New{{.name}}() *{{.name}} { 9 | return &{{.name}}{} 10 | } 11 | 12 | func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | // TODO generate middleware implement function, delete after code implementation 15 | 16 | // Passthrough to next handler if need 17 | next(w, r) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/svc.tpl: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | {{.configImport}} 5 | ) 6 | 7 | type ServiceContext struct { 8 | Config {{.config}} 9 | {{.middleware}} 10 | } 11 | 12 | func NewServiceContext(c {{.config}}) *ServiceContext { 13 | return &ServiceContext{ 14 | Config: c, 15 | {{.middlewareAssignment}} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/anonymous_annotation.api: -------------------------------------------------------------------------------- 1 | type Request struct { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | 5 | type Response struct { 6 | Message string `json:"message"` 7 | } 8 | 9 | service A-api { 10 | @handler GreetHandler 11 | get /greet/from/:name(Request) returns (Response) 12 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/another_import_api.api: -------------------------------------------------------------------------------- 1 | import "importApi.api" 2 | 3 | type AnotherRequest { 4 | Name string `path:"name,options=you|me"` 5 | } 6 | 7 | type AnotherResponse { 8 | Message string `json:"message"` // message 9 | } 10 | 11 | @server( 12 | group: greet 13 | ) 14 | service A-api { 15 | @handler AnotherImportHandler 16 | get /greet/from/another/:name(AnotherRequest) returns (AnotherResponse) 17 | } 18 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/ap_ino_info.api: -------------------------------------------------------------------------------- 1 | type Request struct { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | 5 | type Response struct { 6 | Message string `json:"message"` 7 | } 8 | 9 | service A-api { 10 | @server( 11 | handler: GreetHandler 12 | ) 13 | get /greet/from/:name(Request) returns (Response) 14 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/api_has_middleware.api: -------------------------------------------------------------------------------- 1 | type Request struct { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | 5 | type Response struct { 6 | Message string `json:"message"` 7 | } 8 | 9 | @server( 10 | middleware: TokenValidate 11 | ) 12 | service A-api { 13 | @handler GreetHandler 14 | get /greet/from/:name(Request) returns (Response) 15 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/api_has_no_request.api: -------------------------------------------------------------------------------- 1 | service A-api { 2 | @handler GreetHandler 3 | post /greet/ping () 4 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/api_jwt.api: -------------------------------------------------------------------------------- 1 | type Request struct { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | 5 | type Response struct { 6 | Message string `json:"message"` 7 | } 8 | 9 | @server( 10 | jwt: Auth 11 | signature: true 12 | ) 13 | service A-api { 14 | @handler GreetHandler 15 | get /greet/from/:name(Request) returns (Response) 16 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/api_jwt_with_middleware.api: -------------------------------------------------------------------------------- 1 | type Request struct { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | 5 | type Response struct { 6 | Message string `json:"message"` 7 | } 8 | 9 | @server( 10 | jwt: Auth 11 | jwtTransition: Trans 12 | middleware: TokenValidate 13 | ) 14 | service A-api { 15 | @handler GreetHandler 16 | get /greet/from/:name(Request) returns (Response) 17 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/api_route_test.api: -------------------------------------------------------------------------------- 1 | type Request struct { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | type Response struct { 5 | Message string `json:"message"` 6 | } 7 | service A-api { 8 | @handler NormalHandler 9 | get /greet/from/:name(Request) returns (Response) 10 | @handler NoResponseHandler 11 | get /greet/from/:sex(Request) 12 | @handler NoRequestHandler 13 | get /greet/from/request returns (Response) 14 | @handler NoRequestNoResponseHandler 15 | get /greet/from 16 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/has_comment_api_test.api: -------------------------------------------------------------------------------- 1 | type Inline struct { 2 | 3 | } 4 | 5 | type Request struct { 6 | Inline 7 | Name string `path:"name,options=you|me"` // name in path 8 | } 9 | 10 | type Response struct { 11 | Message string `json:"msg"` // message 12 | } 13 | 14 | service A-api { 15 | @doc ("helloworld") 16 | @server( 17 | handler: GreetHandler 18 | ) 19 | get /greet/from/:name (Request) returns (Response) 20 | 21 | @doc( 22 | summary: "comment comment comment" 23 | ) 24 | @handler NoResponseHandler 25 | get /greet/get (Request) 26 | } 27 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/has_import_api.api: -------------------------------------------------------------------------------- 1 | import "importApi.api" 2 | 3 | type Request { 4 | Name string `path:"name,options=you|me"` 5 | } 6 | 7 | type Response { 8 | Message string `json:"message"` // message 9 | } 10 | 11 | @server( 12 | group: greet 13 | ) 14 | service A-api { 15 | @handler GreetHandler 16 | get /greet/from/:name(Request) returns (Response) 17 | } 18 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/has_inline_no_exist_test.api: -------------------------------------------------------------------------------- 1 | type Request struct { 2 | Inline 3 | Name string `path:"name,options=you|me"` 4 | } 5 | 6 | type Response struct { 7 | Message string `json:"message"` // message 8 | } 9 | 10 | service A-api { 11 | @doc ("helloworld") 12 | @server( 13 | handler: GreetHandler 14 | ) 15 | get /greet/from/:name(Request) returns (Response) 16 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/import_api.api: -------------------------------------------------------------------------------- 1 | type ImportData { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/import_twice.api: -------------------------------------------------------------------------------- 1 | import "hasImportApi.api" 2 | import "anotherImportApi.api" 3 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/invalid_api_file.api: -------------------------------------------------------------------------------- 1 | type Request struct { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | 5 | type Response struct { 6 | Message string `json:"message"` 7 | } 8 | 9 | service A-api 10 | @server( 11 | handler: GreetHandler 12 | ) 13 | get /greet/from/:name(Request) returns (Response) 14 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/nest_type_api.api: -------------------------------------------------------------------------------- 1 | type Request { 2 | Name string `path:"name,options=you|me"` 3 | XXX struct { 4 | } 5 | } 6 | 7 | service A-api { 8 | @handler GreetHandler 9 | get /greet/from/:name(Request) 10 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/no_struct_tag_api.api: -------------------------------------------------------------------------------- 1 | type Request { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | 5 | type XXX {} 6 | 7 | type ( 8 | Response { 9 | Message string `json:"message"` 10 | } 11 | 12 | A {} 13 | 14 | B struct {} 15 | ) 16 | 17 | service A-api { 18 | @handler GreetHandler 19 | get /greet/from/:name(Request) returns (Response) 20 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/test_api_template.api: -------------------------------------------------------------------------------- 1 | info( 2 | title: doc title 3 | desc: "> 4 | doc description first part, 5 | doc description second part<" 6 | version: 1.0 7 | ) 8 | 9 | // TODO: test 10 | // { 11 | type Request struct { // TODO: test 12 | // TODO 13 | Name string `path:"name,options=you|me"` // } 14 | } // TODO: test 15 | 16 | // TODO: test 17 | type Response struct { 18 | Message string `json:"message"` 19 | } 20 | 21 | @server( 22 | // C0 23 | group: greet/s1 24 | ) 25 | // C1 26 | service A-api { 27 | // C2 28 | @server( // C3 29 | handler: GreetHandler 30 | ) 31 | get /greet/from/:name(Request) returns (Response) // hello 32 | 33 | // C4 34 | @handler NoResponseHandler // C5 35 | get /greet/get(Request) 36 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/testdata/test_multi_service_template.api: -------------------------------------------------------------------------------- 1 | info( 2 | title: doc title 3 | desc: doc description first part 4 | version: 1.0 5 | ) 6 | 7 | type Request struct { 8 | Name string `path:"name,options=you|me"` 9 | } 10 | 11 | type Response struct { 12 | Message string `json:"message"` 13 | } 14 | 15 | service A-api { 16 | @server( 17 | handler: GreetHandler 18 | ) 19 | get /greet/from/:name(Request) returns (Response) 20 | } 21 | 22 | service A-api { 23 | @server( 24 | handler: NoResponseHandler 25 | ) 26 | get /greet/get(Request) 27 | } -------------------------------------------------------------------------------- /tools/goctl/api/gogen/types.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT. 2 | package types{{if .containsTime}} 3 | import ( 4 | "time" 5 | ){{end}} 6 | {{.types}} 7 | -------------------------------------------------------------------------------- /tools/goctl/api/gogen/vars.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | const ( 4 | internal = "internal/" 5 | typesPacket = "types" 6 | configDir = internal + "config" 7 | contextDir = internal + "svc" 8 | handlerDir = internal + "handler" 9 | logicDir = internal + "logic" 10 | middlewareDir = internal + "middleware" 11 | typesDir = internal + typesPacket 12 | groupProperty = "group" 13 | ) 14 | -------------------------------------------------------------------------------- /tools/goctl/api/javagen/bool.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{.indent}}{{.decorator}} 3 | {{.indent}}public {{.returnType}} is{{.property}}() { 4 | {{.indent}} return this.{{.tagValue}}; 5 | {{.indent}}} 6 | 7 | {{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) { 8 | {{.indent}} this.{{.tagValue}} = {{.propertyValue}}; 9 | {{.indent}}} 10 | -------------------------------------------------------------------------------- /tools/goctl/api/javagen/component.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT. 2 | package com.xhb.logic.http.packet.{{.packet}}.model; 3 | 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | {{.imports}} 7 | 8 | public class {{.className}} extends {{.superClassName}} { 9 | 10 | {{.properties}} 11 | {{if .HasProperty}} 12 | 13 | public {{.className}}() { 14 | } 15 | 16 | public {{.className}}({{.params}}) { 17 | {{.constructorSetter}} 18 | } 19 | {{end}} 20 | 21 | {{.getSet}} 22 | } 23 | -------------------------------------------------------------------------------- /tools/goctl/api/javagen/getset.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{.indent}}{{.decorator}} 3 | {{.indent}}public {{.returnType}} get{{.property}}() { 4 | {{.indent}} return this.{{.tagValue}}; 5 | {{.indent}}} 6 | 7 | {{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) { 8 | {{.indent}} this.{{.tagValue}} = {{.propertyValue}}; 9 | {{.indent}}} 10 | -------------------------------------------------------------------------------- /tools/goctl/api/javagen/packet.tpl: -------------------------------------------------------------------------------- 1 | package com.xhb.logic.http.packet.{{.packet}}; 2 | 3 | import com.xhb.core.packet.HttpPacket; 4 | import com.xhb.core.network.HttpRequestClient; 5 | {{.imports}} 6 | 7 | {{.doc}} 8 | public class {{.packetName}} extends HttpPacket<{{.responseType}}> { 9 | {{.paramsDeclaration}} 10 | 11 | public {{.packetName}}({{.params}}{{if .HasRequestBody}}{{.requestType}} request{{end}}) { 12 | {{if .HasRequestBody}}super(request);{{else}}super(EmptyRequest.instance);{{end}} 13 | {{if .HasRequestBody}}this.request = request;{{end}}{{.paramsSetter}} 14 | } 15 | 16 | @Override 17 | public HttpRequestClient.Method requestMethod() { 18 | return HttpRequestClient.Method.{{.method}}; 19 | } 20 | 21 | @Override 22 | public String requestUri() { 23 | return {{.uri}}; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tools/goctl/api/javagen/vars.go: -------------------------------------------------------------------------------- 1 | package javagen 2 | 3 | const modelDir = "model" 4 | -------------------------------------------------------------------------------- /tools/goctl/api/new/api.tpl: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | type Request { 4 | Name string `path:"name,options=you|me"` 5 | } 6 | 7 | type Response { 8 | Message string `json:"message"` 9 | } 10 | 11 | service {{.name}}-api { 12 | @handler {{.handler}}Handler 13 | get /from/:name(Request) returns (Response) 14 | } 15 | -------------------------------------------------------------------------------- /tools/goctl/api/parser/g4/ast/importstack.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "errors" 4 | 5 | // ErrImportCycleNotAllowed defines an error for circular importing 6 | var ErrImportCycleNotAllowed = errors.New("import cycle not allowed") 7 | 8 | // importStack a stack of import paths 9 | type importStack []string 10 | 11 | func (s *importStack) push(p string) error { 12 | for _, x := range *s { 13 | if x == p { 14 | return ErrImportCycleNotAllowed 15 | } 16 | } 17 | *s = append(*s, p) 18 | return nil 19 | } 20 | 21 | func (s *importStack) pop() { 22 | *s = (*s)[0 : len(*s)-1] 23 | } 24 | -------------------------------------------------------------------------------- /tools/goctl/api/parser/g4/ast/placeholder.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Holder defines a default instance for PlaceHolder 4 | var Holder PlaceHolder 5 | 6 | // PlaceHolder defines an empty struct 7 | type PlaceHolder struct{} 8 | -------------------------------------------------------------------------------- /tools/goctl/api/parser/g4/gen/api/baseparser_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMatch(t *testing.T) { 10 | assert.False(t, matchRegex("v1ddd", versionRegex)) 11 | } 12 | 13 | func TestImportRegex(t *testing.T) { 14 | tests := []struct { 15 | value string 16 | matched bool 17 | }{ 18 | {`"bar.api"`, true}, 19 | {`"foo/bar.api"`, true}, 20 | {`"/foo/bar.api"`, true}, 21 | {`"../foo/bar.api"`, true}, 22 | {`"../../foo/bar.api"`, true}, 23 | 24 | {`"//bar.api"`, false}, 25 | {`"/foo/foo_bar.api"`, true}, 26 | } 27 | for _, tt := range tests { 28 | t.Run(tt.value, func(t *testing.T) { 29 | assert.Equal(t, tt.matched, matchRegex(tt.value, importValueRegex)) 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tools/goctl/api/parser/g4/test/apis/empty.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevwan/go-zero/527de1c50e6602e9ce1ceec9ce114ad6062df5d4/tools/goctl/api/parser/g4/test/apis/empty.api -------------------------------------------------------------------------------- /tools/goctl/api/parser/g4/test/apis/info.api: -------------------------------------------------------------------------------- 1 | info( 2 | author: songmeizi 3 | desc: "the sample of 4 | info" 5 | date: "2020-01-06" 6 | ) 7 | -------------------------------------------------------------------------------- /tools/goctl/api/parser/g4/test/apis/service.api: -------------------------------------------------------------------------------- 1 | type Foo {} 2 | 3 | @server( 4 | foo: foo 5 | bar: "bar" 6 | fooBar: "foo 7 | bar" 8 | ) 9 | service foo-api { 10 | @doc("foo") 11 | @handler foo 12 | get /foo (Foo) returns (Foo) 13 | @handler bar 14 | post /foo (Foo) 15 | @handler fooBar 16 | post /foo/bar 17 | @server( 18 | handler: getFoo 19 | ) 20 | post /foo/:id returns(Foo) 21 | } 22 | 23 | service foo-api { 24 | @doc( 25 | summary:"post foo" 26 | ) 27 | @handler postFoo 28 | post /foo/bar/post (Foo) 29 | } 30 | -------------------------------------------------------------------------------- /tools/goctl/api/parser/g4/test/apis/syntax.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" -------------------------------------------------------------------------------- /tools/goctl/api/parser/g4/test/apis/types.api: -------------------------------------------------------------------------------- 1 | type Foo{ 2 | } 3 | 4 | type Bar struct{ 5 | } 6 | 7 | type FooBar { 8 | Foo int 9 | Bar bool 10 | Map map[string]int 11 | Map1 map[string]Bar 12 | Map2 map[string]*Bar 13 | Map3 map[string][]int 14 | Map4 map[string][]Bar 15 | Map5 map[string][]*Bar 16 | Map6 map[string]map[string]int 17 | Array []int 18 | Array1 []*Bar 19 | Array2 []Bar 20 | Pointer *Bar 21 | Bar 22 | } 23 | 24 | -------------------------------------------------------------------------------- /tools/goctl/api/parser/g4/test/grammar_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | var files = []string{ 10 | "example", 11 | "empty", 12 | "syntax", 13 | "info", 14 | "types", 15 | "service", 16 | } 17 | 18 | func TestGrammar(t *testing.T) { 19 | for _, file := range files { 20 | t.Run(file, func(t *testing.T) { 21 | _, err := parser.Parse("./apis/" + file + ".api") 22 | assert.Nil(t, err) 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tools/goctl/api/parser/testdata/test.api: -------------------------------------------------------------------------------- 1 | // syntax doc 2 | syntax = "v1" // syntax comment 3 | 4 | // type doc 5 | type Request { 6 | Name string `path:"name,options=you|me"` 7 | } 8 | 9 | type Response { 10 | Message string `json:"message"` 11 | } 12 | 13 | // service doc 14 | @server( 15 | group: test 16 | middleware: m1,m2 17 | prefix: v1 18 | ) 19 | service greet-api { 20 | // handler doc 21 | @handler GreetHandler // handler comment 22 | get /from/:name(Request) returns (Response); 23 | } -------------------------------------------------------------------------------- /tools/goctl/api/spec/example_test.go: -------------------------------------------------------------------------------- 1 | package spec_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/zeromicro/go-zero/tools/goctl/api/spec" 7 | ) 8 | 9 | func ExampleMember_GetEnumOptions() { 10 | member := spec.Member{ 11 | Tag: `json:"foo,options=foo|bar|options|123"`, 12 | } 13 | fmt.Println(member.GetEnumOptions()) 14 | // Output: 15 | // [foo bar options 123] 16 | } 17 | -------------------------------------------------------------------------------- /tools/goctl/api/spec/validate.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import "errors" 4 | 5 | var ErrMissingService = errors.New("missing service") 6 | 7 | // Validate validates Validate the integrity of the spec. 8 | func (s *ApiSpec) Validate() error { 9 | if len(s.Service.Name) == 0 { 10 | return ErrMissingService 11 | } 12 | if len(s.Service.Groups) == 0 { 13 | return ErrMissingService 14 | } 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /tools/goctl/api/tsgen/components.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT. 2 | 3 | {{.componentTypes}} 4 | -------------------------------------------------------------------------------- /tools/goctl/api/tsgen/genrequest.go: -------------------------------------------------------------------------------- 1 | package tsgen 2 | 3 | import ( 4 | _ "embed" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/zeromicro/go-zero/tools/goctl/util/pathx" 9 | ) 10 | 11 | //go:embed request.ts 12 | var requestTemplate string 13 | 14 | func genRequest(dir string) error { 15 | abs, err := filepath.Abs(dir) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | filename := filepath.Join(abs, "gocliRequest.ts") 21 | if pathx.FileExists(filename) { 22 | return nil 23 | } 24 | 25 | return os.WriteFile(filename, []byte(requestTemplate), 0644) 26 | } 27 | -------------------------------------------------------------------------------- /tools/goctl/api/tsgen/handler.tpl: -------------------------------------------------------------------------------- 1 | {{.imports}} 2 | 3 | {{.apis}} 4 | -------------------------------------------------------------------------------- /tools/goctl/api/tsgen/vars.go: -------------------------------------------------------------------------------- 1 | package tsgen 2 | 3 | const ( 4 | packagePrefix = "components." 5 | pathPrefix = "pathPrefix" 6 | ) 7 | -------------------------------------------------------------------------------- /tools/goctl/api/validate/validate.go: -------------------------------------------------------------------------------- 1 | package validate 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/gookit/color" 8 | "github.com/spf13/cobra" 9 | "github.com/zeromicro/go-zero/tools/goctl/api/parser" 10 | ) 11 | 12 | // VarStringAPI describes an API. 13 | var VarStringAPI string 14 | 15 | // GoValidateApi verifies whether the api has a syntax error 16 | func GoValidateApi(_ *cobra.Command, _ []string) error { 17 | apiFile := VarStringAPI 18 | 19 | if len(apiFile) == 0 { 20 | return errors.New("missing -api") 21 | } 22 | 23 | spec, err := parser.Parse(apiFile) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | err = spec.Validate() 29 | if err == nil { 30 | fmt.Println(color.Green.Render("api format ok")) 31 | } 32 | return err 33 | } 34 | -------------------------------------------------------------------------------- /tools/goctl/bug/cmd.go: -------------------------------------------------------------------------------- 1 | package bug 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "github.com/zeromicro/go-zero/tools/goctl/internal/cobrax" 6 | ) 7 | 8 | // Cmd describes a bug command. 9 | var Cmd = cobrax.NewCommand("bug", cobrax.WithRunE(cobra.NoArgs), cobrax.WithArgs(cobra.NoArgs)) 10 | -------------------------------------------------------------------------------- /tools/goctl/bug/env.go: -------------------------------------------------------------------------------- 1 | package bug 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | 9 | "github.com/zeromicro/go-zero/tools/goctl/internal/version" 10 | ) 11 | 12 | type env map[string]string 13 | 14 | func (e env) string() string { 15 | if e == nil { 16 | return "" 17 | } 18 | 19 | w := bytes.NewBuffer(nil) 20 | for k, v := range e { 21 | w.WriteString(fmt.Sprintf("%s = %q\n", k, v)) 22 | } 23 | 24 | return strings.TrimSuffix(w.String(), "\n") 25 | } 26 | 27 | func getEnv() env { 28 | e := make(env) 29 | e[os] = runtime.GOOS 30 | e[arch] = runtime.GOARCH 31 | e[goctlVersion] = version.BuildVersion 32 | e[goVersion] = runtime.Version() 33 | return e 34 | } 35 | -------------------------------------------------------------------------------- /tools/goctl/compare/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "github.com/zeromicro/go-zero/tools/goctl/compare/testdata" 6 | "github.com/zeromicro/go-zero/tools/goctl/util/console" 7 | ) 8 | 9 | var rootCmd = &cobra.Command{ 10 | Use: "compare", 11 | Short: "Compare the goctl commands generated results between urfave and cobra", 12 | Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), 13 | Run: func(cmd *cobra.Command, args []string) { 14 | dir := args[0] 15 | testdata.MustRun(dir) 16 | }, 17 | } 18 | 19 | func Execute() { 20 | if err := rootCmd.Execute(); err != nil { 21 | console.Error("%+v", err) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tools/goctl/compare/compare.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/zeromicro/go-zero/tools/goctl/compare/cmd" 4 | 5 | // EXPERIMENTAL: compare goctl generated code results between old and new, it will be removed in the feature. 6 | // TODO: BEFORE RUNNING: export DSN=$datasource, the database must be gozero, and there has no limit for tables. 7 | // TODO: AFTER RUNNING: diff --recursive old_fs new_fs 8 | 9 | func main() { 10 | cmd.Execute() 11 | } 12 | -------------------------------------------------------------------------------- /tools/goctl/compare/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wd=`dirname $0` 4 | GOBIN="$GOPATH/bin" 5 | EXE=goctl-compare 6 | go build -o $EXE $wd 7 | mv $EXE $GOBIN 8 | -------------------------------------------------------------------------------- /tools/goctl/compare/testdata/kotlin.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | info( 4 | title: "type title here" 5 | desc: "type desc here" 6 | author: "type author here" 7 | email: "type email here" 8 | version: "type version here" 9 | ) 10 | 11 | type req{} 12 | type reply{} 13 | 14 | service kotlin-api{ 15 | @handler ping 16 | post /ping 17 | } 18 | -------------------------------------------------------------------------------- /tools/goctl/compare/testdata/unformat.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | type Foo struct{} 4 | -------------------------------------------------------------------------------- /tools/goctl/env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/zeromicro/go-zero/tools/goctl/pkg/env" 8 | ) 9 | 10 | func write(_ *cobra.Command, args []string) error { 11 | if len(sliceVarWriteValue) > 0 { 12 | return env.WriteEnv(sliceVarWriteValue) 13 | } 14 | fmt.Println(env.Print(args...)) 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /tools/goctl/env/install.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func install(_ *cobra.Command, _ []string) error { 6 | return Prepare(true, boolVarForce, boolVarVerbose) 7 | } 8 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hello; 4 | 5 | option go_package = "./hello"; 6 | 7 | message HelloReq { 8 | string in = 1; 9 | } 10 | 11 | message HelloResp { 12 | string msg = 1; 13 | } 14 | 15 | service Greet { 16 | rpc SayHello(HelloReq) returns (HelloResp); 17 | } -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hello/etc/hello.yaml: -------------------------------------------------------------------------------- 1 | Name: hello.rpc 2 | ListenOn: 127.0.0.1:8080 3 | Etcd: 4 | Hosts: 5 | - 127.0.0.1:2379 6 | Key: hello.rpc 7 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hello/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/zeromicro/go-zero/zrpc" 4 | 5 | type Config struct { 6 | zrpc.RpcServerConf 7 | } 8 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hello/internal/logic/greet/sayhellologic.go: -------------------------------------------------------------------------------- 1 | package greetlogic 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/svc" 7 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/pb/hello" 8 | 9 | "github.com/zeromicro/go-zero/core/logx" 10 | ) 11 | 12 | type SayHelloLogic struct { 13 | ctx context.Context 14 | svcCtx *svc.ServiceContext 15 | logx.Logger 16 | } 17 | 18 | func NewSayHelloLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SayHelloLogic { 19 | return &SayHelloLogic{ 20 | ctx: ctx, 21 | svcCtx: svcCtx, 22 | Logger: logx.WithContext(ctx), 23 | } 24 | } 25 | 26 | func (l *SayHelloLogic) SayHello(in *hello.HelloReq) (*hello.HelloResp, error) { 27 | // todo: add your logic here and delete this line 28 | 29 | return &hello.HelloResp{}, nil 30 | } 31 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hello/internal/server/greet/greetserver.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT! 2 | // Source: hello.proto 3 | 4 | package server 5 | 6 | import ( 7 | "context" 8 | 9 | greetlogic "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/logic/greet" 10 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/svc" 11 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/pb/hello" 12 | ) 13 | 14 | type GreetServer struct { 15 | svcCtx *svc.ServiceContext 16 | hello.UnimplementedGreetServer 17 | } 18 | 19 | func NewGreetServer(svcCtx *svc.ServiceContext) *GreetServer { 20 | return &GreetServer{ 21 | svcCtx: svcCtx, 22 | } 23 | } 24 | 25 | func (s *GreetServer) SayHello(ctx context.Context, in *hello.HelloReq) (*hello.HelloResp, error) { 26 | l := greetlogic.NewSayHelloLogic(ctx, s.svcCtx) 27 | return l.SayHello(in) 28 | } 29 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hello/internal/svc/servicecontext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/config" 4 | 5 | type ServiceContext struct { 6 | Config config.Config 7 | } 8 | 9 | func NewServiceContext(c config.Config) *ServiceContext { 10 | return &ServiceContext{ 11 | Config: c, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hi.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hi; 4 | 5 | option go_package = "./hi"; 6 | 7 | message HiReq { 8 | string in = 1; 9 | } 10 | 11 | message HelloReq { 12 | string in = 1; 13 | } 14 | 15 | message HiResp { 16 | string msg = 1; 17 | } 18 | 19 | message HelloResp { 20 | string msg = 1; 21 | } 22 | 23 | service Greet { 24 | rpc SayHi(HiReq) returns (HiResp); 25 | rpc SayHello(HelloReq) returns (HelloResp); 26 | } 27 | 28 | message EventReq{} 29 | message EventResp{} 30 | 31 | service Event { 32 | rpc AskQuestion(EventReq) returns (EventResp); 33 | } -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hi/etc/hi.yaml: -------------------------------------------------------------------------------- 1 | Name: hi.rpc 2 | ListenOn: 127.0.0.1:8080 3 | Etcd: 4 | Hosts: 5 | - 127.0.0.1:2379 6 | Key: hi.rpc 7 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hi/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/zeromicro/go-zero/zrpc" 4 | 5 | type Config struct { 6 | zrpc.RpcServerConf 7 | } 8 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hi/internal/logic/event/askquestionlogic.go: -------------------------------------------------------------------------------- 1 | package eventlogic 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc" 7 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi" 8 | 9 | "github.com/zeromicro/go-zero/core/logx" 10 | ) 11 | 12 | type AskQuestionLogic struct { 13 | ctx context.Context 14 | svcCtx *svc.ServiceContext 15 | logx.Logger 16 | } 17 | 18 | func NewAskQuestionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AskQuestionLogic { 19 | return &AskQuestionLogic{ 20 | ctx: ctx, 21 | svcCtx: svcCtx, 22 | Logger: logx.WithContext(ctx), 23 | } 24 | } 25 | 26 | func (l *AskQuestionLogic) AskQuestion(in *hi.EventReq) (*hi.EventResp, error) { 27 | // todo: add your logic here and delete this line 28 | 29 | return &hi.EventResp{}, nil 30 | } 31 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hi/internal/logic/greet/sayhellologic.go: -------------------------------------------------------------------------------- 1 | package greetlogic 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc" 7 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi" 8 | 9 | "github.com/zeromicro/go-zero/core/logx" 10 | ) 11 | 12 | type SayHelloLogic struct { 13 | ctx context.Context 14 | svcCtx *svc.ServiceContext 15 | logx.Logger 16 | } 17 | 18 | func NewSayHelloLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SayHelloLogic { 19 | return &SayHelloLogic{ 20 | ctx: ctx, 21 | svcCtx: svcCtx, 22 | Logger: logx.WithContext(ctx), 23 | } 24 | } 25 | 26 | func (l *SayHelloLogic) SayHello(in *hi.HelloReq) (*hi.HelloResp, error) { 27 | // todo: add your logic here and delete this line 28 | 29 | return &hi.HelloResp{}, nil 30 | } 31 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hi/internal/logic/greet/sayhilogic.go: -------------------------------------------------------------------------------- 1 | package greetlogic 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc" 7 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi" 8 | 9 | "github.com/zeromicro/go-zero/core/logx" 10 | ) 11 | 12 | type SayHiLogic struct { 13 | ctx context.Context 14 | svcCtx *svc.ServiceContext 15 | logx.Logger 16 | } 17 | 18 | func NewSayHiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SayHiLogic { 19 | return &SayHiLogic{ 20 | ctx: ctx, 21 | svcCtx: svcCtx, 22 | Logger: logx.WithContext(ctx), 23 | } 24 | } 25 | 26 | func (l *SayHiLogic) SayHi(in *hi.HiReq) (*hi.HiResp, error) { 27 | // todo: add your logic here and delete this line 28 | 29 | return &hi.HiResp{}, nil 30 | } 31 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hi/internal/server/event/eventserver.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT! 2 | // Source: hi.proto 3 | 4 | package server 5 | 6 | import ( 7 | "context" 8 | 9 | eventlogic "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/logic/event" 10 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc" 11 | "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi" 12 | ) 13 | 14 | type EventServer struct { 15 | svcCtx *svc.ServiceContext 16 | hi.UnimplementedEventServer 17 | } 18 | 19 | func NewEventServer(svcCtx *svc.ServiceContext) *EventServer { 20 | return &EventServer{ 21 | svcCtx: svcCtx, 22 | } 23 | } 24 | 25 | func (s *EventServer) AskQuestion(ctx context.Context, in *hi.EventReq) (*hi.EventResp, error) { 26 | l := eventlogic.NewAskQuestionLogic(ctx, s.svcCtx) 27 | return l.AskQuestion(in) 28 | } 29 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/hi/internal/svc/servicecontext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/config" 4 | 5 | type ServiceContext struct { 6 | Config config.Config 7 | } 8 | 9 | func NewServiceContext(c config.Config) *ServiceContext { 10 | return &ServiceContext{ 11 | Config: c, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tools/goctl/example/rpc/multiple_rpc_service_generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wd=$(pwd) 4 | output="$wd/hi" 5 | 6 | rm -rf $output 7 | 8 | goctl rpc protoc -I $wd "$wd/hi.proto" --go_out="$output/pb" --go-grpc_out="$output/pb" --zrpc_out="$output" --multiple 9 | 10 | if [ $? -ne 0 ]; then 11 | echo "Generate failed" 12 | exit 1 13 | fi 14 | 15 | GOPROXY="https://goproxy.cn,direct" && go mod tidy 16 | 17 | if [ $? -ne 0 ]; then 18 | echo "Tidy failed" 19 | exit 1 20 | fi 21 | 22 | go test ./... -------------------------------------------------------------------------------- /tools/goctl/example/rpc/single_rpc_service_generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wd=$(pwd) 4 | output="$wd/hello" 5 | 6 | rm -rf $output 7 | 8 | goctl rpc protoc -I $wd "$wd/hello.proto" --go_out="$output/pb" --go-grpc_out="$output/pb" --zrpc_out="$output" --multiple 9 | 10 | if [ $? -ne 0 ]; then 11 | echo "Generate failed" 12 | exit 1 13 | fi 14 | 15 | GOPROXY="https://goproxy.cn,direct" && go mod tidy 16 | 17 | if [ $? -ne 0 ]; then 18 | echo "Tidy failed" 19 | exit 1 20 | fi 21 | 22 | go test ./... -------------------------------------------------------------------------------- /tools/goctl/gateway/conf.yml: -------------------------------------------------------------------------------- 1 | Name: gateway-example # gateway name 2 | Host: localhost # gateway host 3 | Port: 8888 # gateway port 4 | Upstreams: # upstreams 5 | - Grpc: # grpc upstream 6 | Target: 0.0.0.0:8080 # grpc target,the direct grpc server address,for only one node 7 | # Endpoints: [0.0.0.0:8080,192.168.120.1:8080] # grpc endpoints, the grpc server address list, for multiple nodes 8 | # Etcd: # etcd config, if you want to use etcd to discover the grpc server address 9 | # Hosts: [127.0.0.1:2378,127.0.0.1:2379] # etcd hosts 10 | # Key: greet.grpc # the discovery key 11 | # protoset mode 12 | ProtoSets: 13 | - hello.pb 14 | # Mappings can also be written in proto options 15 | # Mappings: # routes mapping 16 | # - Method: get 17 | # Path: /ping 18 | # RpcPath: hello.Hello/Ping 19 | -------------------------------------------------------------------------------- /tools/goctl/gateway/gateway.tpl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/zeromicro/go-zero/core/conf" 7 | "github.com/zeromicro/go-zero/gateway" 8 | ) 9 | 10 | var configFile = flag.String("f", "etc/gateway.yaml", "config file") 11 | 12 | func main() { 13 | flag.Parse() 14 | 15 | var c gateway.GatewayConf 16 | conf.MustLoad(*configFile, &c) 17 | gw := gateway.MustNewServer(c) 18 | defer gw.Stop() 19 | gw.Start() 20 | } 21 | -------------------------------------------------------------------------------- /tools/goctl/goctl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/core/load" 5 | "github.com/zeromicro/go-zero/core/logx" 6 | "github.com/zeromicro/go-zero/tools/goctl/cmd" 7 | ) 8 | 9 | func main() { 10 | logx.Disable() 11 | load.Disable() 12 | cmd.Execute() 13 | } 14 | -------------------------------------------------------------------------------- /tools/goctl/internal/errorx/errorx._test.go: -------------------------------------------------------------------------------- 1 | package errorx 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestWrap(t *testing.T) { 11 | err := errors.New("foo") 12 | err = Wrap(err) 13 | _, ok := err.(*GoctlError) 14 | assert.True(t, ok) 15 | } 16 | -------------------------------------------------------------------------------- /tools/goctl/migrate/cancel+polyfill.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package migrate 4 | 5 | func cancelOnSignals() { 6 | } 7 | -------------------------------------------------------------------------------- /tools/goctl/migrate/cancel.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package migrate 4 | 5 | import ( 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/zeromicro/go-zero/core/syncx" 11 | "github.com/zeromicro/go-zero/tools/goctl/util/console" 12 | ) 13 | 14 | func cancelOnSignals() { 15 | doneChan := syncx.NewDoneChan() 16 | defer doneChan.Close() 17 | 18 | go func(dc *syncx.DoneChan) { 19 | c := make(chan os.Signal) 20 | signal.Notify(c, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGINT, syscall.SIGTSTP, syscall.SIGQUIT) 21 | select { 22 | case <-c: 23 | console.Error(` 24 | migrate failed, reason: "User Canceled"`) 25 | os.Exit(0) 26 | case <-dc.Done(): 27 | return 28 | } 29 | }(doneChan) 30 | } 31 | -------------------------------------------------------------------------------- /tools/goctl/migrate/cmd.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import "github.com/zeromicro/go-zero/tools/goctl/internal/cobrax" 4 | 5 | var ( 6 | boolVarVerbose bool 7 | stringVarVersion string 8 | // Cmd describes a migrate command. 9 | Cmd = cobrax.NewCommand("migrate", cobrax.WithRunE(migrate)) 10 | ) 11 | 12 | func init() { 13 | migrateCmdFlags := Cmd.Flags() 14 | migrateCmdFlags.BoolVarP(&boolVarVerbose, "verbose", "v") 15 | migrateCmdFlags.StringVarWithDefaultValue(&stringVarVersion, "version", defaultMigrateVersion) 16 | } 17 | -------------------------------------------------------------------------------- /tools/goctl/model/mongo/template/error.tpl: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/zeromicro/go-zero/core/stores/mon" 7 | ) 8 | 9 | var ( 10 | ErrNotFound = mon.ErrNotFound 11 | ErrInvalidObjectId = errors.New("invalid objectId") 12 | ) 13 | -------------------------------------------------------------------------------- /tools/goctl/model/mongo/template/template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import _ "embed" 4 | 5 | var ( 6 | // ModelText provides the default template for model to generate. 7 | //go:embed model.tpl 8 | ModelText string 9 | 10 | // ModelCustomText provides the default template for model to generate. 11 | //go:embed model_custom.tpl 12 | ModelCustomText string 13 | 14 | // ModelTypesText provides the default template for model to generate. 15 | //go:embed types.tpl 16 | ModelTypesText string 17 | 18 | // Error provides the default template for error definition in mongo code generation. 19 | //go:embed error.tpl 20 | Error string 21 | ) 22 | -------------------------------------------------------------------------------- /tools/goctl/model/mongo/template/types.tpl: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | type {{.Type}} struct { 10 | ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` 11 | // TODO: Fill your own fields 12 | UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"` 13 | CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"` 14 | } 15 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## 2020-10-19 4 | 5 | * 增加template 6 | 7 | ## 2020-08-20 8 | 9 | * 新增支持通过连接数据库生成model 10 | * 支持数据库多表生成 11 | * 优化stringx 12 | 13 | ## 2020-08-19 14 | 15 | * 重构model代码生成逻辑 16 | * 实现从ddl解析表信息生成代码 17 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/builderx/builder.go: -------------------------------------------------------------------------------- 1 | package builderx 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/core/stores/builder" 5 | ) 6 | 7 | // Deprecated: Use github.com/zeromicro/go-zero/core/stores/builder.RawFieldNames instead. 8 | func FieldNames(in any) []string { 9 | return builder.RawFieldNames(in) 10 | } 11 | 12 | // Deprecated: Use github.com/zeromicro/go-zero/core/stores/builder.RawFieldNames instead. 13 | func RawFieldNames(in any, postgresSql ...bool) []string { 14 | return builder.RawFieldNames(in, postgresSql...) 15 | } 16 | 17 | // Deprecated: Use github.com/zeromicro/go-zero/core/stores/builderx.PostgreSqlJoin instead. 18 | func PostgreSqlJoin(elems []string) string { 19 | return builder.PostgreSqlJoin(elems) 20 | } 21 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/command/migrationnotes/migrationnotes.go: -------------------------------------------------------------------------------- 1 | package migrationnotes 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/tools/goctl/config" 5 | "github.com/zeromicro/go-zero/tools/goctl/util/format" 6 | ) 7 | 8 | // BeforeCommands run before comamnd run to show some migration notes 9 | func BeforeCommands(dir, style string) error { 10 | if err := migrateBefore1_3_4(dir, style); err != nil { 11 | return err 12 | } 13 | return nil 14 | } 15 | 16 | func getModelSuffix(style string) (string, error) { 17 | cfg, err := config.NewConfig(style) 18 | if err != nil { 19 | return "", err 20 | } 21 | baseSuffix, err := format.FileNamingFormat(cfg.NamingFormat, "_model") 22 | if err != nil { 23 | return "", err 24 | } 25 | return baseSuffix + ".go", nil 26 | } 27 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/gen/tablename.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/tools/goctl/model/sql/template" 5 | "github.com/zeromicro/go-zero/tools/goctl/util" 6 | "github.com/zeromicro/go-zero/tools/goctl/util/pathx" 7 | ) 8 | 9 | func genTableName(table Table) (string, error) { 10 | text, err := pathx.LoadTemplate(category, tableNameTemplateFile, template.TableName) 11 | if err != nil { 12 | return "", err 13 | } 14 | 15 | output, err := util.With("tableName"). 16 | Parse(text). 17 | Execute(map[string]any{ 18 | "tableName": table.Name.Source(), 19 | "upperStartCamelObject": table.Name.ToCamel(), 20 | }) 21 | if err != nil { 22 | return "", nil 23 | } 24 | 25 | return output.String(), nil 26 | } 27 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/gen/tag.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/tools/goctl/model/sql/template" 5 | "github.com/zeromicro/go-zero/tools/goctl/util" 6 | "github.com/zeromicro/go-zero/tools/goctl/util/pathx" 7 | ) 8 | 9 | func genTag(table Table, in string) (string, error) { 10 | if in == "" { 11 | return in, nil 12 | } 13 | 14 | text, err := pathx.LoadTemplate(category, tagTemplateFile, template.Tag) 15 | if err != nil { 16 | return "", err 17 | } 18 | 19 | output, err := util.With("tag").Parse(text).Execute(map[string]any{ 20 | "field": in, 21 | "data": table, 22 | }) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | return output.String(), nil 28 | } 29 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/gen/testdata/user.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `test_user` 2 | ( 3 | `id` bigint NOT NULL AUTO_INCREMENT, 4 | `mobile` varchar(255) COLLATE utf8mb4_bin NOT NULL, 5 | `class` bigint NOT NULL, 6 | `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, 7 | `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, 8 | `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 9 | PRIMARY KEY (`id`), 10 | UNIQUE KEY `mobile_unique` (`mobile`), 11 | UNIQUE KEY `class_name_unique` (`class`,`name`), 12 | KEY `create_index` (`create_time`), 13 | KEY `name_index` (`name`) 14 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/customized.tpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevwan/go-zero/527de1c50e6602e9ce1ceec9ce114ad6062df5d4/tools/goctl/model/sql/template/tpl/customized.tpl -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/err.tpl: -------------------------------------------------------------------------------- 1 | package {{.pkg}} 2 | 3 | import "github.com/zeromicro/go-zero/core/stores/sqlx" 4 | 5 | var ErrNotFound = sqlx.ErrNotFound 6 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/field.tpl: -------------------------------------------------------------------------------- 1 | {{.name}} {{.type}} {{.tag}} {{if .hasComment}}// {{.comment}}{{end}} -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/find-one-by-field-extra-method.tpl: -------------------------------------------------------------------------------- 1 | func (m *default{{.upperStartCamelObject}}Model) formatPrimary(primary any) string { 2 | return fmt.Sprintf("%s%v", {{.primaryKeyLeft}}, primary) 3 | } 4 | 5 | func (m *default{{.upperStartCamelObject}}Model) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error { 6 | query := fmt.Sprintf("select %s from %s where {{.originalPrimaryField}} = {{if .postgreSql}}$1{{else}}?{{end}} limit 1", {{.lowerStartCamelObject}}Rows, m.table ) 7 | return conn.QueryRowCtx(ctx, v, query, primary) 8 | } 9 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/import-no-cache.tpl: -------------------------------------------------------------------------------- 1 | import ( 2 | "context" 3 | "database/sql" 4 | "fmt" 5 | "strings" 6 | {{if .time}}"time"{{end}} 7 | 8 | {{if .containsPQ}}"github.com/lib/pq"{{end}} 9 | "github.com/zeromicro/go-zero/core/stores/builder" 10 | "github.com/zeromicro/go-zero/core/stores/sqlx" 11 | "github.com/zeromicro/go-zero/core/stringx" 12 | 13 | {{.third}} 14 | ) 15 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/import.tpl: -------------------------------------------------------------------------------- 1 | import ( 2 | "context" 3 | "database/sql" 4 | "fmt" 5 | "strings" 6 | {{if .time}}"time"{{end}} 7 | 8 | {{if .containsPQ}}"github.com/lib/pq"{{end}} 9 | "github.com/zeromicro/go-zero/core/stores/builder" 10 | "github.com/zeromicro/go-zero/core/stores/cache" 11 | "github.com/zeromicro/go-zero/core/stores/sqlc" 12 | "github.com/zeromicro/go-zero/core/stores/sqlx" 13 | "github.com/zeromicro/go-zero/core/stringx" 14 | 15 | {{.third}} 16 | ) 17 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/insert.tpl: -------------------------------------------------------------------------------- 1 | func (m *default{{.upperStartCamelObject}}Model) Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error) { 2 | {{if .withCache}}{{.keys}} 3 | ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { 4 | query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet) 5 | return conn.ExecCtx(ctx, query, {{.expressionValues}}) 6 | }, {{.keyValues}}){{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet) 7 | ret,err:=m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}} 8 | return ret,err 9 | } 10 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/interface-delete.tpl: -------------------------------------------------------------------------------- 1 | Delete(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/interface-find-one-by-field.tpl: -------------------------------------------------------------------------------- 1 | FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/interface-find-one.tpl: -------------------------------------------------------------------------------- 1 | FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/interface-insert.tpl: -------------------------------------------------------------------------------- 1 | Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error) -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/interface-update.tpl: -------------------------------------------------------------------------------- 1 | Update(ctx context.Context, {{if .containsIndexCache}}newData{{else}}data{{end}} *{{.upperStartCamelObject}}) error -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/model-new.tpl: -------------------------------------------------------------------------------- 1 | func new{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c cache.CacheConf, opts ...cache.Option{{end}}) *default{{.upperStartCamelObject}}Model { 2 | return &default{{.upperStartCamelObject}}Model{ 3 | {{if .withCache}}CachedConn: sqlc.NewConn(conn, c, opts...){{else}}conn:conn{{end}}, 4 | table: {{.table}}, 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/table-name.tpl: -------------------------------------------------------------------------------- 1 | func (m *default{{.upperStartCamelObject}}Model) tableName() string { 2 | return m.table 3 | } 4 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/tag.tpl: -------------------------------------------------------------------------------- 1 | `db:"{{.field}}"` -------------------------------------------------------------------------------- /tools/goctl/model/sql/template/tpl/types.tpl: -------------------------------------------------------------------------------- 1 | type ( 2 | {{.lowerStartCamelObject}}Model interface{ 3 | {{.method}} 4 | } 5 | 6 | default{{.upperStartCamelObject}}Model struct { 7 | {{if .withCache}}sqlc.CachedConn{{else}}conn sqlx.SqlConn{{end}} 8 | table string 9 | } 10 | 11 | {{.upperStartCamelObject}} struct { 12 | {{.fields}} 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/test/model/vars.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/zeromicro/go-zero/core/stores/sqlx" 4 | 5 | // ErrNotFound types an alias for sqlx.ErrNotFound 6 | var ErrNotFound = sqlx.ErrNotFound 7 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/util/matcher.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | // MatchFiles returns the match values by globbing patterns 9 | func MatchFiles(in string) ([]string, error) { 10 | dir, pattern := filepath.Split(in) 11 | abs, err := filepath.Abs(dir) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | files, err := os.ReadDir(abs) 17 | if err != nil { 18 | return nil, err 19 | } 20 | var res []string 21 | for _, file := range files { 22 | if file.IsDir() { 23 | continue 24 | } 25 | name := file.Name() 26 | match, err := filepath.Match(pattern, name) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | if !match { 32 | continue 33 | } 34 | 35 | res = append(res, filepath.Join(abs, name)) 36 | } 37 | return res, nil 38 | } 39 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/util/newline.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "strings" 4 | 5 | // TrimNewLine trims \r and \n chars. 6 | func TrimNewLine(s string) string { 7 | return strings.NewReplacer("\r", "", "\n", "").Replace(s) 8 | } 9 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/util/slice.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // TrimStringSlice returns a copy slice without empty string item 4 | func TrimStringSlice(list []string) []string { 5 | var out []string 6 | for _, item := range list { 7 | if len(item) == 0 { 8 | continue 9 | } 10 | out = append(out, item) 11 | } 12 | return out 13 | } 14 | -------------------------------------------------------------------------------- /tools/goctl/model/sql/util/studeat.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevwan/go-zero/527de1c50e6602e9ce1ceec9ce114ad6062df5d4/tools/goctl/model/sql/util/studeat.sql -------------------------------------------------------------------------------- /tools/goctl/model/sql/util/student.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevwan/go-zero/527de1c50e6602e9ce1ceec9ce114ad6062df5d4/tools/goctl/model/sql/util/student.sql -------------------------------------------------------------------------------- /tools/goctl/model/sql/util/sub/sub.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevwan/go-zero/527de1c50e6602e9ce1ceec9ce114ad6062df5d4/tools/goctl/model/sql/util/sub/sub.sql -------------------------------------------------------------------------------- /tools/goctl/model/sql/util/xx.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevwan/go-zero/527de1c50e6602e9ce1ceec9ce114ad6062df5d4/tools/goctl/model/sql/util/xx.sql -------------------------------------------------------------------------------- /tools/goctl/model/sql/util/xx.sql1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevwan/go-zero/527de1c50e6602e9ce1ceec9ce114ad6062df5d4/tools/goctl/model/sql/util/xx.sql1 -------------------------------------------------------------------------------- /tools/goctl/pkg/downloader/downloader.go: -------------------------------------------------------------------------------- 1 | package downloader 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "os" 7 | ) 8 | 9 | func Download(url, filename string) error { 10 | resp, err := http.Get(url) 11 | if err != nil { 12 | return err 13 | } 14 | defer resp.Body.Close() 15 | 16 | f, err := os.Create(filename) 17 | if err != nil { 18 | return err 19 | } 20 | defer f.Close() 21 | _, err = io.Copy(f, resp.Body) 22 | return err 23 | } 24 | -------------------------------------------------------------------------------- /tools/goctl/pkg/golang/bin.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import ( 4 | "go/build" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/zeromicro/go-zero/tools/goctl/util/pathx" 9 | ) 10 | 11 | // GoBin returns a path of GOBIN. 12 | func GoBin() string { 13 | def := build.Default 14 | goroot := os.Getenv("GOPATH") 15 | bin := filepath.Join(goroot, "bin") 16 | if !pathx.FileExists(bin) { 17 | gopath := os.Getenv("GOROOT") 18 | bin = filepath.Join(gopath, "bin") 19 | } 20 | if !pathx.FileExists(bin) { 21 | bin = os.Getenv("GOBIN") 22 | } 23 | if !pathx.FileExists(bin) { 24 | bin = filepath.Join(def.GOPATH, "bin") 25 | } 26 | return bin 27 | } 28 | -------------------------------------------------------------------------------- /tools/goctl/pkg/golang/format.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import goformat "go/format" 4 | 5 | func FormatCode(code string) string { 6 | ret, err := goformat.Source([]byte(code)) 7 | if err != nil { 8 | return code 9 | } 10 | 11 | return string(ret) 12 | } 13 | -------------------------------------------------------------------------------- /tools/goctl/pkg/golang/install.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | ) 7 | 8 | func Install(git string) error { 9 | cmd := exec.Command("go", "install", git) 10 | env := os.Environ() 11 | env = append(env, "GO111MODULE=on") 12 | cmd.Env = env 13 | cmd.Stdout = os.Stdout 14 | cmd.Stderr = os.Stderr 15 | err := cmd.Run() 16 | return err 17 | } 18 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/assertx/error.go: -------------------------------------------------------------------------------- 1 | package assertx 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | // ErrorOrigin is used to assert error and print source and error. 11 | func ErrorOrigin(t *testing.T, source string, err ...error) { 12 | if len(err) == 0 { 13 | t.Fatalf("expected errors, got 0 error") 14 | return 15 | } 16 | for _, e := range err { 17 | fmt.Printf("<%s>: %v\n", source, e) 18 | assert.Error(t, e) 19 | } 20 | } 21 | 22 | // Error is used to assert error. 23 | func Error(t *testing.T, err ...error) { 24 | if len(err) == 0 { 25 | t.Fatalf("expected errors, got 0 error") 26 | return 27 | } 28 | for _, e := range err { 29 | fmt.Println(e) 30 | assert.Error(t, e) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/importstack/importstack.go: -------------------------------------------------------------------------------- 1 | package importstack 2 | 3 | import "errors" 4 | 5 | // ErrImportCycleNotAllowed defines an error for circular importing 6 | var ErrImportCycleNotAllowed = errors.New("import cycle not allowed") 7 | 8 | // ImportStack a stack of import paths 9 | type ImportStack []string 10 | 11 | func New() *ImportStack { 12 | return &ImportStack{} 13 | } 14 | 15 | func (s *ImportStack) Push(p string) error { 16 | for _, x := range *s { 17 | if x == p { 18 | return ErrImportCycleNotAllowed 19 | } 20 | } 21 | *s = append(*s, p) 22 | return nil 23 | } 24 | 25 | func (s *ImportStack) Pop() { 26 | *s = (*s)[0 : len(*s)-1] 27 | } 28 | 29 | func (s *ImportStack) List() []string { 30 | return *s 31 | } 32 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/error.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type errorManager struct { 9 | errors []string 10 | } 11 | 12 | func newErrorManager() *errorManager { 13 | return &errorManager{} 14 | } 15 | 16 | func (e *errorManager) add(err error) { 17 | if err == nil { 18 | return 19 | } 20 | e.errors = append(e.errors, err.Error()) 21 | } 22 | 23 | func (e *errorManager) error() error { 24 | if len(e.errors) == 0 { 25 | return nil 26 | } 27 | return fmt.Errorf(strings.Join(e.errors, "\n")) 28 | } 29 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/atdoc_group_test.api: -------------------------------------------------------------------------------- 1 | @doc( 2 | foo: "foo" 3 | bar: "bar" 4 | baz: "" 5 | ) -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/atdoc_literal_test.api: -------------------------------------------------------------------------------- 1 | @doc "" 2 | @doc "foo" 3 | @doc "bar" -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/athandler_test.api: -------------------------------------------------------------------------------- 1 | @handler foo 2 | @handler foo1 3 | @handler _bar -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/atserver_test.api: -------------------------------------------------------------------------------- 1 | @server( 2 | foo: bar 3 | bar: baz 4 | baz: foo 5 | qux: /v1 6 | quux: /v1/v2 7 | middleware: M1,M2 8 | timeout1: 1h 9 | timeout2: 10m 10 | timeout3: 10s 11 | timeout4: 10ms 12 | timeout5: 10µs 13 | timeout6: 10ns 14 | timeout7: 1h10m10s10ms10µs10ns 15 | maxBytes: 1024 16 | prefix: /v1 17 | prefix1: /v1/v2_test/v2-beta 18 | prefix2: v1/v2_test/v2-beta 19 | prefix3: v1/v2_ 20 | prefix4: a-b-c 21 | summary:"test" 22 | ) 23 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/base.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | import "base1.api" 4 | info ( 5 | title: "type title here" 6 | desc: "type desc here" 7 | author: "type author here" 8 | email: "type email here" 9 | version: "type version here" 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/base/request.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | type Baz { 4 | Foo string `json:"foo"` 5 | Baz bool `json:"bar"` 6 | } 7 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/base/response.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | import "request.api" 4 | type Bar { 5 | Foo int `json:"foo"` 6 | Bar bool `json:"bar"` 7 | Baz 8 | Qux map[string]string `json:"qux"` 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/base1.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | import "base2.api" 4 | info ( 5 | title: "type title here" 6 | desc: "type desc here" 7 | author: "type author here" 8 | email: "type email here" 9 | version: "type version here" 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/base2.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | import "base.api" 4 | info ( 5 | title: "type title here" 6 | desc: "type desc here" 7 | author: "type author here" 8 | email: "type email here" 9 | version: "type version here" 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/comment_test.api: -------------------------------------------------------------------------------- 1 | // foo 2 | // bar 3 | /*foo*/ 4 | /*bar*/ //baz -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/duplicate_type.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | type Example{ 4 | A string 5 | } 6 | type Example{ 7 | B string 8 | } 9 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/example_base.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | type Base{} 4 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/example_base1.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | import "example_base.api" 4 | 5 | info( 6 | title: "type title here" 7 | desc: "type desc here" 8 | author: "type author here" 9 | email: "type email here" 10 | version: "type version here" 11 | ) 12 | 13 | type BaseReq1{} 14 | type BaseResp1{} 15 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/example_base2.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | import "example_base.api" 4 | 5 | info( 6 | title: "type title here" 7 | desc: "type desc here" 8 | author: "type author here" 9 | email: "type email here" 10 | version: "type version here" 11 | ) 12 | 13 | type BaseReq2{} 14 | type BaseResp2{} 15 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/import_group_test.api: -------------------------------------------------------------------------------- 1 | import ( 2 | "" 3 | "foo" 4 | "bar" 5 | ) -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/import_literal_test.api: -------------------------------------------------------------------------------- 1 | import "" 2 | import "foo" 3 | import "bar" -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/info_test.api: -------------------------------------------------------------------------------- 1 | info( 2 | title: "type title here" 3 | desc: "type desc here" 4 | author: "type author here" 5 | email: "type email here" 6 | version: "type version here" 7 | ) -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/link_import.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | import "base/request.api" 4 | import "base/response.api" 5 | 6 | type Foo {} 7 | 8 | service demo { 9 | @handler handlerName 10 | get /users/id/:userId (Baz) returns (Bar) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/parser/testdata/test_format.api: -------------------------------------------------------------------------------- 1 | // format api demo 2 | syntax ="v1" // dd 3 | 4 | info() 5 | info(foo:"") 6 | info(foo:"" 7 | bar: "" 8 | quux: "") 9 | info(foo:"" 10 | bar: "" 11 | quux: "" 12 | ) 13 | // info statement 14 | // info statement 15 | info (// Info bloack 16 | title: "type title here" // title comment 17 | desc: "type desc here" 18 | author: "type author here" 19 | /*aaa*/ 20 | /* 21 | bbb 22 | */ 23 | email: "type email here" // eamil comment 24 | /*aaa*/version:/*bbb*/ "type version here"// version comment 25 | ) 26 | 27 | import "" 28 | import "aaa" 29 | import"bb" 30 | import "cc" 31 | import() 32 | import( 33 | ) 34 | import ( 35 | 36 | ) 37 | import ("aa") 38 | import ("aa" "bb") 39 | import ("aa" 40 | "bb" 41 | ) 42 | import ("aa" 43 | "bb") 44 | import ( 45 | 46 | "aa" 47 | 48 | "bb" 49 | 50 | ) 51 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/placeholder/placeholder.go: -------------------------------------------------------------------------------- 1 | package placeholder 2 | 3 | // Type is the placeholder type. 4 | type Type struct{} 5 | 6 | // PlaceHolder is the placeholder. 7 | var PlaceHolder Type 8 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/scanner/test.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | info( 4 | title: "type title here" 5 | desc: "type desc here" 6 | author: "type author here" 7 | email: "type email here" 8 | version: "type version here" 9 | ) 10 | 11 | 12 | type request { 13 | // TODO: add members here and delete this comment 14 | } 15 | 16 | type response { 17 | // TODO: add members here and delete this comment 18 | } 19 | 20 | @server( 21 | jwt: Auth 22 | group: template 23 | ) 24 | service template { 25 | @doc "foo" /*foo*/ 26 | @handler handlerName // TODO: replace handler name and delete this comment 27 | get /users/id/:userId (request) returns (response) 28 | } 29 | -------------------------------------------------------------------------------- /tools/goctl/pkg/parser/api/token/position.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import "fmt" 4 | 5 | // IllegalPosition is a position that is not valid. 6 | var IllegalPosition = Position{} 7 | 8 | // Position represents a rune position in the source code. 9 | type Position struct { 10 | Filename string 11 | Line int 12 | Column int 13 | } 14 | 15 | // String returns a string representation of the position. 16 | func (p Position) String() string { 17 | if len(p.Filename) == 0 { 18 | return fmt.Sprint(p.Line, ":", p.Column) 19 | } 20 | return fmt.Sprint(p.Filename, " ", p.Line, ":", p.Column) 21 | } 22 | -------------------------------------------------------------------------------- /tools/goctl/plugin/demo/goctlplugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/zeromicro/go-zero/tools/goctl/plugin" 7 | ) 8 | 9 | func main() { 10 | plugin, err := plugin.NewPlugin() 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | if plugin.Api != nil { 16 | fmt.Printf("api: %+v \n", plugin.Api) 17 | } 18 | fmt.Println("Enjoy anything you want.") 19 | } 20 | -------------------------------------------------------------------------------- /tools/goctl/quickstart/cmd.go: -------------------------------------------------------------------------------- 1 | package quickstart 2 | 3 | import "github.com/zeromicro/go-zero/tools/goctl/internal/cobrax" 4 | 5 | const ( 6 | serviceTypeMono = "mono" 7 | serviceTypeMicro = "micro" 8 | ) 9 | 10 | var ( 11 | varStringServiceType string 12 | 13 | // Cmd describes the command to run. 14 | Cmd = cobrax.NewCommand("quickstart", cobrax.WithRunE(run)) 15 | ) 16 | 17 | func init() { 18 | Cmd.Flags().StringVarPWithDefaultValue(&varStringServiceType, "service-type", "t", "mono") 19 | } 20 | -------------------------------------------------------------------------------- /tools/goctl/quickstart/idl/api.yaml: -------------------------------------------------------------------------------- 1 | Name: ping 2 | Host: 127.0.0.1 3 | Port: 8888 4 | Log: 5 | Encoding: plain -------------------------------------------------------------------------------- /tools/goctl/quickstart/idl/apilogic.tpl: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/zeromicro/go-zero/core/logx" 7 | 8 | "{{.svcPkg}}" 9 | "{{.typesPkg}}"{{if .callRPC}} 10 | "{{.rpcClientPkg}}"{{end}} 11 | ) 12 | 13 | type PingLogic struct { 14 | logx.Logger 15 | ctx context.Context 16 | svcCtx *svc.ServiceContext 17 | } 18 | 19 | func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic { 20 | return &PingLogic{ 21 | Logger: logx.WithContext(ctx), 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | } 25 | } 26 | 27 | func (l *PingLogic) Ping() (resp *types.Resp, err error) { 28 | {{if .callRPC}}if _, err = l.svcCtx.GreetRpc.Ping(l.ctx, new(greet.Placeholder)); err != nil { 29 | return 30 | } 31 | 32 | {{end}}resp = new(types.Resp) 33 | resp.Msg = "pong" 34 | 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /tools/goctl/quickstart/idl/greet.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | type resp { 4 | msg string `json:"msg"` 5 | } 6 | 7 | service greet { 8 | @handler ping 9 | get /ping returns (resp) 10 | } 11 | -------------------------------------------------------------------------------- /tools/goctl/quickstart/idl/greet.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package ping; 4 | option go_package = "./pb"; 5 | 6 | message Placeholder{} 7 | 8 | service Greet { 9 | rpc ping (Placeholder) returns (Placeholder); 10 | } 11 | -------------------------------------------------------------------------------- /tools/goctl/quickstart/idl/rpc.yaml: -------------------------------------------------------------------------------- 1 | Name: greet.rpc 2 | ListenOn: 127.0.0.1:8080 3 | Log: 4 | Encoding: plain 5 | -------------------------------------------------------------------------------- /tools/goctl/quickstart/idl/svc.tpl: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "{{.configPkg}}"{{if .callRPC}} 5 | "github.com/zeromicro/go-zero/zrpc" 6 | "{{.rpcClientPkg}}"{{end}} 7 | ) 8 | 9 | type ServiceContext struct { 10 | Config config.Config{{if .callRPC}} 11 | GreetRpc greet.Greet{{end}} 12 | } 13 | 14 | func NewServiceContext(c config.Config) *ServiceContext { 15 | {{if .callRPC}}client := zrpc.MustNewClient(zrpc.RpcClientConf{ 16 | Target: "127.0.0.1:8080", 17 | }){{end}} 18 | return &ServiceContext{ 19 | Config: c, 20 | {{if .callRPC}}GreetRpc: greet.NewGreet(client),{{end}} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tools/goctl/readme-cn.md: -------------------------------------------------------------------------------- 1 | # goctl 2 | 3 | [English](readme.md) | 简体中文 4 | 5 | goctl 使用见文档 https://go-zero.dev/docs/tutorials/cli/overview 6 | -------------------------------------------------------------------------------- /tools/goctl/readme.md: -------------------------------------------------------------------------------- 1 | # goctl 2 | 3 | English | [简体中文](readme-cn.md) 4 | 5 | Read document at https://go-zero.dev/docs/tutorials/cli/overview 6 | -------------------------------------------------------------------------------- /tools/goctl/rpc/generator/base/common.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package common; 4 | option go_package="./common"; 5 | 6 | message User { 7 | string name = 1; 8 | } 9 | -------------------------------------------------------------------------------- /tools/goctl/rpc/generator/call.tpl: -------------------------------------------------------------------------------- 1 | {{.head}} 2 | 3 | package {{.filePackage}} 4 | 5 | import ( 6 | "context" 7 | 8 | {{.pbPackage}} 9 | {{if ne .pbPackage .protoGoPackage}}{{.protoGoPackage}}{{end}} 10 | 11 | "github.com/zeromicro/go-zero/zrpc" 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | type ( 16 | {{.alias}} 17 | 18 | {{.serviceName}} interface { 19 | {{.interface}} 20 | } 21 | 22 | default{{.serviceName}} struct { 23 | cli zrpc.Client 24 | } 25 | ) 26 | 27 | func New{{.serviceName}}(cli zrpc.Client) {{.serviceName}} { 28 | return &default{{.serviceName}}{ 29 | cli: cli, 30 | } 31 | } 32 | 33 | {{.functions}} 34 | -------------------------------------------------------------------------------- /tools/goctl/rpc/generator/config.tpl: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/zeromicro/go-zero/zrpc" 4 | 5 | type Config struct { 6 | zrpc.RpcServerConf 7 | } 8 | -------------------------------------------------------------------------------- /tools/goctl/rpc/generator/etc.tpl: -------------------------------------------------------------------------------- 1 | Name: {{.serviceName}}.rpc 2 | ListenOn: 0.0.0.0:8080 3 | Etcd: 4 | Hosts: 5 | - 127.0.0.1:2379 6 | Key: {{.serviceName}}.rpc 7 | -------------------------------------------------------------------------------- /tools/goctl/rpc/generator/logic.tpl: -------------------------------------------------------------------------------- 1 | package {{.packageName}} 2 | 3 | import ( 4 | "context" 5 | 6 | {{.imports}} 7 | 8 | "github.com/zeromicro/go-zero/core/logx" 9 | ) 10 | 11 | type {{.logicName}} struct { 12 | ctx context.Context 13 | svcCtx *svc.ServiceContext 14 | logx.Logger 15 | } 16 | 17 | func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logicName}} { 18 | return &{{.logicName}}{ 19 | ctx: ctx, 20 | svcCtx: svcCtx, 21 | Logger: logx.WithContext(ctx), 22 | } 23 | } 24 | {{.functions}} 25 | -------------------------------------------------------------------------------- /tools/goctl/rpc/generator/prototmpl_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/zeromicro/go-zero/tools/goctl/util/pathx" 9 | ) 10 | 11 | func TestProtoTmpl(t *testing.T) { 12 | _ = Clean() 13 | // exists dir 14 | err := ProtoTmpl(pathx.MustTempDir()) 15 | assert.Nil(t, err) 16 | 17 | // not exist dir 18 | dir := filepath.Join(pathx.MustTempDir(), "test") 19 | err = ProtoTmpl(dir) 20 | assert.Nil(t, err) 21 | } 22 | -------------------------------------------------------------------------------- /tools/goctl/rpc/generator/rpc.tpl: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package {{.package}}; 4 | option go_package="./{{.package}}"; 5 | 6 | message Request { 7 | string ping = 1; 8 | } 9 | 10 | message Response { 11 | string pong = 1; 12 | } 13 | 14 | service {{.serviceName}} { 15 | rpc Ping(Request) returns(Response); 16 | } 17 | -------------------------------------------------------------------------------- /tools/goctl/rpc/generator/server.tpl: -------------------------------------------------------------------------------- 1 | {{.head}} 2 | 3 | package server 4 | 5 | import ( 6 | {{if .notStream}}"context"{{end}} 7 | 8 | {{.imports}} 9 | ) 10 | 11 | type {{.server}}Server struct { 12 | svcCtx *svc.ServiceContext 13 | {{.unimplementedServer}} 14 | } 15 | 16 | func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server { 17 | return &{{.server}}Server{ 18 | svcCtx: svcCtx, 19 | } 20 | } 21 | 22 | {{.funcs}} 23 | -------------------------------------------------------------------------------- /tools/goctl/rpc/generator/svc.tpl: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import {{.imports}} 4 | 5 | type ServiceContext struct { 6 | Config config.Config 7 | } 8 | 9 | func NewServiceContext(c config.Config) *ServiceContext { 10 | return &ServiceContext{ 11 | Config:c, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/comment.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/emicklei/proto" 7 | ) 8 | 9 | // GetComment returns content with prefix // 10 | func GetComment(comment *proto.Comment) string { 11 | if comment == nil { 12 | return "" 13 | } 14 | return "// " + strings.TrimSpace(comment.Message()) 15 | } 16 | -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/import.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/emicklei/proto" 4 | 5 | // Import embeds proto.Import 6 | type Import struct { 7 | *proto.Import 8 | } 9 | -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/message.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/emicklei/proto" 4 | 5 | // Message embeds proto.Message 6 | type Message struct { 7 | *proto.Message 8 | } 9 | -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/option.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/emicklei/proto" 4 | 5 | // Option embeds proto.Option 6 | type Option struct { 7 | *proto.Option 8 | } 9 | -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/package.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/emicklei/proto" 4 | 5 | // Package defines the protobuf package. 6 | type Package struct { 7 | *proto.Package 8 | } 9 | -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/proto.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | // Proto describes a proto file, 4 | type Proto struct { 5 | Src string 6 | Name string 7 | Package Package 8 | PbPackage string 9 | GoPackage string 10 | Import []Import 11 | Message []Message 12 | Service Services 13 | } 14 | -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/rpc.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/emicklei/proto" 4 | 5 | // RPC embeds proto.RPC 6 | type RPC struct { 7 | *proto.RPC 8 | } 9 | -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/stream.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | message Req{ 6 | string input = 1; 7 | } 8 | 9 | message Reply{ 10 | string output = 1; 11 | } 12 | service TestService{ 13 | rpc ServerStream (Req) returns (stream Reply); 14 | rpc ClientStream (stream Req) returns (Reply); 15 | rpc Stream (stream Req) returns (stream Reply); 16 | } 17 | -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | option go_package = "go"; 5 | 6 | import "base.proto"; 7 | 8 | message TestMessage{} 9 | message TestReq{} 10 | message TestReply{} 11 | message Inline { 12 | message Inner{} 13 | } 14 | 15 | enum TestEnum { 16 | unknown = 0; 17 | male = 1; 18 | female = 2; 19 | } 20 | 21 | service TestService{ 22 | rpc TestRpcOne (TestReq)returns(TestReply); 23 | } -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/test_invalid_request.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | option go_package = "go"; 5 | 6 | import "base.proto"; 7 | 8 | message Reply{} 9 | 10 | 11 | service TestService { 12 | rpc TestRpcTwo (base.Req) returns (Reply); 13 | } -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/test_invalid_response.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | option go_package = "go"; 5 | 6 | import "base.proto"; 7 | 8 | message Req{} 9 | 10 | 11 | service TestService { 12 | rpc TestRpcTwo (Req) returns (base.Reply); 13 | } -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/test_option.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package stream; 4 | 5 | option go_package = "github.com/zeromicro/go-zero"; 6 | 7 | message placeholder {} 8 | 9 | service greet { 10 | rpc hello (placeholder) returns (placeholder); 11 | } -------------------------------------------------------------------------------- /tools/goctl/rpc/parser/test_option2.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package stream; 4 | 5 | 6 | message placeholder {} 7 | 8 | service greet { 9 | rpc hello (placeholder) returns (placeholder); 10 | } -------------------------------------------------------------------------------- /tools/goctl/test/common/echo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function console_red() { 4 | echo -e "\033[31m "$*" \033[0m" 5 | } 6 | 7 | function console_green() { 8 | echo -e "\033[32m "$*" \033[0m" 9 | } 10 | 11 | function console_yellow() { 12 | echo -e "\033[33m "$*" \033[0m" 13 | } 14 | 15 | function console_blue() { 16 | echo -e "\033[34m "$*" \033[0m" 17 | } 18 | 19 | function console_tip() { 20 | console_blue "========================== $* ==============================" 21 | } 22 | 23 | function console_step() { 24 | console_blue "<<<<<<<<<<<<<<<< $* >>>>>>>>>>>>>>>>" 25 | } 26 | -------------------------------------------------------------------------------- /tools/goctl/test/integration/model/mongo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine 2 | 3 | ENV TZ Asia/Shanghai 4 | ENV GOPROXY https://goproxy.cn,direct 5 | 6 | WORKDIR /app 7 | COPY goctl /usr/bin/ 8 | COPY cmd.sh . 9 | 10 | RUN chmod +x /usr/bin/goctl cmd.sh 11 | CMD ["/bin/bash", "cmd.sh"] 12 | -------------------------------------------------------------------------------- /tools/goctl/test/integration/model/mongo/cmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wd=$(dirname $0) 4 | project=test 5 | testDir=$wd/$project 6 | mkdir -p $testDir 7 | 8 | cd $testDir 9 | 10 | # go mod init 11 | go mod init $project 12 | 13 | # generate cache code 14 | goctl model mongo -t User -c --dir cache 15 | if [ $? -ne 0 ]; then 16 | exit 1 17 | fi 18 | 19 | # generate non-cache code 20 | goctl model mongo -t User --dir nocache 21 | if [ $? -ne 0 ]; then 22 | exit 1 23 | fi 24 | 25 | # go mod tidy 26 | GOPROXY=https://goproxy.cn && go mod tidy 27 | 28 | # code inspection 29 | go test -race ./... 30 | if [ $? -ne 0 ]; then 31 | echo 32 | fi 33 | -------------------------------------------------------------------------------- /tools/goctl/test/main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # main.sh is the entry point for the goctl tests. 4 | 5 | # testing mongo code generation. 6 | /bin/bash integration/model/mongo/mongo.sh 7 | -------------------------------------------------------------------------------- /tools/goctl/update/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/zeromicro/go-zero/core/logx" 4 | 5 | // Config defines a service configure for goctl update 6 | type Config struct { 7 | logx.LogConf 8 | ListenOn string 9 | FileDir string 10 | FilePath string 11 | } 12 | -------------------------------------------------------------------------------- /tools/goctl/update/etc/update-api.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "update-api", 3 | "ListenOn": "localhost:7777", 4 | "FileDir": ".", 5 | "FilePath": "/" 6 | } -------------------------------------------------------------------------------- /tools/goctl/upgrade/cmd.go: -------------------------------------------------------------------------------- 1 | package upgrade 2 | 3 | import "github.com/zeromicro/go-zero/tools/goctl/internal/cobrax" 4 | 5 | // Cmd describes an upgrade command. 6 | var Cmd = cobrax.NewCommand("upgrade", cobrax.WithRunE(upgrade)) 7 | -------------------------------------------------------------------------------- /tools/goctl/upgrade/upgrade.go: -------------------------------------------------------------------------------- 1 | package upgrade 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/zeromicro/go-zero/tools/goctl/rpc/execx" 9 | ) 10 | 11 | // upgrade gets the latest goctl by 12 | // go install github.com/zeromicro/go-zero/tools/goctl@latest 13 | func upgrade(_ *cobra.Command, _ []string) error { 14 | cmd := `GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest` 15 | if runtime.GOOS == "windows" { 16 | cmd = `set GOPROXY=https://goproxy.cn,direct && go install github.com/zeromicro/go-zero/tools/goctl@latest` 17 | } 18 | info, err := execx.Run(cmd, "") 19 | if err != nil { 20 | return err 21 | } 22 | 23 | fmt.Print(info) 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /tools/goctl/util/ctx/context_test.go: -------------------------------------------------------------------------------- 1 | package ctx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestBackground(t *testing.T) { 10 | workDir := "." 11 | ctx, err := Prepare(workDir) 12 | assert.Nil(t, err) 13 | assert.True(t, true, func() bool { 14 | return len(ctx.Dir) != 0 && len(ctx.Path) != 0 15 | }()) 16 | } 17 | 18 | func TestBackgroundNilWorkDir(t *testing.T) { 19 | workDir := "" 20 | _, err := Prepare(workDir) 21 | assert.NotNil(t, err) 22 | } 23 | -------------------------------------------------------------------------------- /tools/goctl/util/ctx/modcheck.go: -------------------------------------------------------------------------------- 1 | package ctx 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | 7 | "github.com/zeromicro/go-zero/tools/goctl/rpc/execx" 8 | ) 9 | 10 | // IsGoMod is used to determine whether workDir is a go module project through command `go list -json -m` 11 | func IsGoMod(workDir string) (bool, error) { 12 | if len(workDir) == 0 { 13 | return false, errors.New("the work directory is not found") 14 | } 15 | if _, err := os.Stat(workDir); err != nil { 16 | return false, err 17 | } 18 | 19 | data, err := execx.Run("go list -m -f '{{.GoMod}}'", workDir) 20 | if err != nil || len(data) == 0 { 21 | return false, nil 22 | } 23 | 24 | return true, nil 25 | } 26 | -------------------------------------------------------------------------------- /tools/goctl/util/head.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | const ( 4 | // DoNotEditHead added to the beginning of a file to prompt the user not to edit 5 | DoNotEditHead = "// Code generated by goctl. DO NOT EDIT." 6 | 7 | headTemplate = DoNotEditHead + ` 8 | // Source: {{.source}}` 9 | ) 10 | 11 | // GetHead returns a code head string with source filename 12 | func GetHead(source string) string { 13 | buffer, _ := With("head").Parse(headTemplate).Execute(map[string]any{ 14 | "source": source, 15 | }) 16 | return buffer.String() 17 | } 18 | -------------------------------------------------------------------------------- /tools/goctl/util/pathx/path_test.go: -------------------------------------------------------------------------------- 1 | package pathx 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestReadLink(t *testing.T) { 12 | dir, err := os.MkdirTemp("", "go-zero") 13 | assert.Nil(t, err) 14 | symLink := filepath.Join(dir, "test") 15 | pwd, err := os.Getwd() 16 | assertError(err, t) 17 | 18 | err = os.Symlink(pwd, symLink) 19 | assertError(err, t) 20 | 21 | t.Run("linked", func(t *testing.T) { 22 | ret, err := ReadLink(symLink) 23 | assert.Nil(t, err) 24 | assert.Equal(t, pwd, ret) 25 | }) 26 | 27 | t.Run("unlink", func(t *testing.T) { 28 | ret, err := ReadLink(pwd) 29 | assert.Nil(t, err) 30 | assert.Equal(t, pwd, ret) 31 | }) 32 | } 33 | 34 | func assertError(err error, t *testing.T) { 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tools/goctl/util/pathx/readlink+polyfill.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package pathx 4 | 5 | func ReadLink(name string) (string, error) { 6 | return name, nil 7 | } 8 | -------------------------------------------------------------------------------- /tools/goctl/vars/settings.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | const ( 4 | // ProjectName the const value of zero 5 | ProjectName = "zero" 6 | // ProjectOpenSourceURL the github url of go-zero 7 | ProjectOpenSourceURL = "github.com/zeromicro/go-zero" 8 | // OsWindows represents os windows 9 | OsWindows = "windows" 10 | // OsMac represents os mac 11 | OsMac = "darwin" 12 | // OsLinux represents os linux 13 | OsLinux = "linux" 14 | // OsJs represents os js 15 | OsJs = "js" 16 | // OsIOS represents os ios 17 | OsIOS = "ios" 18 | ) 19 | -------------------------------------------------------------------------------- /zrpc/internal/auth/vars.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | const ( 4 | appKey = "app" 5 | tokenKey = "token" 6 | 7 | accessDenied = "access denied" 8 | missingMetadata = "app/token required" 9 | ) 10 | -------------------------------------------------------------------------------- /zrpc/internal/clientinterceptors/breakerinterceptor.go: -------------------------------------------------------------------------------- 1 | package clientinterceptors 2 | 3 | import ( 4 | "context" 5 | "path" 6 | 7 | "github.com/zeromicro/go-zero/core/breaker" 8 | "github.com/zeromicro/go-zero/zrpc/internal/codes" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | // BreakerInterceptor is an interceptor that acts as a circuit breaker. 13 | func BreakerInterceptor(ctx context.Context, method string, req, reply any, 14 | cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 15 | breakerName := path.Join(cc.Target(), method) 16 | return breaker.DoWithAcceptableCtx(ctx, breakerName, func() error { 17 | return invoker(ctx, method, req, reply, cc, opts...) 18 | }, codes.Acceptable) 19 | } 20 | -------------------------------------------------------------------------------- /zrpc/internal/codes/accept.go: -------------------------------------------------------------------------------- 1 | package codes 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | // Acceptable checks if given error is acceptable. 9 | func Acceptable(err error) bool { 10 | switch status.Code(err) { 11 | case codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss, 12 | codes.Unimplemented, codes.ResourceExhausted: 13 | return false 14 | default: 15 | return true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /zrpc/internal/codes/accept_test.go: -------------------------------------------------------------------------------- 1 | package codes 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func TestAccept(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | err error 15 | accept bool 16 | }{ 17 | { 18 | name: "nil error", 19 | err: nil, 20 | accept: true, 21 | }, 22 | { 23 | name: "deadline error", 24 | err: status.Error(codes.DeadlineExceeded, "deadline"), 25 | accept: false, 26 | }, 27 | } 28 | 29 | for _, test := range tests { 30 | t.Run(test.name, func(t *testing.T) { 31 | assert.Equal(t, test.accept, Acceptable(test.err)) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /zrpc/internal/serverinterceptors/recoverinterceptor_test.go: -------------------------------------------------------------------------------- 1 | package serverinterceptors 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/zeromicro/go-zero/core/logx" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | func init() { 13 | logx.Disable() 14 | } 15 | 16 | func TestStreamCrashInterceptor(t *testing.T) { 17 | err := StreamRecoverInterceptor(nil, nil, nil, func( 18 | svr any, stream grpc.ServerStream) error { 19 | panic("mock panic") 20 | }) 21 | assert.NotNil(t, err) 22 | } 23 | 24 | func TestUnaryCrashInterceptor(t *testing.T) { 25 | _, err := UnaryRecoverInterceptor(context.Background(), nil, nil, 26 | func(ctx context.Context, req any) (any, error) { 27 | panic("mock panic") 28 | }) 29 | assert.NotNil(t, err) 30 | } 31 | -------------------------------------------------------------------------------- /zrpc/resolver/internal/etcdbuilder.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | type etcdBuilder struct { 4 | discovBuilder 5 | } 6 | 7 | func (b *etcdBuilder) Scheme() string { 8 | return EtcdScheme 9 | } 10 | -------------------------------------------------------------------------------- /zrpc/resolver/internal/etcdbuilder_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestEtcdBuilder_Scheme(t *testing.T) { 10 | assert.Equal(t, EtcdScheme, new(etcdBuilder).Scheme()) 11 | } 12 | -------------------------------------------------------------------------------- /zrpc/resolver/internal/kubebuilder_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "google.golang.org/grpc/resolver" 10 | ) 11 | 12 | func TestKubeBuilder_Scheme(t *testing.T) { 13 | var b kubeBuilder 14 | assert.Equal(t, KubernetesScheme, b.Scheme()) 15 | } 16 | 17 | func TestKubeBuilder_Build(t *testing.T) { 18 | var b kubeBuilder 19 | u, err := url.Parse(fmt.Sprintf("%s://%s", KubernetesScheme, "a,b")) 20 | assert.NoError(t, err) 21 | 22 | _, err = b.Build(resolver.Target{ 23 | URL: *u, 24 | }, nil, resolver.BuildOptions{}) 25 | assert.Error(t, err) 26 | 27 | u, err = url.Parse(fmt.Sprintf("%s://%s:9100/a:b:c", KubernetesScheme, "a,b,c,d")) 28 | assert.NoError(t, err) 29 | 30 | _, err = b.Build(resolver.Target{ 31 | URL: *u, 32 | }, nil, resolver.BuildOptions{}) 33 | assert.Error(t, err) 34 | } 35 | -------------------------------------------------------------------------------- /zrpc/resolver/internal/subset.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "math/rand" 4 | 5 | func subset(set []string, sub int) []string { 6 | rand.Shuffle(len(set), func(i, j int) { 7 | set[i], set[j] = set[j], set[i] 8 | }) 9 | if len(set) <= sub { 10 | return set 11 | } 12 | 13 | return set[:sub] 14 | } 15 | -------------------------------------------------------------------------------- /zrpc/resolver/internal/targets/endpoints.go: -------------------------------------------------------------------------------- 1 | package targets 2 | 3 | import ( 4 | "strings" 5 | 6 | "google.golang.org/grpc/resolver" 7 | ) 8 | 9 | const slashSeparator = "/" 10 | 11 | // GetAuthority returns the authority of the target. 12 | func GetAuthority(target resolver.Target) string { 13 | return target.URL.Host 14 | } 15 | 16 | // GetEndpoints returns the endpoints from the given target. 17 | func GetEndpoints(target resolver.Target) string { 18 | return strings.Trim(target.URL.Path, slashSeparator) 19 | } 20 | -------------------------------------------------------------------------------- /zrpc/resolver/register.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import "github.com/zeromicro/go-zero/zrpc/resolver/internal" 4 | 5 | // Register registers schemes defined zrpc. 6 | // Keep it in a separated package to let third party register manually. 7 | func Register() { 8 | internal.RegisterResolver() 9 | } 10 | -------------------------------------------------------------------------------- /zrpc/resolver/register_test.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRegister(t *testing.T) { 10 | assert.NotPanics(t, func() { 11 | Register() 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /zrpc/resolver/target.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/zeromicro/go-zero/zrpc/resolver/internal" 8 | ) 9 | 10 | // BuildDirectTarget returns a string that represents the given endpoints with direct schema. 11 | func BuildDirectTarget(endpoints []string) string { 12 | return fmt.Sprintf("%s:///%s", internal.DirectScheme, 13 | strings.Join(endpoints, internal.EndpointSep)) 14 | } 15 | 16 | // BuildDiscovTarget returns a string that represents the given endpoints with discov schema. 17 | func BuildDiscovTarget(endpoints []string, key string) string { 18 | return fmt.Sprintf("%s://%s/%s", internal.EtcdScheme, 19 | strings.Join(endpoints, internal.EndpointSep), key) 20 | } 21 | -------------------------------------------------------------------------------- /zrpc/resolver/target_test.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestBuildDirectTarget(t *testing.T) { 10 | target := BuildDirectTarget([]string{"localhost:123", "localhost:456"}) 11 | assert.Equal(t, "direct:///localhost:123,localhost:456", target) 12 | } 13 | 14 | func TestBuildDiscovTarget(t *testing.T) { 15 | target := BuildDiscovTarget([]string{"localhost:123", "localhost:456"}, "foo") 16 | assert.Equal(t, "etcd://localhost:123,localhost:456/foo", target) 17 | } 18 | --------------------------------------------------------------------------------