{"id":14976075,"url":"https://github.com/eventuallyconsultant/codegenr","last_synced_at":"2025-04-30T08:24:35.708Z","repository":{"id":45296544,"uuid":"423602993","full_name":"eventuallyconsultant/codegenr","owner":"eventuallyconsultant","description":"Fast handlebars templates based code generator, ingesting swagger/openapi and other json/yaml documents with $refs, or graphql schema, outputs whatever you template","archived":false,"fork":false,"pushed_at":"2024-08-27T20:55:30.000Z","size":481,"stargazers_count":31,"open_issues_count":4,"forks_count":3,"subscribers_count":1,"default_branch":"dev","last_synced_at":"2025-03-30T14:23:03.017Z","etag":null,"topics":["api-first","codegen","codegenerator","graphql","graphql-schema","handlebars","openapi","rhai","rust","swagger","yaml"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eventuallyconsultant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-01T20:13:25.000Z","updated_at":"2025-03-19T10:39:15.000Z","dependencies_parsed_at":"2023-01-23T17:46:06.747Z","dependency_job_id":"4aa57bf9-1b94-4459-8a2e-f01b0b9d6847","html_url":"https://github.com/eventuallyconsultant/codegenr","commit_stats":{"total_commits":300,"total_committers":3,"mean_commits":100.0,"dds":"0.19333333333333336","last_synced_commit":"1617d23430a0a5328fde08cab19bd3a0ec797818"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eventuallyconsultant%2Fcodegenr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eventuallyconsultant%2Fcodegenr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eventuallyconsultant%2Fcodegenr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eventuallyconsultant%2Fcodegenr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eventuallyconsultant","download_url":"https://codeload.github.com/eventuallyconsultant/codegenr/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251667481,"owners_count":21624506,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-first","codegen","codegenerator","graphql","graphql-schema","handlebars","openapi","rhai","rust","swagger","yaml"],"created_at":"2024-09-24T13:53:15.772Z","updated_at":"2025-04-30T08:24:35.689Z","avatar_url":"https://github.com/eventuallyconsultant.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CodeGenR\n\n![codegenr graphical explanation](_assets/codegenr.svg)\n\n## Installation\n\nInstall Rust : https://www.rust-lang.org/tools/install.\nAnd then install `codegenr`\n\n```\ncargo install codegenr\n```\n\nor install the development version\n\n```\ncargo install --git https://github.com/eventuallyconsultant/codegenr --branch dev\n```\n\n## Documentation\n\n[codegenr documentation on docs.rs](https://docs.rs/codegenr/latest)\n\n### Codegen Steps\n\nHere is a simple folders/files Tree we're gonna use in example\n\n```\n|- _specs\n  |- openapi.yaml\n  |- ...\n|- _templates\n    |- rest-tests\n      |- mytemplate.hbs\n      |- ...\n|- _rest-calls\n  |- generated\n    |- file1\n    |- ...\n```\n\n```mermaid\nflowchart LR\n    L[Load] --\u003e R{Is it?}\n    R --\u003e |$ref| L\n    R[Resolve] --\u003e |All resolved| RE\n    RE[Render] --\u003e PP\n    PP[Process]\n```\n\n#### codegenr.toml\n\nTo generate your files, you need to define these parameters :\n\n- `[section_name]` : A unique name representing each section\n- `source` : The file.yaml with the data you want to use for the generation\n- `templates` : the folders containing handlebar templates (`.hbs`) you're using. Only one file in those folders must not be prefixed by `_` and then is considered as the `main` template. The other ones are prefixed like `_partial.hbs` are considered `partial` templates.\n- `output` : The `root folder` where the files will be generated. All files output path write will be computed from this root.\n- `custom_helpers` : A place you can put `.rhai` file, each file is loaded as a custom helper, usable from the `.hbs` templates\n- `intermediate` : (Optional) if set, `codegenr` will output intermediate files for debug purpose\n- `global_parameters` : (Optional) Some values you want to use with the `global_parameter` helper.\n\n##### Here is an example of a section in the `codegenr.toml`.\n\n```toml\n[api_section]\nsource = \"./_specs/openapi.yaml\"\ntemplates = [ \"./_templates/misc/rest-tests\" ]\noutput = \"./_rest-calls\"\ncustom_helpers = [ \"./_templates/_custom_helpers\" ]\nintermediate = \"codegenr\"\nglobal_parameters =  { apiName = \"MyFirstApi\", apiRoot = \"/v1/api\" }\n```\n\n#### Load\n\nThe `load` step will read the `source` file and turn it to json\n\n- if it's a `json` file, it's quite easy\n- if it's a `yaml` file, it's not that hard\n- if it's a `graphql` sdl file, it's lead to some structure changes\n\nIf you look closely to example below, you can see that `$ref: \"#/components/schemas/GetMeResponse\"` refer to a specific path composed in 3 parts:\n\n- The `#` part is refering to the `same` document (Also: `file.yaml#...` would be referring to the document `file.yaml`)\n- `/components/schemas/` is the path `in` the file\n- `GetMeResponse` is the object we're looking for, here is just a simple example with a property `username` which contains a description and a type.\n\n```yaml\n# `some_openapi_file.yaml` example\nopenapi: 3.0.3\ninfo:\n  title: Example openapi\n  description: \"Openapi specifications\"\n  version: 1.0.0\nservers:\n  - url: http://localhost:8000\npaths:\n  /me:\n    get:\n      tags:\n        - user\n      summary: Get current users informations\n      operationId: get_current_user\n      responses:\n        \"200\":\n          description: Successful operation\n          content:\n            application/json:\n              schema:\n                $ref: \"#/components/schemas/GetMeResponse\"\n# ...\n# ...\ncomponents:\n  schemas:\n    GetMeResponse:\n      type: object\n      required:\n        - username\n      properties:\n        username:\n          type: string\n          description: a username/handle\n          example: just_a_username\n```\n\nfor more information : https://swagger.io/docs/specification/using-ref/\n\nthis is where the `resolve` step comes in the game :\n\n#### Resolve\n\nIf the file contains from json references (`$ref: \"...\"`), the resolver will replace the reference by the value pointed at by the reference. If the reference point at another file, it'll be loaded (previous step). Then it will continue to resolve references in the value, and so on, recursively, until all `$ref`s are replaced.\n\nIn this example, the loader finds a `$ref` in the `source.yaml` which is redirecting in the `other.yaml`, the loader will then load the `other.yaml` and resolve the reference that the `$ref` is pointing.\n\n```yaml\n# source.yaml\nopenapi: 3.0.3\ninfo:\n  title: Example\n  description: \"Just an example\"\n  version: 1.0.0\nservers:\n  - url: http://localhost:8000\npaths:\n  /user:\n    get:\n      tags:\n        - user\n      summary: Get current users informations\n      operationId: get_current_user\n      responses:\n        \"200\":\n          description: Successful operation\n          content:\n            application/json:\n              schema:\n                $ref: \"other.yaml#/components/schemas/UserResponse\"\n```\n\n```yaml\n# other.yaml\ncomponents:\n  schemas:\n    UserResponse:\n      type: object\n      required:\n        - username\n      properties:\n        username:\n          type: string\n          description: a username/handle\n          example: just_a_username\n```\n\n```json\n// all resolved \u0026 changed to json\n{\n  \"openapi\": \"3.0.3\",\n  \"info\": {\n    \"title\": \"Example\",\n    \"description\": \"Just an example\",\n    \"version\": \"1.0.0\"\n  },\n  \"servers\": [\n    {\n      \"url\": \"http://localhost:8000\"\n    }\n  ],\n  \"paths\": {\n    \"/user\": {\n      \"get\": {\n        \"tags\": [\"user\"],\n        \"summary\": \"Get current users informations\",\n        \"operationId\": \"get_current_user\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful operation\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"UserResponse\": {\n                    \"type\": \"object\",\n                    \"required\": [\"username\"],\n                    \"properties\": {\n                      \"username\": {\n                        \"type\": \"string\",\n                        \"description\": \"a username/handle\",\n                        \"example\": \"just_a_username\"\n                      }\n                    },\n                    \"x-fromRef\": \"other.yaml#/components/schemas/UserResponse\",\n                    \"x-refName\": \"UserResponse\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n```mermaid\nflowchart LR\n  L[Load] --\u003e F\n  F[source.yaml] --\u003e |All Resolved/No $ref| Rend\n  F --\u003e |$ref: other.yaml| L2\n  Res[Resolve] --\u003e F2\n  F2[other.yaml] --\u003e |No other $ref| Rend\n  L2[Load] --\u003e Res\n  Rend[Render] --\u003e PP\n  PP[Process]\n```\n\nFinally, when all the refs are resolved and all necessary files loaded, the render and process will do their job.\n\n#### Render\n\nHere is our handlebar example file named `mytemplate.hbs` which is in the `./_templates/misc/rest-tests` folder. \nThe goal of this template will be to ouptut a `.rest` file named after the `apiName`, that contains ready to use\none click examples from the swagger documentation. (usage with [Rest Client VsCode extension](https://marketplace.visualstudio.com/items?itemName=humao.rest-client))\n\nThis step will use the template folder you defined (`./_templates/misc/rest-tests` in the example above) to find all handlebars files (`mytemplate.hbs`, ...) \nand render the ONE `.bhs` main file (the one with no `_underscore`) using the `load \u0026 resolve` result as source, and the parameters defined in the `global_parameters` if there are some.\n\nfor more information about the handlebar syntax : https://handlebarsjs.com/guide/\n\n```handlebars\n{{!\nIn the following example, here is what the render will do :\nIt will define the variable `fileName` using the `snake_case` helper\nand the `global parameter` named `apiName` which is \"MyFirstApi\" as \nwe defined it in our example.\n\nSo the value set in `fileName` will be \"my_first_api\"\n}}\n{{set \"fileName\" (snake_case (global_parameter \"apiName\"))}}\n\n{{!\nThen it will reuse the `fileName` variable to output an instruction writing to a file.\nHere it will be `my_first_api.generated.rest`.\n(The process/processor instructions step will be explained later)\n}}\n### FILE {{get \"fileName\"}}.generated.rest\n\n{{!\nThos 3 variables, will be used later by the `.rest` rest client extensions.\n`apiRoot` is passed from the `codegenr` call arguments\n}}\n@host = localhost\n@port = 8000\n@api_root ={{global_parameter \"apiRoot\"}}\n\n{{!\nWe're now iterating with the helper `each` for each item contained\nin the \"paths\" section of the \"openapi.yaml\" file.\n`#each` is a standard handlebars helper. \nGo and RTFM to know more about [handlebars syntax](https://handlebarsjs.com/guide/). \n}}\n{{#each paths}}\n  {{!\n  We set a variable named `path` with the value from `@key`.\n  The variable scope will stay until the `with_set` block helper is ended \n  The first iteration will have the value \"/user\" here.\n  }}\n  {{#with_set \"path\" @key}}\n    {{!\n    Next we'll loop over the current value of the handlebars context: `this`.\n    \"this\" is the openapi path so it's children are http methods.\n    So we set the `httpMethod`. \n    }}\n    {{#each this}}\n    {{#with_set \"httpMethod\" @key}}\n        {{!\n        Now we output some nice `.rest` comments, to make it nice to read.\n        ex: it's `get_current_user` here\n        }}\n        # {{operationId}}\n        # --- --- --- --- --- --- --- --- --- --- --- ---\n        {{!\n        Is there a request body json example\n        }}\n        {{#if requestBody}}\n        {{#each requestBody.content}}{{#with_set \"contentType\" @key}}\n        {{#each examples}}\n        {{#no_empty_lines}}\n        {{!\n        We're gonna write our request using all informations we have\n        such as our `httpMethod` and `path`.\n        Your can see the `\\` before `{{host` for example. \n        This is in order to escape the helper, it's a `.rest` variable usage, not a `handlebars` helper call\n        }}\n        {{get \"httpMethod\"}} http://\\{{host}}:\\{{port}}\\{{api_root}}{{get \"path\"}} HTTP/1.1\n        content-type: {{get \"contentType\"}}\n        {{/no_empty_lines}}\n\n        {{#each this}}\n        {{json_to_str this format=\"json_pretty\"}}\n        {{/each}}\n\n        #### --- --- --- ---\n        {{/each}}\n        {{/with_set}}{{/each}}\n        {{else}}\n        {{!\n        Same with no example\n        }}\n        {{get \"httpMethod\"}} http://\\{{host}}:\\{{port}}\\{{api_root}}{{get \"path\"}} HTTP/1.1\n\n        #### --- --- --- ---\n        {{/if}}\n        {{get \"httpMethod\"}}\n        http://\\{{host}}:\\{{port}}\\{{api_root}}{{get \"path\"}}\n        HTTP/1.1 \n\n        {{#each this}}\n        {{json_to_str this format=\"json_pretty\"}}\n        {{/each}}\n        #### --- --- --- ---\n\n        {{!\n        finally we close all the openned helpers\n        }}\n      {{/with_set}}\n    {{/each}}\n  {{/with_set}}\n{{/each}}\n  \n{{!\nAnd the file written\n}}\n### /FILE\n```\n\nHere is what our render output will look at the end.\n\n```rest\n@host = localhost\n@port = 8080\n@api_root = /v1/api\n\n# get_me\n# --- --- --- --- --- --- --- --- --- --- --- ---\n\nget http://{{host}}:{{port}}{{api_root}}/user HTTP/1.1\n#### --- --- --- ---\n```\n\n#### Helpers\n\nHandlebars is a pretty limited language, but it's extended with `helpers` :\n- it has some standard helpers (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `and`, `or`, `not` ...), provided by the rust handlebars implementation : https://docs.rs/handlebars/latest/handlebars/#built-in-helpers\n- `codegenr` bundles the `handlebars_misc_helpers` to have more helpers : https://github.com/davidB/handlebars_misc_helpers\n- `codegenr` also provides some more home backed helpers : https://docs.rs/codegenr/latest/codegenr/helpers/index.html\n- **Finally** `you` can add your own helpers using the [rhai embedded scripting language](https://rhai.rs/)\n  - any `__myhelper__.rhai` file in the custom_helpers folder will be available as a `myhelper` handlebars inline helper, usable from the .hbs templates.\n\nHere is a simple example and what it is used for:\n\nThis yaml fragment is the definition of a response in your `file.yaml`, you can see that we defined for the `userCount` property the type and the format.\nThis property is also marked as `required` in the required array.\n\n```yaml\nDisplayUserCount:\n  description: Display the number of user\n  type: object\n  required:\n    - userCount\n  properties:\n    userCount:\n      type: integer\n      format: uint64\n```\n\nNow imagine you want to generate some `dart client` for this api, you now have to convert this property type to a type in `Dart`.\nIn this case, we can use the rhai file to perform this complex mapping : `dart_type_convert.rhai`\nIt will define a helper with 3 parameters :\n- `type`: we'll provide the type value (`\"integer\"` here)\n- `format`: we'll provide the format value (`\"uint64\"` here)\n- `optional`: we'll need to use the `is_oapi3_property_required` codegenr helper and negate it with `not` to pass `true` is the parameter is not found in the `required` array.\n\n```handlebars\n{{ dart_type_convert type format (not(is_oapi3_property_required @key ../required)) }}\n```\n\nwill output\n\n```dart\nint\n```\n\nThanks to this custom `rhai` helper :\n\n```rhai\n// dart_type_convert.rhai\nlet type = params[0];\nlet format = params[1];\nlet optional = if params.len() \u003e 2 { params[2] } else { false };\n\n// For more information about the data types, check the swagger doc\n// https://swagger.io/docs/specification/data-models/data-types/\n\nlet type_name = switch [type, format] {\n  [\"string\", \"\"] =\u003e \"String\",\n  [\"integer\", \"uint32\"] =\u003e \"int\",\n  [\"integer\", \"uint64\"] =\u003e \"int\",\n  [\"number\", \"float\"] =\u003e \"double\",\n  [\"number\", \"double\"] =\u003e \"double\",\n  _ =\u003e {\n    throw `UNKNOWN TYPE ${type} - ${format}`;\n    ()\n  }\n};\n\nif optional {\n  `${type_name}?`\n}else{\n  type_name\n}\n\n```\n\n\n\n\n#### Process\n\nThe `process` step is where the `render` output is took from memory to files or console ...\n\nFiles will written by following the instructions defined in handlebars template between `### FILE` and `### /FILE`.\n\nSome very simple example here :\n\n```yaml\n# source.yaml\nfileName: test\n```\n```handlebars\n{{! template.hbs }}\n### FILE {{fileName}}.txt \nHello\n### /FILE\n### CONSOLE \nWorld !\n### /CONSOLE\n```\n`shell output`\n```sh\nWorld !\n```\n`text.txt`\n```txt\nHello\n```\n\n## Contribute\n\nYou can also open the repository in GitPod with this button\n[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/eventuallyconsultant/codegenr)\n\nIn the command line you can :\n\n- `cargo test` to launch all the unit tests\n- `cargo doc --open` to compile and open the local documentation\n\n## Some command lines\n\n- `cargo doc --open` compile and open the documentation\n- `cargo install --path codegenr` installs codegenr command line from sources\n- `cargo install --git https://github.com/eventuallyconsultant/codegenr --branch dev` installs codegenr command line from the latest github `dev` branch\n\n## Legacy\n\nThis tool is based on the design of a precedent one written in `C#` : [CodegenUP](https://github.com/BeezUP/dotnet-codegen).\n\n## See also\n\n- a video in french, explaining a lot on this tool : https://www.youtube.com/watch?v=G--KdojP8pc\n\n## Roadmap :\n\n- [x] Load a yaml or json to serde::json\n- [x] Resolve `$ref` tags\n- [x] Pass all the resulting document to an handlebar template\n- [x] Implement some default helper (and make some doc tests about them)\n- [x] A plugin system\n- [x] Migrate C# custom helpers to this new plugin system\n- [x] A this point, we could use `codegenr` in place of `CodegenUP`, just by calling some commands\n- [x] Rename `codegenr-cli` to `codegenr` \u0026 `codegenr` to `codegenr-lib`\n- [x] Publish on `crates.io`\n- [x] Be able to have a `codegenr.toml` on a workspace root to describe all the templates to execute around the workspace\n- [x] Better Errors (typed ones)\n- [x] Resolved Json Cache optimisation\n- [x] all tests passing on windows too\n- [ ] Verbose/Tracing? mode\n- [ ] Be able to output some flowchart (mermaid?) of the codegenr section (source.yaml + templates + helpers =\u003e files in folders)\n- [ ] Be able to relate all sections in the same flowchart ... some target files can be source for other steps !!\n- [ ] Better examples\n- [ ] Smol strings optimisation ?\n- [ ] Watch mode for the file changes\n- [ ] Make a VSCode extension about all of this to make it live / super user friendly for `everyone`\n\n- [ ] Allow multiple swagger2 documents merging\n- [ ] Allow multiple swagger3 documents merging\n- [ ] Transform the json to an [OpenApi Generator](https://openapi-generator.tech/) model, and be able to use all the `OpenApi Generator` templates ?\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feventuallyconsultant%2Fcodegenr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feventuallyconsultant%2Fcodegenr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feventuallyconsultant%2Fcodegenr/lists"}