garicchi
Posted on January 25, 2024
ASP.NetはSwaggerをサポートしており、Controllerを書くだけでopenapi schemaが自動生成されて便利です。
そして自動生成されたopenapi schemaから、openapi-generatorなどのツールを利用して、TypeScriptのコードを自動生成すれば、Single Page Applicationが作りやすくなります。
レスポンスのPayloadにEnum型があった場合
しかし、レスポンスのPayloadにEnum型のプロパティがあった場合、あまり良いコードが得られません。
例えば、こんなC#のenumがあったとして、
public enum WeatherType
{
Sunny = 0,
Cloudy = 1,
Rainy = 2,
}
普通にopenapi-generatorで自動生成すると、以下のTypeScriptのコードが得られます。
export const WeatherType = {
NUMBER_0: 0,
NUMBER_1: 1,
NUMBER_2: 2
} as const;
export type WeatherType = typeof WeatherType[keyof typeof WeatherType];
EnumのメンバーがNUMBER_{数字} になってわかりにくいです。
x-enum-varnames
メンバーのキー名を変更するには、openapi schemaにx-enum-varnamesという名前で配列を入れてやればよいです。
https://github.com/OpenAPITools/openapi-generator/issues/893#issuecomment-416617460
ASP.NetのSwaggerジェネレータ (今回はSwashbuckle)の場合、SchemaFilterという機能があり、これを使えば、出力されるopenapi schemaをいじることができます。
こんな感じのSchemaFilterを用意して
public class EnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
var varNamesArray = new OpenApiArray();
foreach (var memberVal in Enum.GetValues(context.Type))
{
var memberName = Enum.GetName(context.Type, memberVal);
varNamesArray.Add(new OpenApiString(memberName));
}
model.Extensions.Add("x-enum-varnames", varNamesArray);
}
}
}
AddSwaggerGenで指定してやります
builder.Services.AddSwaggerGen(options =>
{
options.SchemaFilter<EnumSchemaFilter>();
});
すると、openapi schemaにはx-enum-varnamesにメンバー名が入るようになります。
"WeatherType": {
"enum": [
0,
1,
2
],
"type": "integer",
"format": "int32",
"x-enum-varnames": [
"Sunny",
"Cloudy",
"Rainy"
]
}
これを使ってTypeScriptを生成すると、キーが正しくメンバー名になります。
export const WeatherType = {
Sunny: 0,
Cloudy: 1,
Rainy: 2
} as const;
export type WeatherType = typeof WeatherType[keyof typeof WeatherType];
表示名を変えたい
x-enum-varnamesを使えば、TypeScriptのオブジェクトのキーとして、Enumのメンバー名を使用できましたが、
実際にUIに値を表示したい時は、もっと違う文字列を表示したいかもしれません。
例えばメンバー名を Sunny=
とした場合、=
はTypeScriptのオブジェクトのキーとして使えないのでエラーになります。
また、各国の言語に翻訳した名前を表示したいかもしれません。
EnumのAttributeを表示する
そこで、EnumにAttributeをつけて、それを表示するようにしてみます。
今回は System.ComponentModel.DataAnnotations.DisplayAttribute
を使用します。
public enum WeatherType
{
[Display(Name = "晴れ")]
Sunny = 0,
[Display(Name = "曇り")]
Cloudy = 1,
[Display(Name = "雨")]
Rainy = 2,
}
openapi schemaのテキトーなキーに、この表示名をつけれればいいので、
EnumSchmeFilterを以下のように修正し、 x-enum-displays
というスキーマを追加してみます。
public class EnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
var varNamesArray = new OpenApiArray();
+ var displayArray = new OpenApiArray();
foreach (var memberVal in Enum.GetValues(context.Type))
{
var memberName = Enum.GetName(context.Type, memberVal);
varNamesArray.Add(new OpenApiString(memberName));
+ var obj = new OpenApiObject();
+ var display = (DisplayAttribute?)memberVal.GetType().GetMember(memberVal.ToString() ?? "").FirstOrDefault()?.GetCustomAttributes(typeof(DisplayAttribute), true).FirstOrDefault();
+ obj["value"] = new OpenApiInteger(Convert.ToInt32(memberVal));
+ obj["display"] = new OpenApiString(display?.Name);
+ displayArray.Add(obj);
}
model.Extensions.Add("x-enum-varnames", varNamesArray);
+ model.Extensions.Add("x-enum-displays", displayArray);
}
}
}
これで生成されたスキーマは以下のようになります。
"WeatherType": {
"enum": [
0,
1,
2
],
"type": "integer",
"format": "int32",
"x-enum-varnames": [
"Sunny",
"Cloudy",
"Rainy"
],
"x-enum-displays": [
{
"value": 0,
"display": "晴れ"
},
{
"value": 1,
"display": "曇り"
},
{
"value": 2,
"display": "雨"
}
]
}
あとはこれを使って、TypeScriptコードを生成してやればよいのですが、標準では今回作った x-enum-displays
を読めないので、コード生成テンプレートを修正してやります。
テンプレートはここにあるので落としてきます
https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/typescript-fetch
次に、テンプレートの中にある modelEnumInterfaces.mustache
に以下を追加します。
/*
customized
*/
{{^stringEnums}}
{{#vendorExtensions}}
export function Get{{classname}}DisplayName(v: {{classname}}) {
switch(v) {
{{#x-enum-displays}}
case {{value}}:
return "{{display}}";
{{/x-enum-displays}}
default:
throw new Error(`value ${v} is not supported`);
}
}
{{/vendorExtensions}}
{{/stringEnums}}
あとはopenapi generatorで生成する時に、 -t
オプションで、先ほど修正したテンプレートのディレクトリを指定します。
テンプレートのカスタマイズはこのドキュメントを参考にしてください。
これで、生成されたTypeScriptには、enumから表示名を取得する関数が追加されます。
export const WeatherType = {
Sunny: 0,
Cloudy: 1,
Rainy: 2
} as const;
export type WeatherType = typeof WeatherType[keyof typeof WeatherType];
/*
customized
*/
export function GetWeatherTypeDisplayName(v: WeatherType) {
switch(v) {
case 0:
return "晴れ";
case 1:
return "曇り";
case 2:
return "雨";
default:
throw new Error(`value ${v} is not supported`);
}
}
こんな感じで表示名を取得できます。
// 晴れ が返される
GetWeatherTypeDisplayName(WeatherType.Sunny)
Posted on January 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.