├── LICENSE ├── README.md ├── _examples ├── authors │ ├── README.md │ ├── pgx │ │ ├── Dockerfile │ │ ├── api │ │ │ ├── apidocs.swagger.json │ │ │ └── authors │ │ │ │ └── v1 │ │ │ │ ├── authors.pb.go │ │ │ │ └── v1connect │ │ │ │ └── authors.connect.go │ │ ├── buf.gen.yaml │ │ ├── buf.lock │ │ ├── buf.yaml │ │ ├── docker-compose.yml │ │ ├── gen.sh │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal │ │ │ ├── authors │ │ │ │ ├── adapters.go │ │ │ │ ├── db.go │ │ │ │ ├── models.go │ │ │ │ ├── querier.go │ │ │ │ ├── queries.sql.go │ │ │ │ ├── service.factory.go │ │ │ │ └── service.go │ │ │ └── validation │ │ │ │ └── validation.go │ │ ├── main.go │ │ ├── migration.go │ │ ├── proto │ │ │ └── authors │ │ │ │ └── v1 │ │ │ │ └── authors.proto │ │ ├── registry.go │ │ ├── sql │ │ │ ├── migrations │ │ │ │ ├── 001_authors.down.sql │ │ │ │ └── 001_authors.up.sql │ │ │ └── queries.sql │ │ ├── sqlc.yaml │ │ ├── tools │ │ │ └── tools.go │ │ └── validation │ │ │ └── validation.go │ └── sqlite │ │ ├── Dockerfile │ │ ├── api │ │ ├── apidocs.swagger.json │ │ └── authors │ │ │ └── v1 │ │ │ ├── authors.pb.go │ │ │ └── v1connect │ │ │ └── authors.connect.go │ │ ├── buf.gen.yaml │ │ ├── buf.lock │ │ ├── buf.yaml │ │ ├── configs │ │ └── reflex.conf │ │ ├── docker-compose.yml │ │ ├── gen.sh │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal │ │ ├── authors │ │ │ ├── adapters.go │ │ │ ├── db.go │ │ │ ├── models.go │ │ │ ├── queries.sql.go │ │ │ ├── service.factory.go │ │ │ └── service.go │ │ ├── server │ │ │ ├── litefs │ │ │ │ ├── forward.go │ │ │ │ └── litefs.go │ │ │ └── litestream │ │ │ │ └── litestream.go │ │ └── validation │ │ │ └── validation.go │ │ ├── main.go │ │ ├── migration.go │ │ ├── proto │ │ └── authors │ │ │ └── v1 │ │ │ └── authors.proto │ │ ├── registry.go │ │ ├── sql │ │ ├── migrations │ │ │ └── 001_authors.sql │ │ └── queries.sql │ │ ├── sqlc.yaml │ │ ├── tools │ │ └── tools.go │ │ └── validation │ │ └── validation.go ├── booktest │ ├── Dockerfile │ ├── README.md │ ├── api │ │ ├── apidocs.swagger.json │ │ └── books │ │ │ └── v1 │ │ │ ├── books.pb.go │ │ │ └── v1connect │ │ │ └── books.connect.go │ ├── buf.gen.yaml │ ├── buf.lock │ ├── buf.yaml │ ├── configs │ │ ├── grafana │ │ │ ├── dashboards │ │ │ │ ├── dashboard.yml │ │ │ │ └── grpc-dashboard.json │ │ │ └── datasources │ │ │ │ └── datasource.yml │ │ ├── prometheus.yml │ │ └── reflex.conf │ ├── docker-compose.yml │ ├── gen.sh │ ├── go.mod │ ├── go.sum │ ├── internal │ │ ├── books │ │ │ ├── adapters.go │ │ │ ├── db.go │ │ │ ├── models.go │ │ │ ├── queries.sql.go │ │ │ ├── service.factory.go │ │ │ └── service.go │ │ ├── server │ │ │ └── instrumentation │ │ │ │ ├── metric │ │ │ │ └── metric.go │ │ │ │ └── trace │ │ │ │ └── tracing.go │ │ └── validation │ │ │ └── validation.go │ ├── main.go │ ├── proto │ │ └── books │ │ │ └── v1 │ │ │ └── books.proto │ ├── registry.go │ ├── sql │ │ ├── queries.sql │ │ └── schema.sql │ ├── sqlc.yaml │ └── tools │ │ └── tools.go └── uuidcheck │ ├── api │ ├── apidocs.swagger.json │ ├── googleuuid │ │ └── v1 │ │ │ ├── googleuuid.pb.go │ │ │ └── v1connect │ │ │ └── googleuuid.connect.go │ └── pguuid │ │ └── v1 │ │ ├── pguuid.pb.go │ │ └── v1connect │ │ └── pguuid.connect.go │ ├── buf.gen.yaml │ ├── buf.lock │ ├── buf.yaml │ ├── go.mod │ ├── go.sum │ ├── internal │ └── validation │ │ └── validation.go │ ├── main.go │ ├── proto │ ├── googleuuid │ │ └── v1 │ │ │ └── googleuuid.proto │ └── pguuid │ │ └── v1 │ │ └── pguuid.proto │ ├── query │ └── uuid │ │ └── user.sql │ ├── registry.go │ ├── schema.sql │ ├── sqlc.yaml │ ├── store │ ├── googleuuid │ │ ├── adapters.go │ │ ├── db.go │ │ ├── models.go │ │ ├── service.factory.go │ │ ├── service.go │ │ └── user.sql.go │ └── pguuid │ │ ├── adapters.go │ │ ├── db.go │ │ ├── models.go │ │ ├── service.factory.go │ │ ├── service.go │ │ └── user.sql.go │ └── tools │ └── tools.go ├── engine.go ├── go.mod ├── go.sum ├── main.go ├── metadata └── bind.go ├── templates ├── adapters.go.tmpl ├── buf.gen.yaml ├── buf.yaml ├── internal │ ├── server │ │ ├── instrumentation │ │ │ ├── metric │ │ │ │ └── metric.go.tmpl │ │ │ └── trace │ │ │ │ └── tracing.go.tmpl │ │ ├── litefs │ │ │ ├── forward.go.tmpl │ │ │ └── litefs.go.tmpl │ │ └── litestream │ │ │ └── litestream.go.tmpl │ └── validation │ │ └── validation.go.tmpl ├── main.go.tmpl ├── migration.go.tmpl ├── proto │ └── service.proto.tmpl ├── registry.go.tmpl ├── service.factory.go.tmpl ├── service.go.tmpl ├── templates.go └── tools │ └── tools.go.tmpl └── version.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Walter Wanderley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sqlc-connect 2 | Generate [connect-go](https://connect.build/) server from SQL. If you’re searching for a SQLC plugin, use [sqlc-gen-go-server](https://github.com/walterwanderley/sqlc-gen-go-server/). 3 | 4 | ### Requirements 5 | 6 | - Go 1.21 or superior 7 | - [sqlc](https://sqlc.dev/) 8 | - [buf](https://buf.build/) 9 | 10 | ```sh 11 | go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest 12 | go install github.com/bufbuild/buf/cmd/buf@latest 13 | ``` 14 | 15 | ### Installation 16 | 17 | ```sh 18 | go install github.com/walterwanderley/sqlc-connect@latest 19 | ``` 20 | 21 | ### Example 22 | 23 | 1. Create a queries.sql file: 24 | 25 | ```sql 26 | --queries.sql 27 | 28 | CREATE TABLE authors ( 29 | id BIGSERIAL PRIMARY KEY, 30 | name text NOT NULL, 31 | bio text, 32 | created_at TIMESTAMP 33 | ); 34 | 35 | -- name: GetAuthor :one 36 | SELECT * FROM authors 37 | WHERE id = $1 LIMIT 1; 38 | 39 | -- name: ListAuthors :many 40 | SELECT * FROM authors 41 | ORDER BY name; 42 | 43 | -- name: CreateAuthor :one 44 | INSERT INTO authors ( 45 | name, bio, created_at 46 | ) VALUES ( 47 | $1, $2, $3 48 | ) 49 | RETURNING *; 50 | 51 | -- name: DeleteAuthor :exec 52 | DELETE FROM authors 53 | WHERE id = $1; 54 | 55 | ``` 56 | 57 | 2. Create a sqlc.yaml file 58 | 59 | ```yaml 60 | version: "2" 61 | sql: 62 | - schema: "./queries.sql" 63 | queries: "./queries.sql" 64 | engine: "postgresql" 65 | gen: 66 | go: 67 | out: "internal/author" 68 | 69 | ``` 70 | 71 | 3. Execute sqlc 72 | 73 | ```sh 74 | sqlc generate 75 | ``` 76 | 77 | 4. Execute sqlc-connect 78 | 79 | ```sh 80 | sqlc-connect -m "authors" 81 | ``` 82 | 83 | 5. Run the generated server 84 | 85 | ```sh 86 | go run . -db [Database Connection URL] -dev 87 | ``` 88 | 89 | 6. Enjoy! 90 | 91 | [http://localhost:5000/swagger](http://localhost:5000/swagger) 92 | 93 | or 94 | 95 | ```sh 96 | go install github.com/fullstorydev/grpcui/cmd/grpcui@latest 97 | grpcui -plaintext localhost:5000 98 | ``` 99 | 100 | ### Editing the generated code 101 | 102 | - It's safe to edit any generated code that doesn't have the `DO NOT EDIT` indication at the very first line. 103 | 104 | - After modify a SQL file, execute these commands below: 105 | 106 | ```sh 107 | sqlc generate 108 | go generate 109 | ``` 110 | 111 | - After modify a *.proto file, execute `buf generate`. 112 | 113 | ### Similar Projects 114 | 115 | - [sqlc-grpc](https://github.com/walterwanderley/sqlc-grpc) 116 | - [xo-grpc](https://github.com/walterwanderley/xo-grpc) -------------------------------------------------------------------------------- /_examples/authors/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | Author example taken from [sqlc][sqlc] Git repository [examples][sqlc-git]. 4 | 5 | [sqlc]: https://sqlc.dev 6 | [sqlc-git]: https://github.com/sqlc-dev/sqlc/tree/main/examples/authors 7 | 8 | ## Running 9 | 10 | ```sh 11 | ./gen.sh 12 | docker compose up 13 | ``` 14 | 15 | ### Exploring with gRPC UI 16 | 17 | ```sh 18 | go install github.com/fullstorydev/grpcui/cmd/grpcui@latest 19 | grpcui -plaintext localhost:8080 20 | ``` 21 | -------------------------------------------------------------------------------- /_examples/authors/pgx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21 2 | 3 | RUN go install github.com/cespare/reflex@latest 4 | 5 | WORKDIR /app 6 | COPY go.mod . 7 | COPY go.sum . 8 | 9 | RUN go mod download -x 10 | 11 | COPY configs/reflex.conf / 12 | 13 | ENTRYPOINT ["reflex", "-c", "/reflex.conf"] -------------------------------------------------------------------------------- /_examples/authors/pgx/api/apidocs.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "authors/v1/authors.proto", 5 | "version": "version not set" 6 | }, 7 | "tags": [ 8 | { 9 | "name": "AuthorsService" 10 | } 11 | ], 12 | "consumes": [ 13 | "application/json" 14 | ], 15 | "produces": [ 16 | "application/json" 17 | ], 18 | "paths": { 19 | "/authors.v1.AuthorsService/CreateAuthor": { 20 | "post": { 21 | "operationId": "AuthorsService_CreateAuthor", 22 | "responses": { 23 | "200": { 24 | "description": "A successful response.", 25 | "schema": { 26 | "$ref": "#/definitions/v1CreateAuthorResponse" 27 | } 28 | }, 29 | "default": { 30 | "description": "An unexpected error response.", 31 | "schema": { 32 | "$ref": "#/definitions/rpcStatus" 33 | } 34 | } 35 | }, 36 | "parameters": [ 37 | { 38 | "name": "body", 39 | "in": "body", 40 | "required": true, 41 | "schema": { 42 | "$ref": "#/definitions/v1CreateAuthorRequest" 43 | } 44 | } 45 | ], 46 | "tags": [ 47 | "AuthorsService" 48 | ] 49 | } 50 | }, 51 | "/authors.v1.AuthorsService/DeleteAuthor": { 52 | "post": { 53 | "operationId": "AuthorsService_DeleteAuthor", 54 | "responses": { 55 | "200": { 56 | "description": "A successful response.", 57 | "schema": { 58 | "$ref": "#/definitions/v1DeleteAuthorResponse" 59 | } 60 | }, 61 | "default": { 62 | "description": "An unexpected error response.", 63 | "schema": { 64 | "$ref": "#/definitions/rpcStatus" 65 | } 66 | } 67 | }, 68 | "parameters": [ 69 | { 70 | "name": "body", 71 | "in": "body", 72 | "required": true, 73 | "schema": { 74 | "$ref": "#/definitions/v1DeleteAuthorRequest" 75 | } 76 | } 77 | ], 78 | "tags": [ 79 | "AuthorsService" 80 | ] 81 | } 82 | }, 83 | "/authors.v1.AuthorsService/GetAuthor": { 84 | "post": { 85 | "operationId": "AuthorsService_GetAuthor", 86 | "responses": { 87 | "200": { 88 | "description": "A successful response.", 89 | "schema": { 90 | "$ref": "#/definitions/v1GetAuthorResponse" 91 | } 92 | }, 93 | "default": { 94 | "description": "An unexpected error response.", 95 | "schema": { 96 | "$ref": "#/definitions/rpcStatus" 97 | } 98 | } 99 | }, 100 | "parameters": [ 101 | { 102 | "name": "body", 103 | "in": "body", 104 | "required": true, 105 | "schema": { 106 | "$ref": "#/definitions/v1GetAuthorRequest" 107 | } 108 | } 109 | ], 110 | "tags": [ 111 | "AuthorsService" 112 | ] 113 | } 114 | }, 115 | "/authors.v1.AuthorsService/ListAuthors": { 116 | "post": { 117 | "operationId": "AuthorsService_ListAuthors", 118 | "responses": { 119 | "200": { 120 | "description": "A successful response.", 121 | "schema": { 122 | "$ref": "#/definitions/v1ListAuthorsResponse" 123 | } 124 | }, 125 | "default": { 126 | "description": "An unexpected error response.", 127 | "schema": { 128 | "$ref": "#/definitions/rpcStatus" 129 | } 130 | } 131 | }, 132 | "parameters": [ 133 | { 134 | "name": "body", 135 | "in": "body", 136 | "required": true, 137 | "schema": { 138 | "$ref": "#/definitions/v1ListAuthorsRequest" 139 | } 140 | } 141 | ], 142 | "tags": [ 143 | "AuthorsService" 144 | ] 145 | } 146 | } 147 | }, 148 | "definitions": { 149 | "protobufAny": { 150 | "type": "object", 151 | "properties": { 152 | "@type": { 153 | "type": "string" 154 | } 155 | }, 156 | "additionalProperties": {} 157 | }, 158 | "rpcStatus": { 159 | "type": "object", 160 | "properties": { 161 | "code": { 162 | "type": "integer", 163 | "format": "int32" 164 | }, 165 | "message": { 166 | "type": "string" 167 | }, 168 | "details": { 169 | "type": "array", 170 | "items": { 171 | "type": "object", 172 | "$ref": "#/definitions/protobufAny" 173 | } 174 | } 175 | } 176 | }, 177 | "v1Authors": { 178 | "type": "object", 179 | "properties": { 180 | "id": { 181 | "type": "string", 182 | "format": "int64" 183 | }, 184 | "name": { 185 | "type": "string" 186 | }, 187 | "bio": { 188 | "type": "string" 189 | } 190 | } 191 | }, 192 | "v1CreateAuthorRequest": { 193 | "type": "object", 194 | "properties": { 195 | "name": { 196 | "type": "string" 197 | }, 198 | "bio": { 199 | "type": "string" 200 | } 201 | } 202 | }, 203 | "v1CreateAuthorResponse": { 204 | "type": "object", 205 | "properties": { 206 | "authors": { 207 | "$ref": "#/definitions/v1Authors" 208 | } 209 | } 210 | }, 211 | "v1DeleteAuthorRequest": { 212 | "type": "object", 213 | "properties": { 214 | "id": { 215 | "type": "string", 216 | "format": "int64" 217 | } 218 | } 219 | }, 220 | "v1DeleteAuthorResponse": { 221 | "type": "object" 222 | }, 223 | "v1GetAuthorRequest": { 224 | "type": "object", 225 | "properties": { 226 | "id": { 227 | "type": "string", 228 | "format": "int64" 229 | } 230 | } 231 | }, 232 | "v1GetAuthorResponse": { 233 | "type": "object", 234 | "properties": { 235 | "authors": { 236 | "$ref": "#/definitions/v1Authors" 237 | } 238 | } 239 | }, 240 | "v1ListAuthorsRequest": { 241 | "type": "object" 242 | }, 243 | "v1ListAuthorsResponse": { 244 | "type": "object", 245 | "properties": { 246 | "list": { 247 | "type": "array", 248 | "items": { 249 | "type": "object", 250 | "$ref": "#/definitions/v1Authors" 251 | } 252 | } 253 | } 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /_examples/authors/pgx/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | plugins: 3 | - local: protoc-gen-go 4 | out: api 5 | opt: paths=source_relative 6 | - local: protoc-gen-connect-go 7 | out: api 8 | opt: paths=source_relative 9 | - local: protoc-gen-openapiv2 10 | out: api 11 | opt: 12 | - generate_unbound_methods=true 13 | - logtostderr=true 14 | - allow_merge=true 15 | strategy: all -------------------------------------------------------------------------------- /_examples/authors/pgx/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v2 3 | deps: 4 | - name: buf.build/googleapis/googleapis 5 | commit: 751cbe31638d43a9bfb6162cd2352e67 6 | digest: b5:51ba5c31f244fd74420f0e66d13f2b5dd6024dcfe1a29dc45bd8f6e61c1444c828b9add9e7dd25a4513ebbee8097a970e0712a2e2cd955c2d60cf8905204f51a 7 | -------------------------------------------------------------------------------- /_examples/authors/pgx/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | modules: 3 | - path: proto 4 | deps: 5 | - buf.build/googleapis/googleapis 6 | lint: 7 | use: 8 | - DEFAULT 9 | except: 10 | - FIELD_NOT_REQUIRED 11 | - PACKAGE_NO_IMPORT_CYCLE 12 | disallow_comment_ignores: true 13 | breaking: 14 | use: 15 | - FILE 16 | except: 17 | - EXTENSION_NO_DELETE 18 | - FIELD_SAME_DEFAULT 19 | -------------------------------------------------------------------------------- /_examples/authors/pgx/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | build: . 5 | ports: 6 | - "8080:8080" 7 | volumes: 8 | - .:/app 9 | depends_on: 10 | - postgres 11 | 12 | postgres: 13 | image: postgres 14 | environment: 15 | - POSTGRES_USER=postgres 16 | - POSTGRES_PASSWORD=secret 17 | -------------------------------------------------------------------------------- /_examples/authors/pgx/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -u 3 | set -e 4 | set -x 5 | 6 | go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest 7 | 8 | rm -rf internal proto api go.mod go.sum main.go registry.go buf* 9 | 10 | sqlc generate 11 | sqlc-connect -m authors -migration-path sql/migrations -migration-lib migrate 12 | -------------------------------------------------------------------------------- /_examples/authors/pgx/go.mod: -------------------------------------------------------------------------------- 1 | module authors 2 | 3 | go 1.23.2 4 | 5 | require ( 6 | connectrpc.com/connect v1.18.1 7 | connectrpc.com/grpcreflect v1.3.0 8 | github.com/bufbuild/buf v1.50.0 9 | github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b 10 | github.com/golang-migrate/migrate/v4 v4.18.2 11 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 12 | github.com/jackc/pgx/v5 v5.7.2 13 | go.uber.org/automaxprocs v1.6.0 14 | golang.org/x/net v0.36.0 15 | google.golang.org/protobuf v1.36.5 16 | ) 17 | 18 | require ( 19 | buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20241031151143-70f632351282.1 // indirect 20 | buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.3-20241127180247-a33202765966.1 // indirect 21 | buf.build/gen/go/bufbuild/registry/connectrpc/go v1.18.1-20250106231242-56271afbd6ce.1 // indirect 22 | buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.3-20250106231242-56271afbd6ce.1 // indirect 23 | buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.3-20241007202033-cf42259fcbfc.1 // indirect 24 | buf.build/go/bufplugin v0.6.0 // indirect 25 | buf.build/go/protoyaml v0.3.1 // indirect 26 | buf.build/go/spdx v0.2.0 // indirect 27 | cel.dev/expr v0.19.1 // indirect 28 | connectrpc.com/otelconnect v0.7.1 // indirect 29 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect 30 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect 31 | github.com/Microsoft/go-winio v0.6.2 // indirect 32 | github.com/antlr4-go/antlr/v4 v4.13.1 // indirect 33 | github.com/bufbuild/protocompile v0.14.1 // indirect 34 | github.com/bufbuild/protoplugin v0.0.0-20250106231243-3a819552c9d9 // indirect 35 | github.com/bufbuild/protovalidate-go v0.8.2 // indirect 36 | github.com/containerd/log v0.1.0 // indirect 37 | github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect 38 | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect 39 | github.com/distribution/reference v0.6.0 // indirect 40 | github.com/docker/cli v27.5.0+incompatible // indirect 41 | github.com/docker/distribution v2.8.3+incompatible // indirect 42 | github.com/docker/docker v28.0.1+incompatible // indirect 43 | github.com/docker/docker-credential-helpers v0.8.2 // indirect 44 | github.com/docker/go-connections v0.5.0 // indirect 45 | github.com/docker/go-units v0.5.0 // indirect 46 | github.com/felixge/fgprof v0.9.5 // indirect 47 | github.com/felixge/httpsnoop v1.0.4 // indirect 48 | github.com/go-chi/chi/v5 v5.2.0 // indirect 49 | github.com/go-logr/logr v1.4.2 // indirect 50 | github.com/go-logr/stdr v1.2.2 // indirect 51 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 52 | github.com/gofrs/flock v0.12.1 // indirect 53 | github.com/gogo/protobuf v1.3.2 // indirect 54 | github.com/google/cel-go v0.22.1 // indirect 55 | github.com/google/go-containerregistry v0.20.2 // indirect 56 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect 57 | github.com/google/uuid v1.6.0 // indirect 58 | github.com/hashicorp/errwrap v1.1.0 // indirect 59 | github.com/hashicorp/go-multierror v1.1.1 // indirect 60 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 61 | github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect 62 | github.com/jackc/pgpassfile v1.0.0 // indirect 63 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect 64 | github.com/jackc/puddle/v2 v2.2.2 // indirect 65 | github.com/jdx/go-netrc v1.0.0 // indirect 66 | github.com/klauspost/compress v1.17.11 // indirect 67 | github.com/klauspost/pgzip v1.2.6 // indirect 68 | github.com/mattn/go-isatty v0.0.20 // indirect 69 | github.com/mitchellh/go-homedir v1.1.0 // indirect 70 | github.com/moby/docker-image-spec v1.3.1 // indirect 71 | github.com/moby/locker v1.0.1 // indirect 72 | github.com/moby/patternmatcher v0.6.0 // indirect 73 | github.com/moby/sys/mount v0.3.4 // indirect 74 | github.com/moby/sys/mountinfo v0.7.2 // indirect 75 | github.com/moby/sys/reexec v0.1.0 // indirect 76 | github.com/moby/sys/sequential v0.6.0 // indirect 77 | github.com/moby/sys/user v0.3.0 // indirect 78 | github.com/moby/sys/userns v0.1.0 // indirect 79 | github.com/moby/term v0.5.2 // indirect 80 | github.com/morikuni/aec v1.0.0 // indirect 81 | github.com/onsi/ginkgo/v2 v2.22.2 // indirect 82 | github.com/opencontainers/go-digest v1.0.0 // indirect 83 | github.com/opencontainers/image-spec v1.1.0 // indirect 84 | github.com/opencontainers/selinux v1.11.1 // indirect 85 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 86 | github.com/pkg/errors v0.9.1 // indirect 87 | github.com/pkg/profile v1.7.0 // indirect 88 | github.com/quic-go/qpack v0.5.1 // indirect 89 | github.com/quic-go/quic-go v0.48.2 // indirect 90 | github.com/rs/cors v1.11.1 // indirect 91 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 92 | github.com/segmentio/asm v1.2.0 // indirect 93 | github.com/segmentio/encoding v0.4.1 // indirect 94 | github.com/sirupsen/logrus v1.9.3 // indirect 95 | github.com/spf13/cobra v1.8.1 // indirect 96 | github.com/spf13/pflag v1.0.5 // indirect 97 | github.com/stoewer/go-strcase v1.3.0 // indirect 98 | github.com/tetratelabs/wazero v1.8.2 // indirect 99 | github.com/vbatts/tar-split v0.11.6 // indirect 100 | go.lsp.dev/jsonrpc2 v0.10.0 // indirect 101 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect 102 | go.lsp.dev/protocol v0.12.0 // indirect 103 | go.lsp.dev/uri v0.3.0 // indirect 104 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 105 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect 106 | go.opentelemetry.io/otel v1.34.0 // indirect 107 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect 108 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 109 | go.opentelemetry.io/otel/sdk v1.34.0 // indirect 110 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 111 | go.uber.org/atomic v1.9.0 // indirect 112 | go.uber.org/mock v0.5.0 // indirect 113 | go.uber.org/multierr v1.11.0 // indirect 114 | go.uber.org/zap v1.27.0 // indirect 115 | go.uber.org/zap/exp v0.3.0 // indirect 116 | golang.org/x/crypto v0.35.0 // indirect 117 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect 118 | golang.org/x/mod v0.22.0 // indirect 119 | golang.org/x/sync v0.11.0 // indirect 120 | golang.org/x/sys v0.30.0 // indirect 121 | golang.org/x/term v0.29.0 // indirect 122 | golang.org/x/text v0.22.0 // indirect 123 | golang.org/x/tools v0.29.0 // indirect 124 | google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect 125 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect 126 | google.golang.org/grpc v1.70.0 // indirect 127 | gopkg.in/yaml.v3 v3.0.1 // indirect 128 | pluginrpc.com/pluginrpc v0.5.0 // indirect 129 | ) 130 | -------------------------------------------------------------------------------- /_examples/authors/pgx/internal/authors/adapters.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package authors 4 | 5 | import ( 6 | "google.golang.org/protobuf/types/known/wrapperspb" 7 | 8 | pb "authors/api/authors/v1" 9 | ) 10 | 11 | func toAuthors(in *Authors) *pb.Authors { 12 | if in == nil { 13 | return nil 14 | } 15 | out := new(pb.Authors) 16 | out.Id = in.ID 17 | out.Name = in.Name 18 | if in.Bio.Valid { 19 | out.Bio = wrapperspb.String(in.Bio.String) 20 | } 21 | return out 22 | } 23 | -------------------------------------------------------------------------------- /_examples/authors/pgx/internal/authors/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.28.0 4 | 5 | package authors 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/jackc/pgx/v5" 11 | "github.com/jackc/pgx/v5/pgconn" 12 | ) 13 | 14 | type DBTX interface { 15 | Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) 16 | Query(context.Context, string, ...interface{}) (pgx.Rows, error) 17 | QueryRow(context.Context, string, ...interface{}) pgx.Row 18 | } 19 | 20 | func New() *Queries { 21 | return &Queries{} 22 | } 23 | 24 | type Queries struct { 25 | } 26 | -------------------------------------------------------------------------------- /_examples/authors/pgx/internal/authors/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.28.0 4 | 5 | package authors 6 | 7 | import ( 8 | "github.com/jackc/pgx/v5/pgtype" 9 | ) 10 | 11 | type Authors struct { 12 | ID int64 `json:"id"` 13 | Name string `json:"name"` 14 | Bio pgtype.Text `json:"bio"` 15 | } 16 | -------------------------------------------------------------------------------- /_examples/authors/pgx/internal/authors/querier.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.28.0 4 | 5 | package authors 6 | 7 | import ( 8 | "context" 9 | ) 10 | 11 | type Querier interface { 12 | CreateAuthor(ctx context.Context, db DBTX, arg *CreateAuthorParams) (*Authors, error) 13 | DeleteAuthor(ctx context.Context, db DBTX, id int64) error 14 | GetAuthor(ctx context.Context, db DBTX, id int64) (*Authors, error) 15 | ListAuthors(ctx context.Context, db DBTX) ([]*Authors, error) 16 | } 17 | 18 | var _ Querier = (*Queries)(nil) 19 | -------------------------------------------------------------------------------- /_examples/authors/pgx/internal/authors/queries.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.28.0 4 | // source: queries.sql 5 | 6 | package authors 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/jackc/pgx/v5/pgtype" 12 | ) 13 | 14 | const CreateAuthor = `-- name: CreateAuthor :one 15 | INSERT INTO authors ( 16 | name, bio 17 | ) VALUES ( 18 | $1, $2 19 | ) 20 | RETURNING id, name, bio 21 | ` 22 | 23 | type CreateAuthorParams struct { 24 | Name string `json:"name"` 25 | Bio pgtype.Text `json:"bio"` 26 | } 27 | 28 | func (q *Queries) CreateAuthor(ctx context.Context, db DBTX, arg *CreateAuthorParams) (*Authors, error) { 29 | row := db.QueryRow(ctx, CreateAuthor, arg.Name, arg.Bio) 30 | var i Authors 31 | err := row.Scan(&i.ID, &i.Name, &i.Bio) 32 | return &i, err 33 | } 34 | 35 | const DeleteAuthor = `-- name: DeleteAuthor :exec 36 | DELETE FROM authors 37 | WHERE id = $1 38 | ` 39 | 40 | func (q *Queries) DeleteAuthor(ctx context.Context, db DBTX, id int64) error { 41 | _, err := db.Exec(ctx, DeleteAuthor, id) 42 | return err 43 | } 44 | 45 | const GetAuthor = `-- name: GetAuthor :one 46 | SELECT id, name, bio FROM authors 47 | WHERE id = $1 LIMIT 1 48 | ` 49 | 50 | func (q *Queries) GetAuthor(ctx context.Context, db DBTX, id int64) (*Authors, error) { 51 | row := db.QueryRow(ctx, GetAuthor, id) 52 | var i Authors 53 | err := row.Scan(&i.ID, &i.Name, &i.Bio) 54 | return &i, err 55 | } 56 | 57 | const ListAuthors = `-- name: ListAuthors :many 58 | SELECT id, name, bio FROM authors 59 | ORDER BY name 60 | ` 61 | 62 | func (q *Queries) ListAuthors(ctx context.Context, db DBTX) ([]*Authors, error) { 63 | rows, err := db.Query(ctx, ListAuthors) 64 | if err != nil { 65 | return nil, err 66 | } 67 | defer rows.Close() 68 | items := []*Authors{} 69 | for rows.Next() { 70 | var i Authors 71 | if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { 72 | return nil, err 73 | } 74 | items = append(items, &i) 75 | } 76 | if err := rows.Err(); err != nil { 77 | return nil, err 78 | } 79 | return items, nil 80 | } 81 | -------------------------------------------------------------------------------- /_examples/authors/pgx/internal/authors/service.factory.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package authors 4 | 5 | import ( 6 | "github.com/jackc/pgx/v5/pgxpool" 7 | 8 | "authors/api/authors/v1/v1connect" 9 | ) 10 | 11 | // NewService is a constructor of a v1.AuthorsServiceHandler implementation. 12 | // Use this function to customize the server by adding middlewares to it. 13 | func NewService(querier Querier, db *pgxpool.Pool) v1connect.AuthorsServiceHandler { 14 | return &Service{querier: querier, db: db} 15 | } 16 | -------------------------------------------------------------------------------- /_examples/authors/pgx/internal/authors/service.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package authors 4 | 5 | import ( 6 | "context" 7 | "log/slog" 8 | 9 | "connectrpc.com/connect" 10 | "github.com/jackc/pgx/v5/pgtype" 11 | "github.com/jackc/pgx/v5/pgxpool" 12 | 13 | pb "authors/api/authors/v1" 14 | "authors/api/authors/v1/v1connect" 15 | ) 16 | 17 | type Service struct { 18 | v1connect.UnimplementedAuthorsServiceHandler 19 | querier Querier 20 | db *pgxpool.Pool 21 | } 22 | 23 | func (s *Service) CreateAuthor(ctx context.Context, req *connect.Request[pb.CreateAuthorRequest]) (*connect.Response[pb.CreateAuthorResponse], error) { 24 | arg := new(CreateAuthorParams) 25 | arg.Name = req.Msg.GetName() 26 | if v := req.Msg.GetBio(); v != nil { 27 | arg.Bio = pgtype.Text{Valid: true, String: v.Value} 28 | } 29 | 30 | result, err := s.querier.CreateAuthor(ctx, s.db, arg) 31 | if err != nil { 32 | slog.Error("sql call failed", "error", err, "method", "CreateAuthor") 33 | return nil, err 34 | } 35 | return connect.NewResponse(&pb.CreateAuthorResponse{Authors: toAuthors(result)}), nil 36 | } 37 | 38 | func (s *Service) DeleteAuthor(ctx context.Context, req *connect.Request[pb.DeleteAuthorRequest]) (*connect.Response[pb.DeleteAuthorResponse], error) { 39 | id := req.Msg.GetId() 40 | 41 | err := s.querier.DeleteAuthor(ctx, s.db, id) 42 | if err != nil { 43 | slog.Error("sql call failed", "error", err, "method", "DeleteAuthor") 44 | return nil, err 45 | } 46 | return connect.NewResponse(&pb.DeleteAuthorResponse{}), nil 47 | } 48 | 49 | func (s *Service) GetAuthor(ctx context.Context, req *connect.Request[pb.GetAuthorRequest]) (*connect.Response[pb.GetAuthorResponse], error) { 50 | id := req.Msg.GetId() 51 | 52 | result, err := s.querier.GetAuthor(ctx, s.db, id) 53 | if err != nil { 54 | slog.Error("sql call failed", "error", err, "method", "GetAuthor") 55 | return nil, err 56 | } 57 | return connect.NewResponse(&pb.GetAuthorResponse{Authors: toAuthors(result)}), nil 58 | } 59 | 60 | func (s *Service) ListAuthors(ctx context.Context, req *connect.Request[pb.ListAuthorsRequest]) (*connect.Response[pb.ListAuthorsResponse], error) { 61 | 62 | result, err := s.querier.ListAuthors(ctx, s.db) 63 | if err != nil { 64 | slog.Error("sql call failed", "error", err, "method", "ListAuthors") 65 | return nil, err 66 | } 67 | res := new(pb.ListAuthorsResponse) 68 | for _, r := range result { 69 | res.List = append(res.List, toAuthors(r)) 70 | } 71 | return connect.NewResponse(res), nil 72 | } 73 | -------------------------------------------------------------------------------- /_examples/authors/pgx/internal/validation/validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import "errors" 4 | 5 | var ErrUserInput = errors.New("") 6 | -------------------------------------------------------------------------------- /_examples/authors/pgx/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | _ "embed" 9 | "errors" 10 | "flag" 11 | "fmt" 12 | "log/slog" 13 | "net/http" 14 | "os" 15 | "os/signal" 16 | "runtime" 17 | "syscall" 18 | "time" 19 | 20 | "connectrpc.com/connect" 21 | "github.com/flowchartsman/swaggerui" 22 | "go.uber.org/automaxprocs/maxprocs" 23 | "golang.org/x/net/http2" 24 | "golang.org/x/net/http2/h2c" 25 | 26 | // database driver 27 | "github.com/jackc/pgx/v5/pgxpool" 28 | _ "github.com/jackc/pgx/v5/stdlib" 29 | ) 30 | 31 | //go:generate sqlc-connect -m authors -migration-path sql/migrations -migration-lib migrate -append 32 | 33 | const serviceName = "authors" 34 | 35 | var ( 36 | dbURL string 37 | port int 38 | 39 | //go:embed api/apidocs.swagger.json 40 | openAPISpec []byte 41 | ) 42 | 43 | func main() { 44 | var dev bool 45 | flag.StringVar(&dbURL, "db", "", "The Database connection URL") 46 | flag.IntVar(&port, "port", 5000, "The server port") 47 | 48 | flag.BoolVar(&dev, "dev", false, "Set logger to development mode") 49 | 50 | flag.Parse() 51 | 52 | initLogger(dev) 53 | 54 | if err := run(); err != nil && !errors.Is(err, http.ErrServerClosed) { 55 | slog.Error("server error", "error", err) 56 | os.Exit(1) 57 | } 58 | } 59 | 60 | func run() error { 61 | _, err := maxprocs.Set() 62 | if err != nil { 63 | slog.Warn("startup", "error", err) 64 | } 65 | slog.Info("startup", "GOMAXPROCS", runtime.GOMAXPROCS(0)) 66 | 67 | db, err := pgxpool.New(context.Background(), dbURL) 68 | if err != nil { 69 | return err 70 | } 71 | defer db.Close() 72 | 73 | dbMigration, err := sql.Open("pgx", dbURL) 74 | if err != nil { 75 | return err 76 | } 77 | err = ensureSchema(dbMigration) 78 | if err != nil { 79 | slog.Error("migration error", "error", err) 80 | } 81 | dbMigration.Close() 82 | 83 | mux := http.NewServeMux() 84 | var interceptors []connect.Interceptor 85 | 86 | registerHandlers(mux, db, interceptors) 87 | mux.Handle("/swagger/", http.StripPrefix("/swagger", swaggerui.Handler(openAPISpec))) 88 | 89 | server := &http.Server{ 90 | Addr: fmt.Sprintf(":%d", port), 91 | Handler: h2c.NewHandler(mux, &http2.Server{}), 92 | // Please, configure timeouts! 93 | } 94 | 95 | done := make(chan os.Signal, 1) 96 | signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) 97 | go func() { 98 | sig := <-done 99 | slog.Warn("signal detected...", "signal", sig) 100 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 101 | defer cancel() 102 | server.Shutdown(ctx) 103 | }() 104 | slog.Info("Listening...", "port", port) 105 | return server.ListenAndServe() 106 | } 107 | 108 | func initLogger(dev bool) { 109 | var handler slog.Handler 110 | opts := slog.HandlerOptions{ 111 | AddSource: true, 112 | } 113 | switch { 114 | case dev: 115 | handler = slog.NewTextHandler(os.Stderr, &opts) 116 | default: 117 | handler = slog.NewJSONHandler(os.Stderr, &opts) 118 | } 119 | 120 | logger := slog.New(handler) 121 | slog.SetDefault(logger) 122 | } 123 | -------------------------------------------------------------------------------- /_examples/authors/pgx/migration.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "embed" 8 | 9 | "github.com/golang-migrate/migrate/v4" 10 | driver "github.com/golang-migrate/migrate/v4/database/pgx/v5" 11 | "github.com/golang-migrate/migrate/v4/source/iofs" 12 | ) 13 | 14 | //go:embed sql/migrations 15 | var migrations embed.FS 16 | 17 | func ensureSchema(db *sql.DB) error { 18 | source, err := iofs.New(migrations, "sql/migrations") 19 | if err != nil { 20 | return err 21 | } 22 | target, err := driver.WithInstance(db, new(driver.Config)) 23 | if err != nil { 24 | return err 25 | } 26 | m, err := migrate.NewWithInstance("iofs", source, "postgresql", target) 27 | if err != nil { 28 | return err 29 | } 30 | err = m.Up() 31 | if err != nil && err != migrate.ErrNoChange { 32 | return err 33 | } 34 | return source.Close() 35 | } 36 | -------------------------------------------------------------------------------- /_examples/authors/pgx/proto/authors/v1/authors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package authors.v1; 4 | 5 | import "google/protobuf/wrappers.proto"; 6 | 7 | option go_package = "authors/api/authors/v1"; 8 | 9 | service AuthorsService { 10 | rpc CreateAuthor(CreateAuthorRequest) returns (CreateAuthorResponse) {} 11 | 12 | rpc DeleteAuthor(DeleteAuthorRequest) returns (DeleteAuthorResponse) {} 13 | 14 | rpc GetAuthor(GetAuthorRequest) returns (GetAuthorResponse) {} 15 | 16 | rpc ListAuthors(ListAuthorsRequest) returns (ListAuthorsResponse) {} 17 | } 18 | 19 | message Authors { 20 | int64 id = 1; 21 | string name = 2; 22 | google.protobuf.StringValue bio = 3; 23 | } 24 | 25 | message CreateAuthorRequest { 26 | string name = 1; 27 | google.protobuf.StringValue bio = 2; 28 | } 29 | 30 | message CreateAuthorResponse { 31 | Authors authors = 1; 32 | } 33 | 34 | message DeleteAuthorRequest { 35 | int64 id = 1; 36 | } 37 | 38 | message DeleteAuthorResponse {} 39 | 40 | message GetAuthorRequest { 41 | int64 id = 1; 42 | } 43 | 44 | message GetAuthorResponse { 45 | Authors authors = 1; 46 | } 47 | 48 | message ListAuthorsRequest {} 49 | 50 | message ListAuthorsResponse { 51 | repeated Authors list = 1; 52 | } 53 | -------------------------------------------------------------------------------- /_examples/authors/pgx/registry.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "net/http" 7 | 8 | "connectrpc.com/connect" 9 | "connectrpc.com/grpcreflect" 10 | "github.com/jackc/pgx/v5/pgxpool" 11 | 12 | authors_v1connect "authors/api/authors/v1/v1connect" 13 | authors_app "authors/internal/authors" 14 | ) 15 | 16 | func registerHandlers(mux *http.ServeMux, db *pgxpool.Pool, interceptors []connect.Interceptor) { 17 | authorsService := authors_app.NewService(authors_app.New(), db) 18 | authorsPath, authorsHandler := authors_v1connect.NewAuthorsServiceHandler(authorsService, 19 | connect.WithInterceptors( 20 | interceptors..., 21 | ), 22 | ) 23 | mux.Handle(authorsPath, authorsHandler) 24 | 25 | reflector := grpcreflect.NewStaticReflector( 26 | authors_v1connect.AuthorsServiceName, 27 | ) 28 | mux.Handle(grpcreflect.NewHandlerV1(reflector)) 29 | mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) 30 | } 31 | -------------------------------------------------------------------------------- /_examples/authors/pgx/sql/migrations/001_authors.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS authors; -------------------------------------------------------------------------------- /_examples/authors/pgx/sql/migrations/001_authors.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS authors ( 2 | id BIGSERIAL PRIMARY KEY, 3 | name text NOT NULL, 4 | bio text 5 | ); -------------------------------------------------------------------------------- /_examples/authors/pgx/sql/queries.sql: -------------------------------------------------------------------------------- 1 | -- name: GetAuthor :one 2 | SELECT * FROM authors 3 | WHERE id = $1 LIMIT 1; 4 | 5 | -- name: ListAuthors :many 6 | SELECT * FROM authors 7 | ORDER BY name; 8 | 9 | -- name: CreateAuthor :one 10 | INSERT INTO authors ( 11 | name, bio 12 | ) VALUES ( 13 | $1, $2 14 | ) 15 | RETURNING *; 16 | 17 | -- name: DeleteAuthor :exec 18 | DELETE FROM authors 19 | WHERE id = $1; -------------------------------------------------------------------------------- /_examples/authors/pgx/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "./sql/migrations" 4 | queries: "./sql/queries.sql" 5 | engine: "postgresql" 6 | gen: 7 | go: 8 | package: "authors" 9 | out: "internal/authors" 10 | sql_package: "pgx/v5" 11 | emit_interface: true 12 | emit_exact_table_names: true 13 | emit_empty_slices: true 14 | emit_exported_queries: true 15 | emit_json_tags: true 16 | emit_result_struct_pointers: true 17 | emit_params_struct_pointers: true 18 | emit_methods_with_db_argument: true -------------------------------------------------------------------------------- /_examples/authors/pgx/tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | _ "connectrpc.com/connect/cmd/protoc-gen-connect-go" 8 | _ "github.com/bufbuild/buf/cmd/buf" 9 | _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" 10 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 11 | ) 12 | -------------------------------------------------------------------------------- /_examples/authors/pgx/validation/validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import "errors" 4 | 5 | var ErrUserInput = errors.New("") 6 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19 2 | 3 | RUN go install github.com/cespare/reflex@latest 4 | 5 | WORKDIR /app 6 | COPY go.mod . 7 | COPY go.sum . 8 | 9 | RUN go mod download -x 10 | 11 | COPY configs/reflex.conf / 12 | 13 | ENTRYPOINT ["reflex", "-c", "/reflex.conf"] -------------------------------------------------------------------------------- /_examples/authors/sqlite/api/apidocs.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "authors/v1/authors.proto", 5 | "version": "version not set" 6 | }, 7 | "tags": [ 8 | { 9 | "name": "AuthorsService" 10 | } 11 | ], 12 | "consumes": [ 13 | "application/json" 14 | ], 15 | "produces": [ 16 | "application/json" 17 | ], 18 | "paths": { 19 | "/authors.v1.AuthorsService/CreateAuthor": { 20 | "post": { 21 | "operationId": "AuthorsService_CreateAuthor", 22 | "responses": { 23 | "200": { 24 | "description": "A successful response.", 25 | "schema": { 26 | "$ref": "#/definitions/v1CreateAuthorResponse" 27 | } 28 | }, 29 | "default": { 30 | "description": "An unexpected error response.", 31 | "schema": { 32 | "$ref": "#/definitions/rpcStatus" 33 | } 34 | } 35 | }, 36 | "parameters": [ 37 | { 38 | "name": "body", 39 | "in": "body", 40 | "required": true, 41 | "schema": { 42 | "$ref": "#/definitions/v1CreateAuthorRequest" 43 | } 44 | } 45 | ], 46 | "tags": [ 47 | "AuthorsService" 48 | ] 49 | } 50 | }, 51 | "/authors.v1.AuthorsService/DeleteAuthor": { 52 | "post": { 53 | "operationId": "AuthorsService_DeleteAuthor", 54 | "responses": { 55 | "200": { 56 | "description": "A successful response.", 57 | "schema": { 58 | "$ref": "#/definitions/v1DeleteAuthorResponse" 59 | } 60 | }, 61 | "default": { 62 | "description": "An unexpected error response.", 63 | "schema": { 64 | "$ref": "#/definitions/rpcStatus" 65 | } 66 | } 67 | }, 68 | "parameters": [ 69 | { 70 | "name": "body", 71 | "in": "body", 72 | "required": true, 73 | "schema": { 74 | "$ref": "#/definitions/v1DeleteAuthorRequest" 75 | } 76 | } 77 | ], 78 | "tags": [ 79 | "AuthorsService" 80 | ] 81 | } 82 | }, 83 | "/authors.v1.AuthorsService/GetAuthor": { 84 | "post": { 85 | "operationId": "AuthorsService_GetAuthor", 86 | "responses": { 87 | "200": { 88 | "description": "A successful response.", 89 | "schema": { 90 | "$ref": "#/definitions/v1GetAuthorResponse" 91 | } 92 | }, 93 | "default": { 94 | "description": "An unexpected error response.", 95 | "schema": { 96 | "$ref": "#/definitions/rpcStatus" 97 | } 98 | } 99 | }, 100 | "parameters": [ 101 | { 102 | "name": "body", 103 | "in": "body", 104 | "required": true, 105 | "schema": { 106 | "$ref": "#/definitions/v1GetAuthorRequest" 107 | } 108 | } 109 | ], 110 | "tags": [ 111 | "AuthorsService" 112 | ] 113 | } 114 | }, 115 | "/authors.v1.AuthorsService/ListAuthors": { 116 | "post": { 117 | "operationId": "AuthorsService_ListAuthors", 118 | "responses": { 119 | "200": { 120 | "description": "A successful response.", 121 | "schema": { 122 | "$ref": "#/definitions/v1ListAuthorsResponse" 123 | } 124 | }, 125 | "default": { 126 | "description": "An unexpected error response.", 127 | "schema": { 128 | "$ref": "#/definitions/rpcStatus" 129 | } 130 | } 131 | }, 132 | "parameters": [ 133 | { 134 | "name": "body", 135 | "in": "body", 136 | "required": true, 137 | "schema": { 138 | "$ref": "#/definitions/v1ListAuthorsRequest" 139 | } 140 | } 141 | ], 142 | "tags": [ 143 | "AuthorsService" 144 | ] 145 | } 146 | } 147 | }, 148 | "definitions": { 149 | "protobufAny": { 150 | "type": "object", 151 | "properties": { 152 | "@type": { 153 | "type": "string" 154 | } 155 | }, 156 | "additionalProperties": {} 157 | }, 158 | "rpcStatus": { 159 | "type": "object", 160 | "properties": { 161 | "code": { 162 | "type": "integer", 163 | "format": "int32" 164 | }, 165 | "message": { 166 | "type": "string" 167 | }, 168 | "details": { 169 | "type": "array", 170 | "items": { 171 | "type": "object", 172 | "$ref": "#/definitions/protobufAny" 173 | } 174 | } 175 | } 176 | }, 177 | "v1Author": { 178 | "type": "object", 179 | "properties": { 180 | "id": { 181 | "type": "string", 182 | "format": "int64" 183 | }, 184 | "name": { 185 | "type": "string" 186 | }, 187 | "bio": { 188 | "type": "string" 189 | } 190 | } 191 | }, 192 | "v1CreateAuthorRequest": { 193 | "type": "object", 194 | "properties": { 195 | "name": { 196 | "type": "string" 197 | }, 198 | "bio": { 199 | "type": "string" 200 | } 201 | } 202 | }, 203 | "v1CreateAuthorResponse": { 204 | "type": "object", 205 | "properties": { 206 | "value": { 207 | "$ref": "#/definitions/v1ExecResult" 208 | } 209 | } 210 | }, 211 | "v1DeleteAuthorRequest": { 212 | "type": "object", 213 | "properties": { 214 | "id": { 215 | "type": "string", 216 | "format": "int64" 217 | } 218 | } 219 | }, 220 | "v1DeleteAuthorResponse": { 221 | "type": "object" 222 | }, 223 | "v1ExecResult": { 224 | "type": "object", 225 | "properties": { 226 | "rowsAffected": { 227 | "type": "string", 228 | "format": "int64" 229 | }, 230 | "lastInsertId": { 231 | "type": "string", 232 | "format": "int64" 233 | } 234 | } 235 | }, 236 | "v1GetAuthorRequest": { 237 | "type": "object", 238 | "properties": { 239 | "id": { 240 | "type": "string", 241 | "format": "int64" 242 | } 243 | } 244 | }, 245 | "v1GetAuthorResponse": { 246 | "type": "object", 247 | "properties": { 248 | "author": { 249 | "$ref": "#/definitions/v1Author" 250 | } 251 | } 252 | }, 253 | "v1ListAuthorsRequest": { 254 | "type": "object" 255 | }, 256 | "v1ListAuthorsResponse": { 257 | "type": "object", 258 | "properties": { 259 | "list": { 260 | "type": "array", 261 | "items": { 262 | "type": "object", 263 | "$ref": "#/definitions/v1Author" 264 | } 265 | } 266 | } 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | plugins: 3 | - local: protoc-gen-go 4 | out: api 5 | opt: paths=source_relative 6 | - local: protoc-gen-connect-go 7 | out: api 8 | opt: paths=source_relative 9 | - local: protoc-gen-openapiv2 10 | out: api 11 | opt: 12 | - generate_unbound_methods=true 13 | - logtostderr=true 14 | - allow_merge=true 15 | strategy: all -------------------------------------------------------------------------------- /_examples/authors/sqlite/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v2 3 | deps: 4 | - name: buf.build/googleapis/googleapis 5 | commit: 751cbe31638d43a9bfb6162cd2352e67 6 | digest: b5:51ba5c31f244fd74420f0e66d13f2b5dd6024dcfe1a29dc45bd8f6e61c1444c828b9add9e7dd25a4513ebbee8097a970e0712a2e2cd955c2d60cf8905204f51a 7 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | modules: 3 | - path: proto 4 | deps: 5 | - buf.build/googleapis/googleapis 6 | lint: 7 | use: 8 | - DEFAULT 9 | except: 10 | - FIELD_NOT_REQUIRED 11 | - PACKAGE_NO_IMPORT_CYCLE 12 | disallow_comment_ignores: true 13 | breaking: 14 | use: 15 | - FILE 16 | except: 17 | - EXTENSION_NO_DELETE 18 | - FIELD_SAME_DEFAULT 19 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/configs/reflex.conf: -------------------------------------------------------------------------------- 1 | -r '(\.go$|go\.mod)' -s -- sh -c 'go run . -dev=true -db=example.db -port=8080 -replication=s3://somebucketname/example.db' 2 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | build: . 5 | ports: 6 | - "8080:8080" 7 | environment: 8 | - "AWS_ACCESS_KEY_ID=minio" 9 | - "AWS_SECRET_ACCESS_KEY=minio123" 10 | - "LITESTREAM_ENDPOINT=minio:9000" 11 | - "LITESTREAM_FORCE_PATH_STYLE=true" 12 | - "LITESTREAM_SCHEME=http" 13 | volumes: 14 | - .:/app 15 | depends_on: 16 | - minio 17 | 18 | minio: 19 | image: minio/minio 20 | command: server /data --console-address ":9001" 21 | environment: 22 | - "MINIO_ROOT_USER=minio" 23 | - "MINIO_ROOT_PASSWORD=minio123" 24 | ports: 25 | - "9000:9000" 26 | - "9001:9001" 27 | 28 | createbuckets: 29 | image: minio/mc 30 | depends_on: 31 | - minio 32 | entrypoint: > 33 | /bin/sh -c " 34 | /usr/bin/mc config host add myminio http://minio:9000 minio minio123; 35 | /usr/bin/mc rm -r --force myminio/somebucketname; 36 | /usr/bin/mc mb myminio/somebucketname; 37 | /usr/bin/mc policy set public myminio/somebucketname; 38 | exit 0; 39 | " -------------------------------------------------------------------------------- /_examples/authors/sqlite/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -u 3 | set -e 4 | set -x 5 | 6 | go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest 7 | 8 | rm -rf internal proto api go.mod go.sum *.go buf* 9 | 10 | sqlc generate 11 | sqlc-connect -m authors -migration-path sql/migrations -litefs -litestream 12 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/go.mod: -------------------------------------------------------------------------------- 1 | module authors 2 | 3 | go 1.23.2 4 | 5 | require ( 6 | connectrpc.com/connect v1.18.1 7 | connectrpc.com/grpcreflect v1.3.0 8 | github.com/benbjohnson/litestream v0.3.13 9 | github.com/bufbuild/buf v1.50.0 10 | github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b 11 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 12 | github.com/hashicorp/raft v1.7.2 13 | github.com/hashicorp/raft-boltdb v0.0.0-20250225060035-8f7048cdfa53 14 | github.com/mattn/go-sqlite3 v1.14.24 15 | github.com/pressly/goose/v3 v3.24.1 16 | github.com/superfly/litefs v0.5.11 17 | github.com/superfly/ltx v0.3.14 18 | github.com/walterwanderley/litefs-raft v0.0.0-20231130032048-a2979ac7e817 19 | go.uber.org/automaxprocs v1.6.0 20 | golang.org/x/net v0.36.0 21 | google.golang.org/protobuf v1.36.5 22 | ) 23 | 24 | require ( 25 | bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5 // indirect 26 | buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20241031151143-70f632351282.1 // indirect 27 | buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.3-20241127180247-a33202765966.1 // indirect 28 | buf.build/gen/go/bufbuild/registry/connectrpc/go v1.18.1-20250106231242-56271afbd6ce.1 // indirect 29 | buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.3-20250106231242-56271afbd6ce.1 // indirect 30 | buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.3-20241007202033-cf42259fcbfc.1 // indirect 31 | buf.build/go/bufplugin v0.6.0 // indirect 32 | buf.build/go/protoyaml v0.3.1 // indirect 33 | buf.build/go/spdx v0.2.0 // indirect 34 | cel.dev/expr v0.19.1 // indirect 35 | connectrpc.com/otelconnect v0.7.1 // indirect 36 | filippo.io/age v1.1.1 // indirect 37 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect 38 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect 39 | github.com/Microsoft/go-winio v0.6.2 // indirect 40 | github.com/antlr4-go/antlr/v4 v4.13.1 // indirect 41 | github.com/armon/go-metrics v0.4.1 // indirect 42 | github.com/aws/aws-sdk-go v1.44.318 // indirect 43 | github.com/beorn7/perks v1.0.1 // indirect 44 | github.com/boltdb/bolt v1.3.1 // indirect 45 | github.com/bufbuild/protocompile v0.14.1 // indirect 46 | github.com/bufbuild/protoplugin v0.0.0-20250106231243-3a819552c9d9 // indirect 47 | github.com/bufbuild/protovalidate-go v0.8.2 // indirect 48 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 49 | github.com/containerd/log v0.1.0 // indirect 50 | github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect 51 | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect 52 | github.com/distribution/reference v0.6.0 // indirect 53 | github.com/docker/cli v27.5.0+incompatible // indirect 54 | github.com/docker/distribution v2.8.3+incompatible // indirect 55 | github.com/docker/docker v28.0.1+incompatible // indirect 56 | github.com/docker/docker-credential-helpers v0.8.2 // indirect 57 | github.com/docker/go-connections v0.5.0 // indirect 58 | github.com/docker/go-units v0.5.0 // indirect 59 | github.com/fatih/color v1.16.0 // indirect 60 | github.com/felixge/fgprof v0.9.5 // indirect 61 | github.com/felixge/httpsnoop v1.0.4 // indirect 62 | github.com/go-chi/chi/v5 v5.2.0 // indirect 63 | github.com/go-logr/logr v1.4.2 // indirect 64 | github.com/go-logr/stdr v1.2.2 // indirect 65 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 66 | github.com/gofrs/flock v0.12.1 // indirect 67 | github.com/gogo/protobuf v1.3.2 // indirect 68 | github.com/google/cel-go v0.22.1 // indirect 69 | github.com/google/go-containerregistry v0.20.2 // indirect 70 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect 71 | github.com/google/uuid v1.6.0 // indirect 72 | github.com/hashicorp/go-hclog v1.6.2 // indirect 73 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 74 | github.com/hashicorp/go-metrics v0.5.4 // indirect 75 | github.com/hashicorp/go-msgpack v0.5.5 // indirect 76 | github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect 77 | github.com/hashicorp/golang-lru v1.0.2 // indirect 78 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 79 | github.com/jdx/go-netrc v1.0.0 // indirect 80 | github.com/jmespath/go-jmespath v0.4.0 // indirect 81 | github.com/klauspost/compress v1.17.11 // indirect 82 | github.com/klauspost/pgzip v1.2.6 // indirect 83 | github.com/mattn/go-colorable v0.1.13 // indirect 84 | github.com/mattn/go-isatty v0.0.20 // indirect 85 | github.com/mfridman/interpolate v0.0.2 // indirect 86 | github.com/mitchellh/go-homedir v1.1.0 // indirect 87 | github.com/moby/docker-image-spec v1.3.1 // indirect 88 | github.com/moby/locker v1.0.1 // indirect 89 | github.com/moby/patternmatcher v0.6.0 // indirect 90 | github.com/moby/sys/mount v0.3.4 // indirect 91 | github.com/moby/sys/mountinfo v0.7.2 // indirect 92 | github.com/moby/sys/reexec v0.1.0 // indirect 93 | github.com/moby/sys/sequential v0.6.0 // indirect 94 | github.com/moby/sys/user v0.3.0 // indirect 95 | github.com/moby/sys/userns v0.1.0 // indirect 96 | github.com/moby/term v0.5.2 // indirect 97 | github.com/morikuni/aec v1.0.0 // indirect 98 | github.com/onsi/ginkgo/v2 v2.22.2 // indirect 99 | github.com/opencontainers/go-digest v1.0.0 // indirect 100 | github.com/opencontainers/image-spec v1.1.0 // indirect 101 | github.com/opencontainers/selinux v1.11.1 // indirect 102 | github.com/pierrec/lz4/v4 v4.1.21 // indirect 103 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 104 | github.com/pkg/errors v0.9.1 // indirect 105 | github.com/pkg/profile v1.7.0 // indirect 106 | github.com/prometheus/client_golang v1.19.1 // indirect 107 | github.com/prometheus/client_model v0.5.0 // indirect 108 | github.com/prometheus/common v0.48.0 // indirect 109 | github.com/prometheus/procfs v0.12.0 // indirect 110 | github.com/quic-go/qpack v0.5.1 // indirect 111 | github.com/quic-go/quic-go v0.48.2 // indirect 112 | github.com/rs/cors v1.11.1 // indirect 113 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 114 | github.com/segmentio/asm v1.2.0 // indirect 115 | github.com/segmentio/encoding v0.4.1 // indirect 116 | github.com/sethvargo/go-retry v0.3.0 // indirect 117 | github.com/sirupsen/logrus v1.9.3 // indirect 118 | github.com/spf13/cobra v1.8.1 // indirect 119 | github.com/spf13/pflag v1.0.5 // indirect 120 | github.com/stoewer/go-strcase v1.3.0 // indirect 121 | github.com/tetratelabs/wazero v1.8.2 // indirect 122 | github.com/vbatts/tar-split v0.11.6 // indirect 123 | go.lsp.dev/jsonrpc2 v0.10.0 // indirect 124 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect 125 | go.lsp.dev/protocol v0.12.0 // indirect 126 | go.lsp.dev/uri v0.3.0 // indirect 127 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 128 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect 129 | go.opentelemetry.io/otel v1.34.0 // indirect 130 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect 131 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 132 | go.opentelemetry.io/otel/sdk v1.34.0 // indirect 133 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 134 | go.uber.org/mock v0.5.0 // indirect 135 | go.uber.org/multierr v1.11.0 // indirect 136 | go.uber.org/zap v1.27.0 // indirect 137 | go.uber.org/zap/exp v0.3.0 // indirect 138 | golang.org/x/crypto v0.35.0 // indirect 139 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect 140 | golang.org/x/mod v0.22.0 // indirect 141 | golang.org/x/sync v0.11.0 // indirect 142 | golang.org/x/sys v0.30.0 // indirect 143 | golang.org/x/term v0.29.0 // indirect 144 | golang.org/x/text v0.22.0 // indirect 145 | golang.org/x/tools v0.29.0 // indirect 146 | google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect 147 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect 148 | google.golang.org/grpc v1.70.0 // indirect 149 | gopkg.in/yaml.v3 v3.0.1 // indirect 150 | pluginrpc.com/pluginrpc v0.5.0 // indirect 151 | ) 152 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/internal/authors/adapters.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package authors 4 | 5 | import ( 6 | "database/sql" 7 | 8 | "google.golang.org/protobuf/types/known/wrapperspb" 9 | 10 | pb "authors/api/authors/v1" 11 | ) 12 | 13 | func toAuthor(in Author) *pb.Author { 14 | 15 | out := new(pb.Author) 16 | out.Id = in.ID 17 | out.Name = in.Name 18 | if in.Bio.Valid { 19 | out.Bio = wrapperspb.String(in.Bio.String) 20 | } 21 | return out 22 | } 23 | 24 | func toExecResult(in sql.Result) *pb.ExecResult { 25 | lastInsertId, _ := in.LastInsertId() 26 | rowsAffected, _ := in.RowsAffected() 27 | return &pb.ExecResult{ 28 | LastInsertId: lastInsertId, 29 | RowsAffected: rowsAffected, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/internal/authors/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.28.0 4 | 5 | package authors 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/internal/authors/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.28.0 4 | 5 | package authors 6 | 7 | import ( 8 | "database/sql" 9 | ) 10 | 11 | type Author struct { 12 | ID int64 13 | Name string 14 | Bio sql.NullString 15 | } 16 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/internal/authors/queries.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.28.0 4 | // source: queries.sql 5 | 6 | package authors 7 | 8 | import ( 9 | "context" 10 | "database/sql" 11 | ) 12 | 13 | const createAuthor = `-- name: createAuthor :execresult 14 | INSERT INTO authors ( 15 | name, bio 16 | ) VALUES ( 17 | ?, ? 18 | ) 19 | ` 20 | 21 | type createAuthorParams struct { 22 | Name string 23 | Bio sql.NullString 24 | } 25 | 26 | func (q *Queries) createAuthor(ctx context.Context, arg createAuthorParams) (sql.Result, error) { 27 | return q.db.ExecContext(ctx, createAuthor, arg.Name, arg.Bio) 28 | } 29 | 30 | const deleteAuthor = `-- name: deleteAuthor :exec 31 | DELETE FROM authors 32 | WHERE id = ? 33 | ` 34 | 35 | func (q *Queries) deleteAuthor(ctx context.Context, id int64) error { 36 | _, err := q.db.ExecContext(ctx, deleteAuthor, id) 37 | return err 38 | } 39 | 40 | const getAuthor = `-- name: getAuthor :one 41 | SELECT id, name, bio FROM authors 42 | WHERE id = ? LIMIT 1 43 | ` 44 | 45 | func (q *Queries) getAuthor(ctx context.Context, id int64) (Author, error) { 46 | row := q.db.QueryRowContext(ctx, getAuthor, id) 47 | var i Author 48 | err := row.Scan(&i.ID, &i.Name, &i.Bio) 49 | return i, err 50 | } 51 | 52 | const listAuthors = `-- name: listAuthors :many 53 | SELECT id, name, bio FROM authors 54 | ORDER BY name 55 | ` 56 | 57 | func (q *Queries) listAuthors(ctx context.Context) ([]Author, error) { 58 | rows, err := q.db.QueryContext(ctx, listAuthors) 59 | if err != nil { 60 | return nil, err 61 | } 62 | defer rows.Close() 63 | var items []Author 64 | for rows.Next() { 65 | var i Author 66 | if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { 67 | return nil, err 68 | } 69 | items = append(items, i) 70 | } 71 | if err := rows.Close(); err != nil { 72 | return nil, err 73 | } 74 | if err := rows.Err(); err != nil { 75 | return nil, err 76 | } 77 | return items, nil 78 | } 79 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/internal/authors/service.factory.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package authors 4 | 5 | import ( 6 | "authors/api/authors/v1/v1connect" 7 | ) 8 | 9 | // NewService is a constructor of a v1.AuthorsServiceHandler implementation. 10 | // Use this function to customize the server by adding middlewares to it. 11 | func NewService(querier *Queries) v1connect.AuthorsServiceHandler { 12 | return &Service{querier: querier} 13 | } 14 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/internal/authors/service.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package authors 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | "log/slog" 9 | 10 | "connectrpc.com/connect" 11 | 12 | pb "authors/api/authors/v1" 13 | "authors/api/authors/v1/v1connect" 14 | ) 15 | 16 | type Service struct { 17 | v1connect.UnimplementedAuthorsServiceHandler 18 | querier *Queries 19 | } 20 | 21 | func (s *Service) CreateAuthor(ctx context.Context, req *connect.Request[pb.CreateAuthorRequest]) (*connect.Response[pb.CreateAuthorResponse], error) { 22 | var arg createAuthorParams 23 | arg.Name = req.Msg.GetName() 24 | if v := req.Msg.GetBio(); v != nil { 25 | arg.Bio = sql.NullString{Valid: true, String: v.Value} 26 | } 27 | 28 | result, err := s.querier.createAuthor(ctx, arg) 29 | if err != nil { 30 | slog.Error("sql call failed", "error", err, "method", "createAuthor") 31 | return nil, err 32 | } 33 | return connect.NewResponse(&pb.CreateAuthorResponse{Value: toExecResult(result)}), nil 34 | } 35 | 36 | func (s *Service) DeleteAuthor(ctx context.Context, req *connect.Request[pb.DeleteAuthorRequest]) (*connect.Response[pb.DeleteAuthorResponse], error) { 37 | id := req.Msg.GetId() 38 | 39 | err := s.querier.deleteAuthor(ctx, id) 40 | if err != nil { 41 | slog.Error("sql call failed", "error", err, "method", "deleteAuthor") 42 | return nil, err 43 | } 44 | return connect.NewResponse(&pb.DeleteAuthorResponse{}), nil 45 | } 46 | 47 | func (s *Service) GetAuthor(ctx context.Context, req *connect.Request[pb.GetAuthorRequest]) (*connect.Response[pb.GetAuthorResponse], error) { 48 | id := req.Msg.GetId() 49 | 50 | result, err := s.querier.getAuthor(ctx, id) 51 | if err != nil { 52 | slog.Error("sql call failed", "error", err, "method", "getAuthor") 53 | return nil, err 54 | } 55 | return connect.NewResponse(&pb.GetAuthorResponse{Author: toAuthor(result)}), nil 56 | } 57 | 58 | func (s *Service) ListAuthors(ctx context.Context, req *connect.Request[pb.ListAuthorsRequest]) (*connect.Response[pb.ListAuthorsResponse], error) { 59 | 60 | result, err := s.querier.listAuthors(ctx) 61 | if err != nil { 62 | slog.Error("sql call failed", "error", err, "method", "listAuthors") 63 | return nil, err 64 | } 65 | res := new(pb.ListAuthorsResponse) 66 | for _, r := range result { 67 | res.List = append(res.List, toAuthor(r)) 68 | } 69 | return connect.NewResponse(res), nil 70 | } 71 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/internal/server/litefs/forward.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-grpc). 2 | 3 | package litefs 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "fmt" 9 | "io" 10 | "log/slog" 11 | "net/http" 12 | "time" 13 | 14 | "github.com/superfly/ltx" 15 | ) 16 | 17 | const txCookieName = "_txid" 18 | 19 | type RedirectTarget func() string 20 | 21 | func (lfs *LiteFS) ForwardToLeader(timeout time.Duration, methods ...string) func(http.Handler) http.Handler { 22 | return func(h http.Handler) http.Handler { 23 | return http.HandlerFunc(lfs.ForwardToLeaderFunc(h.ServeHTTP, timeout, methods...)) 24 | } 25 | } 26 | 27 | func (lfs *LiteFS) ForwardToLeaderFunc(h http.HandlerFunc, timeout time.Duration, methods ...string) http.HandlerFunc { 28 | return func(w http.ResponseWriter, r *http.Request) { 29 | var match bool 30 | 31 | for _, method := range methods { 32 | if r.Method == method { 33 | match = true 34 | break 35 | } 36 | } 37 | 38 | if !match { 39 | h(w, r) 40 | return 41 | } 42 | 43 | isLeader := lfs.store.IsPrimary() 44 | if isLeader { 45 | h(&responseWriter{w: w, lfs: lfs}, r) 46 | return 47 | } 48 | 49 | target := lfs.redirectTarget() 50 | if target == "" { 51 | http.Error(w, "leader redirect URL not found", http.StatusInternalServerError) 52 | return 53 | } 54 | 55 | if r.URL.Query().Get("forward") == "false" { 56 | w.Header().Set("location", string(target)) 57 | w.WriteHeader(http.StatusMovedPermanently) 58 | return 59 | } 60 | 61 | resp, err := forwardTo(target, r, timeout) 62 | if err != nil { 63 | http.Error(w, err.Error(), http.StatusInternalServerError) 64 | return 65 | } 66 | defer resp.Body.Close() 67 | for k, v := range resp.Header { 68 | for i, value := range v { 69 | if i == 0 { 70 | w.Header().Set(k, value) 71 | continue 72 | } 73 | w.Header().Add(k, value) 74 | } 75 | } 76 | w.WriteHeader(resp.StatusCode) 77 | io.Copy(w, resp.Body) 78 | } 79 | } 80 | 81 | func (lfs *LiteFS) ConsistentReader(timeout time.Duration, methods ...string) func(http.Handler) http.Handler { 82 | return func(h http.Handler) http.Handler { 83 | return http.HandlerFunc(lfs.ConsistentReaderFunc(h.ServeHTTP, timeout, methods...)) 84 | } 85 | } 86 | 87 | func (lfs *LiteFS) ConsistentReaderFunc(h http.HandlerFunc, timeout time.Duration, methods ...string) http.HandlerFunc { 88 | return func(w http.ResponseWriter, r *http.Request) { 89 | var match bool 90 | 91 | for _, method := range methods { 92 | if r.Method == method { 93 | match = true 94 | break 95 | } 96 | } 97 | 98 | if !match || lfs.store.IsPrimary() { 99 | h(w, r) 100 | return 101 | } 102 | 103 | var txID ltx.TXID 104 | if cookie, _ := r.Cookie(txCookieName); cookie != nil { 105 | var err error 106 | txID, err = ltx.ParseTXID(cookie.Value) 107 | if err != nil { 108 | slog.Warn("invalid cookie", "name", txCookieName, "error", err) 109 | h(w, r) 110 | return 111 | } 112 | } 113 | 114 | ticker := time.NewTicker(time.Millisecond) 115 | defer ticker.Stop() 116 | 117 | ctx, cancel := context.WithTimeout(r.Context(), timeout) 118 | defer cancel() 119 | 120 | var pos ltx.Pos 121 | LOOP: 122 | for { 123 | if pos = lfs.store.DBs()[0].Pos(); pos.TXID >= txID { 124 | break LOOP 125 | } 126 | 127 | select { 128 | case <-ctx.Done(): 129 | if r.URL.Query().Get("forward") == "false" { 130 | http.Error(w, "cosistent reader timeout", http.StatusGatewayTimeout) 131 | return 132 | } 133 | target := lfs.redirectTarget() 134 | if target == "" { 135 | http.Error(w, "leader redirect URL not found", http.StatusInternalServerError) 136 | return 137 | } 138 | resp, err := forwardTo(target, r, timeout) 139 | if err != nil { 140 | http.Error(w, err.Error(), http.StatusInternalServerError) 141 | return 142 | } 143 | defer resp.Body.Close() 144 | for k, v := range resp.Header { 145 | for i, value := range v { 146 | if i == 0 { 147 | w.Header().Set(k, value) 148 | continue 149 | } 150 | w.Header().Add(k, value) 151 | } 152 | } 153 | w.WriteHeader(resp.StatusCode) 154 | io.Copy(w, resp.Body) 155 | return 156 | case <-ticker.C: 157 | } 158 | } 159 | h(w, r) 160 | } 161 | } 162 | 163 | func forwardTo(addr string, req *http.Request, timeout time.Duration) (*http.Response, error) { 164 | newURL := addr + req.URL.Path + "?" + req.URL.RawQuery 165 | 166 | var buf bytes.Buffer 167 | defer req.Body.Close() 168 | _, err := io.Copy(&buf, req.Body) 169 | if err != nil { 170 | return nil, err 171 | } 172 | ctx, cancel := context.WithTimeout(req.Context(), timeout) 173 | defer cancel() 174 | newReq, err := http.NewRequestWithContext(ctx, req.Method, newURL, &buf) 175 | if err != nil { 176 | return nil, err 177 | } 178 | for k, v := range req.Header { 179 | for i, value := range v { 180 | if i == 0 { 181 | newReq.Header.Set(k, value) 182 | continue 183 | } 184 | newReq.Header.Add(k, value) 185 | } 186 | } 187 | return http.DefaultClient.Do(newReq) 188 | } 189 | 190 | type responseWriter struct { 191 | w http.ResponseWriter 192 | lfs *LiteFS 193 | statusCode int 194 | } 195 | 196 | func (rw *responseWriter) Header() http.Header { 197 | return rw.w.Header() 198 | } 199 | 200 | func (rw *responseWriter) Write(b []byte) (int, error) { 201 | if rw.statusCode == 0 || (rw.statusCode >= 200 && rw.statusCode < 300) { 202 | http.SetCookie(rw.w, &http.Cookie{ 203 | Name: txCookieName, 204 | Value: fmt.Sprint(rw.lfs.store.DBs()[0].Pos().TXID.String()), 205 | Expires: time.Now().Add(5 * time.Minute), 206 | HttpOnly: true, 207 | }) 208 | } 209 | return rw.w.Write(b) 210 | } 211 | 212 | func (rw *responseWriter) WriteHeader(statusCode int) { 213 | rw.statusCode = statusCode 214 | rw.w.WriteHeader(statusCode) 215 | } 216 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/internal/server/litestream/litestream.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-grpc). 2 | 3 | package litestream 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "net/url" 9 | "os" 10 | "path" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/benbjohnson/litestream" 15 | lss3 "github.com/benbjohnson/litestream/s3" 16 | ) 17 | 18 | func Replicate(ctx context.Context, dsn, replicaURL string) (*litestream.DB, error) { 19 | if i := strings.Index(dsn, "?"); i > 0 { 20 | dsn = dsn[0:i] 21 | } 22 | dsn = strings.TrimPrefix(dsn, "file:") 23 | 24 | lsdb := litestream.NewDB(dsn) 25 | 26 | u, err := url.Parse(replicaURL) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | scheme := "https" 32 | host := u.Host 33 | path := strings.TrimPrefix(path.Clean(u.Path), "/") 34 | bucket, region, endpoint, forcePathStyle := lss3.ParseHost(host) 35 | 36 | if s := os.Getenv("LITESTREAM_SCHEME"); s != "" { 37 | if s != "https" && s != "http" { 38 | return nil, fmt.Errorf("unsupported LITESTREAM_SCHEME value: %q", s) 39 | } else { 40 | scheme = s 41 | } 42 | } 43 | 44 | if e := os.Getenv("LITESTREAM_ENDPOINT"); e != "" { 45 | endpoint = e 46 | } 47 | 48 | if r := os.Getenv("LITESTREAM_REGION"); r != "" { 49 | region = r 50 | } 51 | 52 | if endpoint != "" { 53 | endpoint = scheme + "://" + endpoint 54 | } 55 | 56 | if fps := os.Getenv("LITESTREAM_FORCE_PATH_STYLE"); fps != "" { 57 | if b, err := strconv.ParseBool(fps); err != nil { 58 | return nil, fmt.Errorf("invalid LITESTREAM_FORCE_PATH_STYLE value: %q", fps) 59 | } else { 60 | forcePathStyle = b 61 | } 62 | } 63 | 64 | client := lss3.NewReplicaClient() 65 | client.Bucket = bucket 66 | client.Path = path 67 | client.Region = region 68 | client.Endpoint = endpoint 69 | client.ForcePathStyle = forcePathStyle 70 | 71 | replica := litestream.NewReplica(lsdb, lss3.ReplicaClientType) 72 | replica.Client = client 73 | 74 | lsdb.Replicas = append(lsdb.Replicas, replica) 75 | 76 | if err := restore(ctx, replica); err != nil { 77 | return nil, err 78 | } 79 | 80 | if err := lsdb.Open(); err != nil { 81 | return nil, err 82 | } 83 | 84 | if err := lsdb.Sync(ctx); err != nil { 85 | return nil, err 86 | } 87 | 88 | return lsdb, nil 89 | } 90 | 91 | func restore(ctx context.Context, replica *litestream.Replica) error { 92 | if _, err := os.Stat(replica.DB().Path()); err == nil { 93 | return nil 94 | } else if !os.IsNotExist(err) { 95 | return err 96 | } 97 | 98 | opt := litestream.NewRestoreOptions() 99 | opt.OutputPath = replica.DB().Path() 100 | 101 | var err error 102 | if opt.Generation, _, err = replica.CalcRestoreTarget(ctx, opt); err != nil { 103 | return err 104 | } 105 | 106 | if opt.Generation == "" { 107 | return nil 108 | } 109 | 110 | if err := replica.Restore(ctx, opt); err != nil { 111 | return err 112 | } 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/internal/validation/validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import "errors" 4 | 5 | var ErrUserInput = errors.New("") 6 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | _ "embed" 9 | "errors" 10 | "flag" 11 | "fmt" 12 | "log/slog" 13 | "net/http" 14 | "os" 15 | "os/signal" 16 | "path/filepath" 17 | "runtime" 18 | "syscall" 19 | "time" 20 | 21 | "connectrpc.com/connect" 22 | "github.com/flowchartsman/swaggerui" 23 | "go.uber.org/automaxprocs/maxprocs" 24 | "golang.org/x/net/http2" 25 | "golang.org/x/net/http2/h2c" 26 | 27 | // database driver 28 | _ "github.com/mattn/go-sqlite3" 29 | 30 | "authors/internal/server/litefs" 31 | "authors/internal/server/litestream" 32 | ) 33 | 34 | //go:generate sqlc-connect -m authors -migration-path sql/migrations -litefs -litestream -append 35 | 36 | const ( 37 | serviceName = "authors" 38 | forwardTimeout = 10 * time.Second 39 | ) 40 | 41 | var ( 42 | dbURL string 43 | port int 44 | replicationURL string 45 | 46 | litefsConfig litefs.Config 47 | liteFS *litefs.LiteFS 48 | //go:embed api/apidocs.swagger.json 49 | openAPISpec []byte 50 | ) 51 | 52 | func main() { 53 | var dev bool 54 | flag.StringVar(&dbURL, "db", "", "The Database connection URL") 55 | flag.IntVar(&port, "port", 5000, "The server port") 56 | 57 | flag.BoolVar(&dev, "dev", false, "Set logger to development mode") 58 | 59 | flag.StringVar(&replicationURL, "replication", "", "S3 replication URL") 60 | litefs.SetFlags(&litefsConfig) 61 | flag.Parse() 62 | 63 | dbURL = filepath.Join(litefsConfig.MountDir, dbURL) 64 | 65 | initLogger(dev) 66 | 67 | if err := run(); err != nil && !errors.Is(err, http.ErrServerClosed) { 68 | slog.Error("server error", "error", err) 69 | os.Exit(1) 70 | } 71 | } 72 | 73 | func run() error { 74 | _, err := maxprocs.Set() 75 | if err != nil { 76 | slog.Warn("startup", "error", err) 77 | } 78 | slog.Info("startup", "GOMAXPROCS", runtime.GOMAXPROCS(0)) 79 | 80 | db, err := sql.Open("sqlite3", dbURL) 81 | if err != nil { 82 | return err 83 | } 84 | defer db.Close() 85 | 86 | if replicationURL != "" { 87 | slog.Info("replication", "url", replicationURL) 88 | lsdb, err := litestream.Replicate(context.Background(), dbURL, replicationURL) 89 | if err != nil { 90 | return fmt.Errorf("init replication error: %w", err) 91 | } 92 | defer lsdb.Close() 93 | } 94 | if err := ensureSchema(db); err != nil { 95 | return fmt.Errorf("migration error: %w", err) 96 | } 97 | 98 | mux := http.NewServeMux() 99 | var interceptors []connect.Interceptor 100 | 101 | registerHandlers(mux, db, interceptors) 102 | mux.Handle("/swagger/", http.StripPrefix("/swagger", swaggerui.Handler(openAPISpec))) 103 | 104 | var handler http.Handler = mux 105 | if litefsConfig.MountDir != "" { 106 | err := litefsConfig.Validate() 107 | if err != nil { 108 | return fmt.Errorf("liteFS parameters validation: %w", err) 109 | } 110 | 111 | liteFS, err = litefs.Start(litefsConfig) 112 | if err != nil { 113 | return fmt.Errorf("cannot start LiteFS: %w", err) 114 | } 115 | defer liteFS.Close() 116 | 117 | <-liteFS.ReadyCh() 118 | slog.Info("LiteFS cluster is ready") 119 | 120 | mux.HandleFunc("/nodes/", liteFS.ClusterHandler) 121 | handler = liteFS.ForwardToLeader(forwardTimeout, "POST", "PUT", "PATCH", "DELETE")(handler) 122 | handler = liteFS.ConsistentReader(forwardTimeout, "GET")(handler) 123 | } 124 | server := &http.Server{ 125 | Addr: fmt.Sprintf(":%d", port), 126 | Handler: h2c.NewHandler(handler, &http2.Server{}), 127 | // Please, configure timeouts! 128 | } 129 | 130 | done := make(chan os.Signal, 1) 131 | signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) 132 | go func() { 133 | sig := <-done 134 | slog.Warn("signal detected...", "signal", sig) 135 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 136 | defer cancel() 137 | server.Shutdown(ctx) 138 | }() 139 | slog.Info("Listening...", "port", port) 140 | return server.ListenAndServe() 141 | } 142 | 143 | func initLogger(dev bool) { 144 | var handler slog.Handler 145 | opts := slog.HandlerOptions{ 146 | AddSource: true, 147 | } 148 | switch { 149 | case dev: 150 | handler = slog.NewTextHandler(os.Stderr, &opts) 151 | default: 152 | handler = slog.NewJSONHandler(os.Stderr, &opts) 153 | } 154 | 155 | logger := slog.New(handler) 156 | slog.SetDefault(logger) 157 | } 158 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/migration.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "embed" 8 | 9 | "github.com/pressly/goose/v3" 10 | ) 11 | 12 | //go:embed sql/migrations 13 | var migrations embed.FS 14 | 15 | func ensureSchema(db *sql.DB) error { 16 | goose.SetBaseFS(migrations) 17 | 18 | if err := goose.SetDialect("sqlite"); err != nil { 19 | return err 20 | } 21 | 22 | return goose.Up(db, "sql/migrations") 23 | 24 | } 25 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/proto/authors/v1/authors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package authors.v1; 4 | 5 | import "google/protobuf/wrappers.proto"; 6 | 7 | option go_package = "authors/api/authors/v1"; 8 | 9 | service AuthorsService { 10 | rpc CreateAuthor(CreateAuthorRequest) returns (CreateAuthorResponse) {} 11 | 12 | rpc DeleteAuthor(DeleteAuthorRequest) returns (DeleteAuthorResponse) {} 13 | 14 | rpc GetAuthor(GetAuthorRequest) returns (GetAuthorResponse) {} 15 | 16 | rpc ListAuthors(ListAuthorsRequest) returns (ListAuthorsResponse) {} 17 | } 18 | 19 | message Author { 20 | int64 id = 1; 21 | string name = 2; 22 | google.protobuf.StringValue bio = 3; 23 | } 24 | 25 | message CreateAuthorRequest { 26 | string name = 1; 27 | google.protobuf.StringValue bio = 2; 28 | } 29 | 30 | message CreateAuthorResponse { 31 | ExecResult value = 1; 32 | } 33 | 34 | message DeleteAuthorRequest { 35 | int64 id = 1; 36 | } 37 | 38 | message DeleteAuthorResponse {} 39 | 40 | message GetAuthorRequest { 41 | int64 id = 1; 42 | } 43 | 44 | message GetAuthorResponse { 45 | Author author = 1; 46 | } 47 | 48 | message ListAuthorsRequest {} 49 | 50 | message ListAuthorsResponse { 51 | repeated Author list = 1; 52 | } 53 | 54 | message ExecResult { 55 | int64 rowsAffected = 1; 56 | int64 lastInsertId = 2; 57 | } 58 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/registry.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "net/http" 8 | 9 | "connectrpc.com/connect" 10 | "connectrpc.com/grpcreflect" 11 | 12 | authors_v1connect "authors/api/authors/v1/v1connect" 13 | authors_app "authors/internal/authors" 14 | ) 15 | 16 | func registerHandlers(mux *http.ServeMux, db *sql.DB, interceptors []connect.Interceptor) { 17 | authorsService := authors_app.NewService(authors_app.New(db)) 18 | authorsPath, authorsHandler := authors_v1connect.NewAuthorsServiceHandler(authorsService, 19 | connect.WithInterceptors( 20 | interceptors..., 21 | ), 22 | ) 23 | mux.Handle(authorsPath, authorsHandler) 24 | 25 | reflector := grpcreflect.NewStaticReflector( 26 | authors_v1connect.AuthorsServiceName, 27 | ) 28 | mux.Handle(grpcreflect.NewHandlerV1(reflector)) 29 | mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) 30 | } 31 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/sql/migrations/001_authors.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE IF NOT EXISTS authors ( 3 | id integer PRIMARY KEY AUTOINCREMENT, 4 | name text NOT NULL, 5 | bio text 6 | ); 7 | 8 | -- +goose Down 9 | DROP TABLE IF EXISTS authors; -------------------------------------------------------------------------------- /_examples/authors/sqlite/sql/queries.sql: -------------------------------------------------------------------------------- 1 | /* name: getAuthor :one */ 2 | SELECT * FROM authors 3 | WHERE id = ? LIMIT 1; 4 | 5 | /* name: listAuthors :many */ 6 | SELECT * FROM authors 7 | ORDER BY name; 8 | 9 | /* name: createAuthor :execresult */ 10 | INSERT INTO authors ( 11 | name, bio 12 | ) VALUES ( 13 | ?, ? 14 | ); 15 | 16 | /* name: deleteAuthor :exec */ 17 | DELETE FROM authors 18 | WHERE id = ?; -------------------------------------------------------------------------------- /_examples/authors/sqlite/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "./sql/migrations" 4 | queries: "./sql/queries.sql" 5 | engine: "sqlite" 6 | gen: 7 | go: 8 | out: "internal/authors" 9 | emit_interface: false 10 | emit_exact_table_names: false 11 | emit_empty_slices: false 12 | emit_exported_queries: false 13 | emit_json_tags: false 14 | emit_result_struct_pointers: false 15 | emit_params_struct_pointers: false 16 | emit_methods_with_db_argument: false -------------------------------------------------------------------------------- /_examples/authors/sqlite/tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | _ "connectrpc.com/connect/cmd/protoc-gen-connect-go" 8 | _ "github.com/bufbuild/buf/cmd/buf" 9 | _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" 10 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 11 | ) 12 | -------------------------------------------------------------------------------- /_examples/authors/sqlite/validation/validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import "errors" 4 | 5 | var ErrUserInput = errors.New("") 6 | -------------------------------------------------------------------------------- /_examples/booktest/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21 2 | 3 | RUN go install github.com/cespare/reflex@latest 4 | 5 | WORKDIR /app 6 | COPY go.mod . 7 | COPY go.sum . 8 | 9 | RUN go mod download -x 10 | 11 | COPY configs/reflex.conf / 12 | 13 | ENTRYPOINT ["reflex", "-c", "/reflex.conf"] -------------------------------------------------------------------------------- /_examples/booktest/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | Booktest example taken from [sqlc][sqlc] Git repository [examples][sqlc-git]. 4 | 5 | [sqlc]: https://sqlc.dev 6 | [sqlc-git]: https://github.com/sqlc-dev/sqlc/tree/main/examples/booktest 7 | 8 | ## Running 9 | 10 | ```sh 11 | ./gen.sh 12 | docker compose up 13 | ``` 14 | 15 | ### Exploring with gRPC UI 16 | 17 | ```sh 18 | go install github.com/fullstorydev/grpcui/cmd/grpcui@latest 19 | grpcui -plaintext localhost:8080 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /_examples/booktest/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | plugins: 3 | - local: protoc-gen-go 4 | out: api 5 | opt: paths=source_relative 6 | - local: protoc-gen-connect-go 7 | out: api 8 | opt: paths=source_relative 9 | - local: protoc-gen-openapiv2 10 | out: api 11 | opt: 12 | - generate_unbound_methods=true 13 | - logtostderr=true 14 | - allow_merge=true 15 | strategy: all -------------------------------------------------------------------------------- /_examples/booktest/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v2 3 | deps: 4 | - name: buf.build/googleapis/googleapis 5 | commit: 8bc2c51e08c447cd8886cdea48a73e14 6 | digest: b5:b7e0ac9d192bd0eae88160101269550281448c51f25121cd0d51957661a350aab07001bc145fe9029a8da10b99ff000ae5b284ecaca9c75f2a99604a04d9b4ab 7 | -------------------------------------------------------------------------------- /_examples/booktest/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | modules: 3 | - path: proto 4 | deps: 5 | - buf.build/googleapis/googleapis 6 | lint: 7 | use: 8 | - DEFAULT 9 | except: 10 | - FIELD_NOT_REQUIRED 11 | - PACKAGE_NO_IMPORT_CYCLE 12 | disallow_comment_ignores: true 13 | breaking: 14 | use: 15 | - FILE 16 | except: 17 | - EXTENSION_NO_DELETE 18 | - FIELD_SAME_DEFAULT 19 | -------------------------------------------------------------------------------- /_examples/booktest/configs/grafana/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'xo-grpc' 5 | folder: '' 6 | type: file 7 | disableDeletion: false 8 | editable: true 9 | updateIntervalSeconds: 10 10 | options: 11 | path: /etc/grafana/provisioning/dashboards 12 | -------------------------------------------------------------------------------- /_examples/booktest/configs/grafana/datasources/datasource.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | - access: 'proxy' 3 | editable: true 4 | is_default: true 5 | name: 'prom1' 6 | org_id: 1 7 | type: 'prometheus' 8 | url: 'http://prometheus:9090' 9 | version: 1 10 | -------------------------------------------------------------------------------- /_examples/booktest/configs/prometheus.yml: -------------------------------------------------------------------------------- 1 | scrape_configs: 2 | - job_name: 'api-server' 3 | scrape_interval: 1s 4 | static_configs: 5 | - targets: ['app:8081'] 6 | -------------------------------------------------------------------------------- /_examples/booktest/configs/reflex.conf: -------------------------------------------------------------------------------- 1 | -r '(\.go$|go\.mod)' -s -- sh -c 'go run . -db postgres://postgres:secret@postgres:5432/postgres?sslmode=disable -port 8080 -prometheus-port 8081 -otlp-endpoint jaeger:4317 -dev' 2 | -------------------------------------------------------------------------------- /_examples/booktest/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | build: . 5 | ports: 6 | - "8080:8080" 7 | volumes: 8 | - .:/app 9 | depends_on: 10 | - postgres 11 | 12 | postgres: 13 | image: postgres 14 | volumes: 15 | - ./sql/schema.sql:/docker-entrypoint-initdb.d/1-ddl.sql 16 | environment: 17 | - POSTGRES_USER=postgres 18 | - POSTGRES_PASSWORD=secret 19 | 20 | prometheus: 21 | image: prom/prometheus 22 | command: --config.file=/etc/config/prometheus.yml 23 | volumes: 24 | - ./configs/prometheus.yml:/etc/config/prometheus.yml 25 | 26 | grafana: 27 | image: grafana/grafana 28 | volumes: 29 | - ./configs/grafana/datasources:/etc/grafana/provisioning/datasources/ 30 | - ./configs/grafana/dashboards:/etc/grafana/provisioning/dashboards/ 31 | ports: 32 | - "3000:3000" 33 | 34 | jaeger: 35 | image: jaegertracing/all-in-one 36 | ports: 37 | - "16686:16686" 38 | -------------------------------------------------------------------------------- /_examples/booktest/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -u 3 | set -e 4 | set -x 5 | 6 | go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest 7 | 8 | rm -rf internal proto tools api go.mod go.sum main.go registry.go buf* 9 | 10 | sqlc generate 11 | sqlc-connect -m booktest -tracing -metric 12 | -------------------------------------------------------------------------------- /_examples/booktest/go.mod: -------------------------------------------------------------------------------- 1 | module booktest 2 | 3 | go 1.22.5 4 | 5 | require ( 6 | connectrpc.com/connect v1.16.2 7 | connectrpc.com/grpcreflect v1.2.0 8 | connectrpc.com/otelconnect v0.7.1 9 | github.com/XSAM/otelsql v0.32.0 10 | github.com/bufbuild/buf v1.37.0 11 | github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b 12 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 13 | github.com/jackc/pgx/v5 v5.6.0 14 | github.com/lib/pq v1.10.9 15 | github.com/prometheus/client_golang v1.20.0 16 | go.opentelemetry.io/otel v1.28.0 17 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 18 | go.opentelemetry.io/otel/exporters/prometheus v0.50.0 19 | go.opentelemetry.io/otel/sdk v1.28.0 20 | go.opentelemetry.io/otel/sdk/metric v1.28.0 21 | go.uber.org/automaxprocs v1.5.3 22 | golang.org/x/net v0.28.0 23 | google.golang.org/protobuf v1.34.2 24 | ) 25 | 26 | require ( 27 | buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2 // indirect 28 | buf.build/gen/go/bufbuild/registry/connectrpc/go v1.16.2-20240801134127-09fbc17f7c9e.1 // indirect 29 | buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.34.2-20240801134127-09fbc17f7c9e.2 // indirect 30 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 31 | github.com/Microsoft/go-winio v0.6.2 // indirect 32 | github.com/Microsoft/hcsshim v0.12.5 // indirect 33 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 34 | github.com/beorn7/perks v1.0.1 // indirect 35 | github.com/bufbuild/protocompile v0.14.0 // indirect 36 | github.com/bufbuild/protoplugin v0.0.0-20240323223605-e2735f6c31ee // indirect 37 | github.com/bufbuild/protovalidate-go v0.6.3 // indirect 38 | github.com/bufbuild/protoyaml-go v0.1.10 // indirect 39 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 40 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 41 | github.com/containerd/cgroups/v3 v3.0.3 // indirect 42 | github.com/containerd/containerd v1.7.20 // indirect 43 | github.com/containerd/continuity v0.4.3 // indirect 44 | github.com/containerd/errdefs v0.1.0 // indirect 45 | github.com/containerd/log v0.1.0 // indirect 46 | github.com/containerd/platforms v0.2.1 // indirect 47 | github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect 48 | github.com/containerd/ttrpc v1.2.5 // indirect 49 | github.com/containerd/typeurl/v2 v2.2.0 // indirect 50 | github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect 51 | github.com/distribution/reference v0.6.0 // indirect 52 | github.com/docker/cli v27.1.2+incompatible // indirect 53 | github.com/docker/distribution v2.8.3+incompatible // indirect 54 | github.com/docker/docker v27.1.2+incompatible // indirect 55 | github.com/docker/docker-credential-helpers v0.8.2 // indirect 56 | github.com/docker/go-connections v0.5.0 // indirect 57 | github.com/docker/go-units v0.5.0 // indirect 58 | github.com/felixge/fgprof v0.9.4 // indirect 59 | github.com/felixge/httpsnoop v1.0.4 // indirect 60 | github.com/go-chi/chi/v5 v5.1.0 // indirect 61 | github.com/go-logr/logr v1.4.2 // indirect 62 | github.com/go-logr/stdr v1.2.2 // indirect 63 | github.com/gofrs/uuid/v5 v5.3.0 // indirect 64 | github.com/gogo/protobuf v1.3.2 // indirect 65 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 66 | github.com/google/cel-go v0.21.0 // indirect 67 | github.com/google/go-containerregistry v0.20.2 // indirect 68 | github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect 69 | github.com/google/uuid v1.6.0 // indirect 70 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 71 | github.com/jackc/pgpassfile v1.0.0 // indirect 72 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 73 | github.com/jackc/puddle/v2 v2.2.1 // indirect 74 | github.com/jdx/go-netrc v1.0.0 // indirect 75 | github.com/klauspost/compress v1.17.9 // indirect 76 | github.com/klauspost/pgzip v1.2.6 // indirect 77 | github.com/mitchellh/go-homedir v1.1.0 // indirect 78 | github.com/moby/docker-image-spec v1.3.1 // indirect 79 | github.com/moby/locker v1.0.1 // indirect 80 | github.com/moby/patternmatcher v0.6.0 // indirect 81 | github.com/moby/sys/mount v0.3.4 // indirect 82 | github.com/moby/sys/mountinfo v0.7.2 // indirect 83 | github.com/moby/sys/sequential v0.6.0 // indirect 84 | github.com/moby/sys/user v0.3.0 // indirect 85 | github.com/moby/sys/userns v0.1.0 // indirect 86 | github.com/moby/term v0.5.0 // indirect 87 | github.com/morikuni/aec v1.0.0 // indirect 88 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 89 | github.com/opencontainers/go-digest v1.0.0 // indirect 90 | github.com/opencontainers/image-spec v1.1.0 // indirect 91 | github.com/opencontainers/runtime-spec v1.2.0 // indirect 92 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 93 | github.com/pkg/errors v0.9.1 // indirect 94 | github.com/pkg/profile v1.7.0 // indirect 95 | github.com/prometheus/client_model v0.6.1 // indirect 96 | github.com/prometheus/common v0.55.0 // indirect 97 | github.com/prometheus/procfs v0.15.1 // indirect 98 | github.com/rs/cors v1.11.0 // indirect 99 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 100 | github.com/sirupsen/logrus v1.9.3 // indirect 101 | github.com/spf13/cobra v1.8.1 // indirect 102 | github.com/spf13/pflag v1.0.5 // indirect 103 | github.com/stoewer/go-strcase v1.3.0 // indirect 104 | github.com/vbatts/tar-split v0.11.5 // indirect 105 | go.opencensus.io v0.24.0 // indirect 106 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect 107 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect 108 | go.opentelemetry.io/otel/metric v1.28.0 // indirect 109 | go.opentelemetry.io/otel/trace v1.28.0 // indirect 110 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect 111 | go.uber.org/atomic v1.11.0 // indirect 112 | go.uber.org/multierr v1.11.0 // indirect 113 | go.uber.org/zap v1.27.0 // indirect 114 | golang.org/x/crypto v0.26.0 // indirect 115 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect 116 | golang.org/x/mod v0.20.0 // indirect 117 | golang.org/x/sync v0.8.0 // indirect 118 | golang.org/x/sys v0.24.0 // indirect 119 | golang.org/x/term v0.23.0 // indirect 120 | golang.org/x/text v0.17.0 // indirect 121 | google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect 122 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect 123 | google.golang.org/grpc v1.65.0 // indirect 124 | gopkg.in/yaml.v3 v3.0.1 // indirect 125 | ) 126 | -------------------------------------------------------------------------------- /_examples/booktest/internal/books/adapters.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package books 4 | 5 | import ( 6 | "google.golang.org/protobuf/types/known/timestamppb" 7 | "google.golang.org/protobuf/types/known/wrapperspb" 8 | 9 | pb "booktest/api/books/v1" 10 | ) 11 | 12 | func toAuthor(in Author) *pb.Author { 13 | 14 | out := new(pb.Author) 15 | out.AuthorId = in.AuthorID 16 | out.Name = in.Name 17 | return out 18 | } 19 | 20 | func toBook(in Book) *pb.Book { 21 | 22 | out := new(pb.Book) 23 | out.BookId = in.BookID 24 | out.AuthorId = in.AuthorID 25 | out.Isbn = in.Isbn 26 | out.BookType = string(in.BookType) 27 | out.Title = in.Title 28 | out.Year = in.Year 29 | out.Available = timestamppb.New(in.Available) 30 | out.Tags = in.Tags 31 | return out 32 | } 33 | 34 | func toBooksByTagsRow(in BooksByTagsRow) *pb.BooksByTagsRow { 35 | 36 | out := new(pb.BooksByTagsRow) 37 | out.BookId = in.BookID 38 | out.Title = in.Title 39 | if in.Name.Valid { 40 | out.Name = wrapperspb.String(in.Name.String) 41 | } 42 | out.Isbn = in.Isbn 43 | out.Tags = in.Tags 44 | return out 45 | } 46 | -------------------------------------------------------------------------------- /_examples/booktest/internal/books/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | 5 | package books 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /_examples/booktest/internal/books/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | 5 | package books 6 | 7 | import ( 8 | "database/sql/driver" 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | type BookType string 14 | 15 | const ( 16 | BookTypeFICTION BookType = "FICTION" 17 | BookTypeNONFICTION BookType = "NONFICTION" 18 | ) 19 | 20 | func (e *BookType) Scan(src interface{}) error { 21 | switch s := src.(type) { 22 | case []byte: 23 | *e = BookType(s) 24 | case string: 25 | *e = BookType(s) 26 | default: 27 | return fmt.Errorf("unsupported scan type for BookType: %T", src) 28 | } 29 | return nil 30 | } 31 | 32 | type NullBookType struct { 33 | BookType BookType 34 | Valid bool // Valid is true if BookType is not NULL 35 | } 36 | 37 | // Scan implements the Scanner interface. 38 | func (ns *NullBookType) Scan(value interface{}) error { 39 | if value == nil { 40 | ns.BookType, ns.Valid = "", false 41 | return nil 42 | } 43 | ns.Valid = true 44 | return ns.BookType.Scan(value) 45 | } 46 | 47 | // Value implements the driver Valuer interface. 48 | func (ns NullBookType) Value() (driver.Value, error) { 49 | if !ns.Valid { 50 | return nil, nil 51 | } 52 | return string(ns.BookType), nil 53 | } 54 | 55 | type Author struct { 56 | AuthorID int32 57 | Name string 58 | } 59 | 60 | type Book struct { 61 | BookID int32 62 | AuthorID int32 63 | Isbn string 64 | BookType BookType 65 | Title string 66 | Year int32 67 | Available time.Time 68 | Tags []string 69 | } 70 | -------------------------------------------------------------------------------- /_examples/booktest/internal/books/queries.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | // source: queries.sql 5 | 6 | package books 7 | 8 | import ( 9 | "context" 10 | "database/sql" 11 | "time" 12 | 13 | "github.com/lib/pq" 14 | ) 15 | 16 | const booksByTags = `-- name: BooksByTags :many 17 | SELECT 18 | book_id, 19 | title, 20 | name, 21 | isbn, 22 | tags 23 | FROM books 24 | LEFT JOIN authors ON books.author_id = authors.author_id 25 | WHERE tags && $1::varchar[] 26 | ` 27 | 28 | type BooksByTagsRow struct { 29 | BookID int32 30 | Title string 31 | Name sql.NullString 32 | Isbn string 33 | Tags []string 34 | } 35 | 36 | func (q *Queries) BooksByTags(ctx context.Context, dollar_1 []string) ([]BooksByTagsRow, error) { 37 | rows, err := q.db.QueryContext(ctx, booksByTags, pq.Array(dollar_1)) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer rows.Close() 42 | var items []BooksByTagsRow 43 | for rows.Next() { 44 | var i BooksByTagsRow 45 | if err := rows.Scan( 46 | &i.BookID, 47 | &i.Title, 48 | &i.Name, 49 | &i.Isbn, 50 | pq.Array(&i.Tags), 51 | ); err != nil { 52 | return nil, err 53 | } 54 | items = append(items, i) 55 | } 56 | if err := rows.Close(); err != nil { 57 | return nil, err 58 | } 59 | if err := rows.Err(); err != nil { 60 | return nil, err 61 | } 62 | return items, nil 63 | } 64 | 65 | const booksByTitleYear = `-- name: BooksByTitleYear :many 66 | SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books 67 | WHERE title = $1 AND year = $2 68 | ` 69 | 70 | type BooksByTitleYearParams struct { 71 | Title string 72 | Year int32 73 | } 74 | 75 | func (q *Queries) BooksByTitleYear(ctx context.Context, arg BooksByTitleYearParams) ([]Book, error) { 76 | rows, err := q.db.QueryContext(ctx, booksByTitleYear, arg.Title, arg.Year) 77 | if err != nil { 78 | return nil, err 79 | } 80 | defer rows.Close() 81 | var items []Book 82 | for rows.Next() { 83 | var i Book 84 | if err := rows.Scan( 85 | &i.BookID, 86 | &i.AuthorID, 87 | &i.Isbn, 88 | &i.BookType, 89 | &i.Title, 90 | &i.Year, 91 | &i.Available, 92 | pq.Array(&i.Tags), 93 | ); err != nil { 94 | return nil, err 95 | } 96 | items = append(items, i) 97 | } 98 | if err := rows.Close(); err != nil { 99 | return nil, err 100 | } 101 | if err := rows.Err(); err != nil { 102 | return nil, err 103 | } 104 | return items, nil 105 | } 106 | 107 | const createAuthor = `-- name: CreateAuthor :one 108 | INSERT INTO authors (name) VALUES ($1) 109 | RETURNING author_id, name 110 | ` 111 | 112 | func (q *Queries) CreateAuthor(ctx context.Context, name string) (Author, error) { 113 | row := q.db.QueryRowContext(ctx, createAuthor, name) 114 | var i Author 115 | err := row.Scan(&i.AuthorID, &i.Name) 116 | return i, err 117 | } 118 | 119 | const createBook = `-- name: CreateBook :one 120 | INSERT INTO books ( 121 | author_id, 122 | isbn, 123 | book_type, 124 | title, 125 | year, 126 | available, 127 | tags 128 | ) VALUES ( 129 | $1, 130 | $2, 131 | $3, 132 | $4, 133 | $5, 134 | $6, 135 | $7 136 | ) 137 | RETURNING book_id, author_id, isbn, book_type, title, year, available, tags 138 | ` 139 | 140 | type CreateBookParams struct { 141 | AuthorID int32 142 | Isbn string 143 | BookType BookType 144 | Title string 145 | Year int32 146 | Available time.Time 147 | Tags []string 148 | } 149 | 150 | func (q *Queries) CreateBook(ctx context.Context, arg CreateBookParams) (Book, error) { 151 | row := q.db.QueryRowContext(ctx, createBook, 152 | arg.AuthorID, 153 | arg.Isbn, 154 | arg.BookType, 155 | arg.Title, 156 | arg.Year, 157 | arg.Available, 158 | pq.Array(arg.Tags), 159 | ) 160 | var i Book 161 | err := row.Scan( 162 | &i.BookID, 163 | &i.AuthorID, 164 | &i.Isbn, 165 | &i.BookType, 166 | &i.Title, 167 | &i.Year, 168 | &i.Available, 169 | pq.Array(&i.Tags), 170 | ) 171 | return i, err 172 | } 173 | 174 | const deleteBook = `-- name: DeleteBook :exec 175 | DELETE FROM books 176 | WHERE book_id = $1 177 | ` 178 | 179 | func (q *Queries) DeleteBook(ctx context.Context, bookID int32) error { 180 | _, err := q.db.ExecContext(ctx, deleteBook, bookID) 181 | return err 182 | } 183 | 184 | const getAuthor = `-- name: GetAuthor :one 185 | SELECT author_id, name FROM authors 186 | WHERE author_id = $1 187 | ` 188 | 189 | func (q *Queries) GetAuthor(ctx context.Context, authorID int32) (Author, error) { 190 | row := q.db.QueryRowContext(ctx, getAuthor, authorID) 191 | var i Author 192 | err := row.Scan(&i.AuthorID, &i.Name) 193 | return i, err 194 | } 195 | 196 | const getBook = `-- name: GetBook :one 197 | SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books 198 | WHERE book_id = $1 199 | ` 200 | 201 | func (q *Queries) GetBook(ctx context.Context, bookID int32) (Book, error) { 202 | row := q.db.QueryRowContext(ctx, getBook, bookID) 203 | var i Book 204 | err := row.Scan( 205 | &i.BookID, 206 | &i.AuthorID, 207 | &i.Isbn, 208 | &i.BookType, 209 | &i.Title, 210 | &i.Year, 211 | &i.Available, 212 | pq.Array(&i.Tags), 213 | ) 214 | return i, err 215 | } 216 | 217 | const updateBook = `-- name: UpdateBook :exec 218 | UPDATE books 219 | SET title = $1, tags = $2, book_type = $3 220 | WHERE book_id = $4 221 | ` 222 | 223 | type UpdateBookParams struct { 224 | Title string 225 | Tags []string 226 | BookType BookType 227 | BookID int32 228 | } 229 | 230 | func (q *Queries) UpdateBook(ctx context.Context, arg UpdateBookParams) error { 231 | _, err := q.db.ExecContext(ctx, updateBook, 232 | arg.Title, 233 | pq.Array(arg.Tags), 234 | arg.BookType, 235 | arg.BookID, 236 | ) 237 | return err 238 | } 239 | 240 | const updateBookISBN = `-- name: UpdateBookISBN :exec 241 | UPDATE books 242 | SET title = $1, tags = $2, isbn = $4 243 | WHERE book_id = $3 244 | ` 245 | 246 | type UpdateBookISBNParams struct { 247 | Title string 248 | Tags []string 249 | BookID int32 250 | Isbn string 251 | } 252 | 253 | func (q *Queries) UpdateBookISBN(ctx context.Context, arg UpdateBookISBNParams) error { 254 | _, err := q.db.ExecContext(ctx, updateBookISBN, 255 | arg.Title, 256 | pq.Array(arg.Tags), 257 | arg.BookID, 258 | arg.Isbn, 259 | ) 260 | return err 261 | } 262 | -------------------------------------------------------------------------------- /_examples/booktest/internal/books/service.factory.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package books 4 | 5 | import ( 6 | "booktest/api/books/v1/v1connect" 7 | ) 8 | 9 | // NewService is a constructor of a v1.BooksServiceHandler implementation. 10 | // Use this function to customize the server by adding middlewares to it. 11 | func NewService(querier *Queries) v1connect.BooksServiceHandler { 12 | return &Service{querier: querier} 13 | } 14 | -------------------------------------------------------------------------------- /_examples/booktest/internal/books/service.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package books 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "log/slog" 9 | 10 | "connectrpc.com/connect" 11 | 12 | pb "booktest/api/books/v1" 13 | "booktest/api/books/v1/v1connect" 14 | "booktest/internal/validation" 15 | ) 16 | 17 | type Service struct { 18 | v1connect.UnimplementedBooksServiceHandler 19 | querier *Queries 20 | } 21 | 22 | func (s *Service) BooksByTags(ctx context.Context, req *connect.Request[pb.BooksByTagsRequest]) (*connect.Response[pb.BooksByTagsResponse], error) { 23 | dollar_1 := req.Msg.GetDollar_1() 24 | 25 | result, err := s.querier.BooksByTags(ctx, dollar_1) 26 | if err != nil { 27 | slog.Error("sql call failed", "error", err, "method", "BooksByTags") 28 | return nil, err 29 | } 30 | res := new(pb.BooksByTagsResponse) 31 | for _, r := range result { 32 | res.List = append(res.List, toBooksByTagsRow(r)) 33 | } 34 | return connect.NewResponse(res), nil 35 | } 36 | 37 | func (s *Service) BooksByTitleYear(ctx context.Context, req *connect.Request[pb.BooksByTitleYearRequest]) (*connect.Response[pb.BooksByTitleYearResponse], error) { 38 | var arg BooksByTitleYearParams 39 | arg.Title = req.Msg.GetTitle() 40 | arg.Year = req.Msg.GetYear() 41 | 42 | result, err := s.querier.BooksByTitleYear(ctx, arg) 43 | if err != nil { 44 | slog.Error("sql call failed", "error", err, "method", "BooksByTitleYear") 45 | return nil, err 46 | } 47 | res := new(pb.BooksByTitleYearResponse) 48 | for _, r := range result { 49 | res.List = append(res.List, toBook(r)) 50 | } 51 | return connect.NewResponse(res), nil 52 | } 53 | 54 | func (s *Service) CreateAuthor(ctx context.Context, req *connect.Request[pb.CreateAuthorRequest]) (*connect.Response[pb.CreateAuthorResponse], error) { 55 | name := req.Msg.GetName() 56 | 57 | result, err := s.querier.CreateAuthor(ctx, name) 58 | if err != nil { 59 | slog.Error("sql call failed", "error", err, "method", "CreateAuthor") 60 | return nil, err 61 | } 62 | return connect.NewResponse(&pb.CreateAuthorResponse{Author: toAuthor(result)}), nil 63 | } 64 | 65 | func (s *Service) CreateBook(ctx context.Context, req *connect.Request[pb.CreateBookRequest]) (*connect.Response[pb.CreateBookResponse], error) { 66 | var arg CreateBookParams 67 | arg.AuthorID = req.Msg.GetAuthorId() 68 | arg.Isbn = req.Msg.GetIsbn() 69 | arg.BookType = BookType(req.Msg.GetBookType()) 70 | arg.Title = req.Msg.GetTitle() 71 | arg.Year = req.Msg.GetYear() 72 | if v := req.Msg.GetAvailable(); v != nil { 73 | if err := v.CheckValid(); err != nil { 74 | err = fmt.Errorf("invalid Available: %s%w", err.Error(), validation.ErrUserInput) 75 | return nil, err 76 | } 77 | arg.Available = v.AsTime() 78 | } else { 79 | err := fmt.Errorf("field Available is required%w", validation.ErrUserInput) 80 | return nil, err 81 | } 82 | arg.Tags = req.Msg.GetTags() 83 | 84 | result, err := s.querier.CreateBook(ctx, arg) 85 | if err != nil { 86 | slog.Error("sql call failed", "error", err, "method", "CreateBook") 87 | return nil, err 88 | } 89 | return connect.NewResponse(&pb.CreateBookResponse{Book: toBook(result)}), nil 90 | } 91 | 92 | func (s *Service) DeleteBook(ctx context.Context, req *connect.Request[pb.DeleteBookRequest]) (*connect.Response[pb.DeleteBookResponse], error) { 93 | bookID := req.Msg.GetBookId() 94 | 95 | err := s.querier.DeleteBook(ctx, bookID) 96 | if err != nil { 97 | slog.Error("sql call failed", "error", err, "method", "DeleteBook") 98 | return nil, err 99 | } 100 | return connect.NewResponse(&pb.DeleteBookResponse{}), nil 101 | } 102 | 103 | func (s *Service) GetAuthor(ctx context.Context, req *connect.Request[pb.GetAuthorRequest]) (*connect.Response[pb.GetAuthorResponse], error) { 104 | authorID := req.Msg.GetAuthorId() 105 | 106 | result, err := s.querier.GetAuthor(ctx, authorID) 107 | if err != nil { 108 | slog.Error("sql call failed", "error", err, "method", "GetAuthor") 109 | return nil, err 110 | } 111 | return connect.NewResponse(&pb.GetAuthorResponse{Author: toAuthor(result)}), nil 112 | } 113 | 114 | func (s *Service) GetBook(ctx context.Context, req *connect.Request[pb.GetBookRequest]) (*connect.Response[pb.GetBookResponse], error) { 115 | bookID := req.Msg.GetBookId() 116 | 117 | result, err := s.querier.GetBook(ctx, bookID) 118 | if err != nil { 119 | slog.Error("sql call failed", "error", err, "method", "GetBook") 120 | return nil, err 121 | } 122 | return connect.NewResponse(&pb.GetBookResponse{Book: toBook(result)}), nil 123 | } 124 | 125 | func (s *Service) UpdateBook(ctx context.Context, req *connect.Request[pb.UpdateBookRequest]) (*connect.Response[pb.UpdateBookResponse], error) { 126 | var arg UpdateBookParams 127 | arg.Title = req.Msg.GetTitle() 128 | arg.Tags = req.Msg.GetTags() 129 | arg.BookType = BookType(req.Msg.GetBookType()) 130 | arg.BookID = req.Msg.GetBookId() 131 | 132 | err := s.querier.UpdateBook(ctx, arg) 133 | if err != nil { 134 | slog.Error("sql call failed", "error", err, "method", "UpdateBook") 135 | return nil, err 136 | } 137 | return connect.NewResponse(&pb.UpdateBookResponse{}), nil 138 | } 139 | 140 | func (s *Service) UpdateBookISBN(ctx context.Context, req *connect.Request[pb.UpdateBookISBNRequest]) (*connect.Response[pb.UpdateBookISBNResponse], error) { 141 | var arg UpdateBookISBNParams 142 | arg.Title = req.Msg.GetTitle() 143 | arg.Tags = req.Msg.GetTags() 144 | arg.BookID = req.Msg.GetBookId() 145 | arg.Isbn = req.Msg.GetIsbn() 146 | 147 | err := s.querier.UpdateBookISBN(ctx, arg) 148 | if err != nil { 149 | slog.Error("sql call failed", "error", err, "method", "UpdateBookISBN") 150 | return nil, err 151 | } 152 | return connect.NewResponse(&pb.UpdateBookISBNResponse{}), nil 153 | } 154 | -------------------------------------------------------------------------------- /_examples/booktest/internal/server/instrumentation/metric/metric.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-grpc). 2 | 3 | package metric 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "log" 9 | "log/slog" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/prometheus/client_golang/prometheus/promhttp" 14 | "go.opentelemetry.io/otel" 15 | "go.opentelemetry.io/otel/exporters/prometheus" 16 | "go.opentelemetry.io/otel/sdk/metric" 17 | "go.opentelemetry.io/otel/sdk/resource" 18 | semconv "go.opentelemetry.io/otel/semconv/v1.21.0" 19 | ) 20 | 21 | func Init(port int, serviceName string) error { 22 | metricExporter, err := prometheus.New() 23 | if err != nil { 24 | return err 25 | } 26 | meterProvider := metric.NewMeterProvider( 27 | metric.WithReader(metricExporter), 28 | metric.WithResource(resource.NewWithAttributes( 29 | semconv.SchemaURL, 30 | semconv.ServiceNameKey.String(serviceName), 31 | )), 32 | ) 33 | otel.SetMeterProvider(meterProvider) 34 | 35 | mux := http.NewServeMux() 36 | mux.Handle("/metrics", promhttp.Handler()) 37 | httpServer := &http.Server{ 38 | Addr: fmt.Sprintf(":%d", port), 39 | ReadTimeout: 15 * time.Second, 40 | WriteTimeout: 15 * time.Second, 41 | IdleTimeout: 60 * time.Second, 42 | Handler: mux, 43 | } 44 | slog.Info("Metrics server running", "port", port) 45 | go func() { 46 | if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { 47 | log.Fatal(err.Error()) 48 | } 49 | }() 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /_examples/booktest/internal/server/instrumentation/trace/tracing.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-grpc). 2 | 3 | package trace 4 | 5 | import ( 6 | "context" 7 | "log" 8 | 9 | "go.opentelemetry.io/otel" 10 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" 11 | "go.opentelemetry.io/otel/propagation" 12 | "go.opentelemetry.io/otel/sdk/resource" 13 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 14 | semconv "go.opentelemetry.io/otel/semconv/v1.21.0" 15 | ) 16 | 17 | func Init(ctx context.Context, serviceName string, endpoint string) (func(), error) { 18 | exp, err := otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(endpoint), otlptracegrpc.WithInsecure()) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | tp := tracesdk.NewTracerProvider( 24 | tracesdk.WithBatcher(exp), 25 | tracesdk.WithSampler(tracesdk.AlwaysSample()), 26 | tracesdk.WithResource(resource.NewWithAttributes( 27 | semconv.SchemaURL, 28 | semconv.ServiceNameKey.String(serviceName), 29 | )), 30 | ) 31 | 32 | otel.SetTracerProvider(tp) 33 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 34 | 35 | return func() { 36 | if err := tp.Shutdown(ctx); err != nil { 37 | log.Fatal(err.Error()) 38 | } 39 | }, nil 40 | } 41 | -------------------------------------------------------------------------------- /_examples/booktest/internal/validation/validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import "errors" 4 | 5 | var ErrUserInput = errors.New("") 6 | -------------------------------------------------------------------------------- /_examples/booktest/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | _ "embed" 9 | "errors" 10 | "flag" 11 | "fmt" 12 | "log/slog" 13 | "net/http" 14 | "os" 15 | "os/signal" 16 | "runtime" 17 | "syscall" 18 | "time" 19 | 20 | "connectrpc.com/connect" 21 | "connectrpc.com/otelconnect" 22 | "github.com/XSAM/otelsql" 23 | "github.com/flowchartsman/swaggerui" 24 | semconv "go.opentelemetry.io/otel/semconv/v1.23.0" 25 | "go.uber.org/automaxprocs/maxprocs" 26 | "golang.org/x/net/http2" 27 | "golang.org/x/net/http2/h2c" 28 | 29 | // database driver 30 | _ "github.com/jackc/pgx/v5/stdlib" 31 | 32 | "booktest/internal/server/instrumentation/metric" 33 | "booktest/internal/server/instrumentation/trace" 34 | ) 35 | 36 | //go:generate sqlc-connect -m booktest -tracing -metric -append 37 | 38 | const serviceName = "booktest" 39 | 40 | var ( 41 | dbURL string 42 | port, prometheusPort int 43 | 44 | otlpEndpoint string 45 | 46 | //go:embed api/apidocs.swagger.json 47 | openAPISpec []byte 48 | ) 49 | 50 | func main() { 51 | var dev bool 52 | flag.StringVar(&dbURL, "db", "", "The Database connection URL") 53 | flag.IntVar(&port, "port", 5000, "The server port") 54 | flag.IntVar(&prometheusPort, "prometheus-port", 0, "The metrics server port") 55 | flag.BoolVar(&dev, "dev", false, "Set logger to development mode") 56 | flag.StringVar(&otlpEndpoint, "otlp-endpoint", "", "The Open Telemetry Protocol Endpoint (example: localhost:4317)") 57 | 58 | flag.Parse() 59 | 60 | initLogger(dev) 61 | 62 | if err := run(); err != nil && !errors.Is(err, http.ErrServerClosed) { 63 | slog.Error("server error", "error", err) 64 | os.Exit(1) 65 | } 66 | } 67 | 68 | func run() error { 69 | _, err := maxprocs.Set() 70 | if err != nil { 71 | slog.Warn("startup", "error", err) 72 | } 73 | slog.Info("startup", "GOMAXPROCS", runtime.GOMAXPROCS(0)) 74 | 75 | var db *sql.DB 76 | if otlpEndpoint != "" { 77 | 78 | db, err = otelsql.Open("pgx", dbURL, otelsql.WithAttributes( 79 | semconv.DBSystemPostgreSQL, 80 | )) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | err = otelsql.RegisterDBStatsMetrics(db, otelsql.WithAttributes( 86 | semconv.DBSystemPostgreSQL, 87 | )) 88 | if err != nil { 89 | return err 90 | } 91 | } else { 92 | 93 | db, err = sql.Open("pgx", dbURL) 94 | if err != nil { 95 | return err 96 | } 97 | } 98 | defer db.Close() 99 | 100 | mux := http.NewServeMux() 101 | var interceptors []connect.Interceptor 102 | 103 | if prometheusPort > 0 || otlpEndpoint != "" { 104 | observability, err := otelconnect.NewInterceptor() 105 | if err != nil { 106 | return err 107 | } 108 | interceptors = append(interceptors, observability) 109 | } 110 | registerHandlers(mux, db, interceptors) 111 | mux.Handle("/swagger/", http.StripPrefix("/swagger", swaggerui.Handler(openAPISpec))) 112 | 113 | server := &http.Server{ 114 | Addr: fmt.Sprintf(":%d", port), 115 | Handler: h2c.NewHandler(mux, &http2.Server{}), 116 | // Please, configure timeouts! 117 | } 118 | 119 | if prometheusPort > 0 { 120 | err := metric.Init(prometheusPort, serviceName) 121 | if err != nil { 122 | return err 123 | } 124 | } 125 | 126 | if otlpEndpoint != "" { 127 | shutdown, err := trace.Init(context.Background(), serviceName, otlpEndpoint) 128 | if err != nil { 129 | return err 130 | } 131 | defer shutdown() 132 | } 133 | 134 | done := make(chan os.Signal, 1) 135 | signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) 136 | go func() { 137 | sig := <-done 138 | slog.Warn("signal detected...", "signal", sig) 139 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 140 | defer cancel() 141 | server.Shutdown(ctx) 142 | }() 143 | slog.Info("Listening...", "port", port) 144 | return server.ListenAndServe() 145 | } 146 | 147 | func initLogger(dev bool) { 148 | var handler slog.Handler 149 | opts := slog.HandlerOptions{ 150 | AddSource: true, 151 | } 152 | switch { 153 | case dev: 154 | handler = slog.NewTextHandler(os.Stderr, &opts) 155 | default: 156 | handler = slog.NewJSONHandler(os.Stderr, &opts) 157 | } 158 | 159 | logger := slog.New(handler) 160 | slog.SetDefault(logger) 161 | } 162 | -------------------------------------------------------------------------------- /_examples/booktest/proto/books/v1/books.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package books.v1; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | import "google/protobuf/wrappers.proto"; 7 | 8 | option go_package = "booktest/api/books/v1"; 9 | 10 | service BooksService { 11 | rpc BooksByTags(BooksByTagsRequest) returns (BooksByTagsResponse) {} 12 | 13 | rpc BooksByTitleYear(BooksByTitleYearRequest) returns (BooksByTitleYearResponse) {} 14 | 15 | rpc CreateAuthor(CreateAuthorRequest) returns (CreateAuthorResponse) {} 16 | 17 | rpc CreateBook(CreateBookRequest) returns (CreateBookResponse) {} 18 | 19 | rpc DeleteBook(DeleteBookRequest) returns (DeleteBookResponse) {} 20 | 21 | rpc GetAuthor(GetAuthorRequest) returns (GetAuthorResponse) {} 22 | 23 | rpc GetBook(GetBookRequest) returns (GetBookResponse) {} 24 | 25 | rpc UpdateBook(UpdateBookRequest) returns (UpdateBookResponse) {} 26 | 27 | rpc UpdateBookISBN(UpdateBookISBNRequest) returns (UpdateBookISBNResponse) {} 28 | } 29 | 30 | message Author { 31 | int32 author_id = 1; 32 | string name = 2; 33 | } 34 | 35 | message Book { 36 | int32 book_id = 1; 37 | int32 author_id = 2; 38 | string isbn = 3; 39 | string book_type = 4; 40 | string title = 5; 41 | int32 year = 6; 42 | google.protobuf.Timestamp available = 7; 43 | repeated string tags = 8; 44 | } 45 | 46 | message BookType {} 47 | 48 | message BooksByTagsRequest { 49 | repeated string dollar_1 = 1; 50 | } 51 | 52 | message BooksByTagsResponse { 53 | repeated BooksByTagsRow list = 1; 54 | } 55 | 56 | message BooksByTagsRow { 57 | int32 book_id = 1; 58 | string title = 2; 59 | google.protobuf.StringValue name = 3; 60 | string isbn = 4; 61 | repeated string tags = 5; 62 | } 63 | 64 | message BooksByTitleYearRequest { 65 | string title = 1; 66 | int32 year = 2; 67 | } 68 | 69 | message BooksByTitleYearResponse { 70 | repeated Book list = 1; 71 | } 72 | 73 | message CreateAuthorRequest { 74 | string name = 1; 75 | } 76 | 77 | message CreateAuthorResponse { 78 | Author author = 1; 79 | } 80 | 81 | message CreateBookRequest { 82 | int32 author_id = 1; 83 | string isbn = 2; 84 | string book_type = 3; 85 | string title = 4; 86 | int32 year = 5; 87 | google.protobuf.Timestamp available = 6; 88 | repeated string tags = 7; 89 | } 90 | 91 | message CreateBookResponse { 92 | Book book = 1; 93 | } 94 | 95 | message DeleteBookRequest { 96 | int32 book_id = 1; 97 | } 98 | 99 | message DeleteBookResponse {} 100 | 101 | message GetAuthorRequest { 102 | int32 author_id = 1; 103 | } 104 | 105 | message GetAuthorResponse { 106 | Author author = 1; 107 | } 108 | 109 | message GetBookRequest { 110 | int32 book_id = 1; 111 | } 112 | 113 | message GetBookResponse { 114 | Book book = 1; 115 | } 116 | 117 | message NullBookType { 118 | string book_type = 1; 119 | bool valid = 2; 120 | } 121 | 122 | message UpdateBookISBNRequest { 123 | string title = 1; 124 | repeated string tags = 2; 125 | int32 book_id = 3; 126 | string isbn = 4; 127 | } 128 | 129 | message UpdateBookISBNResponse {} 130 | 131 | message UpdateBookRequest { 132 | string title = 1; 133 | repeated string tags = 2; 134 | string book_type = 3; 135 | int32 book_id = 4; 136 | } 137 | 138 | message UpdateBookResponse {} 139 | -------------------------------------------------------------------------------- /_examples/booktest/registry.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "net/http" 8 | 9 | "connectrpc.com/connect" 10 | "connectrpc.com/grpcreflect" 11 | 12 | books_v1connect "booktest/api/books/v1/v1connect" 13 | books_app "booktest/internal/books" 14 | ) 15 | 16 | func registerHandlers(mux *http.ServeMux, db *sql.DB, interceptors []connect.Interceptor) { 17 | booksService := books_app.NewService(books_app.New(db)) 18 | booksPath, booksHandler := books_v1connect.NewBooksServiceHandler(booksService, 19 | connect.WithInterceptors( 20 | interceptors..., 21 | ), 22 | ) 23 | mux.Handle(booksPath, booksHandler) 24 | 25 | reflector := grpcreflect.NewStaticReflector( 26 | books_v1connect.BooksServiceName, 27 | ) 28 | mux.Handle(grpcreflect.NewHandlerV1(reflector)) 29 | mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) 30 | } 31 | -------------------------------------------------------------------------------- /_examples/booktest/sql/queries.sql: -------------------------------------------------------------------------------- 1 | -- name: GetAuthor :one 2 | SELECT * FROM authors 3 | WHERE author_id = $1; 4 | 5 | -- name: GetBook :one 6 | SELECT * FROM books 7 | WHERE book_id = $1; 8 | 9 | -- name: DeleteBook :exec 10 | DELETE FROM books 11 | WHERE book_id = $1; 12 | 13 | -- name: BooksByTitleYear :many 14 | SELECT * FROM books 15 | WHERE title = $1 AND year = $2; 16 | 17 | -- name: BooksByTags :many 18 | SELECT 19 | book_id, 20 | title, 21 | name, 22 | isbn, 23 | tags 24 | FROM books 25 | LEFT JOIN authors ON books.author_id = authors.author_id 26 | WHERE tags && $1::varchar[]; 27 | 28 | -- name: CreateAuthor :one 29 | INSERT INTO authors (name) VALUES ($1) 30 | RETURNING *; 31 | 32 | -- name: CreateBook :one 33 | INSERT INTO books ( 34 | author_id, 35 | isbn, 36 | book_type, 37 | title, 38 | year, 39 | available, 40 | tags 41 | ) VALUES ( 42 | $1, 43 | $2, 44 | $3, 45 | $4, 46 | $5, 47 | $6, 48 | $7 49 | ) 50 | RETURNING *; 51 | 52 | -- name: UpdateBook :exec 53 | UPDATE books 54 | SET title = $1, tags = $2, book_type = $3 55 | WHERE book_id = $4; 56 | 57 | -- name: UpdateBookISBN :exec 58 | UPDATE books 59 | SET title = $1, tags = $2, isbn = $4 60 | WHERE book_id = $3; -------------------------------------------------------------------------------- /_examples/booktest/sql/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE authors ( 2 | author_id SERIAL PRIMARY KEY, 3 | name text NOT NULL DEFAULT '' 4 | ); 5 | 6 | CREATE INDEX authors_name_idx ON authors(name); 7 | 8 | CREATE TYPE book_type AS ENUM ( 9 | 'FICTION', 10 | 'NONFICTION' 11 | ); 12 | 13 | CREATE TABLE books ( 14 | book_id SERIAL PRIMARY KEY, 15 | author_id integer NOT NULL REFERENCES authors(author_id), 16 | isbn text NOT NULL DEFAULT '' UNIQUE, 17 | book_type book_type NOT NULL DEFAULT 'FICTION', 18 | title text NOT NULL DEFAULT '', 19 | year integer NOT NULL DEFAULT 2000, 20 | available timestamp with time zone NOT NULL DEFAULT 'NOW()', 21 | tags varchar[] NOT NULL DEFAULT '{}' 22 | ); 23 | 24 | CREATE INDEX books_title_idx ON books(title, year); 25 | 26 | CREATE FUNCTION say_hello(text) RETURNS text AS $$ 27 | BEGIN 28 | RETURN CONCAT('hello ', $1); 29 | END; 30 | $$ LANGUAGE plpgsql; 31 | 32 | CREATE INDEX books_title_lower_idx ON books(title); -------------------------------------------------------------------------------- /_examples/booktest/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "./sql/schema.sql" 4 | queries: "./sql/queries.sql" 5 | engine: "postgresql" 6 | gen: 7 | go: 8 | package: "books" 9 | out: "internal/books" 10 | emit_interface: false 11 | emit_exact_table_names: false 12 | emit_empty_slices: false 13 | emit_exported_queries: false 14 | emit_json_tags: false 15 | emit_result_struct_pointers: false 16 | emit_params_struct_pointers: false 17 | emit_methods_with_db_argument: false -------------------------------------------------------------------------------- /_examples/booktest/tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | _ "connectrpc.com/connect/cmd/protoc-gen-connect-go" 8 | _ "github.com/bufbuild/buf/cmd/buf" 9 | _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" 10 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 11 | ) 12 | -------------------------------------------------------------------------------- /_examples/uuidcheck/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | plugins: 3 | - local: protoc-gen-go 4 | out: api 5 | opt: paths=source_relative 6 | - local: protoc-gen-connect-go 7 | out: api 8 | opt: paths=source_relative 9 | - local: protoc-gen-openapiv2 10 | out: api 11 | opt: 12 | - generate_unbound_methods=true 13 | - logtostderr=true 14 | - allow_merge=true 15 | strategy: all -------------------------------------------------------------------------------- /_examples/uuidcheck/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v2 3 | deps: 4 | - name: buf.build/googleapis/googleapis 5 | commit: e7f8d366f5264595bcc4cd4139af9973 6 | digest: b5:0cd69a689ee320ed815663d57d1bc3a1d6823224a7a717d46fee3a68197c25a6f5f932c0b0e49f8370c70c247a6635969a6a54af5345cafd51e0667298768aca 7 | -------------------------------------------------------------------------------- /_examples/uuidcheck/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | modules: 3 | - path: proto 4 | deps: 5 | - buf.build/googleapis/googleapis 6 | lint: 7 | use: 8 | - DEFAULT 9 | except: 10 | - FIELD_NOT_REQUIRED 11 | - PACKAGE_NO_IMPORT_CYCLE 12 | disallow_comment_ignores: true 13 | breaking: 14 | use: 15 | - FILE 16 | except: 17 | - EXTENSION_NO_DELETE 18 | - FIELD_SAME_DEFAULT 19 | -------------------------------------------------------------------------------- /_examples/uuidcheck/go.mod: -------------------------------------------------------------------------------- 1 | module uuidcheck 2 | 3 | go 1.22.5 4 | 5 | require ( 6 | connectrpc.com/connect v1.16.2 7 | connectrpc.com/grpcreflect v1.2.0 8 | github.com/bufbuild/buf v1.39.0 9 | github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b 10 | github.com/google/uuid v1.6.0 11 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 12 | github.com/jackc/pgx/v5 v5.6.0 13 | go.uber.org/automaxprocs v1.5.3 14 | golang.org/x/net v0.28.0 15 | google.golang.org/protobuf v1.34.2 16 | ) 17 | 18 | require ( 19 | buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2 // indirect 20 | buf.build/gen/go/bufbuild/registry/connectrpc/go v1.16.2-20240821192916-45ba72cdd479.1 // indirect 21 | buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.34.2-20240821192916-45ba72cdd479.2 // indirect 22 | connectrpc.com/otelconnect v0.7.1 // indirect 23 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 24 | github.com/Microsoft/go-winio v0.6.2 // indirect 25 | github.com/Microsoft/hcsshim v0.12.6 // indirect 26 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 27 | github.com/bufbuild/protocompile v0.14.0 // indirect 28 | github.com/bufbuild/protoplugin v0.0.0-20240323223605-e2735f6c31ee // indirect 29 | github.com/bufbuild/protovalidate-go v0.6.4 // indirect 30 | github.com/bufbuild/protoyaml-go v0.1.11 // indirect 31 | github.com/containerd/cgroups/v3 v3.0.3 // indirect 32 | github.com/containerd/containerd v1.7.20 // indirect 33 | github.com/containerd/continuity v0.4.3 // indirect 34 | github.com/containerd/errdefs v0.1.0 // indirect 35 | github.com/containerd/log v0.1.0 // indirect 36 | github.com/containerd/platforms v0.2.1 // indirect 37 | github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect 38 | github.com/containerd/ttrpc v1.2.5 // indirect 39 | github.com/containerd/typeurl/v2 v2.2.0 // indirect 40 | github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect 41 | github.com/distribution/reference v0.6.0 // indirect 42 | github.com/docker/cli v27.1.2+incompatible // indirect 43 | github.com/docker/distribution v2.8.3+incompatible // indirect 44 | github.com/docker/docker v27.3.1+incompatible // indirect 45 | github.com/docker/docker-credential-helpers v0.8.2 // indirect 46 | github.com/docker/go-connections v0.5.0 // indirect 47 | github.com/docker/go-units v0.5.0 // indirect 48 | github.com/felixge/fgprof v0.9.4 // indirect 49 | github.com/felixge/httpsnoop v1.0.4 // indirect 50 | github.com/go-chi/chi/v5 v5.1.0 // indirect 51 | github.com/go-logr/logr v1.4.2 // indirect 52 | github.com/go-logr/stdr v1.2.2 // indirect 53 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 54 | github.com/gofrs/flock v0.12.1 // indirect 55 | github.com/gofrs/uuid/v5 v5.3.0 // indirect 56 | github.com/gogo/protobuf v1.3.2 // indirect 57 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 58 | github.com/google/cel-go v0.21.0 // indirect 59 | github.com/google/go-containerregistry v0.20.2 // indirect 60 | github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect 61 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 62 | github.com/jackc/pgpassfile v1.0.0 // indirect 63 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 64 | github.com/jackc/puddle/v2 v2.2.1 // indirect 65 | github.com/jdx/go-netrc v1.0.0 // indirect 66 | github.com/klauspost/compress v1.17.9 // indirect 67 | github.com/klauspost/pgzip v1.2.6 // indirect 68 | github.com/mitchellh/go-homedir v1.1.0 // indirect 69 | github.com/moby/docker-image-spec v1.3.1 // indirect 70 | github.com/moby/locker v1.0.1 // indirect 71 | github.com/moby/patternmatcher v0.6.0 // indirect 72 | github.com/moby/sys/mount v0.3.4 // indirect 73 | github.com/moby/sys/mountinfo v0.7.2 // indirect 74 | github.com/moby/sys/sequential v0.6.0 // indirect 75 | github.com/moby/sys/user v0.3.0 // indirect 76 | github.com/moby/sys/userns v0.1.0 // indirect 77 | github.com/moby/term v0.5.0 // indirect 78 | github.com/morikuni/aec v1.0.0 // indirect 79 | github.com/onsi/ginkgo/v2 v2.20.1 // indirect 80 | github.com/opencontainers/go-digest v1.0.0 // indirect 81 | github.com/opencontainers/image-spec v1.1.0 // indirect 82 | github.com/opencontainers/runtime-spec v1.2.0 // indirect 83 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 84 | github.com/pkg/errors v0.9.1 // indirect 85 | github.com/pkg/profile v1.7.0 // indirect 86 | github.com/quic-go/qpack v0.4.0 // indirect 87 | github.com/quic-go/quic-go v0.46.0 // indirect 88 | github.com/rs/cors v1.11.0 // indirect 89 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 90 | github.com/sirupsen/logrus v1.9.3 // indirect 91 | github.com/spf13/cobra v1.8.1 // indirect 92 | github.com/spf13/pflag v1.0.5 // indirect 93 | github.com/stoewer/go-strcase v1.3.0 // indirect 94 | github.com/vbatts/tar-split v0.11.5 // indirect 95 | go.opencensus.io v0.24.0 // indirect 96 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect 97 | go.opentelemetry.io/otel v1.29.0 // indirect 98 | go.opentelemetry.io/otel/metric v1.29.0 // indirect 99 | go.opentelemetry.io/otel/sdk v1.29.0 // indirect 100 | go.opentelemetry.io/otel/trace v1.29.0 // indirect 101 | go.uber.org/atomic v1.11.0 // indirect 102 | go.uber.org/mock v0.4.0 // indirect 103 | go.uber.org/multierr v1.11.0 // indirect 104 | go.uber.org/zap v1.27.0 // indirect 105 | golang.org/x/crypto v0.26.0 // indirect 106 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect 107 | golang.org/x/mod v0.20.0 // indirect 108 | golang.org/x/sync v0.8.0 // indirect 109 | golang.org/x/sys v0.24.0 // indirect 110 | golang.org/x/term v0.23.0 // indirect 111 | golang.org/x/text v0.17.0 // indirect 112 | golang.org/x/tools v0.24.0 // indirect 113 | google.golang.org/genproto/googleapis/api v0.0.0-20240823204242-4ba0660f739c // indirect 114 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c // indirect 115 | google.golang.org/grpc v1.65.0 // indirect 116 | gopkg.in/yaml.v3 v3.0.1 // indirect 117 | ) 118 | -------------------------------------------------------------------------------- /_examples/uuidcheck/internal/validation/validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import "errors" 4 | 5 | var ErrUserInput = errors.New("") 6 | -------------------------------------------------------------------------------- /_examples/uuidcheck/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | _ "embed" 8 | "errors" 9 | "flag" 10 | "fmt" 11 | "log/slog" 12 | "net/http" 13 | "os" 14 | "os/signal" 15 | "runtime" 16 | "syscall" 17 | "time" 18 | 19 | "connectrpc.com/connect" 20 | "github.com/flowchartsman/swaggerui" 21 | "go.uber.org/automaxprocs/maxprocs" 22 | "golang.org/x/net/http2" 23 | "golang.org/x/net/http2/h2c" 24 | 25 | // database driver 26 | "github.com/jackc/pgx/v5/pgxpool" 27 | ) 28 | 29 | //go:generate sqlc-connect -append 30 | 31 | const serviceName = "uuidcheck" 32 | 33 | var ( 34 | dbURL string 35 | port int 36 | 37 | //go:embed api/apidocs.swagger.json 38 | openAPISpec []byte 39 | ) 40 | 41 | func main() { 42 | var dev bool 43 | flag.StringVar(&dbURL, "db", "", "The Database connection URL") 44 | flag.IntVar(&port, "port", 5000, "The server port") 45 | 46 | flag.BoolVar(&dev, "dev", false, "Set logger to development mode") 47 | 48 | flag.Parse() 49 | 50 | initLogger(dev) 51 | 52 | if err := run(); err != nil && !errors.Is(err, http.ErrServerClosed) { 53 | slog.Error("server error", "error", err) 54 | os.Exit(1) 55 | } 56 | } 57 | 58 | func run() error { 59 | _, err := maxprocs.Set() 60 | if err != nil { 61 | slog.Warn("startup", "error", err) 62 | } 63 | slog.Info("startup", "GOMAXPROCS", runtime.GOMAXPROCS(0)) 64 | 65 | db, err := pgxpool.New(context.Background(), dbURL) 66 | if err != nil { 67 | return err 68 | } 69 | defer db.Close() 70 | 71 | mux := http.NewServeMux() 72 | var interceptors []connect.Interceptor 73 | 74 | registerHandlers(mux, db, interceptors) 75 | mux.Handle("/swagger/", http.StripPrefix("/swagger", swaggerui.Handler(openAPISpec))) 76 | 77 | server := &http.Server{ 78 | Addr: fmt.Sprintf(":%d", port), 79 | Handler: h2c.NewHandler(mux, &http2.Server{}), 80 | // Please, configure timeouts! 81 | } 82 | 83 | done := make(chan os.Signal, 1) 84 | signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) 85 | go func() { 86 | sig := <-done 87 | slog.Warn("signal detected...", "signal", sig) 88 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 89 | defer cancel() 90 | server.Shutdown(ctx) 91 | }() 92 | slog.Info("Listening...", "port", port) 93 | return server.ListenAndServe() 94 | } 95 | 96 | func initLogger(dev bool) { 97 | var handler slog.Handler 98 | opts := slog.HandlerOptions{ 99 | AddSource: true, 100 | } 101 | switch { 102 | case dev: 103 | handler = slog.NewTextHandler(os.Stderr, &opts) 104 | default: 105 | handler = slog.NewJSONHandler(os.Stderr, &opts) 106 | } 107 | 108 | logger := slog.New(handler) 109 | slog.SetDefault(logger) 110 | } 111 | -------------------------------------------------------------------------------- /_examples/uuidcheck/proto/googleuuid/v1/googleuuid.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package googleuuid.v1; 4 | 5 | import "google/protobuf/wrappers.proto"; 6 | 7 | option go_package = "uuidcheck/api/googleuuid/v1"; 8 | 9 | service GoogleuuidService { 10 | rpc CreateLocationTransactions(CreateLocationTransactionsRequest) returns (CreateLocationTransactionsResponse) {} 11 | 12 | rpc CreateProduct(CreateProductRequest) returns (CreateProductResponse) {} 13 | 14 | rpc CreateProductReturnAll(CreateProductReturnAllRequest) returns (CreateProductReturnAllResponse) {} 15 | 16 | rpc CreateProductReturnPartial(CreateProductReturnPartialRequest) returns (CreateProductReturnPartialResponse) {} 17 | 18 | rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {} 19 | 20 | rpc CreateUserReturnAll(CreateUserReturnAllRequest) returns (CreateUserReturnAllResponse) {} 21 | 22 | rpc CreateUserReturnPartial(CreateUserReturnPartialRequest) returns (CreateUserReturnPartialResponse) {} 23 | 24 | rpc GetProductsByIds(GetProductsByIdsRequest) returns (GetProductsByIdsResponse) {} 25 | } 26 | 27 | message Category { 28 | int32 id = 1; 29 | } 30 | 31 | message CreateLocationTransactionsRequest { 32 | repeated string column1 = 1; 33 | repeated string column2 = 2; 34 | } 35 | 36 | message CreateLocationTransactionsResponse {} 37 | 38 | message CreateProductRequest { 39 | int32 id = 1; 40 | google.protobuf.Int32Value category = 2; 41 | } 42 | 43 | message CreateProductResponse { 44 | int32 value = 1; 45 | } 46 | 47 | message CreateProductReturnAllRequest { 48 | int32 id = 1; 49 | google.protobuf.Int32Value category = 2; 50 | } 51 | 52 | message CreateProductReturnAllResponse { 53 | Product product = 1; 54 | } 55 | 56 | message CreateProductReturnPartialRequest { 57 | int32 id = 1; 58 | google.protobuf.Int32Value category = 2; 59 | } 60 | 61 | message CreateProductReturnPartialResponse { 62 | CreateProductReturnPartialRow create_product_return_partial_row = 1; 63 | } 64 | 65 | message CreateProductReturnPartialRow { 66 | int32 id = 1; 67 | google.protobuf.StringValue name = 2; 68 | } 69 | 70 | message CreateUserRequest { 71 | string id = 1; 72 | google.protobuf.StringValue location = 2; 73 | } 74 | 75 | message CreateUserResponse { 76 | string value = 1; 77 | } 78 | 79 | message CreateUserReturnAllRequest { 80 | string id = 1; 81 | google.protobuf.StringValue location = 2; 82 | } 83 | 84 | message CreateUserReturnAllResponse { 85 | User user = 1; 86 | } 87 | 88 | message CreateUserReturnPartialRequest { 89 | string id = 1; 90 | google.protobuf.StringValue location = 2; 91 | } 92 | 93 | message CreateUserReturnPartialResponse { 94 | CreateUserReturnPartialRow create_user_return_partial_row = 1; 95 | } 96 | 97 | message CreateUserReturnPartialRow { 98 | string id = 1; 99 | google.protobuf.StringValue name = 2; 100 | } 101 | 102 | message GetProductsByIdsRequest { 103 | repeated string dollar_1 = 1; 104 | } 105 | 106 | message GetProductsByIdsResponse { 107 | repeated Product list = 1; 108 | } 109 | 110 | message Location { 111 | string id = 1; 112 | } 113 | 114 | message LocationTransaction { 115 | string location_id = 1; 116 | string transaction_id = 2; 117 | } 118 | 119 | message Product { 120 | int32 id = 1; 121 | google.protobuf.Int32Value category = 2; 122 | google.protobuf.StringValue name = 3; 123 | } 124 | 125 | message User { 126 | string id = 1; 127 | google.protobuf.StringValue location = 2; 128 | google.protobuf.StringValue name = 3; 129 | } 130 | -------------------------------------------------------------------------------- /_examples/uuidcheck/proto/pguuid/v1/pguuid.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pguuid.v1; 4 | 5 | import "google/protobuf/wrappers.proto"; 6 | 7 | option go_package = "uuidcheck/api/pguuid/v1"; 8 | 9 | service PguuidService { 10 | rpc CreateLocationTransactions(CreateLocationTransactionsRequest) returns (CreateLocationTransactionsResponse) {} 11 | 12 | rpc CreateProduct(CreateProductRequest) returns (CreateProductResponse) {} 13 | 14 | rpc CreateProductReturnAll(CreateProductReturnAllRequest) returns (CreateProductReturnAllResponse) {} 15 | 16 | rpc CreateProductReturnPartial(CreateProductReturnPartialRequest) returns (CreateProductReturnPartialResponse) {} 17 | 18 | rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {} 19 | 20 | rpc CreateUserReturnAll(CreateUserReturnAllRequest) returns (CreateUserReturnAllResponse) {} 21 | 22 | rpc CreateUserReturnPartial(CreateUserReturnPartialRequest) returns (CreateUserReturnPartialResponse) {} 23 | 24 | rpc GetProductsByIds(GetProductsByIdsRequest) returns (GetProductsByIdsResponse) {} 25 | } 26 | 27 | message Category { 28 | int32 id = 1; 29 | } 30 | 31 | message CreateLocationTransactionsRequest { 32 | repeated google.protobuf.StringValue column1 = 1; 33 | repeated google.protobuf.StringValue column2 = 2; 34 | } 35 | 36 | message CreateLocationTransactionsResponse {} 37 | 38 | message CreateProductRequest { 39 | int32 id = 1; 40 | google.protobuf.Int32Value category = 2; 41 | } 42 | 43 | message CreateProductResponse { 44 | int32 value = 1; 45 | } 46 | 47 | message CreateProductReturnAllRequest { 48 | int32 id = 1; 49 | google.protobuf.Int32Value category = 2; 50 | } 51 | 52 | message CreateProductReturnAllResponse { 53 | Product product = 1; 54 | } 55 | 56 | message CreateProductReturnPartialRequest { 57 | int32 id = 1; 58 | google.protobuf.Int32Value category = 2; 59 | } 60 | 61 | message CreateProductReturnPartialResponse { 62 | CreateProductReturnPartialRow create_product_return_partial_row = 1; 63 | } 64 | 65 | message CreateProductReturnPartialRow { 66 | int32 id = 1; 67 | google.protobuf.StringValue name = 2; 68 | } 69 | 70 | message CreateUserRequest { 71 | google.protobuf.StringValue id = 1; 72 | google.protobuf.StringValue location = 2; 73 | } 74 | 75 | message CreateUserResponse { 76 | google.protobuf.StringValue value = 1; 77 | } 78 | 79 | message CreateUserReturnAllRequest { 80 | google.protobuf.StringValue id = 1; 81 | google.protobuf.StringValue location = 2; 82 | } 83 | 84 | message CreateUserReturnAllResponse { 85 | User user = 1; 86 | } 87 | 88 | message CreateUserReturnPartialRequest { 89 | google.protobuf.StringValue id = 1; 90 | google.protobuf.StringValue location = 2; 91 | } 92 | 93 | message CreateUserReturnPartialResponse { 94 | CreateUserReturnPartialRow create_user_return_partial_row = 1; 95 | } 96 | 97 | message CreateUserReturnPartialRow { 98 | google.protobuf.StringValue id = 1; 99 | google.protobuf.StringValue name = 2; 100 | } 101 | 102 | message GetProductsByIdsRequest { 103 | repeated google.protobuf.StringValue dollar_1 = 1; 104 | } 105 | 106 | message GetProductsByIdsResponse { 107 | repeated Product list = 1; 108 | } 109 | 110 | message Location { 111 | google.protobuf.StringValue id = 1; 112 | } 113 | 114 | message LocationTransaction { 115 | google.protobuf.StringValue location_id = 1; 116 | google.protobuf.StringValue transaction_id = 2; 117 | } 118 | 119 | message Product { 120 | int32 id = 1; 121 | google.protobuf.Int32Value category = 2; 122 | google.protobuf.StringValue name = 3; 123 | } 124 | 125 | message User { 126 | google.protobuf.StringValue id = 1; 127 | google.protobuf.StringValue location = 2; 128 | google.protobuf.StringValue name = 3; 129 | } 130 | -------------------------------------------------------------------------------- /_examples/uuidcheck/query/uuid/user.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateUser :one 2 | INSERT INTO users (id, location) VALUES ($1, $2) RETURNING id; 3 | 4 | -- name: CreateUserReturnPartial :one 5 | INSERT INTO users (id, location) VALUES ($1, $2) RETURNING id, name; 6 | 7 | -- name: CreateUserReturnAll :one 8 | INSERT INTO users (id, location) VALUES ($1, $2) RETURNING *; 9 | 10 | ----------- 11 | 12 | -- name: CreateProduct :one 13 | INSERT INTO products (id, category) VALUES ($1, $2) RETURNING id; 14 | 15 | -- name: CreateProductReturnPartial :one 16 | INSERT INTO 17 | products (id, category) 18 | VALUES ($1, $2) RETURNING id, 19 | name; 20 | 21 | -- name: CreateProductReturnAll :one 22 | INSERT INTO products (id, category) VALUES ($1, $2) RETURNING *; 23 | 24 | -- name: GetProductsByIds :many 25 | SELECT * FROM products WHERE id = ANY($1::uuid[]); 26 | 27 | ----------- 28 | 29 | -- name: CreateLocationTransactions :exec 30 | INSERT INTO location_transactions 31 | SELECT 32 | UNNEST($1::UUID[]) as location_id, 33 | UNNEST($2::UUID[]) as transaction_id; -------------------------------------------------------------------------------- /_examples/uuidcheck/registry.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "net/http" 7 | 8 | "connectrpc.com/connect" 9 | "connectrpc.com/grpcreflect" 10 | "github.com/jackc/pgx/v5/pgxpool" 11 | 12 | googleuuid_v1connect "uuidcheck/api/googleuuid/v1/v1connect" 13 | pguuid_v1connect "uuidcheck/api/pguuid/v1/v1connect" 14 | googleuuid_app "uuidcheck/store/googleuuid" 15 | pguuid_app "uuidcheck/store/pguuid" 16 | ) 17 | 18 | func registerHandlers(mux *http.ServeMux, db *pgxpool.Pool, interceptors []connect.Interceptor) { 19 | googleuuidService := googleuuid_app.NewService(googleuuid_app.New(db)) 20 | googleuuidPath, googleuuidHandler := googleuuid_v1connect.NewGoogleuuidServiceHandler(googleuuidService, 21 | connect.WithInterceptors( 22 | interceptors..., 23 | ), 24 | ) 25 | mux.Handle(googleuuidPath, googleuuidHandler) 26 | pguuidService := pguuid_app.NewService(pguuid_app.New(db)) 27 | pguuidPath, pguuidHandler := pguuid_v1connect.NewPguuidServiceHandler(pguuidService, 28 | connect.WithInterceptors( 29 | interceptors..., 30 | ), 31 | ) 32 | mux.Handle(pguuidPath, pguuidHandler) 33 | 34 | reflector := grpcreflect.NewStaticReflector( 35 | googleuuid_v1connect.GoogleuuidServiceName, 36 | pguuid_v1connect.PguuidServiceName, 37 | ) 38 | mux.Handle(grpcreflect.NewHandlerV1(reflector)) 39 | mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) 40 | } 41 | -------------------------------------------------------------------------------- /_examples/uuidcheck/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "locations" ("id" UUID PRIMARY KEY); 2 | 3 | CREATE TABLE IF NOT EXISTS "users" ( 4 | "id" UUID PRIMARY KEY, 5 | "location" UUID REFERENCES "locations" ("id"), 6 | "name" VARCHAR 7 | ); 8 | 9 | CREATE TABLE IF NOT EXISTS "category" ("id" SERIAL PRIMARY KEY); 10 | 11 | CREATE TABLE IF NOT EXISTS "products" ( 12 | "id" SERIAL PRIMARY KEY, 13 | "category" INT REFERENCES "category" ("id"), 14 | "name" VARCHAR 15 | ); 16 | 17 | CREATE TABLE location_transactions ( 18 | location_id UUID NOT NULL, 19 | transaction_id UUID NOT NULL 20 | ); -------------------------------------------------------------------------------- /_examples/uuidcheck/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | 4 | - engine: "postgresql" 5 | queries: "query/uuid" 6 | schema: "schema.sql" 7 | gen: 8 | go: 9 | package: "googleuuid" 10 | out: "store/googleuuid" 11 | sql_package: "pgx/v5" 12 | emit_json_tags: true 13 | emit_prepared_queries: true 14 | emit_empty_slices: true 15 | overrides: 16 | - db_type: "uuid" 17 | go_type: 18 | import: "github.com/google/uuid" 19 | type: "UUID" 20 | 21 | - engine: "postgresql" 22 | queries: "query/uuid" 23 | schema: "schema.sql" 24 | gen: 25 | go: 26 | package: "pguuid" 27 | out: "store/pguuid" 28 | sql_package: "pgx/v5" 29 | emit_json_tags: true 30 | emit_prepared_queries: true 31 | emit_empty_slices: true 32 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/googleuuid/adapters.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package googleuuid 4 | 5 | import ( 6 | "encoding/json" 7 | 8 | "google.golang.org/protobuf/types/known/wrapperspb" 9 | 10 | pb "uuidcheck/api/googleuuid/v1" 11 | ) 12 | 13 | func toCreateProductReturnPartialRow(in CreateProductReturnPartialRow) *pb.CreateProductReturnPartialRow { 14 | 15 | out := new(pb.CreateProductReturnPartialRow) 16 | out.Id = in.ID 17 | if in.Name.Valid { 18 | out.Name = wrapperspb.String(in.Name.String) 19 | } 20 | return out 21 | } 22 | 23 | func toCreateUserReturnPartialRow(in CreateUserReturnPartialRow) *pb.CreateUserReturnPartialRow { 24 | 25 | out := new(pb.CreateUserReturnPartialRow) 26 | out.Id = in.ID.String() 27 | if in.Name.Valid { 28 | out.Name = wrapperspb.String(in.Name.String) 29 | } 30 | return out 31 | } 32 | 33 | func toProduct(in Product) *pb.Product { 34 | 35 | out := new(pb.Product) 36 | out.Id = in.ID 37 | if in.Category.Valid { 38 | out.Category = wrapperspb.Int32(in.Category.Int32) 39 | } 40 | if in.Name.Valid { 41 | out.Name = wrapperspb.String(in.Name.String) 42 | } 43 | return out 44 | } 45 | 46 | func toUser(in User) *pb.User { 47 | 48 | out := new(pb.User) 49 | out.Id = in.ID.String() 50 | if v, err := json.Marshal(in.Location); err == nil { 51 | out.Location = wrapperspb.String(string(v)) 52 | } 53 | if in.Name.Valid { 54 | out.Name = wrapperspb.String(in.Name.String) 55 | } 56 | return out 57 | } 58 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/googleuuid/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | 5 | package googleuuid 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/jackc/pgx/v5" 11 | "github.com/jackc/pgx/v5/pgconn" 12 | ) 13 | 14 | type DBTX interface { 15 | Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) 16 | Query(context.Context, string, ...interface{}) (pgx.Rows, error) 17 | QueryRow(context.Context, string, ...interface{}) pgx.Row 18 | } 19 | 20 | func New(db DBTX) *Queries { 21 | return &Queries{db: db} 22 | } 23 | 24 | type Queries struct { 25 | db DBTX 26 | } 27 | 28 | func (q *Queries) WithTx(tx pgx.Tx) *Queries { 29 | return &Queries{ 30 | db: tx, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/googleuuid/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | 5 | package googleuuid 6 | 7 | import ( 8 | "github.com/google/uuid" 9 | "github.com/jackc/pgx/v5/pgtype" 10 | ) 11 | 12 | type Category struct { 13 | ID int32 `json:"id"` 14 | } 15 | 16 | type Location struct { 17 | ID uuid.UUID `json:"id"` 18 | } 19 | 20 | type LocationTransaction struct { 21 | LocationID uuid.UUID `json:"location_id"` 22 | TransactionID uuid.UUID `json:"transaction_id"` 23 | } 24 | 25 | type Product struct { 26 | ID int32 `json:"id"` 27 | Category pgtype.Int4 `json:"category"` 28 | Name pgtype.Text `json:"name"` 29 | } 30 | 31 | type User struct { 32 | ID uuid.UUID `json:"id"` 33 | Location pgtype.UUID `json:"location"` 34 | Name pgtype.Text `json:"name"` 35 | } 36 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/googleuuid/service.factory.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package googleuuid 4 | 5 | import ( 6 | "uuidcheck/api/googleuuid/v1/v1connect" 7 | ) 8 | 9 | // NewService is a constructor of a v1.GoogleuuidServiceHandler implementation. 10 | // Use this function to customize the server by adding middlewares to it. 11 | func NewService(querier *Queries) v1connect.GoogleuuidServiceHandler { 12 | return &Service{querier: querier} 13 | } 14 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/googleuuid/service.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package googleuuid 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "log/slog" 10 | 11 | "connectrpc.com/connect" 12 | "github.com/google/uuid" 13 | "github.com/jackc/pgx/v5/pgtype" 14 | 15 | pb "uuidcheck/api/googleuuid/v1" 16 | "uuidcheck/api/googleuuid/v1/v1connect" 17 | "uuidcheck/internal/validation" 18 | ) 19 | 20 | type Service struct { 21 | v1connect.UnimplementedGoogleuuidServiceHandler 22 | querier *Queries 23 | } 24 | 25 | func (s *Service) CreateLocationTransactions(ctx context.Context, req *connect.Request[pb.CreateLocationTransactionsRequest]) (*connect.Response[pb.CreateLocationTransactionsResponse], error) { 26 | var arg CreateLocationTransactionsParams 27 | arg.Column1 = make([]uuid.UUID, len(req.Msg.GetColumn1())) 28 | for i, s := range req.Msg.GetColumn1() { 29 | if v, err := uuid.Parse(s); err != nil { 30 | err = fmt.Errorf("invalid Column1: %s%w", err.Error(), validation.ErrUserInput) 31 | return nil, err 32 | } else { 33 | arg.Column1[i] = v 34 | } 35 | } 36 | arg.Column2 = make([]uuid.UUID, len(req.Msg.GetColumn2())) 37 | for i, s := range req.Msg.GetColumn2() { 38 | if v, err := uuid.Parse(s); err != nil { 39 | err = fmt.Errorf("invalid Column2: %s%w", err.Error(), validation.ErrUserInput) 40 | return nil, err 41 | } else { 42 | arg.Column2[i] = v 43 | } 44 | } 45 | 46 | err := s.querier.CreateLocationTransactions(ctx, arg) 47 | if err != nil { 48 | slog.Error("sql call failed", "error", err, "method", "CreateLocationTransactions") 49 | return nil, err 50 | } 51 | return connect.NewResponse(&pb.CreateLocationTransactionsResponse{}), nil 52 | } 53 | 54 | func (s *Service) CreateProduct(ctx context.Context, req *connect.Request[pb.CreateProductRequest]) (*connect.Response[pb.CreateProductResponse], error) { 55 | var arg CreateProductParams 56 | arg.ID = req.Msg.GetId() 57 | if v := req.Msg.GetCategory(); v != nil { 58 | arg.Category = pgtype.Int4{Valid: true, Int32: v.Value} 59 | } 60 | 61 | result, err := s.querier.CreateProduct(ctx, arg) 62 | if err != nil { 63 | slog.Error("sql call failed", "error", err, "method", "CreateProduct") 64 | return nil, err 65 | } 66 | return connect.NewResponse(&pb.CreateProductResponse{Value: result}), nil 67 | } 68 | 69 | func (s *Service) CreateProductReturnAll(ctx context.Context, req *connect.Request[pb.CreateProductReturnAllRequest]) (*connect.Response[pb.CreateProductReturnAllResponse], error) { 70 | var arg CreateProductReturnAllParams 71 | arg.ID = req.Msg.GetId() 72 | if v := req.Msg.GetCategory(); v != nil { 73 | arg.Category = pgtype.Int4{Valid: true, Int32: v.Value} 74 | } 75 | 76 | result, err := s.querier.CreateProductReturnAll(ctx, arg) 77 | if err != nil { 78 | slog.Error("sql call failed", "error", err, "method", "CreateProductReturnAll") 79 | return nil, err 80 | } 81 | return connect.NewResponse(&pb.CreateProductReturnAllResponse{Product: toProduct(result)}), nil 82 | } 83 | 84 | func (s *Service) CreateProductReturnPartial(ctx context.Context, req *connect.Request[pb.CreateProductReturnPartialRequest]) (*connect.Response[pb.CreateProductReturnPartialResponse], error) { 85 | var arg CreateProductReturnPartialParams 86 | arg.ID = req.Msg.GetId() 87 | if v := req.Msg.GetCategory(); v != nil { 88 | arg.Category = pgtype.Int4{Valid: true, Int32: v.Value} 89 | } 90 | 91 | result, err := s.querier.CreateProductReturnPartial(ctx, arg) 92 | if err != nil { 93 | slog.Error("sql call failed", "error", err, "method", "CreateProductReturnPartial") 94 | return nil, err 95 | } 96 | return connect.NewResponse(&pb.CreateProductReturnPartialResponse{CreateProductReturnPartialRow: toCreateProductReturnPartialRow(result)}), nil 97 | } 98 | 99 | func (s *Service) CreateUser(ctx context.Context, req *connect.Request[pb.CreateUserRequest]) (*connect.Response[pb.CreateUserResponse], error) { 100 | var arg CreateUserParams 101 | if v, err := uuid.Parse(req.Msg.GetId()); err != nil { 102 | err = fmt.Errorf("invalid ID: %s%w", err.Error(), validation.ErrUserInput) 103 | return nil, err 104 | } else { 105 | arg.ID = v 106 | } 107 | if v := req.Msg.GetLocation(); v != nil { 108 | if err := json.Unmarshal([]byte(v.GetValue()), &arg.Location); err != nil { 109 | err = fmt.Errorf("invalid Location: %s%w", err.Error(), validation.ErrUserInput) 110 | return nil, err 111 | } 112 | } 113 | 114 | result, err := s.querier.CreateUser(ctx, arg) 115 | if err != nil { 116 | slog.Error("sql call failed", "error", err, "method", "CreateUser") 117 | return nil, err 118 | } 119 | return connect.NewResponse(&pb.CreateUserResponse{Value: result.String()}), nil 120 | } 121 | 122 | func (s *Service) CreateUserReturnAll(ctx context.Context, req *connect.Request[pb.CreateUserReturnAllRequest]) (*connect.Response[pb.CreateUserReturnAllResponse], error) { 123 | var arg CreateUserReturnAllParams 124 | if v, err := uuid.Parse(req.Msg.GetId()); err != nil { 125 | err = fmt.Errorf("invalid ID: %s%w", err.Error(), validation.ErrUserInput) 126 | return nil, err 127 | } else { 128 | arg.ID = v 129 | } 130 | if v := req.Msg.GetLocation(); v != nil { 131 | if err := json.Unmarshal([]byte(v.GetValue()), &arg.Location); err != nil { 132 | err = fmt.Errorf("invalid Location: %s%w", err.Error(), validation.ErrUserInput) 133 | return nil, err 134 | } 135 | } 136 | 137 | result, err := s.querier.CreateUserReturnAll(ctx, arg) 138 | if err != nil { 139 | slog.Error("sql call failed", "error", err, "method", "CreateUserReturnAll") 140 | return nil, err 141 | } 142 | return connect.NewResponse(&pb.CreateUserReturnAllResponse{User: toUser(result)}), nil 143 | } 144 | 145 | func (s *Service) CreateUserReturnPartial(ctx context.Context, req *connect.Request[pb.CreateUserReturnPartialRequest]) (*connect.Response[pb.CreateUserReturnPartialResponse], error) { 146 | var arg CreateUserReturnPartialParams 147 | if v, err := uuid.Parse(req.Msg.GetId()); err != nil { 148 | err = fmt.Errorf("invalid ID: %s%w", err.Error(), validation.ErrUserInput) 149 | return nil, err 150 | } else { 151 | arg.ID = v 152 | } 153 | if v := req.Msg.GetLocation(); v != nil { 154 | if err := json.Unmarshal([]byte(v.GetValue()), &arg.Location); err != nil { 155 | err = fmt.Errorf("invalid Location: %s%w", err.Error(), validation.ErrUserInput) 156 | return nil, err 157 | } 158 | } 159 | 160 | result, err := s.querier.CreateUserReturnPartial(ctx, arg) 161 | if err != nil { 162 | slog.Error("sql call failed", "error", err, "method", "CreateUserReturnPartial") 163 | return nil, err 164 | } 165 | return connect.NewResponse(&pb.CreateUserReturnPartialResponse{CreateUserReturnPartialRow: toCreateUserReturnPartialRow(result)}), nil 166 | } 167 | 168 | func (s *Service) GetProductsByIds(ctx context.Context, req *connect.Request[pb.GetProductsByIdsRequest]) (*connect.Response[pb.GetProductsByIdsResponse], error) { 169 | var dollar_1 []uuid.UUID 170 | dollar_1 = make([]uuid.UUID, len(req.Msg.GetDollar_1())) 171 | for i, s := range req.Msg.GetDollar_1() { 172 | if v, err := uuid.Parse(s); err != nil { 173 | err = fmt.Errorf("invalid Dollar_1: %s%w", err.Error(), validation.ErrUserInput) 174 | return nil, err 175 | } else { 176 | dollar_1[i] = v 177 | } 178 | } 179 | 180 | result, err := s.querier.GetProductsByIds(ctx, dollar_1) 181 | if err != nil { 182 | slog.Error("sql call failed", "error", err, "method", "GetProductsByIds") 183 | return nil, err 184 | } 185 | res := new(pb.GetProductsByIdsResponse) 186 | for _, r := range result { 187 | res.List = append(res.List, toProduct(r)) 188 | } 189 | return connect.NewResponse(res), nil 190 | } 191 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/googleuuid/user.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | // source: user.sql 5 | 6 | package googleuuid 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/google/uuid" 12 | "github.com/jackc/pgx/v5/pgtype" 13 | ) 14 | 15 | const createLocationTransactions = `-- name: CreateLocationTransactions :exec 16 | 17 | INSERT INTO location_transactions 18 | SELECT 19 | UNNEST($1::UUID[]) as location_id, 20 | UNNEST($2::UUID[]) as transaction_id 21 | ` 22 | 23 | type CreateLocationTransactionsParams struct { 24 | Column1 []uuid.UUID `json:"column_1"` 25 | Column2 []uuid.UUID `json:"column_2"` 26 | } 27 | 28 | // --------- 29 | func (q *Queries) CreateLocationTransactions(ctx context.Context, arg CreateLocationTransactionsParams) error { 30 | _, err := q.db.Exec(ctx, createLocationTransactions, arg.Column1, arg.Column2) 31 | return err 32 | } 33 | 34 | const createProduct = `-- name: CreateProduct :one 35 | 36 | INSERT INTO products (id, category) VALUES ($1, $2) RETURNING id 37 | ` 38 | 39 | type CreateProductParams struct { 40 | ID int32 `json:"id"` 41 | Category pgtype.Int4 `json:"category"` 42 | } 43 | 44 | // --------- 45 | func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) (int32, error) { 46 | row := q.db.QueryRow(ctx, createProduct, arg.ID, arg.Category) 47 | var id int32 48 | err := row.Scan(&id) 49 | return id, err 50 | } 51 | 52 | const createProductReturnAll = `-- name: CreateProductReturnAll :one 53 | INSERT INTO products (id, category) VALUES ($1, $2) RETURNING id, category, name 54 | ` 55 | 56 | type CreateProductReturnAllParams struct { 57 | ID int32 `json:"id"` 58 | Category pgtype.Int4 `json:"category"` 59 | } 60 | 61 | func (q *Queries) CreateProductReturnAll(ctx context.Context, arg CreateProductReturnAllParams) (Product, error) { 62 | row := q.db.QueryRow(ctx, createProductReturnAll, arg.ID, arg.Category) 63 | var i Product 64 | err := row.Scan(&i.ID, &i.Category, &i.Name) 65 | return i, err 66 | } 67 | 68 | const createProductReturnPartial = `-- name: CreateProductReturnPartial :one 69 | INSERT INTO 70 | products (id, category) 71 | VALUES ($1, $2) RETURNING id, 72 | name 73 | ` 74 | 75 | type CreateProductReturnPartialParams struct { 76 | ID int32 `json:"id"` 77 | Category pgtype.Int4 `json:"category"` 78 | } 79 | 80 | type CreateProductReturnPartialRow struct { 81 | ID int32 `json:"id"` 82 | Name pgtype.Text `json:"name"` 83 | } 84 | 85 | func (q *Queries) CreateProductReturnPartial(ctx context.Context, arg CreateProductReturnPartialParams) (CreateProductReturnPartialRow, error) { 86 | row := q.db.QueryRow(ctx, createProductReturnPartial, arg.ID, arg.Category) 87 | var i CreateProductReturnPartialRow 88 | err := row.Scan(&i.ID, &i.Name) 89 | return i, err 90 | } 91 | 92 | const createUser = `-- name: CreateUser :one 93 | INSERT INTO users (id, location) VALUES ($1, $2) RETURNING id 94 | ` 95 | 96 | type CreateUserParams struct { 97 | ID uuid.UUID `json:"id"` 98 | Location pgtype.UUID `json:"location"` 99 | } 100 | 101 | func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (uuid.UUID, error) { 102 | row := q.db.QueryRow(ctx, createUser, arg.ID, arg.Location) 103 | var id uuid.UUID 104 | err := row.Scan(&id) 105 | return id, err 106 | } 107 | 108 | const createUserReturnAll = `-- name: CreateUserReturnAll :one 109 | INSERT INTO users (id, location) VALUES ($1, $2) RETURNING id, location, name 110 | ` 111 | 112 | type CreateUserReturnAllParams struct { 113 | ID uuid.UUID `json:"id"` 114 | Location pgtype.UUID `json:"location"` 115 | } 116 | 117 | func (q *Queries) CreateUserReturnAll(ctx context.Context, arg CreateUserReturnAllParams) (User, error) { 118 | row := q.db.QueryRow(ctx, createUserReturnAll, arg.ID, arg.Location) 119 | var i User 120 | err := row.Scan(&i.ID, &i.Location, &i.Name) 121 | return i, err 122 | } 123 | 124 | const createUserReturnPartial = `-- name: CreateUserReturnPartial :one 125 | INSERT INTO users (id, location) VALUES ($1, $2) RETURNING id, name 126 | ` 127 | 128 | type CreateUserReturnPartialParams struct { 129 | ID uuid.UUID `json:"id"` 130 | Location pgtype.UUID `json:"location"` 131 | } 132 | 133 | type CreateUserReturnPartialRow struct { 134 | ID uuid.UUID `json:"id"` 135 | Name pgtype.Text `json:"name"` 136 | } 137 | 138 | func (q *Queries) CreateUserReturnPartial(ctx context.Context, arg CreateUserReturnPartialParams) (CreateUserReturnPartialRow, error) { 139 | row := q.db.QueryRow(ctx, createUserReturnPartial, arg.ID, arg.Location) 140 | var i CreateUserReturnPartialRow 141 | err := row.Scan(&i.ID, &i.Name) 142 | return i, err 143 | } 144 | 145 | const getProductsByIds = `-- name: GetProductsByIds :many 146 | SELECT id, category, name FROM products WHERE id = ANY($1::uuid[]) 147 | ` 148 | 149 | func (q *Queries) GetProductsByIds(ctx context.Context, dollar_1 []uuid.UUID) ([]Product, error) { 150 | rows, err := q.db.Query(ctx, getProductsByIds, dollar_1) 151 | if err != nil { 152 | return nil, err 153 | } 154 | defer rows.Close() 155 | items := []Product{} 156 | for rows.Next() { 157 | var i Product 158 | if err := rows.Scan(&i.ID, &i.Category, &i.Name); err != nil { 159 | return nil, err 160 | } 161 | items = append(items, i) 162 | } 163 | if err := rows.Err(); err != nil { 164 | return nil, err 165 | } 166 | return items, nil 167 | } 168 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/pguuid/adapters.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package pguuid 4 | 5 | import ( 6 | "encoding/json" 7 | 8 | "google.golang.org/protobuf/types/known/wrapperspb" 9 | 10 | pb "uuidcheck/api/pguuid/v1" 11 | ) 12 | 13 | func toCreateProductReturnPartialRow(in CreateProductReturnPartialRow) *pb.CreateProductReturnPartialRow { 14 | 15 | out := new(pb.CreateProductReturnPartialRow) 16 | out.Id = in.ID 17 | if in.Name.Valid { 18 | out.Name = wrapperspb.String(in.Name.String) 19 | } 20 | return out 21 | } 22 | 23 | func toCreateUserReturnPartialRow(in CreateUserReturnPartialRow) *pb.CreateUserReturnPartialRow { 24 | 25 | out := new(pb.CreateUserReturnPartialRow) 26 | if v, err := json.Marshal(in.ID); err == nil { 27 | out.Id = wrapperspb.String(string(v)) 28 | } 29 | if in.Name.Valid { 30 | out.Name = wrapperspb.String(in.Name.String) 31 | } 32 | return out 33 | } 34 | 35 | func toProduct(in Product) *pb.Product { 36 | 37 | out := new(pb.Product) 38 | out.Id = in.ID 39 | if in.Category.Valid { 40 | out.Category = wrapperspb.Int32(in.Category.Int32) 41 | } 42 | if in.Name.Valid { 43 | out.Name = wrapperspb.String(in.Name.String) 44 | } 45 | return out 46 | } 47 | 48 | func toUser(in User) *pb.User { 49 | 50 | out := new(pb.User) 51 | if v, err := json.Marshal(in.ID); err == nil { 52 | out.Id = wrapperspb.String(string(v)) 53 | } 54 | if v, err := json.Marshal(in.Location); err == nil { 55 | out.Location = wrapperspb.String(string(v)) 56 | } 57 | if in.Name.Valid { 58 | out.Name = wrapperspb.String(in.Name.String) 59 | } 60 | return out 61 | } 62 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/pguuid/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | 5 | package pguuid 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/jackc/pgx/v5" 11 | "github.com/jackc/pgx/v5/pgconn" 12 | ) 13 | 14 | type DBTX interface { 15 | Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) 16 | Query(context.Context, string, ...interface{}) (pgx.Rows, error) 17 | QueryRow(context.Context, string, ...interface{}) pgx.Row 18 | } 19 | 20 | func New(db DBTX) *Queries { 21 | return &Queries{db: db} 22 | } 23 | 24 | type Queries struct { 25 | db DBTX 26 | } 27 | 28 | func (q *Queries) WithTx(tx pgx.Tx) *Queries { 29 | return &Queries{ 30 | db: tx, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/pguuid/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | 5 | package pguuid 6 | 7 | import ( 8 | "github.com/jackc/pgx/v5/pgtype" 9 | ) 10 | 11 | type Category struct { 12 | ID int32 `json:"id"` 13 | } 14 | 15 | type Location struct { 16 | ID pgtype.UUID `json:"id"` 17 | } 18 | 19 | type LocationTransaction struct { 20 | LocationID pgtype.UUID `json:"location_id"` 21 | TransactionID pgtype.UUID `json:"transaction_id"` 22 | } 23 | 24 | type Product struct { 25 | ID int32 `json:"id"` 26 | Category pgtype.Int4 `json:"category"` 27 | Name pgtype.Text `json:"name"` 28 | } 29 | 30 | type User struct { 31 | ID pgtype.UUID `json:"id"` 32 | Location pgtype.UUID `json:"location"` 33 | Name pgtype.Text `json:"name"` 34 | } 35 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/pguuid/service.factory.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package pguuid 4 | 5 | import ( 6 | "uuidcheck/api/pguuid/v1/v1connect" 7 | ) 8 | 9 | // NewService is a constructor of a v1.PguuidServiceHandler implementation. 10 | // Use this function to customize the server by adding middlewares to it. 11 | func NewService(querier *Queries) v1connect.PguuidServiceHandler { 12 | return &Service{querier: querier} 13 | } 14 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/pguuid/service.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package pguuid 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "log/slog" 10 | 11 | "connectrpc.com/connect" 12 | "github.com/jackc/pgx/v5/pgtype" 13 | "google.golang.org/protobuf/types/known/wrapperspb" 14 | 15 | pb "uuidcheck/api/pguuid/v1" 16 | "uuidcheck/api/pguuid/v1/v1connect" 17 | "uuidcheck/internal/validation" 18 | ) 19 | 20 | type Service struct { 21 | v1connect.UnimplementedPguuidServiceHandler 22 | querier *Queries 23 | } 24 | 25 | func (s *Service) CreateLocationTransactions(ctx context.Context, req *connect.Request[pb.CreateLocationTransactionsRequest]) (*connect.Response[pb.CreateLocationTransactionsResponse], error) { 26 | var arg CreateLocationTransactionsParams 27 | arg.Column1 = make([]pgtype.UUID, len(req.Msg.GetColumn1())) 28 | for i, s := range req.Msg.GetColumn1() { 29 | if err := arg.Column1[i].Scan(s.Value); err != nil { 30 | return nil, fmt.Errorf("invalid UUID in Column1 at index %d: %w", i, err) 31 | } 32 | } 33 | arg.Column2 = make([]pgtype.UUID, len(req.Msg.GetColumn2())) 34 | for i, s := range req.Msg.GetColumn2() { 35 | if err := arg.Column2[i].Scan(s.Value); err != nil { 36 | return nil, fmt.Errorf("invalid UUID in Column2 at index %d: %w", i, err) 37 | } 38 | } 39 | 40 | err := s.querier.CreateLocationTransactions(ctx, arg) 41 | if err != nil { 42 | slog.Error("sql call failed", "error", err, "method", "CreateLocationTransactions") 43 | return nil, err 44 | } 45 | return connect.NewResponse(&pb.CreateLocationTransactionsResponse{}), nil 46 | } 47 | 48 | func (s *Service) CreateProduct(ctx context.Context, req *connect.Request[pb.CreateProductRequest]) (*connect.Response[pb.CreateProductResponse], error) { 49 | var arg CreateProductParams 50 | arg.ID = req.Msg.GetId() 51 | if v := req.Msg.GetCategory(); v != nil { 52 | arg.Category = pgtype.Int4{Valid: true, Int32: v.Value} 53 | } 54 | 55 | result, err := s.querier.CreateProduct(ctx, arg) 56 | if err != nil { 57 | slog.Error("sql call failed", "error", err, "method", "CreateProduct") 58 | return nil, err 59 | } 60 | return connect.NewResponse(&pb.CreateProductResponse{Value: result}), nil 61 | } 62 | 63 | func (s *Service) CreateProductReturnAll(ctx context.Context, req *connect.Request[pb.CreateProductReturnAllRequest]) (*connect.Response[pb.CreateProductReturnAllResponse], error) { 64 | var arg CreateProductReturnAllParams 65 | arg.ID = req.Msg.GetId() 66 | if v := req.Msg.GetCategory(); v != nil { 67 | arg.Category = pgtype.Int4{Valid: true, Int32: v.Value} 68 | } 69 | 70 | result, err := s.querier.CreateProductReturnAll(ctx, arg) 71 | if err != nil { 72 | slog.Error("sql call failed", "error", err, "method", "CreateProductReturnAll") 73 | return nil, err 74 | } 75 | return connect.NewResponse(&pb.CreateProductReturnAllResponse{Product: toProduct(result)}), nil 76 | } 77 | 78 | func (s *Service) CreateProductReturnPartial(ctx context.Context, req *connect.Request[pb.CreateProductReturnPartialRequest]) (*connect.Response[pb.CreateProductReturnPartialResponse], error) { 79 | var arg CreateProductReturnPartialParams 80 | arg.ID = req.Msg.GetId() 81 | if v := req.Msg.GetCategory(); v != nil { 82 | arg.Category = pgtype.Int4{Valid: true, Int32: v.Value} 83 | } 84 | 85 | result, err := s.querier.CreateProductReturnPartial(ctx, arg) 86 | if err != nil { 87 | slog.Error("sql call failed", "error", err, "method", "CreateProductReturnPartial") 88 | return nil, err 89 | } 90 | return connect.NewResponse(&pb.CreateProductReturnPartialResponse{CreateProductReturnPartialRow: toCreateProductReturnPartialRow(result)}), nil 91 | } 92 | 93 | func (s *Service) CreateUser(ctx context.Context, req *connect.Request[pb.CreateUserRequest]) (*connect.Response[pb.CreateUserResponse], error) { 94 | var arg CreateUserParams 95 | if v := req.Msg.GetId(); v != nil { 96 | if err := json.Unmarshal([]byte(v.GetValue()), &arg.ID); err != nil { 97 | err = fmt.Errorf("invalid ID: %s%w", err.Error(), validation.ErrUserInput) 98 | return nil, err 99 | } 100 | } 101 | if v := req.Msg.GetLocation(); v != nil { 102 | if err := json.Unmarshal([]byte(v.GetValue()), &arg.Location); err != nil { 103 | err = fmt.Errorf("invalid Location: %s%w", err.Error(), validation.ErrUserInput) 104 | return nil, err 105 | } 106 | } 107 | 108 | result, err := s.querier.CreateUser(ctx, arg) 109 | if err != nil { 110 | slog.Error("sql call failed", "error", err, "method", "CreateUser") 111 | return nil, err 112 | } 113 | if uuidStr, err := result.MarshalJSON(); err != nil { 114 | return nil, fmt.Errorf("failed to convert UUID to string: %w", err) 115 | } else { 116 | return connect.NewResponse(&pb.CreateUserResponse{Value: wrapperspb.String(string(uuidStr))}), nil 117 | } 118 | } 119 | 120 | func (s *Service) CreateUserReturnAll(ctx context.Context, req *connect.Request[pb.CreateUserReturnAllRequest]) (*connect.Response[pb.CreateUserReturnAllResponse], error) { 121 | var arg CreateUserReturnAllParams 122 | if v := req.Msg.GetId(); v != nil { 123 | if err := json.Unmarshal([]byte(v.GetValue()), &arg.ID); err != nil { 124 | err = fmt.Errorf("invalid ID: %s%w", err.Error(), validation.ErrUserInput) 125 | return nil, err 126 | } 127 | } 128 | if v := req.Msg.GetLocation(); v != nil { 129 | if err := json.Unmarshal([]byte(v.GetValue()), &arg.Location); err != nil { 130 | err = fmt.Errorf("invalid Location: %s%w", err.Error(), validation.ErrUserInput) 131 | return nil, err 132 | } 133 | } 134 | 135 | result, err := s.querier.CreateUserReturnAll(ctx, arg) 136 | if err != nil { 137 | slog.Error("sql call failed", "error", err, "method", "CreateUserReturnAll") 138 | return nil, err 139 | } 140 | return connect.NewResponse(&pb.CreateUserReturnAllResponse{User: toUser(result)}), nil 141 | } 142 | 143 | func (s *Service) CreateUserReturnPartial(ctx context.Context, req *connect.Request[pb.CreateUserReturnPartialRequest]) (*connect.Response[pb.CreateUserReturnPartialResponse], error) { 144 | var arg CreateUserReturnPartialParams 145 | if v := req.Msg.GetId(); v != nil { 146 | if err := json.Unmarshal([]byte(v.GetValue()), &arg.ID); err != nil { 147 | err = fmt.Errorf("invalid ID: %s%w", err.Error(), validation.ErrUserInput) 148 | return nil, err 149 | } 150 | } 151 | if v := req.Msg.GetLocation(); v != nil { 152 | if err := json.Unmarshal([]byte(v.GetValue()), &arg.Location); err != nil { 153 | err = fmt.Errorf("invalid Location: %s%w", err.Error(), validation.ErrUserInput) 154 | return nil, err 155 | } 156 | } 157 | 158 | result, err := s.querier.CreateUserReturnPartial(ctx, arg) 159 | if err != nil { 160 | slog.Error("sql call failed", "error", err, "method", "CreateUserReturnPartial") 161 | return nil, err 162 | } 163 | return connect.NewResponse(&pb.CreateUserReturnPartialResponse{CreateUserReturnPartialRow: toCreateUserReturnPartialRow(result)}), nil 164 | } 165 | 166 | func (s *Service) GetProductsByIds(ctx context.Context, req *connect.Request[pb.GetProductsByIdsRequest]) (*connect.Response[pb.GetProductsByIdsResponse], error) { 167 | var dollar_1 []pgtype.UUID 168 | dollar_1 = make([]pgtype.UUID, len(req.Msg.GetDollar_1())) 169 | for i, s := range req.Msg.GetDollar_1() { 170 | if err := dollar_1[i].Scan(s.Value); err != nil { 171 | return nil, fmt.Errorf("invalid UUID in Dollar_1 at index %d: %w", i, err) 172 | } 173 | } 174 | 175 | result, err := s.querier.GetProductsByIds(ctx, dollar_1) 176 | if err != nil { 177 | slog.Error("sql call failed", "error", err, "method", "GetProductsByIds") 178 | return nil, err 179 | } 180 | res := new(pb.GetProductsByIdsResponse) 181 | for _, r := range result { 182 | res.List = append(res.List, toProduct(r)) 183 | } 184 | return connect.NewResponse(res), nil 185 | } 186 | -------------------------------------------------------------------------------- /_examples/uuidcheck/store/pguuid/user.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | // source: user.sql 5 | 6 | package pguuid 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/jackc/pgx/v5/pgtype" 12 | ) 13 | 14 | const createLocationTransactions = `-- name: CreateLocationTransactions :exec 15 | 16 | INSERT INTO location_transactions 17 | SELECT 18 | UNNEST($1::UUID[]) as location_id, 19 | UNNEST($2::UUID[]) as transaction_id 20 | ` 21 | 22 | type CreateLocationTransactionsParams struct { 23 | Column1 []pgtype.UUID `json:"column_1"` 24 | Column2 []pgtype.UUID `json:"column_2"` 25 | } 26 | 27 | // --------- 28 | func (q *Queries) CreateLocationTransactions(ctx context.Context, arg CreateLocationTransactionsParams) error { 29 | _, err := q.db.Exec(ctx, createLocationTransactions, arg.Column1, arg.Column2) 30 | return err 31 | } 32 | 33 | const createProduct = `-- name: CreateProduct :one 34 | 35 | INSERT INTO products (id, category) VALUES ($1, $2) RETURNING id 36 | ` 37 | 38 | type CreateProductParams struct { 39 | ID int32 `json:"id"` 40 | Category pgtype.Int4 `json:"category"` 41 | } 42 | 43 | // --------- 44 | func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) (int32, error) { 45 | row := q.db.QueryRow(ctx, createProduct, arg.ID, arg.Category) 46 | var id int32 47 | err := row.Scan(&id) 48 | return id, err 49 | } 50 | 51 | const createProductReturnAll = `-- name: CreateProductReturnAll :one 52 | INSERT INTO products (id, category) VALUES ($1, $2) RETURNING id, category, name 53 | ` 54 | 55 | type CreateProductReturnAllParams struct { 56 | ID int32 `json:"id"` 57 | Category pgtype.Int4 `json:"category"` 58 | } 59 | 60 | func (q *Queries) CreateProductReturnAll(ctx context.Context, arg CreateProductReturnAllParams) (Product, error) { 61 | row := q.db.QueryRow(ctx, createProductReturnAll, arg.ID, arg.Category) 62 | var i Product 63 | err := row.Scan(&i.ID, &i.Category, &i.Name) 64 | return i, err 65 | } 66 | 67 | const createProductReturnPartial = `-- name: CreateProductReturnPartial :one 68 | INSERT INTO 69 | products (id, category) 70 | VALUES ($1, $2) RETURNING id, 71 | name 72 | ` 73 | 74 | type CreateProductReturnPartialParams struct { 75 | ID int32 `json:"id"` 76 | Category pgtype.Int4 `json:"category"` 77 | } 78 | 79 | type CreateProductReturnPartialRow struct { 80 | ID int32 `json:"id"` 81 | Name pgtype.Text `json:"name"` 82 | } 83 | 84 | func (q *Queries) CreateProductReturnPartial(ctx context.Context, arg CreateProductReturnPartialParams) (CreateProductReturnPartialRow, error) { 85 | row := q.db.QueryRow(ctx, createProductReturnPartial, arg.ID, arg.Category) 86 | var i CreateProductReturnPartialRow 87 | err := row.Scan(&i.ID, &i.Name) 88 | return i, err 89 | } 90 | 91 | const createUser = `-- name: CreateUser :one 92 | INSERT INTO users (id, location) VALUES ($1, $2) RETURNING id 93 | ` 94 | 95 | type CreateUserParams struct { 96 | ID pgtype.UUID `json:"id"` 97 | Location pgtype.UUID `json:"location"` 98 | } 99 | 100 | func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (pgtype.UUID, error) { 101 | row := q.db.QueryRow(ctx, createUser, arg.ID, arg.Location) 102 | var id pgtype.UUID 103 | err := row.Scan(&id) 104 | return id, err 105 | } 106 | 107 | const createUserReturnAll = `-- name: CreateUserReturnAll :one 108 | INSERT INTO users (id, location) VALUES ($1, $2) RETURNING id, location, name 109 | ` 110 | 111 | type CreateUserReturnAllParams struct { 112 | ID pgtype.UUID `json:"id"` 113 | Location pgtype.UUID `json:"location"` 114 | } 115 | 116 | func (q *Queries) CreateUserReturnAll(ctx context.Context, arg CreateUserReturnAllParams) (User, error) { 117 | row := q.db.QueryRow(ctx, createUserReturnAll, arg.ID, arg.Location) 118 | var i User 119 | err := row.Scan(&i.ID, &i.Location, &i.Name) 120 | return i, err 121 | } 122 | 123 | const createUserReturnPartial = `-- name: CreateUserReturnPartial :one 124 | INSERT INTO users (id, location) VALUES ($1, $2) RETURNING id, name 125 | ` 126 | 127 | type CreateUserReturnPartialParams struct { 128 | ID pgtype.UUID `json:"id"` 129 | Location pgtype.UUID `json:"location"` 130 | } 131 | 132 | type CreateUserReturnPartialRow struct { 133 | ID pgtype.UUID `json:"id"` 134 | Name pgtype.Text `json:"name"` 135 | } 136 | 137 | func (q *Queries) CreateUserReturnPartial(ctx context.Context, arg CreateUserReturnPartialParams) (CreateUserReturnPartialRow, error) { 138 | row := q.db.QueryRow(ctx, createUserReturnPartial, arg.ID, arg.Location) 139 | var i CreateUserReturnPartialRow 140 | err := row.Scan(&i.ID, &i.Name) 141 | return i, err 142 | } 143 | 144 | const getProductsByIds = `-- name: GetProductsByIds :many 145 | SELECT id, category, name FROM products WHERE id = ANY($1::uuid[]) 146 | ` 147 | 148 | func (q *Queries) GetProductsByIds(ctx context.Context, dollar_1 []pgtype.UUID) ([]Product, error) { 149 | rows, err := q.db.Query(ctx, getProductsByIds, dollar_1) 150 | if err != nil { 151 | return nil, err 152 | } 153 | defer rows.Close() 154 | items := []Product{} 155 | for rows.Next() { 156 | var i Product 157 | if err := rows.Scan(&i.ID, &i.Category, &i.Name); err != nil { 158 | return nil, err 159 | } 160 | items = append(items, i) 161 | } 162 | if err := rows.Err(); err != nil { 163 | return nil, err 164 | } 165 | return items, nil 166 | } 167 | -------------------------------------------------------------------------------- /_examples/uuidcheck/tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | _ "connectrpc.com/connect/cmd/protoc-gen-connect-go" 8 | _ "github.com/bufbuild/buf/cmd/buf" 9 | _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" 10 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 11 | ) 12 | -------------------------------------------------------------------------------- /engine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "io/fs" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "text/template" 14 | 15 | "github.com/walterwanderley/sqlc-grpc/converter" 16 | "github.com/walterwanderley/sqlc-grpc/metadata" 17 | "golang.org/x/tools/imports" 18 | 19 | "github.com/walterwanderley/sqlc-connect/templates" 20 | ) 21 | 22 | func process(def *metadata.Definition, outPath string, appendMode bool) error { 23 | return fs.WalkDir(templates.Files, ".", func(path string, d fs.DirEntry, err error) error { 24 | if err != nil { 25 | log.Println("ERROR ", err.Error()) 26 | return err 27 | } 28 | 29 | newPath := strings.TrimSuffix(path, ".tmpl") 30 | 31 | if d.IsDir() { 32 | if strings.HasSuffix(newPath, "server") { 33 | return nil 34 | } 35 | 36 | if strings.HasSuffix(newPath, "instrumentation") && (!def.DistributedTracing && !def.Metric) { 37 | return nil 38 | } 39 | 40 | if strings.HasSuffix(newPath, "trace") && !def.DistributedTracing { 41 | return nil 42 | } 43 | if strings.HasSuffix(newPath, "metric") && !def.Metric { 44 | return nil 45 | } 46 | if strings.HasSuffix(newPath, "litestream") && !(def.Database() == "sqlite" && def.Litestream) { 47 | return nil 48 | } 49 | 50 | if strings.HasSuffix(newPath, "litefs") && !(def.Database() == "sqlite" && def.LiteFS) { 51 | return nil 52 | } 53 | if _, err := os.Stat(newPath); os.IsNotExist(err) { 54 | err := os.MkdirAll(newPath, 0750) 55 | if err != nil { 56 | return err 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | if strings.HasSuffix(newPath, "templates.go") { 63 | return nil 64 | } 65 | 66 | log.Println(path, "...") 67 | 68 | in, err := templates.Files.Open(path) 69 | if err != nil { 70 | return err 71 | } 72 | defer in.Close() 73 | 74 | if strings.HasSuffix(newPath, "service.proto") { 75 | dir := strings.TrimSuffix(newPath, "service.proto") 76 | tpl, err := io.ReadAll(in) 77 | if err != nil { 78 | return err 79 | } 80 | for _, pkg := range def.Packages { 81 | dest := filepath.Join(dir, converter.ToSnakeCase(pkg.Package), "v1") 82 | if _, err := os.Stat(dest); os.IsNotExist(err) { 83 | err := os.MkdirAll(dest, 0750) 84 | if err != nil { 85 | return err 86 | } 87 | } 88 | destFile := filepath.Join(dest, (converter.ToSnakeCase(pkg.Package) + ".proto")) 89 | if appendMode && fileExists(destFile) { 90 | pkg.LoadOptions(destFile) 91 | } 92 | 93 | err = genFromTemplate(path, string(tpl), pkg, false, destFile) 94 | if err != nil { 95 | return err 96 | } 97 | } 98 | return nil 99 | } 100 | 101 | if strings.HasSuffix(newPath, "service.go") { 102 | tpl, err := io.ReadAll(in) 103 | if err != nil { 104 | return err 105 | } 106 | for _, pkg := range def.Packages { 107 | err = genFromTemplate(path, string(tpl), pkg, true, filepath.Join(pkg.SrcPath, "service.go")) 108 | if err != nil { 109 | return err 110 | } 111 | } 112 | return nil 113 | } 114 | 115 | if strings.HasSuffix(newPath, "service.factory.go") { 116 | tpl, err := io.ReadAll(in) 117 | if err != nil { 118 | return err 119 | } 120 | for _, pkg := range def.Packages { 121 | newPath := filepath.Join(pkg.SrcPath, "service.factory.go") 122 | if appendMode && fileExists(newPath) { 123 | return nil 124 | } 125 | err = genFromTemplate(path, string(tpl), pkg, true, newPath) 126 | if err != nil { 127 | return err 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | if strings.HasSuffix(newPath, "adapters.go") { 134 | tpl, err := io.ReadAll(in) 135 | if err != nil { 136 | return err 137 | } 138 | for _, pkg := range def.Packages { 139 | if len(pkg.OutputAdapters) > 0 || pkg.HasExecResult { 140 | err = genFromTemplate(path, string(tpl), pkg, true, filepath.Join(pkg.SrcPath, "adapters.go")) 141 | if err != nil { 142 | return err 143 | } 144 | } 145 | } 146 | return nil 147 | } 148 | 149 | if strings.HasSuffix(newPath, "tracing.go") && !def.DistributedTracing { 150 | return nil 151 | } 152 | 153 | if strings.HasSuffix(newPath, "metric.go") && !def.Metric { 154 | return nil 155 | } 156 | 157 | if strings.HasSuffix(newPath, "migration.go") && def.MigrationPath == "" { 158 | return nil 159 | } 160 | 161 | if strings.HasSuffix(newPath, "litestream.go") && !(def.Database() == "sqlite" && def.Litestream) { 162 | return nil 163 | } 164 | 165 | if (strings.HasSuffix(newPath, "litefs.go") || strings.HasSuffix(newPath, "forward.go")) && !(def.Database() == "sqlite" && def.LiteFS) { 166 | return nil 167 | } 168 | 169 | if strings.HasSuffix(path, ".tmpl") { 170 | tpl, err := io.ReadAll(in) 171 | if err != nil { 172 | return err 173 | } 174 | goCode := strings.HasSuffix(newPath, ".go") 175 | if goCode && appendMode && fileExists(newPath) && !strings.HasSuffix(newPath, "registry.go") { 176 | return nil 177 | } 178 | return genFromTemplate(path, string(tpl), def, goCode, newPath) 179 | } 180 | 181 | if appendMode && fileExists(newPath) { 182 | return nil 183 | } 184 | 185 | out, err := os.Create(newPath) 186 | if err != nil { 187 | return err 188 | } 189 | defer out.Close() 190 | 191 | _, err = io.Copy(out, in) 192 | return err 193 | }) 194 | } 195 | 196 | func genFromTemplate(name, tmp string, data interface{}, goSource bool, outPath string) error { 197 | w, err := os.Create(outPath) 198 | if err != nil { 199 | return err 200 | } 201 | defer w.Close() 202 | 203 | var b bytes.Buffer 204 | 205 | t, err := template.New(name).Funcs(templates.Funcs).Parse(tmp) 206 | if err != nil { 207 | return err 208 | } 209 | err = t.Execute(&b, data) 210 | if err != nil { 211 | return fmt.Errorf("execute template error: %w", err) 212 | } 213 | 214 | var src []byte 215 | if goSource { 216 | src, err = imports.Process("", b.Bytes(), nil) 217 | if err != nil { 218 | return fmt.Errorf("organize imports error: %w", err) 219 | } 220 | } else { 221 | src = b.Bytes() 222 | } 223 | 224 | fmt.Fprintf(w, "%s", string(src)) 225 | return nil 226 | 227 | } 228 | 229 | func fileExists(path string) bool { 230 | _, err := os.Stat(path) 231 | return !errors.Is(err, os.ErrNotExist) 232 | } 233 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/walterwanderley/sqlc-connect 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/walterwanderley/sqlc-grpc v0.19.11 7 | golang.org/x/mod v0.23.0 8 | golang.org/x/tools v0.30.0 9 | ) 10 | 11 | require ( 12 | github.com/emicklei/proto v1.14.0 // indirect 13 | golang.org/x/sync v0.11.0 // indirect 14 | gopkg.in/yaml.v3 v3.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= 2 | github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= 3 | github.com/emicklei/proto v1.14.0 h1:WYxC0OrBuuC+FUCTZvb8+fzEHdZMwLEF+OnVfZA3LXU= 4 | github.com/emicklei/proto v1.14.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= 5 | github.com/walterwanderley/sqlc-grpc v0.19.10 h1:lQkuIgxHQlSfPelrv2Ra4lJdlt9PXoBRgekBh1kr1xw= 6 | github.com/walterwanderley/sqlc-grpc v0.19.10/go.mod h1:jPSIJGvsNqOTqCpp6xVvIsgKdsxwn+O+/zS/cCFCxZ8= 7 | github.com/walterwanderley/sqlc-grpc v0.19.11 h1:HLcVhL1W2S5yF/Tg9Ler8yn0SBCqvdM+iUAhlU0X3+w= 8 | github.com/walterwanderley/sqlc-grpc v0.19.11/go.mod h1:qEa+8Ktavwnb8zRQtUzU04sfa8u8a79YLpQvqr0NESo= 9 | golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= 10 | golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 11 | golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= 12 | golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 13 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 14 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 15 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 16 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 17 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 18 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 19 | golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= 20 | golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= 21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 23 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 24 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 25 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "regexp" 12 | "sort" 13 | "strings" 14 | 15 | "golang.org/x/mod/modfile" 16 | 17 | "github.com/walterwanderley/sqlc-grpc/config" 18 | "github.com/walterwanderley/sqlc-grpc/metadata" 19 | ) 20 | 21 | var ( 22 | gomodPath string 23 | module string 24 | ignoreQueries string 25 | migrationPath string 26 | migrationLib string 27 | liteFS bool 28 | litestream bool 29 | distributedTracing bool 30 | metric bool 31 | appendMode bool 32 | showVersion bool 33 | help bool 34 | ) 35 | 36 | func main() { 37 | flag.BoolVar(&help, "h", false, "Help for this program") 38 | flag.BoolVar(&showVersion, "v", false, "Show version") 39 | flag.BoolVar(&appendMode, "append", false, "Enable append mode. Don't rewrite editable files") 40 | flag.StringVar(&gomodPath, "go.mod", "go.mod", "Path to go.mod file") 41 | flag.StringVar(&module, "m", "my-project", "Go module name if there are no go.mod") 42 | flag.StringVar(&ignoreQueries, "i", "", "Comma separated list (regex) of queries to ignore") 43 | flag.StringVar(&migrationPath, "migration-path", "", "Path to migration directory") 44 | flag.StringVar(&migrationLib, "migration-lib", "goose", "The migration library. goose or migrate") 45 | flag.BoolVar(&liteFS, "litefs", false, "Enable support to LiteFS") 46 | flag.BoolVar(&litestream, "litestream", false, "Enable support to Litestream") 47 | flag.BoolVar(&distributedTracing, "tracing", false, "Enable support to distributed tracing") 48 | flag.BoolVar(&metric, "metric", false, "Enable support to metrics") 49 | flag.Parse() 50 | 51 | if help { 52 | flag.PrintDefaults() 53 | fmt.Println("\nFor more information, please visit https://github.com/walterwanderley/sqlc-connect") 54 | return 55 | } 56 | 57 | if showVersion { 58 | fmt.Println(version) 59 | return 60 | } 61 | 62 | if migrationPath != "" { 63 | fi, err := os.Stat(migrationPath) 64 | if err != nil { 65 | log.Fatal("invalid -migration-path: ", err.Error()) 66 | } 67 | if !fi.IsDir() { 68 | log.Fatal("-migration-path must be a directory") 69 | } 70 | } 71 | 72 | cfg, err := config.Load() 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | 77 | queriesToIgnore := make([]*regexp.Regexp, 0) 78 | for _, queryName := range strings.Split(ignoreQueries, ",") { 79 | s := strings.TrimSpace(queryName) 80 | if s == "" { 81 | continue 82 | } 83 | queriesToIgnore = append(queriesToIgnore, regexp.MustCompile(s)) 84 | } 85 | 86 | if m := moduleFromGoMod(); m != "" { 87 | log.Println("Using module path from go.mod:", m) 88 | module = m 89 | } 90 | 91 | args := strings.Join(os.Args, " ") 92 | if !strings.Contains(args, " -append") { 93 | args += " -append" 94 | } 95 | 96 | def := metadata.Definition{ 97 | Args: args, 98 | GoModule: module, 99 | MigrationPath: migrationPath, 100 | MigrationLib: migrationLib, 101 | Packages: make([]*metadata.Package, 0), 102 | LiteFS: liteFS, 103 | Litestream: litestream, 104 | DistributedTracing: distributedTracing, 105 | Metric: metric, 106 | } 107 | 108 | for _, p := range cfg.Packages { 109 | pkg, err := metadata.ParsePackage(metadata.PackageOpts{ 110 | Path: p.Path, 111 | EmitInterface: p.EmitInterface, 112 | EmitParamsPointers: p.EmitParamsStructPointers, 113 | EmitResultPointers: p.EmitResultStructPointers, 114 | EmitDbArgument: p.EmitMethodsWithDBArgument, 115 | }, queriesToIgnore) 116 | if err != nil { 117 | log.Fatal("parser error:", err.Error()) 118 | } 119 | 120 | pkg.GoModule = module 121 | pkg.Engine = p.Engine 122 | if p.SqlPackage == "" { 123 | pkg.SqlPackage = "database/sql" 124 | } else { 125 | pkg.SqlPackage = p.SqlPackage 126 | } 127 | 128 | if len(pkg.Services) == 0 { 129 | log.Println("No services on package", pkg.Package) 130 | continue 131 | } 132 | 133 | def.Packages = append(def.Packages, pkg) 134 | } 135 | sort.SliceStable(def.Packages, func(i, j int) bool { 136 | return strings.Compare(def.Packages[i].Package, def.Packages[j].Package) < 0 137 | }) 138 | 139 | if err := def.Validate(); err != nil { 140 | log.Fatal(err.Error()) 141 | } 142 | 143 | wd, err := os.Getwd() 144 | if err != nil { 145 | log.Fatal("unable to get working directory:", err.Error()) 146 | } 147 | 148 | err = process(&def, wd, appendMode) 149 | if err != nil { 150 | log.Fatal("unable to process templates:", err.Error()) 151 | } 152 | 153 | postProcess(&def) 154 | } 155 | 156 | func moduleFromGoMod() string { 157 | f, err := os.Open(gomodPath) 158 | if err != nil { 159 | return "" 160 | } 161 | defer f.Close() 162 | 163 | b, err := io.ReadAll(f) 164 | if err != nil { 165 | return "" 166 | } 167 | 168 | return modfile.ModulePath(b) 169 | } 170 | 171 | func postProcess(def *metadata.Definition) { 172 | log.Printf("Configuring project %s...\n", def.GoModule) 173 | modDir := filepath.Dir(gomodPath) 174 | if modDir != "." { 175 | wd, err := os.Getwd() 176 | if err != nil { 177 | fmt.Println("current working directory: ", err.Error()) 178 | os.Exit(-1) 179 | } 180 | if err := os.Chdir(modDir); err != nil { 181 | fmt.Println("change working directory: ", err.Error()) 182 | os.Exit(-1) 183 | } 184 | execCommand("go mod init " + def.GoModule) 185 | execCommand("go get -u github.com/docker/docker") 186 | execCommand("go mod tidy") 187 | execCommand("go install google.golang.org/protobuf/cmd/protoc-gen-go") 188 | execCommand("go install connectrpc.com/connect/cmd/protoc-gen-connect-go") 189 | execCommand("go install github.com/bufbuild/buf/cmd/buf") 190 | execCommand("go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2") 191 | os.Chdir(wd) 192 | } else { 193 | execCommand("go mod init " + def.GoModule) 194 | execCommand("go get github.com/docker/docker") 195 | execCommand("go mod tidy") 196 | execCommand("go install google.golang.org/protobuf/cmd/protoc-gen-go") 197 | execCommand("go install connectrpc.com/connect/cmd/protoc-gen-connect-go") 198 | execCommand("go install github.com/bufbuild/buf/cmd/buf") 199 | execCommand("go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2") 200 | } 201 | log.Println("Compiling protocol buffers...") 202 | execCommand("buf dep update") 203 | execCommand("buf generate") 204 | execCommand("buf format -w") 205 | execCommand("go mod tidy") 206 | log.Println("Finished!") 207 | } 208 | 209 | func execCommand(command string) error { 210 | line := strings.Split(command, " ") 211 | cmd := exec.Command(line[0], line[1:]...) 212 | cmd.Stderr = os.Stderr 213 | cmd.Stdout = os.Stdout 214 | if err := cmd.Run(); err != nil { 215 | return fmt.Errorf("[error] %q: %w", command, err) 216 | } 217 | return nil 218 | } 219 | -------------------------------------------------------------------------------- /metadata/bind.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/walterwanderley/sqlc-grpc/converter" 8 | "github.com/walterwanderley/sqlc-grpc/metadata" 9 | ) 10 | 11 | func InputGrpc(s *metadata.Service) []string { 12 | res := make([]string, 0) 13 | if s.EmptyInput() { 14 | return res 15 | } 16 | 17 | if s.HasCustomParams() { 18 | typ := s.InputTypes[0] 19 | in := s.InputNames[0] 20 | if strings.HasPrefix(typ, "*") { 21 | res = append(res, fmt.Sprintf("%s := new(%s)", in, typ[1:])) 22 | } else { 23 | res = append(res, fmt.Sprintf("var %s %s", in, typ)) 24 | } 25 | m := s.Messages[converter.CanonicalName(typ)] 26 | for _, f := range m.Fields { 27 | attrName := converter.UpperFirstCharacter(f.Name) 28 | res = append(res, converter.BindToGo("req.Msg", fmt.Sprintf("%s.%s", in, attrName), attrName, f.Type, false)...) 29 | } 30 | } else { 31 | for i, n := range s.InputNames { 32 | res = append(res, converter.BindToGo("req.Msg", n, converter.UpperFirstCharacter(n), s.InputTypes[i], true)...) 33 | } 34 | } 35 | 36 | return res 37 | } 38 | 39 | func OutputGrpc(s *metadata.Service) []string { 40 | name := converter.UpperFirstCharacter(s.Name) 41 | res := make([]string, 0) 42 | if s.HasArrayOutput() { 43 | res = append(res, fmt.Sprintf("res := new(pb.%sResponse)", name)) 44 | res = append(res, "for _, r := range result {") 45 | res = append(res, fmt.Sprintf("res.List = append(res.List, to%s(r))", converter.CanonicalName(s.Output))) 46 | res = append(res, "}") 47 | res = append(res, "return connect.NewResponse(res), nil") 48 | return res 49 | } 50 | 51 | if s.HasCustomOutput() { 52 | res = append(res, fmt.Sprintf("return connect.NewResponse(&pb.%sResponse{%s: to%s(result)}), nil", name, converter.CamelCaseProto(converter.CanonicalName(s.Output)), converter.CanonicalName(s.Output))) 53 | return res 54 | } 55 | if s.EmptyOutput() { 56 | res = append(res, fmt.Sprintf("return connect.NewResponse(&pb.%sResponse{}), nil", name)) 57 | } else { 58 | if s.Output == "sql.Result" { 59 | res = append(res, fmt.Sprintf("return connect.NewResponse(&pb.%sResponse{Value: toExecResult(result)}), nil", name)) 60 | return res 61 | } 62 | if s.Output == "uuid.UUID" { 63 | res = append(res, fmt.Sprintf("return connect.NewResponse(&pb.%sResponse{Value: result.String()}), nil", name)) 64 | } else if s.Output == "pgtype.UUID" { 65 | res = append(res, "if uuidStr, err := result.MarshalJSON(); err != nil {") 66 | res = append(res, "return nil, fmt.Errorf(\"failed to convert UUID to string: %w\", err)") 67 | res = append(res, "} else {") 68 | res = append(res, fmt.Sprintf("return connect.NewResponse(&pb.%sResponse{Value: wrapperspb.String(string(uuidStr))}), nil", name)) 69 | res = append(res, "}") 70 | } else { 71 | res = append(res, fmt.Sprintf("return connect.NewResponse(&pb.%sResponse{Value: result}), nil", name)) 72 | } 73 | } 74 | 75 | return res 76 | } 77 | -------------------------------------------------------------------------------- /templates/adapters.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package {{.Package}} 4 | 5 | import ( 6 | "database/sql" 7 | "encoding/json" 8 | "fmt" 9 | "net" 10 | 11 | "github.com/google/uuid" 12 | "github.com/jackc/pgx/v5/pgconn" 13 | "google.golang.org/protobuf/types/known/timestamppb" 14 | "google.golang.org/protobuf/types/known/wrapperspb" 15 | 16 | pb "{{ .GoModule}}/api/{{.Package}}/v1" 17 | "{{.GoModule}}/internal/validation" 18 | ) 19 | 20 | {{$emitParamsPointers := .EmitParamsPointers}} 21 | {{$emitResultPointers := .EmitResultPointers}} 22 | {{range .OutputAdapters}} 23 | func to{{.Name}}(in {{if $emitResultPointers}}*{{end}}{{.Name}}) *pb.{{.Name | UpperFirstCharacter}} { 24 | {{if $emitResultPointers}}if in == nil { return nil }{{end}} 25 | out := new(pb.{{.Name | UpperFirstCharacter}}) 26 | {{range .AdapterToProto "in" "out"}}{{.}} 27 | {{end }}return out 28 | } 29 | {{end}} 30 | {{if .HasExecResult}} 31 | func toExecResult(in {{if eq .SqlPackage "pgx/v5"}}pgconn.CommandTag{{else}}sql.Result{{end}}) *pb.ExecResult { 32 | {{if eq .SqlPackage "pgx/v5"}}return &pb.ExecResult{ 33 | RowsAffected: in.RowsAffected(), 34 | }{{else}}lastInsertId, _ := in.LastInsertId() 35 | rowsAffected, _ := in.RowsAffected() 36 | return &pb.ExecResult{ 37 | LastInsertId: lastInsertId, 38 | RowsAffected: rowsAffected, 39 | }{{end}} 40 | } 41 | {{end}} -------------------------------------------------------------------------------- /templates/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | plugins: 3 | - local: protoc-gen-go 4 | out: api 5 | opt: paths=source_relative 6 | - local: protoc-gen-connect-go 7 | out: api 8 | opt: paths=source_relative 9 | - local: protoc-gen-openapiv2 10 | out: api 11 | opt: 12 | - generate_unbound_methods=true 13 | - logtostderr=true 14 | - allow_merge=true 15 | strategy: all -------------------------------------------------------------------------------- /templates/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | modules: 3 | - path: proto 4 | deps: 5 | - buf.build/googleapis/googleapis 6 | lint: 7 | use: 8 | - DEFAULT 9 | except: 10 | - FIELD_NOT_REQUIRED 11 | - PACKAGE_NO_IMPORT_CYCLE 12 | disallow_comment_ignores: true 13 | breaking: 14 | use: 15 | - FILE 16 | except: 17 | - EXTENSION_NO_DELETE 18 | - FIELD_SAME_DEFAULT 19 | -------------------------------------------------------------------------------- /templates/internal/server/instrumentation/metric/metric.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-grpc). 2 | 3 | package metric 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "log" 9 | "log/slog" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/prometheus/client_golang/prometheus/promhttp" 14 | "go.opentelemetry.io/otel" 15 | "go.opentelemetry.io/otel/exporters/prometheus" 16 | "go.opentelemetry.io/otel/sdk/metric" 17 | "go.opentelemetry.io/otel/sdk/resource" 18 | semconv "go.opentelemetry.io/otel/semconv/v1.21.0" 19 | ) 20 | 21 | func Init(port int, serviceName string) error { 22 | metricExporter, err := prometheus.New() 23 | if err != nil { 24 | return err 25 | } 26 | meterProvider := metric.NewMeterProvider( 27 | metric.WithReader(metricExporter), 28 | metric.WithResource(resource.NewWithAttributes( 29 | semconv.SchemaURL, 30 | semconv.ServiceNameKey.String(serviceName), 31 | )), 32 | ) 33 | otel.SetMeterProvider(meterProvider) 34 | 35 | mux := http.NewServeMux() 36 | mux.Handle("/metrics", promhttp.Handler()) 37 | httpServer := &http.Server{ 38 | Addr: fmt.Sprintf(":%d", port), 39 | ReadTimeout: 15 * time.Second, 40 | WriteTimeout: 15 * time.Second, 41 | IdleTimeout: 60 * time.Second, 42 | Handler: mux, 43 | } 44 | slog.Info("Metrics server running", "port", port) 45 | go func() { 46 | if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { 47 | log.Fatal(err.Error()) 48 | } 49 | }() 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /templates/internal/server/instrumentation/trace/tracing.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-grpc). 2 | 3 | package trace 4 | 5 | import ( 6 | "context" 7 | "log" 8 | 9 | "go.opentelemetry.io/otel" 10 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" 11 | "go.opentelemetry.io/otel/propagation" 12 | "go.opentelemetry.io/otel/sdk/resource" 13 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 14 | semconv "go.opentelemetry.io/otel/semconv/v1.21.0" 15 | ) 16 | 17 | func Init(ctx context.Context, serviceName string, endpoint string) (func(), error) { 18 | exp, err := otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(endpoint), otlptracegrpc.WithInsecure()) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | tp := tracesdk.NewTracerProvider( 24 | tracesdk.WithBatcher(exp), 25 | tracesdk.WithSampler(tracesdk.AlwaysSample()), 26 | tracesdk.WithResource(resource.NewWithAttributes( 27 | semconv.SchemaURL, 28 | semconv.ServiceNameKey.String(serviceName), 29 | )), 30 | ) 31 | 32 | otel.SetTracerProvider(tp) 33 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 34 | 35 | return func() { 36 | if err := tp.Shutdown(ctx); err != nil { 37 | log.Fatal(err.Error()) 38 | } 39 | }, nil 40 | } 41 | -------------------------------------------------------------------------------- /templates/internal/server/litefs/forward.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-grpc). 2 | 3 | package litefs 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "fmt" 9 | "io" 10 | "log/slog" 11 | "net/http" 12 | "time" 13 | 14 | "github.com/superfly/ltx" 15 | ) 16 | 17 | const txCookieName = "_txid" 18 | 19 | type RedirectTarget func() string 20 | 21 | func (lfs *LiteFS) ForwardToLeader(timeout time.Duration, methods ...string) func(http.Handler) http.Handler { 22 | return func(h http.Handler) http.Handler { 23 | return http.HandlerFunc(lfs.ForwardToLeaderFunc(h.ServeHTTP, timeout, methods...)) 24 | } 25 | } 26 | 27 | func (lfs *LiteFS) ForwardToLeaderFunc(h http.HandlerFunc, timeout time.Duration, methods ...string) http.HandlerFunc { 28 | return func(w http.ResponseWriter, r *http.Request) { 29 | var match bool 30 | 31 | for _, method := range methods { 32 | if r.Method == method { 33 | match = true 34 | break 35 | } 36 | } 37 | 38 | if !match { 39 | h(w, r) 40 | return 41 | } 42 | 43 | isLeader := lfs.store.IsPrimary() 44 | if isLeader { 45 | h(&responseWriter{w: w, lfs: lfs}, r) 46 | return 47 | } 48 | 49 | target := lfs.redirectTarget() 50 | if target == "" { 51 | http.Error(w, "leader redirect URL not found", http.StatusInternalServerError) 52 | return 53 | } 54 | 55 | if r.URL.Query().Get("forward") == "false" { 56 | w.Header().Set("location", string(target)) 57 | w.WriteHeader(http.StatusMovedPermanently) 58 | return 59 | } 60 | 61 | resp, err := forwardTo(target, r, timeout) 62 | if err != nil { 63 | http.Error(w, err.Error(), http.StatusInternalServerError) 64 | return 65 | } 66 | defer resp.Body.Close() 67 | for k, v := range resp.Header { 68 | for i, value := range v { 69 | if i == 0 { 70 | w.Header().Set(k, value) 71 | continue 72 | } 73 | w.Header().Add(k, value) 74 | } 75 | } 76 | w.WriteHeader(resp.StatusCode) 77 | io.Copy(w, resp.Body) 78 | } 79 | } 80 | 81 | func (lfs *LiteFS) ConsistentReader(timeout time.Duration, methods ...string) func(http.Handler) http.Handler { 82 | return func(h http.Handler) http.Handler { 83 | return http.HandlerFunc(lfs.ConsistentReaderFunc(h.ServeHTTP, timeout, methods...)) 84 | } 85 | } 86 | 87 | func (lfs *LiteFS) ConsistentReaderFunc(h http.HandlerFunc, timeout time.Duration, methods ...string) http.HandlerFunc { 88 | return func(w http.ResponseWriter, r *http.Request) { 89 | var match bool 90 | 91 | for _, method := range methods { 92 | if r.Method == method { 93 | match = true 94 | break 95 | } 96 | } 97 | 98 | if !match || lfs.store.IsPrimary() { 99 | h(w, r) 100 | return 101 | } 102 | 103 | var txID ltx.TXID 104 | if cookie, _ := r.Cookie(txCookieName); cookie != nil { 105 | var err error 106 | txID, err = ltx.ParseTXID(cookie.Value) 107 | if err != nil { 108 | slog.Warn("invalid cookie", "name", txCookieName, "error", err) 109 | h(w, r) 110 | return 111 | } 112 | } 113 | 114 | ticker := time.NewTicker(time.Millisecond) 115 | defer ticker.Stop() 116 | 117 | ctx, cancel := context.WithTimeout(r.Context(), timeout) 118 | defer cancel() 119 | 120 | var pos ltx.Pos 121 | LOOP: 122 | for { 123 | if pos = lfs.store.DBs()[0].Pos(); pos.TXID >= txID { 124 | break LOOP 125 | } 126 | 127 | select { 128 | case <-ctx.Done(): 129 | if r.URL.Query().Get("forward") == "false" { 130 | http.Error(w, "cosistent reader timeout", http.StatusGatewayTimeout) 131 | return 132 | } 133 | target := lfs.redirectTarget() 134 | if target == "" { 135 | http.Error(w, "leader redirect URL not found", http.StatusInternalServerError) 136 | return 137 | } 138 | resp, err := forwardTo(target, r, timeout) 139 | if err != nil { 140 | http.Error(w, err.Error(), http.StatusInternalServerError) 141 | return 142 | } 143 | defer resp.Body.Close() 144 | for k, v := range resp.Header { 145 | for i, value := range v { 146 | if i == 0 { 147 | w.Header().Set(k, value) 148 | continue 149 | } 150 | w.Header().Add(k, value) 151 | } 152 | } 153 | w.WriteHeader(resp.StatusCode) 154 | io.Copy(w, resp.Body) 155 | return 156 | case <-ticker.C: 157 | } 158 | } 159 | h(w, r) 160 | } 161 | } 162 | 163 | func forwardTo(addr string, req *http.Request, timeout time.Duration) (*http.Response, error) { 164 | newURL := addr + req.URL.Path + "?" + req.URL.RawQuery 165 | 166 | var buf bytes.Buffer 167 | defer req.Body.Close() 168 | _, err := io.Copy(&buf, req.Body) 169 | if err != nil { 170 | return nil, err 171 | } 172 | ctx, cancel := context.WithTimeout(req.Context(), timeout) 173 | defer cancel() 174 | newReq, err := http.NewRequestWithContext(ctx, req.Method, newURL, &buf) 175 | if err != nil { 176 | return nil, err 177 | } 178 | for k, v := range req.Header { 179 | for i, value := range v { 180 | if i == 0 { 181 | newReq.Header.Set(k, value) 182 | continue 183 | } 184 | newReq.Header.Add(k, value) 185 | } 186 | } 187 | return http.DefaultClient.Do(newReq) 188 | } 189 | 190 | type responseWriter struct { 191 | w http.ResponseWriter 192 | lfs *LiteFS 193 | statusCode int 194 | } 195 | 196 | func (rw *responseWriter) Header() http.Header { 197 | return rw.w.Header() 198 | } 199 | 200 | func (rw *responseWriter) Write(b []byte) (int, error) { 201 | if rw.statusCode == 0 || (rw.statusCode >= 200 && rw.statusCode < 300) { 202 | http.SetCookie(rw.w, &http.Cookie{ 203 | Name: txCookieName, 204 | Value: fmt.Sprint(rw.lfs.store.DBs()[0].Pos().TXID.String()), 205 | Expires: time.Now().Add(5 * time.Minute), 206 | HttpOnly: true, 207 | }) 208 | } 209 | return rw.w.Write(b) 210 | } 211 | 212 | func (rw *responseWriter) WriteHeader(statusCode int) { 213 | rw.statusCode = statusCode 214 | rw.w.WriteHeader(statusCode) 215 | } 216 | -------------------------------------------------------------------------------- /templates/internal/server/litestream/litestream.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-grpc). 2 | 3 | package litestream 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "net/url" 9 | "os" 10 | "path" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/benbjohnson/litestream" 15 | lss3 "github.com/benbjohnson/litestream/s3" 16 | ) 17 | 18 | func Replicate(ctx context.Context, dsn, replicaURL string) (*litestream.DB, error) { 19 | if i := strings.Index(dsn, "?"); i > 0 { 20 | dsn = dsn[0:i] 21 | } 22 | dsn = strings.TrimPrefix(dsn, "file:") 23 | 24 | lsdb := litestream.NewDB(dsn) 25 | 26 | u, err := url.Parse(replicaURL) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | scheme := "https" 32 | host := u.Host 33 | path := strings.TrimPrefix(path.Clean(u.Path), "/") 34 | bucket, region, endpoint, forcePathStyle := lss3.ParseHost(host) 35 | 36 | if s := os.Getenv("LITESTREAM_SCHEME"); s != "" { 37 | if s != "https" && s != "http" { 38 | return nil, fmt.Errorf("unsupported LITESTREAM_SCHEME value: %q", s) 39 | } else { 40 | scheme = s 41 | } 42 | } 43 | 44 | if e := os.Getenv("LITESTREAM_ENDPOINT"); e != "" { 45 | endpoint = e 46 | } 47 | 48 | if r := os.Getenv("LITESTREAM_REGION"); r != "" { 49 | region = r 50 | } 51 | 52 | if endpoint != "" { 53 | endpoint = scheme + "://" + endpoint 54 | } 55 | 56 | if fps := os.Getenv("LITESTREAM_FORCE_PATH_STYLE"); fps != "" { 57 | if b, err := strconv.ParseBool(fps); err != nil { 58 | return nil, fmt.Errorf("invalid LITESTREAM_FORCE_PATH_STYLE value: %q", fps) 59 | } else { 60 | forcePathStyle = b 61 | } 62 | } 63 | 64 | client := lss3.NewReplicaClient() 65 | client.Bucket = bucket 66 | client.Path = path 67 | client.Region = region 68 | client.Endpoint = endpoint 69 | client.ForcePathStyle = forcePathStyle 70 | 71 | replica := litestream.NewReplica(lsdb, lss3.ReplicaClientType) 72 | replica.Client = client 73 | 74 | lsdb.Replicas = append(lsdb.Replicas, replica) 75 | 76 | if err := restore(ctx, replica); err != nil { 77 | return nil, err 78 | } 79 | 80 | if err := lsdb.Open(); err != nil { 81 | return nil, err 82 | } 83 | 84 | if err := lsdb.Sync(ctx); err != nil { 85 | return nil, err 86 | } 87 | 88 | return lsdb, nil 89 | } 90 | 91 | func restore(ctx context.Context, replica *litestream.Replica) error { 92 | if _, err := os.Stat(replica.DB().Path()); err == nil { 93 | return nil 94 | } else if !os.IsNotExist(err) { 95 | return err 96 | } 97 | 98 | opt := litestream.NewRestoreOptions() 99 | opt.OutputPath = replica.DB().Path() 100 | 101 | var err error 102 | if opt.Generation, _, err = replica.CalcRestoreTarget(ctx, opt); err != nil { 103 | return err 104 | } 105 | 106 | if opt.Generation == "" { 107 | return nil 108 | } 109 | 110 | if err := replica.Restore(ctx, opt); err != nil { 111 | return err 112 | } 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /templates/internal/validation/validation.go.tmpl: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import "errors" 4 | 5 | var ErrUserInput = errors.New("") -------------------------------------------------------------------------------- /templates/migration.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-grpc (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "embed" 8 | "fmt" 9 | 10 | "github.com/golang-migrate/migrate/v4" 11 | driver "github.com/golang-migrate/migrate/v4/database/{{if eq .Database "postgresql"}}pgx/v5{{else}}{{.DatabaseDriver}}{{end}}" 12 | "github.com/golang-migrate/migrate/v4/source/iofs" 13 | "github.com/pressly/goose/v3" 14 | ) 15 | 16 | //go:embed {{.MigrationPath}} 17 | var migrations embed.FS 18 | 19 | func ensureSchema(db *sql.DB) error { 20 | {{if eq .MigrationLib "migrate"}}source, err := iofs.New(migrations, "{{.MigrationPath}}") 21 | if err != nil { 22 | return err 23 | } 24 | target, err := driver.WithInstance(db, new(driver.Config)) 25 | if err != nil { 26 | return err 27 | } 28 | m, err := migrate.NewWithInstance("iofs", source, "{{.Database}}", target) 29 | if err != nil { 30 | return err 31 | } 32 | err = m.Up() 33 | if err != nil && err != migrate.ErrNoChange { 34 | return err 35 | } 36 | return source.Close(){{else}}goose.SetBaseFS(migrations) 37 | 38 | if err := goose.SetDialect("{{if eq .Database "postgresql"}}postgres{{else}}{{.Database}}{{end}}"); err != nil { 39 | return err 40 | } 41 | 42 | return goose.Up(db, "{{.MigrationPath}}") 43 | {{end}} 44 | } -------------------------------------------------------------------------------- /templates/proto/service.proto.tmpl: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package {{.Package | SnakeCase}}.v1; 4 | 5 | {{range .ProtoImports}}{{ .}} 6 | {{end}} 7 | {{if .CustomProtoOptions}}{{range .CustomProtoOptions}}{{ .}} 8 | {{end}}{{else}}option go_package = "{{.GoModule}}/api/{{.Package | SnakeCase}}/v1"; 9 | {{end}} 10 | {{range .CustomServiceProtoComments}}// {{ .}} 11 | {{end -}} 12 | service {{.Package | PascalCase}}Service { 13 | {{range .CustomServiceProtoOptions}}{{ .}} 14 | {{end}} 15 | {{- range .CustomProtoRPCs}}{{ .}} 16 | {{end}} 17 | {{- range .Services}} 18 | {{range .CustomProtoComments}}// {{ .}} 19 | {{end -}} 20 | rpc {{.Name | UpperFirstCharacter}}({{.Name | UpperFirstCharacter}}Request) returns ({{.Name | UpperFirstCharacter}}Response) { } 21 | {{end}} 22 | } 23 | 24 | {{- range .CustomProtoMessages}}{{ .}} 25 | {{end}} 26 | 27 | {{range $key, $value := .Messages}} 28 | {{range $value.CustomProtoComments}}// {{ .}} 29 | {{end -}} 30 | message {{$value.ProtoName | UpperFirstCharacter}} { 31 | {{range $value.CustomProtoOptions}}{{ .}} 32 | {{end -}} 33 | {{$value.ProtoAttributes -}} 34 | } 35 | {{end}} 36 | {{if .HasExecResult}} 37 | message ExecResult { 38 | int64 rowsAffected = 1; 39 | int64 lastInsertId = 2; 40 | } 41 | {{end}} -------------------------------------------------------------------------------- /templates/registry.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "net/http" 8 | 9 | "connectrpc.com/connect" 10 | "connectrpc.com/grpcreflect" 11 | "github.com/jackc/pgx/v5/pgxpool" 12 | 13 | {{range .Packages}}{{.Package}}_app "{{ .GoModule}}/{{.SrcPath}}" 14 | {{.Package}}_v1connect "{{ .GoModule}}/api/{{.Package | SnakeCase}}/v1/v1connect" 15 | {{end}} 16 | ) 17 | 18 | 19 | func registerHandlers(mux *http.ServeMux, db {{if eq .SqlPackage "pgx/v5"}}*pgxpool.Pool{{else}}*sql.DB{{end}}, interceptors []connect.Interceptor) { 20 | {{range .Packages}}{{.Package}}Service := {{.Package}}_app.NewService({{if .EmitDbArgument}}{{.Package}}_app.New(), db{{else}}{{.Package}}_app.New(db){{end}}) 21 | {{.Package}}Path, {{.Package}}Handler := {{.Package}}_v1connect.New{{.Package | PascalCase}}ServiceHandler({{.Package}}Service, 22 | connect.WithInterceptors( 23 | interceptors..., 24 | ), 25 | ) 26 | mux.Handle({{.Package}}Path, {{.Package}}Handler) 27 | {{end}} 28 | 29 | reflector := grpcreflect.NewStaticReflector( 30 | {{range .Packages}}{{.Package}}_v1connect.{{.Package | PascalCase}}ServiceName, 31 | {{end}} 32 | ) 33 | mux.Handle(grpcreflect.NewHandlerV1(reflector)) 34 | mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) 35 | } -------------------------------------------------------------------------------- /templates/service.factory.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). 2 | 3 | package {{.Package}} 4 | 5 | import ( 6 | "database/sql" 7 | 8 | "github.com/jackc/pgx/v5/pgxpool" 9 | 10 | pb "{{ .GoModule}}/api/{{.Package}}/v1" 11 | "{{ .GoModule}}/api/{{.Package}}/v1/v1connect" 12 | ) 13 | 14 | // NewService is a constructor of a v1.{{ .Package | PascalCase}}ServiceHandler implementation. 15 | // Use this function to customize the server by adding middlewares to it. 16 | func NewService(querier {{if .EmitInterface}}Querier{{else}}*Queries{{end}}{{if .EmitDbArgument}}, db {{if eq .SqlPackage "pgx/v5"}}*pgxpool.Pool{{else}}*sql.DB{{end}}{{end}}) v1connect.{{ .Package | PascalCase}}ServiceHandler { 17 | return &Service{querier: querier{{if .EmitDbArgument}}, db: db{{end}}} 18 | } 19 | -------------------------------------------------------------------------------- /templates/service.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc-connect (https://github.com/walterwanderley/sqlc-connect). DO NOT EDIT. 2 | 3 | package {{.Package}} 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | "encoding/json" 9 | "fmt" 10 | "log/slog" 11 | "net" 12 | 13 | "connectrpc.com/connect" 14 | "github.com/google/uuid" 15 | "github.com/jackc/pgx/v5" 16 | "github.com/jackc/pgx/v5/pgtype" 17 | "github.com/jackc/pgx/v5/pgxpool" 18 | "google.golang.org/protobuf/types/known/timestamppb" 19 | "google.golang.org/protobuf/types/known/wrapperspb" 20 | 21 | pb "{{ .GoModule}}/api/{{.Package}}/v1" 22 | "{{ .GoModule}}/api/{{.Package}}/v1/v1connect" 23 | "{{ .GoModule}}/internal/validation" 24 | ) 25 | 26 | type Service struct { 27 | v1connect.Unimplemented{{ .Package | PascalCase}}ServiceHandler 28 | querier {{if .EmitInterface}}Querier{{else}}*Queries{{end}} 29 | {{if .EmitDbArgument}}db {{if eq .SqlPackage "database/sql"}}*sql.DB{{else}}*pgxpool.Pool{{end}}{{end}} 30 | } 31 | 32 | {{$emitDbArgument := .EmitDbArgument}} 33 | {{ range .Services }} 34 | func (s *Service) {{.Name | UpperFirstCharacter}}(ctx context.Context, req *connect.Request[pb.{{.Name | UpperFirstCharacter}}Request]) (*connect.Response[pb.{{.Name | UpperFirstCharacter}}Response], error) { 35 | {{ range . | Input}}{{ .}} 36 | {{end}} 37 | {{if not .EmptyOutput}}result, {{end}}err := s.querier.{{ .Name}}(ctx{{if $emitDbArgument}}, s.db{{end}}{{ .ParamsCallDatabase}}) 38 | if err != nil { 39 | slog.Error("sql call failed", "error", err, "method", "{{.Name}}") 40 | return nil, err 41 | } 42 | {{ range . | Output}}{{ .}} 43 | {{end -}} 44 | } 45 | {{ end }} 46 | -------------------------------------------------------------------------------- /templates/templates.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | import ( 4 | "embed" 5 | "html/template" 6 | 7 | "github.com/walterwanderley/sqlc-connect/metadata" 8 | "github.com/walterwanderley/sqlc-grpc/converter" 9 | ) 10 | 11 | //go:embed * 12 | var Files embed.FS 13 | 14 | var Funcs = template.FuncMap{ 15 | "PascalCase": converter.ToPascalCase, 16 | "SnakeCase": converter.ToSnakeCase, 17 | "UpperFirstCharacter": converter.UpperFirstCharacter, 18 | "Input": metadata.InputGrpc, 19 | "Output": metadata.OutputGrpc, 20 | } 21 | -------------------------------------------------------------------------------- /templates/tools/tools.go.tmpl: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | package tools 4 | 5 | import ( 6 | _ "github.com/bufbuild/buf/cmd/buf" 7 | _ "connectrpc.com/connect/cmd/protoc-gen-connect-go" 8 | _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" 9 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 10 | ) 11 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const version = "v0.3.9" 4 | --------------------------------------------------------------------------------