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.82k stars 6.58k forks source link

[C#] Codegen for Unity #853

Closed iBicha closed 1 year ago

iBicha commented 6 years ago
Description

This is an issue to discuss how a C# generator can be effectively useful in a Unity project. I've been told the C# codegen is under a refactor phase, so my suggestion is to consider the next few points, to maybe include in this refactor.

I don't know if Unity will require a separate template, but it's a possibility.

Related issues/PRs

This issue will extend the points discussed here, kindly consider taking a look at it before continuing: https://github.com/swagger-api/swagger-codegen/issues/8569

Suggest a fix/enhancement

Here's a list of things one might want to consider when supporting codegen for Unity:

Note that the response is returned on the main thread (and of course, the deserialization of the response should happen on a background thread).

Also, this can easily be made into an async/await style by creating an Awaiter, maybe something like this:

    //UnityWebRequestAsyncOperation is an AsyncOperation
    public static TaskAwaiter GetAwaiter(this AsyncOperation asyncOperation)
    {
        var taskCompletionSource = new TaskCompletionSource<bool>();

        asyncOperation.completed += operation => { taskCompletionSource.TrySetResult(true); };

        return ((Task) taskCompletionSource.Task).GetAwaiter();
    }

Which will bring me to the next point:

public class API {
//
//APIs using Coroutines here, which is supported on both runtime versions
//

#if NET_4_6

//
//APIs using async/await here, only available in 4.6
//

#endif
}
#if ENABLE_IL2CPP
//THIS IS NEEDED SO THAT IL2CPP CAN JSON DESERIALIZE List<decimal?>
    void UsedOnlyForAOTCodeGeneration()
    {
        new CollectionWrapper<decimal?>((IList<decimal?>) new List<decimal?>());
    }
#endif
}

Once the constructor is actually called anywhere in the code, the code converter picks it up and generate equivalent C++ code for it.

Note 1: When il2cpp backend is used, the symbol ENABLE_IL2CPP is defined. Note 2: in this case, CollectionWrapper is internal, that's why I had to make it public, and recompile the Json.Net library. For this reason, any method generic virtual method needed for the correct deserialization can be generated accordingly, as needed (a work around for private/internal types might be needed)

Please feel free to ask questions, and discuss. But from my experience with codegen and Unity, these are the main points that would make working with REST APIs a seamless experience when using Unity.

iBicha commented 6 years ago

@wing328 Here's my take on a Unity C# generator

wing328 commented 6 years ago

@iBicha thanks for sharing your feedback. I've copied the C# technical committee below:

cc @mandrean (2017/08) @jimschubert (2017/09)

ikriz commented 6 years ago

What's stopping you from using the unity editor integrated JSON utility? Code generation will never run at runtime. I think that's better than introducing a new dependency (aka newtonsoft)

iBicha commented 6 years ago

That's a good question. Just to clarify, the JSON deserialization will run at runtime during api calls. But the reason the json utility probably not the best suited is because it's very limited when it comes to supported types.

jimschubert commented 6 years ago

@iBicha have you taken a look at the refactor work you've linked? I'm wondering if the API abstractions I've made would support the UnityWebRequest cleanly.

The intention of the refactor is that it allows users to write their own simple adaptors for api query operations such that you can have a task-based asynchronous client or a synchronous client. The API implementation types then allow you to set these client instances directly.

So, for example, you could create a file, UnityAsyncClient:

class UnityAsyncClient : IAsynchronousClient {
    public Task<ApiResponse<T>> GetAsync<T>(String path, RequestOptions options, IReadableConfiguration configuration = null) {
        var config = configuration ?? GlobalConfiguration.Instance;
        using (UnityWebRequest www = UnityWebRequest.Get(config.BasePath + path))
        {
            var query = www.Send();

            if (www.isNetworkError || www.isHttpError)
            {
                Debug.Log(www.error);
            }
            else
            {
                // Show results as text
                Debug.Log(www.downloadHandler.text);

                // Or retrieve results as binary data
                byte[] results = www.downloadHandler.data;

                // await + construct ApiResponse<T>
            }
        }
    }

    // etc.
}

Then, you could construct APIs:

var api = new PetApi(){
   AsynchronousClient = new UnityAsyncClient(),
   SynchronousClient = null // or other implementation
}

I haven't worked with Unity, and their docs look pretty light, so the above is just a copy/paste from their example code.

iBicha commented 6 years ago

Sorry it's taking me a while to respond. I've been meaning to respond with proper feedback for some time now but I didn't get the time around it. Will do asap

kolodi commented 5 years ago

I've been working on Unity friendly OpenAPI parser and assets generator for some time, it resolves all issues with the multi-platform support and leverage many Unity specific tools to provide an easiest possible way to work with restful API

wing328 commented 5 years ago

Maybe we can leverage https://github.com/proyecto26/RestClient instead of using RestSharp.

nk2580 commented 3 years ago

this feels like a duplicate of #6800 in some ways, AFAIK the biggest blocker to using any of the current generators in a unity project is the use of restsharp

wing328 commented 3 years ago

@nk2580 have you considered the suggestion by @jimschubert: https://github.com/OpenAPITools/openapi-generator/issues/853#issuecomment-417163199 ?

nk2580 commented 3 years ago

@wing328 when it’s not a part of a unity package that works fine. When considering a unity package it’s a pretty big issue to rely upon restsharp when there’s no official package for it.

wing328 commented 3 years ago

@nk2580 ok. What would you suggest to replace RestSharp?

My previous suggestion: https://github.com/OpenAPITools/openapi-generator/issues/853#issuecomment-477476526

nk2580 commented 3 years ago

IMO using vanilla net.http is going to be the best and most compatible solution for unity and dotnet

wing328 commented 3 years ago

We've added a new library option httpclient (e.g. in CLI --library httpclient) to the csharp-netcore client generator. Please pull the latest master or using the SNAPSHOT version to give it a try and let us know if you've any feedback

nstdspace commented 2 years ago

The library option helps getting rid of RestSharp, but is there a way to disable the use of classes in System.ComponentModel.DataAnnotations (IValidationContext and other) ? This dll is not available in unity per default as well.

bawahakim commented 2 years ago

@nstdspace You can add a define in csc.rsp with -r:System.ComponentModel.DataAnnotations.dll, that works for us (using RestSharp, but I don't think that should change the fix)

Edit: you must use .Net Framework and not .Net Standard

justonia commented 2 years ago

I would add to this conversation that relying on the built-in C# APIs or Restsharp will bite you hard when you need to ship on two of the three major consoles.

UnityWebRequest integrates with custom logic on these consoles to use their platform libraries:

I am in the process of writing a modified C# code generator implementation that uses UnityWebRequest. Unfortunately I doubt I can make a PR for this because 1) it won't hook into a unit test framework, 2) the odds of it being approved in this project seem low with 380 pending PRs, 3) adding a new library choice to the csharp-netcore is very limiting because there are other poor design choices that are untenable (such as a complete lack of notification of connection errors or assumption that a response will always be JSON) and 4) it might require NDA console-specific code.

justonia commented 2 years ago

I put up a PR containing a working implementation of a UnityWebRequest csharp-netcore library plugin:

https://github.com/OpenAPITools/openapi-generator/pull/13890

pixelpax commented 1 year ago

@bawahakim

You can add a define in csc.rsp with r:System.ComponentModel.DataAnnotations.dll

Could you help me understand what this means? I've tried putting this into my Player Settings "additional compiler arguments" but it says error CS0006: Metadata file 'System.ComponentModel.DataAnnotations.dll' could not be found

Copying from /Library/Mono.framework doesn't seem to be a fix (Invalid

@wing328 First party documentation on how to bootstrap a Unity compatible client would be enormously helpful. Especially with the httpclient option this feels very close to being a godsend for the millions of indie game devs who want to utilize REST clients.

EDIT: FWIW, I'm using MacOS, and my understanding is that DataAnotations is generally less available for MacOS. I wonder if this could be avoided as a dependency to improve cross platform compatibility?

bawahakim commented 1 year ago

@pixelpax You just need to create a file anywhere in your Assets folder called csc.rsp, and add the line r:System.ComponentModel.DataAnnotations.dll inside the file. We also develop and build for Mac and never had any issue with DataAnnotations, as long as we're using .net Framework

Also, we switched to Nswag which supports using our own implementation of the HTTP client (we use UnityWebRequest), which makes things very smooth and compatible across all platforms: https://github.com/RicoSuter/NSwag/issues/2608#issuecomment-1294728900

The migration for us was relatively simple :)

pixelpax commented 1 year ago

@bawahakim Can't thank you enough for pointing me in the right direction here.

To summarize for anyone finding this thread while trying to build a generated Unity client circa early 2023:

Sincerely thanks for giving back to the community on this @bawahakim, I'm not very experienced with the nitty gritty of C# compilation, as is probably the case with many indie game devs, so thank you for breaking this down and giving back to the community :)

To the maintainers: @mandrean @jimschubert , an option to inject our own HttpClient implementation based on some IHttpClient interface would be a great convenience here so that those of us using Unity can make this part of our build pipeline instead of having to patch generated code. A default option to use UnityWebRequest would be even better :D

bawahakim commented 1 year ago

@pixelpax you are most welcome! Your encouragements pushed me to publish an article on medium, with a bit more details :D

https://medium.com/@bawahakim/generated-api-client-for-unity-in-c-with-unitywebrequest-78c8418228ba

dpradell-dev commented 1 year ago

Thanks a lot for the effort everyone, I'm also trying to implement this. I'll try with @bawahakim solution using NSwag as adding NuGet dlls (like Polly) to Unity is probematic when trying to build for WebGL and other IL2CPP platforms.

dpradell-dev commented 1 year ago

Hi @bawahakim. I followed your Medium article (amazing, thanks!) but I can't get it to work for WebGL. Encountering this issue when executing a request: image

Have you encountered something similar?

Thanks!

wing328 commented 1 year ago

Hi everyone, can you please also review the following PR to support UnityWebRequest in csharp-netcore client generato?

https://github.com/OpenAPITools/openapi-generator/pull/13890

bawahakim commented 1 year ago

Hi @bawahakim. I followed your Medium article (amazing, thanks!) but I can't get it to work for WebGL. Encountering this issue when executing a request: image

Have you encountered something similar?

Thanks!

@dpradell-moralis Ah yes, I've updated the method CreateHttpResponseMessage, please give this a go and let me know if it works.

dpradell-dev commented 1 year ago

@bawahakim thanks for that. I removed the GetResponseHeaders() part and it worked already but I'll try your solution. Thanks!