Extend Codegen CLI - Templates and Debugging
Overview
This guide provides an in-depth look at how the Extend Codegen CLI generates SDK code from OpenAPI 2.0 specifications. Understanding the template engine, custom filters, and debugging tools will help you troubleshoot issues and potentially customize the code generation process.
This information applies to all SDK template packs (Unreal, Unity, Go, etc.) as they all use the same underlying Jinja2 templating engine with AccelByte-specific extensions.
The Code Generation Workflow
At its core, the Extend Codegen CLI is a simple template-based code generator. It takes two inputs and produces one output:
How the Makefile Orchestrates Multiple Generations
When you run make all in a template pack, it's actually calling the Extend Codegen CLI multiple times with different template and output combinations:
- Unreal
- Unity
- Go
# Simplified example of what the Unreal Makefile does:
# Generation 1: Model classes
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/operations-models.j2 \
--output Models/GuildModels.h
# Generation 2: Client API declarations
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/Client-operations-decl.j2 \
--output Api/GuildApi.h
# Generation 3: Client API implementations
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/Client-operations-def.j2 \
--output Api/GuildApi.cpp
# ... and so on for each file type
# Simplified example of what the Unity Makefile does:
# Generation 1: Model classes
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/operations-models.j2 \
--output Models/GuildModels.cs
# Generation 2: Client API classes
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/Client-operations.j2 \
--output Api/Guild.cs
# Generation 3: Client API wrapper
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/Client-wrapper.j2 \
--output Wrapper/GuildWrapper.cs
# ... and so on for each file type
# Simplified example of what the Go Makefile does:
# Generation 1: Model structs
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/operations-models.j2 \
--output guildService-sdk/pkg/guildserviceclientmodels/guild_models.go
# Generation 2: Client API operations
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/Client-operations.j2 \
--output guildService-sdk/pkg/guildserviceclient/guild_operations.go
# Generation 3: Short operations wrapper
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/Client-short-operations.j2 \
--output guildService-sdk/pkg/guildserviceclient/guild_short.go
# ... and so on for each file type
Each make target is essentially:
- Same INPUT 1: Your OpenAPI spec file (e.g.,
guild.json) - Different INPUT 2: A specific template file (e.g.,
operations-models.j2for models,Client-operations-decl.j2for headers) - Different OUTPUT: Target file in the appropriate directory with the appropriate name
To understand how code generation works for your SDK:
- Unreal
- Unity
- Go
- Download and extract
accelbyte-unreal-sdk-template-pack.zip - Open the
Makefile- each target shows which template, input, and output files are used - Examine template files in
templates/sdk_customization/to see Jinja2 syntax - Compare templates with generated
.hand.cppfiles to see the transformation
- Download and extract
accelbyte-unity-sdk-template-pack.zip - Open the
Makefile- each target shows which template, input, and output files are used - Examine template files in
templates/sdk_customization/to see Jinja2 syntax - Compare templates with generated
.csfiles to see the transformation
- Download and extract
accelbyte-go-sdk-template-pack.zip - Open the
Makefile- each target shows which template, input, and output files are used - Examine template files in
templates/sdk_customization/to see Jinja2 syntax - Compare templates with generated
.gofiles to see the transformation
This hands-on exploration is the best way to understand the generation process.
Jinja2 Template Engine
The code generation uses Jinja2, a powerful Python templating language.
Basic Template Syntax
{# This is a comment - not included in output #}
{{ variable }} {# Output a variable value #}
{% for item in items %} {# Loop through a list #}
Process {{ item }}
{% endfor %}
{% if condition %} {# Conditional logic #}
Do something
{% elif other_condition %}
Do something else
{% else %}
Default action
{% endif %}
{% set my_var = "value" %} {# Set a variable #}
{% import 'module.j2' as module %} {# Import macros from another template #}
{% macro func_name(params) %} {# Define a reusable template function #}
Macro content
{% endmacro %}
Example from SDK Templates
- Unreal
- Unity
- Go
Here's a real example from the Unreal SDK template (Client-operations-decl.j2):
{% import 'common/services.j2' as services -%}
{% set w_class_name = service.file_stem | to_pascal %}
{% set model_prefix = "FAccelByte" + service.file_stem | to_pascal %}
class ACCELBYTEUE4SDKCUSTOMIZATION_API {{ w_class_name }} : public Core::FApiBase
{
public:
{% for operation in operations %}
/**
* @brief {{ operation.summary }}.
*/
{% if operation.responses[0].field %}
{% set response_type = operation.responses[0].field | field_unreal_type(model_prefix) %}
THandler<{{ response_type }}> const& OnSuccess,
{% endif %}
{% endfor %}
};
This generates C++ code like:
class ACCELBYTEUE4SDKCUSTOMIZATION_API Guild : public Core::FApiBase
{
public:
/**
* @brief Create a new guild.
*/
THandler<FAccelByteGuildModelsCreateGuildResponse> const& OnSuccess,
};
Here's a real example from the Unity SDK template (Client-operations.j2):
{% set class_name = service.file_stem | to_pascal %}
{% set namespace = "AccelByte.Api" %}
namespace {{ namespace }}
{
public class {{ class_name }}
{
{% for operation in operations %}
/// <summary>
/// {{ operation.summary }}
/// </summary>
public void {{ operation.operation_id | to_pascal }}(
{% if operation.request_body %}
{{ operation.request_body.field | field_csharp_type }} body,
{% endif %}
{% if operation.responses[0].field %}
ResultCallback<{{ operation.responses[0].field | field_csharp_type }}> callback
{% endif %}
)
{
// Implementation...
}
{% endfor %}
}
}
This generates C# code like:
namespace AccelByte.Api
{
public class Guild
{
/// <summary>
/// Create a new guild
/// </summary>
public void CreateGuild(
CreateGuildRequest body,
ResultCallback<GuildResponse> callback
)
{
// Implementation...
}
}
}
Here's a real example from the Go SDK template (Client-operations.j2):
{% set package_name = "guildserviceclient" %}
package {{ package_name }}
import (
"github.com/AccelByte/accelbyte-go-modular-sdk/services-api/pkg/repository"
)
{% for operation in operations %}
// {{ operation.operation_id | to_pascal }} {{ operation.summary }}
func (a *{{ service.file_stem | to_pascal }}Service) {{ operation.operation_id | to_pascal }}(
{% if operation.request_body %}
body *{{ operation.request_body.field | field_go_type }},
{% endif %}
) (*{{ operation.responses[0].field | field_go_type }}, error) {
// Implementation...
}
{% endfor %}
This generates Go code like:
package guildserviceclient
import (
"github.com/AccelByte/accelbyte-go-modular-sdk/services-api/pkg/repository"
)
// CreateGuild Create a new guild
func (a *GuildService) CreateGuild(
body *CreateGuildRequest,
) (*GuildResponse, error) {
// Implementation...
}
Custom Jinja2 Filters
The Extend Codegen CLI provides extensive custom filters for code generation. These filters transform data from the OpenAPI spec into language-specific code.
The filters are organized into several categories:
Case Conversion Filters
Convert between different naming conventions (PascalCase, camelCase, snake_case, kebab-case, etc.). These are essential for generating code that follows language-specific naming conventions.
Example usage:
{% set class_name = service.file_stem | to_pascal %}
class {{ class_name }} {
// Converts "guild_service" to "GuildService"
}
Common filters: to_pascal, to_camel, to_snake, to_kebab, to_sentence, to_title
Language-Specific Type Filters
Convert OpenAPI types to target language types. Each SDK (Unreal, Unity, Go, etc.) has its own set of type conversion filters.
Unreal C++ filters:
- Type conversion:
field_unreal_type,unreal_class,unreal_enum_value - Formatting:
unreal_var,unreal_enum_name,field_unreal_param - Validation:
field_unreal_is_list_type
C# (Unity) filters:
- Type conversion:
field_csharp_type,csharp_class,csharp_enum_value - Formatting:
csharp_prop,csharp_var,csharp_enum_name - Generic handling:
csharp_generic_names
Go filters:
- Type conversion:
field_go_type,go_struct,go_var
Example:
{% for field in model.properties %}
{{ field | field_unreal_type("FAccelByte") }} {{ field.name | to_pascal }};
// Generates: FString GuildName;
{% endfor %}
String Manipulation Filters
Transform and manipulate strings for code generation:
- Add/remove:
prefix,suffix,affix,removeprefix,removesuffix - Parse:
split,strip,substr - Format:
inquotes
Regular Expression Filters
Pattern matching and text extraction:
- Search:
regex_search,regex_findall - Filter:
alnumonly,alphaonly,digitonly,numericonly
File and Content Filters
Include external content in templates:
filecontents- Read entire file contentsfilesnippet- Extract specific lines from files
Utility Filters
General-purpose transformation filters:
- Collections:
flatten,merge - Conditionals:
onlyif,ternary,boolean - Type handling:
typename,to_csv
Example Generation Filters
Create sample data for documentation and testing:
create_example- Generate example values based on OpenAPI schemacreate_example_json- Generate JSON examplescreate_example_wsm- Generate WebSocket message examples
To see the complete list of available filters for your template pack version, use the --inspect verbose flag as described in the next section.
Debugging and Exploring with Custom Templates
One of the most powerful ways to understand code generation is to create your own simple templates and experiment with the data and filters available.
Prerequisites for Testing
To test and debug template rendering, you need three things from the template pack:
config.yaml- Template pack configuration (included in every template pack)- Your OpenAPI spec - The
guild.json(or similar) file describing your API - A template file - Either an existing
.j2file or your own custom test template
Creating a Test Template
You can create a minimal template to explore what data is available. Create a file called test-template.j2:
{# Simple template to explore available data #}
Service Information:
File Stem: {{ service.file_stem }}
Base Path: {{ service.base_path }}
Operations Count: {{ operations | length }}
First Operation (if exists):
{% if operations %}
{% set op = operations[0] %}
Summary: {{ op.summary }}
Method: {{ op.method }}
Path: {{ op.path }}
{% endif %}
{# Test filters #}
Testing Filters:
to_pascal: {{ "hello_world" | to_pascal }}
to_camel: {{ "HelloWorld" | to_camel }}
to_snake: {{ "HelloWorld" | to_snake }}
Running Your Test Template
- Unreal
- Unity
- Go
# From within the extracted accelbyte-unreal-sdk-template-pack/ directory
docker run --rm \
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
accelbyte/extend-codegen-cli:0.0.28 renderc \
config.yaml \
--input /path/to/my-unreal-project/spec/guild.json \
--template test-template.j2
# From within the extracted accelbyte-unity-sdk-template-pack/ directory
docker run --rm \
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
accelbyte/extend-codegen-cli:0.0.28 renderc \
config.yaml \
--input /path/to/my-unity-project/spec/guild.json \
--template test-template.j2
# From within the extracted accelbyte-go-sdk-template-pack/ directory
docker run --rm \
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
accelbyte/extend-codegen-cli:0.0.28 renderc \
config.yaml \
--input /path/to/my/extend-service-extension-module-sdk/spec/guild.json \
--template test-template.j2
This outputs the rendered template to stdout, letting you see:
- What data structures are available
- What properties each object has
- How filters transform values
Using --inspect verbose for Deep Exploration
The --inspect verbose flag reveals the complete template environment:
- Unreal
- Unity
- Go
# From within accelbyte-unreal-sdk-template-pack/
docker run --rm \
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
accelbyte/extend-codegen-cli:0.0.28 renderc \
config.yaml \
--input /path/to/my-unreal-project/spec/guild.json \
--template test-template.j2 \
--inspect verbose
# From within accelbyte-unity-sdk-template-pack/
docker run --rm \
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
accelbyte/extend-codegen-cli:0.0.28 renderc \
config.yaml \
--input /path/to/my-unity-project/spec/guild.json \
--template test-template.j2 \
--inspect verbose
# From within accelbyte-go-sdk-template-pack/
docker run --rm \
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
accelbyte/extend-codegen-cli:0.0.28 renderc \
config.yaml \
--input /path/to/my/extend-service-extension-module-sdk/spec/guild.json \
--template test-template.j2 \
--inspect verbose
What --inspect verbose shows:
- Input processor information - How your OpenAPI spec is parsed
- Available filters - Complete list of all Jinja2 filters (200+) with their function signatures
- Global functions - Built-in functions like
range,dict,lipsum - Test functions - Conditionals like
is odd,is defined,is divisibleby - Template loader paths - Where templates are searched
- Start simple: Create a barebones template that just prints variable names
- Inspect data structure: Use
{{ variable }}to see what's in each object - Test filters: Try different filters on sample data to see their output
- Compare with existing templates: Look at the template pack's templates to see real-world usage
- Save inspect output: Run
--inspect verbose > filters.txtto reference later
Example exploration template:
{# Debug template - see all available data #}
Service: {{ service }}
Operations: {{ operations }}
Models: {{ models }}
Iterative Template Development
Recommended approach:
- Extract template pack and navigate to its directory
- Copy an existing template as a starting point (e.g.,
operations-models.j2) - Create a simple test case - minimal OpenAPI spec with one or two endpoints
- Modify and test - make small changes and re-run immediately
- Use
--inspect verbose- when you need to find a specific filter or check availability
Common Debugging Scenarios
Finding the right filter:
# Save inspect output and search it
docker run ... --inspect verbose > inspect.txt
grep "unreal" inspect.txt # Find Unreal-specific filters
grep "to_" inspect.txt # Find case conversion filters
Testing filter behavior:
{# Create a test template #}
Original: guild_service
to_pascal: {{ "guild_service" | to_pascal }}
to_camel: {{ "guild_service" | to_camel }}
to_snake: {{ "GuildService" | to_snake }}
Understanding data structure:
{# Explore what properties exist #}
{% for operation in operations %}
Operation {{ loop.index }}:
Path: {{ operation.path }}
Method: {{ operation.method }}
Summary: {{ operation.summary }}
Parameters: {{ operation.parameters | length }}
{% endfor %}
Configuration File (config.yaml)
The config.yaml file in each template pack configures the rendering process:
input_processor: 'accelbyte_codegen.ext.openapi2.legacy.OA2LegacyProcessor'
template_processor: 'textf'
renderer: 'accelbyte_codegen.ext.openapi2.legacy.OA2LegacyRenderer'
extension:
- '*jinja' # Standard Jinja2 extensions
- 'accelbyte_codegen.ext.openapi2.legacy.OA2LegacyExtension' # Custom filters
loader:
- './templates' # Template search path
output: 'stdout'
Key fields:
input_processor: Handles OpenAPI 2.0 parsingrenderer: Applies templates to parsed dataextension: Loads custom filters and functionsloader: Directories to search for templates
Common Debugging Scenarios
Generated Code Has Wrong Type
Problem: OpenAPI integer generates string instead of int.
Debug steps:
- Check the OpenAPI spec: Ensure the field is correctly defined as
"type": "integer" - Use
--inspect verboseto find the correct type conversion filter for your target language - Examine the template file to see which filter is being applied
- Verify the filter is called with the correct parameters
Missing or Incorrect Namespace
Problem: Generated code has wrong namespace or package name.
Debug steps:
- Verify the
basePathin your OpenAPI spec is correct - Check the OpenAPI
info.titlefield - Use
--inspect verboseto search for namespace-related filters - Examine how the template constructs namespace/package names
Custom Filter Not Working
Problem: Template uses a filter but output is incorrect or missing.
Debug steps:
- Run
--inspect verboseto verify the filter exists in your template pack version - Check the filter name spelling in your template
- Verify the input data type matches what the filter expects (filters may behave differently with strings vs. objects)
- Test with a minimal template to isolate the issue
Template Not Found Error
Problem: Code generation fails with "template not found" error.
Debug steps:
- Run
--inspect verboseto see the template loader paths - Verify the template file exists at the expected location
- Check for typos in the template filename or path
- Ensure Docker volume mounts include the template directory
Customizing Templates (Advanced)
While the template packs are designed to work out of the box, you can customize them for specific needs.
Customizing templates may make it difficult to upgrade to newer template pack versions. Document your changes carefully.
Steps to Customize
-
Extract the template pack:
unzip accelbyte-unreal-sdk-template-pack.zip
cd accelbyte-unreal-sdk-template-pack/ -
Locate the template to modify:
Templates are in
templates/sdk_customization/:operations-decl.j2- API class declarationsoperations-def.j2- API class implementationsoperations-models.j2- Model definitions
-
Make your changes:
Edit the Jinja2 template files using the filters documented above.
-
Test your changes:
Run the
makecommand and verify the generated output. -
Document your customizations:
Keep notes on what you changed and why for future reference.
Example Customization
Add a custom comment to all generated API classes:
{# In operations-decl.j2 #}
class {{ w_class_name }} : public Core::FApiBase
{
// Custom comment: Generated by AccelByte Extend Codegen CLI
// OpenAPI file: {{ service.file_stem }}.json
// Generated on: {{ "now" | strftime("%Y-%m-%d") }}
public:
{% for operation in operations %}
...
{% endfor %}
};
Additional Resources
- Extend Codegen CLI GitHub Repository - Source code and issue tracker
- Extend Codegen CLI Docker Hub - Available Docker image versions
- Jinja2 Documentation - Official Jinja2 template engine documentation
- Jinja2 Template Designer Documentation - Comprehensive template syntax guide
- OpenAPI 2.0 Specification - OpenAPI 2.0 (Swagger) specification reference
Summary
Understanding the Extend Codegen CLI's template system empowers you to:
- Debug code generation issues effectively using
--inspect verbose - Understand how OpenAPI specs map to generated code through Jinja2 templates
- Use the right filters for your target language and use case
- Customize templates when needed for special requirements
The combination of Jinja2's flexibility and AccelByte's extensive custom filters creates a powerful code generation system that can target multiple programming languages and SDKs from a single OpenAPI specification.