ppy / osu-web

the browser-facing portion of osu!
https://osu.ppy.sh
GNU Affero General Public License v3.0
970 stars 380 forks source link

Not working well with OpenAPI Generator #10356

Open stevefan1999-personal opened 1 year ago

stevefan1999-personal commented 1 year ago

I really want to make a discussion thread instead of putting this as an issue but whatever for now.

I want to generate a C# OpenAPI client through the use of OpenAPI and the OpenAPI Generator, here's the command I used:

openapi-generator-cli generate `
    -g csharp `
    -i https://osu.ppy.sh/docs/openapi.yaml `
    -c config.yaml

config.yaml:

packageName: OsuApi.V2.OpenAPI.Generated
returnICollection: true
targetFramework: netstandard2.1
useCollection: true
nullableReferenceTypes: true
netCoreProjectFile: true
packageAuthors: stevefan1999
packageVersion: 0.0.1
sourceFolder: .
useDateTimeOffset: true
validatable: false
optionalEmitDefaultValues: true
library: httpclient

I did generate a pretty nice-looking C# OpenAPI library (maybe make it officially generated by peppy someday), but there is a couple of problems along the journey.

First it seems like HTML inside an example is not parsed correctly: https://github.com/ppy/osu-web/blob/708f7f859e2cbfc27b4b32f297a2b7c1ae98ea2c/resources/views/docs/_structures/changelog_entry.md?plain=1#L13

Results in this:

        /// <summary>
        /// Gets or Sets MessageHtml
        /// </summary>
        /// <example>&lt;div class&#x3D;&#39;changelog-md&#39;&gt;&lt;p class&#x3D;&quot;changelog-md__paragraph&quot;&gt;New seasonal backgrounds ahoy! Amazing work by the artists.&lt;/p&gt;
&lt;/div&gt;</example>
        [DataMember(Name = "message_html", EmitDefaultValue = true)]
        public string MessageHtml { get; set; }

Another problem is that the response format for TopicsController\show is not well formed: https://github.com/ppy/osu-web/blob/708f7f859e2cbfc27b4b32f297a2b7c1ae98ea2c/app/Http/Controllers/Forum/TopicsController.php#L294-L303

Which results in this:

        /// <summary>
        /// Initializes a new instance of the <see cref="GetTopicAndPosts200ResponseTopic" /> class.
        /// </summary>
        /// <param name="id">id.</param>
        /// <param name="">.</param>
        public GetTopicAndPosts200ResponseTopic(int id = default(int), string  = default(string))
        {
            this.Id = id;
            this.___ = ;
        }

After fixing both problem we see another problem with body parameter not well-parsable:

    /// <summary>
        /// Initializes a new instance of the <see cref="GetBeatmapAttributesRequest" /> class.
        /// </summary>
        /// <param name="mods">Mod combination. Can be either a bitset of mods, array of mod acronyms, or array of mods. Defaults to no mods..</param>
        /// <param name="ruleset">Ruleset of the difficulty attributes. Only valid if it&#39;s the beatmap ruleset or the beatmap can be converted to the specified ruleset. Defaults to ruleset of the specified beatmap..</param>
        /// <param name="rulesetId">The same as &#x60;ruleset&#x60; but in integer form..</param>
        public GetBeatmapAttributesRequest(Collection<NumberStringMod> mods = default(Collection<NumberStringMod>), GameMode ruleset = default(GameMode), int rulesetId = default(int))
        {
            this.Mods = mods;
            this.Ruleset = ruleset;
            this.RulesetId = rulesetId;
        }

        /// <summary>
        /// Mod combination. Can be either a bitset of mods, array of mod acronyms, or array of mods. Defaults to no mods.
        /// </summary>
        /// <value>Mod combination. Can be either a bitset of mods, array of mod acronyms, or array of mods. Defaults to no mods.</value>
        /// <example>1</example>
        [DataMember(Name = "mods", EmitDefaultValue = true)]
        public Collection<NumberStringMod> Mods { get; set; }

        /// <summary>
        /// Ruleset of the difficulty attributes. Only valid if it&#39;s the beatmap ruleset or the beatmap can be converted to the specified ruleset. Defaults to ruleset of the specified beatmap.
        /// </summary>
        /// <value>Ruleset of the difficulty attributes. Only valid if it&#39;s the beatmap ruleset or the beatmap can be converted to the specified ruleset. Defaults to ruleset of the specified beatmap.</value>
        /// <example>osu</example>
        [DataMember(Name = "ruleset", EmitDefaultValue = true)]
        public GameMode Ruleset { get; set; }

https://github.com/ppy/osu-web/blob/708f7f859e2cbfc27b4b32f297a2b7c1ae98ea2c/app/Http/Controllers/BeatmapsController.php#L34-L59

This is however not a solvable issue because C# did not support discriminated union which is proposed (but it exists in F#): https://github.com/dotnet/csharplang/blob/main/proposals/discriminated-unions.md

So this is not an easy problem to solve for now. I simply just yeeted all NumberStringMod into string and hope it will work. After that all the source code can be compiled and I got a semi-working osu API v2 client in C#!

@jimschubert Should I cross reference this to https://github.com/OpenAPITools/openapi-generator team?

Another small issue is that I did not see the GameMode definition in the OpenAPI schema either so the class GameMode for the Ruleset doesn't really exists for some reason, but it is here as an enum: https://github.com/ppy/osu-web/blob/HEAD/resources/js/interfaces/game-mode.ts

nanaya commented 1 year ago

I don't think anyone ever looked into it before 👀 it's just part of the document generator