OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.48k stars 6.5k forks source link

Switching template system from Mustache to Handlebars (or something else) #510

Open wing328 opened 6 years ago

wing328 commented 6 years ago
Description

There were discussions about switching from Mustache to Handlebars (or something else) before. The goal was to simplify the templates as Mustache is a logicless template system and therefore we will need to create a lot of mustache tag(e.g. isInteger, isHttpBasic, etc) every time we need to compare values.

As discussed in https://github.com/swagger-api/swagger-codegen/issues/6077, Handlebars seems to be a good replacement. Here are a couple of questions we need to address:

The goal of this discussion is to collect feedback from the community and formula a plan to replace Mustache if needed

openapi-generator version

Future major release (4.x or later)

Related issues/PRs

It was previously discussed in https://github.com/swagger-api/swagger-codegen/issues/6077

wing328 commented 6 years ago

Here is my view:

grokify commented 6 years ago

Here are my thoughts:

jmini commented 6 years ago

I hope @jimschubert will join this discussion because I think that he has some vision to change the architecture of the responsabilities between the component.

For the moment we have:

If I remember well, @jimschubert wanted to organize responsibilities in an other way. If you want to introduce multiple template engine, thinking about who is responsible for what make sense.

grokify commented 6 years ago

If the interface to the generators, like JavaClientCodegen, GoClientCodegen, changes and we decide we need to have old and new systems running simultaneously because not all generators should be required to move at the same time, then one decision is whether a new template system would (a) only be on the new architecture or (b) be on the old and new architectures.

If the new template system is only on the new architecture, then it becomes more important to know the timing of the re-architecture.

Some of the re-architecture discussion in https://github.com/OpenAPITools/openapi-generator/issues/503. Should we have some wiki or repo markdown pages to discuss?

chatelao commented 6 years ago

Is Handlebars in the 4.0.x branch already available?

grokify commented 5 years ago

Is Handlebars in the 4.0.x branch already available?

You can see work being done on this in PR https://github.com/OpenAPITools/openapi-generator/pull/690 which hasn't been merged yet.

For now, you can get this in the rienafairefr/openapi-generator:templating branch.

cognifloyd commented 5 years ago

What about adding additional template engines that are never meant to become the default?

I'm in DevOps at a Python+Java (+ some JS though I haven't touched that yet) shop and rarely work with mustache or handlebars. I had to learn mustache to write the templates in #2270. Plus, the handlebars templates I've seen in swagger-codegen seem even more obtuse to me than mustache. Both feel clunky even if there are implementations in many languages.

I work with Jinja almost every day thanks to my DevOps work with Ansible, StackStorm, and other DevOps tools. So, for the python generators (this is the one I'm writing: #2270) in particular, I would prefer to work with Jinja templates to craft the python code. So for a python-focused generator I would like to use HubSpot/jinjava or something like that, but only for that generator. Of course, it's possible to write awful templates no matter the language, but allowing different languages to choose different template engines (assuming a java implementation is available) would be really nice.

jimschubert commented 5 years ago

@cognifloyd the long-term idea of #690 is to allow users to extend the "custom" template engines with whatever they prefer. For instance, to use Jinja this would require the user to create an adapter (or pull one from the community and add to claaspath), then explicitly select the template engine at execution time.

690 gets us a lot closer to that use case than we are today. I plan to get #690 into master this week, with a note that only Mustache is supported for embedded templates.

dkarlovi commented 4 years ago

Since https://github.com/OpenAPITools/openapi-generator/pull/2657 was merged, does that mean Handlebars is now fully supported?

lindgrenfredrik commented 4 years ago

I looking at migrating from Swagger-generator to Openapi-gen due to creating a issue on their git and not receiving any response for months

Currently have Handlebar templates for project so looking at running with it as template engine but found a issue with it:

In generateSupportingFiles in DefaultGenerator.java it looks if files end with correct template extension before compiling them as templates otherwise just writing them as files. This works fine when mustache files registered for the different languages end with correct mustache but when switching engine it expects handlebars extension which isn't whats registered by "language" generator The name conversion is done later in current setup (that is why it works for i.e. model files), which never gets called for Supporting files.

Any ideas on how to solve this properly?

Should I write separate issue for this?

My temporary workaround is to allow mustache named targets to pass into compile section https://github.com/OpenAPITools/openapi-generator/compare/master...lindgrenfredrik:aspnetcore_handlebars#diff-bd1652e990ffe227072a5c8908fd3054

behrangsa commented 4 years ago

Looks like this is merged to master. Is there a version based on master available?

jimschubert commented 4 years ago

@behrangsa I'm not sure what you're referring to as merged. We've had handlebars support throughout the 4.0 release. We don't have any built-in handlebars templates, and no real plans on the horizon to convert from mustache to handlebars as this would be an enormous effort.

spacether commented 3 years ago

When switching from mustache to handlebars one must:

spacether commented 2 years ago

I have made a tool that will allow us to automate conversion from mustache to handlebars templates It is here: https://github.com/spacether/mustache_to_handlebars

If people want to use this tool to switch their openapi generator templates, they can invoke it like:

mustache_to_handlebars /Users/yourusername/programming/openapi-generator/modules/openapi-generator/src/main/resources/python-experimental -handlebars_if_tags="hasProduces isString hasItems hasHttpSignatureMethods hasAuthMethods isBodyParam isHeaderParam isNullable isKeyInHeader isDouble hasBearerMethods isKeyInQuery isFloat isDateTime hasHttpBasicMethods hasRequiredVars isOAuth isUnboundedInteger isByteArray isCookieParam hasVars getIsNull isEnum isDate isBinary isAnyType isPathParam hasMore isFormParam hasConsumes isNumber getHasRequired isBasic hasApiKeyMethods useNose isHttpSignature hasValidation hasRequired isArray isBasicBearer isAlias isBasicBasic isShort isReadOnly isMap isLong isBoolean hasOAuthMethods isApiKey isQueryParam getHasDiscriminatorWithNonEmptyMapping isKeyInCookie indent appName appDescription version infoEmail complexType appDescriptionWithNewLines produces consumes minItems maxItems notes getUniqueItems minProperties pattern maxLength exclusiveMinimum maximum multipleOf returnTypeIsPrimitive minLength required additionalPropertiesIsAnyType arrayModelType recursionLimit httpUserAgent licenseInfo baseType infoName returnType defaultValue asyncio tornado additionalPropertiesType infoUrl exclusiveMaximum minimum nameInSnakeCase description collectionFormat bearerFormat summary vendorExtensions.x-regex maxProperties" -handlebars_each_tags="servers scopes vendorExtensions.x-auth-id-alias getComposedSchemas enumVars optionalParams optionalVars headers imports mappedModels requiredVars vars models apis authMethods requiredParams enumValues vendorExtensions.x-modifiers variables allParams responses allOf oneOf anyOf operation" -handlebars_with_tags="items additionalProperties model apiInfo operations allowableValues discriminator"

spacether commented 2 years ago

The python-experimental generator now successfully uses the handlebars templating engine by default. All templates for that generator are handlebars and must be handlebars for that generator to work. I specifically used that templating engine because it allows recursive indented partials. I use that to render schema definitions at any depth of inlining.

dziedrius commented 1 year ago

I've tried to use https://github.com/spacether/mustache_to_handlebars for typescript fetch template - it does some work, but there's more work needed to be do manually.

Where I got stuck and decided not to go further - seems that you need to register partials in code that is running templating engine - so in generator's javascript, which takes away lots of flexibility, like we're using partials to isolate custom code from original template, so that template upgrade would be much easier. Probably that could be easily solved by scanning template directory and registering all partials dynamically, but intent was not to touch generator's code.

Without support from others and mass migration to handlebars, I don't see this initiative as viable.

spacether commented 1 year ago

partials templates should work without registering them. If you have specific helper functions in java that are custom, they need to be registered. When using partials, partial templates need to indicate where they are relative to the root folder containing that generators templating files. So values like:

{{> components/schemas/_helper_getschemas }}

where components + schemas are folders and _helper_getschemas.hbs is the partial file

The only helpers that I use are these:

        Handlebars handlebars = new Handlebars(loader);
        handlebars.registerHelperMissing((obj, options) -> {
            LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
            return "";
        });
        handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
        StringHelpers.register(handlebars);
        handlebars.registerHelpers(ConditionalHelpers.class);
        handlebars.registerHelpers(org.openapijsonschematools.codegen.templating.handlebars.StringHelpers.class);
        handlebars.registerHelpers(CustomHelpers.class);
        handlebars.setInfiniteLoops(infiniteLoops);
        handlebars.setPrettyPrint(prettyPrint);
        Template tmpl = handlebars.compile(templateFile);
        return tmpl.apply(context);
bodograumann commented 10 months ago

Personally I have never missed logic in mustache when writing/modifying generator templates. (That doesn't mean it cannot improve things.) What I find more difficult, is knowing the data structure that I am actually working with in the templates.

I was thinking about mustache in a different context and wanted to mention the following point here. Mustache, like many templating tools, is mainly intended for generating HTML. That means

We are not only generating HTML here, but mostly source code. So wouldn't it be better to switch to a templating language that is geared towards code generation instead of website generation?

dobromyslov commented 3 months ago

This bug makes it impossible migrating to Handlebars - https://github.com/jknack/handlebars.java/issues/940#issuecomment-2206413325

It seems to me that HandlebarsEngineAdapter uses resolvers in incorrect order:

Context context = Context
        .newBuilder(bundle)
        .resolver(
                MapValueResolver.INSTANCE,
                JavaBeanValueResolver.INSTANCE,
                MY_FIELD_VALUE_RESOLVER.INSTANCE,
                MethodValueResolver.INSTANCE)
        .build();

Handlebars maintainer said that FieldValueResolver must be the last one and be used to access public fields to avoid illegal access exception:

Caused by: java.lang.IllegalStateException: Shouldn't be illegal to access field 'size'
at com.github.jknack.handlebars.context.FieldValueResolver.invokeMember (FieldValueResolver.java:217)
Caused by: java.lang.IllegalAccessException: class com.github.jknack.handlebars.context.FieldValueResolver$FieldMember cannot access a member of class java.util.HashMap (in module java.base) with modifiers "transient"
at com.github.jknack.handlebars.context.FieldValueResolver$FieldMember.get (FieldValueResolver.java:135)