diff --git a/.gitignore b/.gitignore index c16c516..1d8a9b2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ node_modules/ # Buf cache .buf/ +# OpenAPI Generator CLI version/config (created when running gen.sh) +openapitools.json + # Build artifacts *.log *.tmp diff --git a/README.md b/README.md index 4014688..2104e5a 100644 --- a/README.md +++ b/README.md @@ -27,16 +27,34 @@ Used to store protos and auto-generated clients for public- and external-facing 3. **Big Integers**: Go's `big.Int` fields are represented as `string` fields in protobuf for maximum precision and compatibility. 4. **Timestamps**: Go's `time.Time` is mapped to `google.protobuf.Timestamp`. +## Prerequisites + +- **Go** – for protobuf/OpenAPI code generation (see `install-tool.sh`). +- **Node.js** – for Swagger merge, Redoc, and OpenAPI client generation. +- **Java 11+** – required only when generating API clients (TypeScript, Java, Python, C#). The client generator is Java-based. + ## Code Generation -Generate clients (for validation purposes): +Generate all code (Go, OpenAPI specs, docs) and API clients for all supported languages: ```bash make gen -# or directly: +# or: ./gen.sh +./gen.sh all +``` + +Generate clients for a single language: + +```bash +./gen.sh typescript # TypeScript (Fetch API) +./gen.sh java # Java (native HttpClient) +./gen.sh python # Python (urllib3) +./gen.sh csharp # C# (HttpClient, netstandard2.0) ``` +Clients are framework-agnostic. Each language is generated for both **egress** (shop APIs) and **ingress** (studio APIs) from their merged OpenAPI specs. + Lint protobuf files: ```bash @@ -51,37 +69,39 @@ make format ## Generated Output -To validate correctness of the protos, this repo comes with commands to generate: +Generation produces: - Go structs and gRPC client/server code - gRPC-Gateway HTTP/JSON bindings - OpenAPI v2 (swagger) documentation +- API clients for TypeScript, Java, Python, and C# for both egress and ingress (when requested via `./gen.sh [language]`) -Generated files are placed in the `gen/` directory and are not committed to version control. Instead, each project using these protos should build their own clients as needed. +Generated files are in the `gen/` directory and are not committed. Each consuming project should generate its own clients as needed. ### Generated Output Structure -Running `make gen` from the repo root creates: +Running `make gen` or `./gen.sh all` creates: ``` gen/ ├── proto/ # Generated Go code -│ └── server/shop/ -│ ├── catalog/v1/ # Catalog service Go structs & gRPC -│ ├── purchase/v1/ # Purchase service Go structs & gRPC -│ └── user/v1/ # User service Go structs & gRPC -├── openapiv2/ # OpenAPI/Swagger documentation -│ └── server/shop/ -│ ├── catalog/v1/ # service.swagger.json (rich) + enums.swagger.json (minimal) -│ ├── purchase/v1/ # service.swagger.json (rich) + enums.swagger.json (minimal) -│ └── user/v1/ # service.swagger.json (rich) -└── typescript/ # TypeScript API clients - └── server/shop/ - ├── catalog/ # catalog-client.ts - ├── purchase/ # purchase-client.ts - └── user/ # user-client.ts +│ └── server/... +├── openapiv2/ # Per-service OpenAPI/Swagger specs +│ └── server/... +├── clients-egress/ # Egress API clients (shop: catalog, user, purchase) +│ ├── typescript/ +│ ├── java/ +│ ├── python/ # package: stash_api +│ └── csharp/ +└── clients-ingress/ # Ingress API clients (studio) + ├── typescript/ + ├── java/ + ├── python/ # package: stash_api_ingress + └── csharp/ ``` +Merged Swagger and Redoc HTML are written to `docs/gen/`. + ### About Swagger Files - **Service files** (`service.swagger.json`) are rich with API endpoints and full documentation diff --git a/gen.sh b/gen.sh index 41d5e75..caf2bca 100755 --- a/gen.sh +++ b/gen.sh @@ -1,48 +1,125 @@ #!/bin/bash -# This script generates code from protobuf files using buf. -# It generates Go code, gRPC gateway code, and OpenAPI documentation. -# The generated code is placed in the gen/ directory for use in the project. +# Generates code from protobuf files (Go, OpenAPI) and API clients for TypeScript, Java, Python, C#. +# Clients are generated for both egress (gen/clients-egress/) and ingress (gen/clients-ingress/) specs. +# Usage: ./gen.sh [language] +# language: typescript | java | python | csharp | all (default: all) -set -ex # halt on error + print commands +set -e -# Install necessary tools -./install-tool.sh +usage() { + echo "Usage: ./gen.sh [language]" + echo " language: typescript | java | python | csharp | all (default: all)" + exit 1 +} + +LANG="${1:-all}" +case "$LANG" in + typescript|java|python|csharp|all) ;; + *) usage ;; +esac -# Add Go bin directory to PATH so protoc plugins can be found -# Go installs binaries to $GOPATH/bin or $HOME/go/bin +# Add Go bin to PATH for protoc plugins if [ -n "$GOPATH" ]; then - export PATH="$GOPATH/bin:$PATH" + export PATH="$GOPATH/bin:$PATH" else - export PATH="$HOME/go/bin:$PATH" + export PATH="$HOME/go/bin:$PATH" fi -# Clean up previous generated files +./install-tool.sh + rm -rf gen/ rm -rf docs/gen/ -# Generate Go code, gRPC services, gateway code, and OpenAPI docs +echo "Generating Go code and OpenAPI specs..." buf generate -# Generate TypeScript clients from OpenAPI specs -echo "Generating TypeScript clients..." -mkdir -p gen/typescript/server/egress/shop/catalog gen/typescript/server/egress/shop/user gen/typescript/server/egress/shop/purchase -npx --yes swagger-typescript-api@9.3.1 -p ./gen/openapiv2/server/egress/shop/catalog/v1/service.swagger.json -o ./gen/typescript/server/egress/shop/catalog/ -n catalog-client.ts --route-types --module-name-index=1 --no-client -npx swagger-typescript-api@9.3.1 -p ./gen/openapiv2/server/egress/shop/user/v1/service.swagger.json -o ./gen/typescript/server/egress/shop/user/ -n user-client.ts --route-types --module-name-index=1 --no-client -npx swagger-typescript-api@9.3.1 -p ./gen/openapiv2/server/egress/shop/purchase/v1/service.swagger.json -o ./gen/typescript/server/egress/shop/purchase/ -n purchase-client.ts --route-types --module-name-index=1 --no-client - -# Merge all OpenAPI specs into a single swagger file echo "Merging Swagger files..." -mkdir -p ./docs/gen/ +mkdir -p docs/gen npx --yes swagger-merger@1.5.4 -i ./docs/config/swagger-merger-config.json -o ./docs/gen/swagger.v1.json +npx --yes swagger-merger@1.5.4 -i ./docs/config/swagger-merger-ingress-config.json -o ./docs/gen/swagger.ingress.v1.json -echo "Building Redoc static HTML file..." +echo "Building Redoc docs..." npx --yes @redocly/cli@2.6.0 build-docs ./docs/gen/swagger.v1.json -o ./docs/gen/redoc.v1.html +npx --yes @redocly/cli@2.6.0 build-docs ./docs/gen/swagger.ingress.v1.json -o ./docs/gen/redoc.ingress.v1.html + +check_java() { + if ! command -v java &>/dev/null; then + echo "Java 11+ is required for client generation. Install Java and try again." + exit 1 + fi +} + +run_openapi_gen() { + check_java + local spec="$1" + local generator="$2" + local output_dir="$3" + shift 3 + npx --yes @openapitools/openapi-generator-cli generate \ + -i "$spec" \ + -g "$generator" \ + -o "$output_dir" \ + --skip-validate-spec \ + "$@" +} + +generate_for_lang() { + local lang="$1" + local egress_out="$2" + local ingress_out="$3" + local py_package_egress="$4" + local py_package_ingress="$5" -echo "Merging Ingress Swagger files..." -npx swagger-merger@1.5.4 -i ./docs/config/swagger-merger-ingress-config.json -o ./docs/gen/swagger.ingress.v1.json + case "$lang" in + typescript) + echo "Generating TypeScript client (egress)..." + mkdir -p "$egress_out" + run_openapi_gen ./docs/gen/swagger.v1.json typescript-fetch "$egress_out" + echo "Generating TypeScript client (ingress)..." + mkdir -p "$ingress_out" + run_openapi_gen ./docs/gen/swagger.ingress.v1.json typescript-fetch "$ingress_out" + ;; + java) + echo "Generating Java client (egress)..." + mkdir -p "$egress_out" + run_openapi_gen ./docs/gen/swagger.v1.json java "$egress_out" --additional-properties=library=native + echo "Generating Java client (ingress)..." + mkdir -p "$ingress_out" + run_openapi_gen ./docs/gen/swagger.ingress.v1.json java "$ingress_out" --additional-properties=library=native + ;; + python) + echo "Generating Python client (egress)..." + mkdir -p "$egress_out" + run_openapi_gen ./docs/gen/swagger.v1.json python "$egress_out" --additional-properties=packageName="$py_package_egress" + echo "Generating Python client (ingress)..." + mkdir -p "$ingress_out" + run_openapi_gen ./docs/gen/swagger.ingress.v1.json python "$ingress_out" --additional-properties=packageName="$py_package_ingress" + ;; + csharp) + echo "Generating C# client (egress)..." + mkdir -p "$egress_out" + run_openapi_gen ./docs/gen/swagger.v1.json csharp "$egress_out" \ + --additional-properties=library=httpclient,targetFramework=netstandard2.0 + echo "Generating C# client (ingress)..." + mkdir -p "$ingress_out" + run_openapi_gen ./docs/gen/swagger.ingress.v1.json csharp "$ingress_out" \ + --additional-properties=library=httpclient,targetFramework=netstandard2.0 + ;; + esac +} -echo "Building Ingress Redoc static HTML file..." -npx @redocly/cli@2.6.0 build-docs ./docs/gen/swagger.ingress.v1.json -o ./docs/gen/redoc.ingress.v1.html +case "$LANG" in + typescript) generate_for_lang typescript gen/clients-egress/typescript gen/clients-ingress/typescript stash_api stash_api_ingress ;; + java) generate_for_lang java gen/clients-egress/java gen/clients-ingress/java stash_api stash_api_ingress ;; + python) generate_for_lang python gen/clients-egress/python gen/clients-ingress/python stash_api stash_api_ingress ;; + csharp) generate_for_lang csharp gen/clients-egress/csharp gen/clients-ingress/csharp stash_api stash_api_ingress ;; + all) + generate_for_lang typescript gen/clients-egress/typescript gen/clients-ingress/typescript stash_api stash_api_ingress + generate_for_lang java gen/clients-egress/java gen/clients-ingress/java stash_api stash_api_ingress + generate_for_lang python gen/clients-egress/python gen/clients-ingress/python stash_api stash_api_ingress + generate_for_lang csharp gen/clients-egress/csharp gen/clients-ingress/csharp stash_api stash_api_ingress + ;; +esac echo "Code generation completed successfully!"