Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP, lib v19] Source generators for Types and Requests with System.Text.Json support #1078

Draft
wants to merge 25 commits into
base: develop
Choose a base branch
from

Conversation

vova-lantsov-dev
Copy link

@vova-lantsov-dev vova-lantsov-dev commented Apr 9, 2022

Summary

This PR introduces 2 source generators that generate the classes for Types and Requests folders. Also it brings an API parser app.

API parser

It produces a JSON file according to the following schema: https://github.com/TelegramBots/Telegram.Bot/blob/abdab6f0fdc5dcf9e84364dfc431abce766ddcf4/src/Telegram.Bot.ApiParser/schema.json

Types source generator

Scriban template that will be rendered for each Bot API type:

private const string TemplateText = @"//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by the Telegram.Bot.Generators.ApiTypesGenerator source generator
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable
namespace {{ type_namespace }};
/// <summary>
{{~ for type_description_line in type_description | string.split '\n' ~}}
/// {{ type_description_line }}
{{~ end ~}}
/// </summary>
public partial class {{ type_name }}
{
{{~ for parameter in parameters ~}}
{{
func to_pascal_case(input)
$separated = input | string.split ""_"" | array.each do
ret $0 | string.capitalize
end
ret $separated | array.join """"
end
func get_parameter_type_name(input)
if parameter.is_enum
ret ""Telegram.Bot.Types.Enums."" | string.append input
else
ret input
end
end
pascal_case_name = parameter.parameter_name | to_pascal_case
is_required = parameter.parameter_description | !string.starts_with ""Optional""
parameter_type_name = parameter.parameter_type_name | get_parameter_type_name
}}
/// <summary>
{{~ for parameter_description_line in parameter.parameter_description | string.split '\n' ~}}
/// {{ parameter_description_line }}
{{~ end ~}}
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName(""{{ parameter.parameter_name }}"")]
{{~ if !is_required ~}}
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]
{{~ end ~}}
[Newtonsoft.Json.JsonProperty(""{{ parameter.parameter_name }}"", {{ if is_required ~}}Required = Newtonsoft.Json.Required.Always{{~ else ~}}DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore{{~ end ~}})]
public {{ parameter_type_name + "" "" + pascal_case_name }} { get; set; }
{{~ end ~}}
}";

where:

  • {{ type_namespace }} is namespace (starts with Telegram.Bot.Types but can additionally contain sub-namespace such as Telegram.Bot.Types.InlineQueryResults)
  • {{ type_description }} is type's multi-line description parsed from official Bot API page
  • {{ type_name }} is the class'es name, for example for Bot API type Chat we will generate Chat class located in file Chat.generated.cs
  • {{ parameter.parameter_description }} is property (parameter) multi-line description parsed from official Bot API page
  • {{ parameter.parameter_name }} is snake_case name of parameter parsed from official Bot API page
  • {{ parameter_type_name }} is fully qualified type name of property, including its namespace (if needed)
  • {{ pascal_case_name }} is property name in PascalCase

The main concepts of this generation are:

  • JsonIgnore attribute as well as Newtonsoft's Required and DefaultValueHandling arguments are only added when necessary
  • Generated classes are partial so they can be extended with custom logic
  • If class with corresponding name already exist and IS NOT partial - we skip generation of such class. If it exists and IS partial - class will be generated.

At the moment, the source generator looks for class types with public and without static modifiers, located inside Types folder and all sub-folders.

TODO

  • Add support for Required and Ignore attributes/arguments
  • Extend new Enum source generator with STJ serializers
  • Allow sub-namespaces for some types (such as Telegram.Bot.Types.InlineQueryResults)
  • Add support for custom mapping of type names (f.e. ForceReply -> ForceReplyMarkup)
  • Write unit tests

Requests source generator

TODO

@vova-lantsov-dev
Copy link
Author

@tuscen @karb0f0s @MihaZupan let's discuss this feature if we can bring it in 18.0.0 release

@karb0f0s karb0f0s linked an issue May 3, 2022 that may be closed by this pull request
@vova-lantsov-dev vova-lantsov-dev changed the title Source generator for System.Text.Json integration Source generator for Types folder May 8, 2022
@vova-lantsov-dev vova-lantsov-dev changed the title Source generator for Types folder Source generators for Types and Requests with System.Text.Json support May 9, 2022
@vova-lantsov-dev vova-lantsov-dev changed the title Source generators for Types and Requests with System.Text.Json support [WIP, lib v19] Source generators for Types and Requests with System.Text.Json support May 10, 2022
@Lorymi
Copy link

Lorymi commented Aug 8, 2022

Any update?

@tuscen
Copy link
Member

tuscen commented Aug 8, 2022

Unfortunately, I don’t have enough free time at the moment to review and test this PR.

@TelegramBots TelegramBots deleted a comment from ductai230894 Aug 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support System.Text.Json (as well as Newtonsoft.Json)
3 participants