くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

TypeSpec(OpenAPI)でTypeScriptとDartのモデルを共通化する

Firestoreを使ってアプリとサーバを開発していて、
FlutterとCloud FunctionsやNitro Serverでモデルを共通化したいなと思い、
いろいろ調べてみたときの備忘録(*´ω`*)

TypeSpecでOpenAPIの定義を書いて、
それぞれの言語で生成するのがいいかもしれない。

まとめ

こんか感じだといいかも

いろいろためしたときのリポジトリ

それぞれの簡単な使い方

ざっくりメモ

openapi.yamlの生成: TypeSpec

$ pnpm --package=@typespec/compiler dlx tsp init
$ pnpm add -D @typespec/compiler
$ pnpm install

$ pnpm tsp compile
# or
$ pnpm tsp compile main.tsp
$ ls tsp-output/@typespec/openapi3/openapi.yaml

# オプションで出力先も変更できる
$ pn tsp compile main.tsp \
      --emit="@typespec/openapi3" \
      --options='@typespec/openapi3.emitter-output-dir={project-root}/dist'
$ ls dist/openapi.yaml

ts生成: openapi-typescript-codegen

$ pnpm dlx openapi-typescript-codegen --input ./dist/openapi.yaml --output ./dist/ts
$ ls dist/ts
core  index.ts  models  services

models配下にcomponentsが生成され、
servicesにfetchを利用したAPI Clientが生成される。

enumenumのまま出力されるが、unionにもできるらしい

ts生成: openapi-typescript

# インストール
$ pnpm add -D openapi-typescript
# スキーマの生成
$ pnpm openapi-typescript dist/openapi.yaml -o dist/schema.d.ts

openapi-fetchをつかって、
API Clientも生成できる。READMEのサンプルより。

import createClient from "openapi-fetch";
import type { paths } from "./dist/schema.d.ts"; // generated by openapi-typescript

const client = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });

const {
  data, // only present if 2XX response
  error, // only present if 4XX or 5XX response
} = await client.GET("/blogposts/{post_id}", {
  params: { path: { post_id: "123" } },
});

await client.PUT("/blogposts", {
  body: { title: "My New Post" },
});

生成されるスキーマはこんな感じ。(公式サンプルより)

export interface components {
  schemas: {
    Pet: {
      id: number;
      name: string;
      none?: null;
      tag?: null | string | number;
      arr?: unknown[];
      either?: string | null;
    };
  };
  responses: never;
  parameters: never;
  requestBodies: never;
  headers: never;
  pathItems: never;
}

モデルごとにタイプができるわけではないので注意。
自前でtype aliasを書けばいいかも

dart生成: openapi-generator-cli(dart)

# インストール
$ pnpm add -D @openapitools/openapi-generator-cli

# スキーマ&Clinet生成
$ pnpm openapi-generator-cli generate -g dart -i dist/openapi.yaml -o dist/dart
$ ls dist/dart
README.md  analysis_options.yaml  doc  git_push.sh  lib  pubspec.lock  pubspec.yaml  test

モデルとAPI Clientを含めて、dart packageを生成してくれる
enumdartenumでは生成してくれないっぽい

あとは、pubspec.yamlでパス指定のdependenciesを追加すればOK

dependencies:
  myclient:
    path: ./dist/dart

doc生成: openapi-generator-cli

# インストール
$ pnpm add -D @openapitools/openapi-generator-cli

# htmlの生成
$ pnpm openapi-generator-cli generate -g html -i dist/openapi.yaml -o dist/doc-html
$ ls dist/dart
README.md  analysis_options.yaml  doc  git_push.sh  lib  pubspec.lock  pubspec.yaml  test

doc生成: redoc

# インストール
$ pnpm add -D redoc-cli

# htmlの生成
$ pnpm redoc-cli build-docs openapi.yaml -o dist/doc-redoc/index.html
$ ls dist/doc-redoc/index.html

doc生成: swagger-ui

htmlを生成してくれるCLIとかはないので、ちょっとめんどくさいい。。

  1. swagger-api/swagger-uiからzipをダウンロード
  2. zipを展開して、distディレクトリをコピー
  3. swagger-initializer.jsのurlを書き換え
  4. swagger-initializer.jsと同じ階層にopenapi.yamlを配置
  window.onload = function () {
    //<editor-fold desc="Changeable Configuration Block">

    // the following lines will be replaced by docker/configurator, when it runs in a docker-container
    window.ui = SwaggerUIBundle({
-     url: "https://petstore.swagger.io/v2/swagger.json",
+     url: "openapi.yaml",
      dom_id: '#swagger-ui',
      deepLinking: true,
      // 略...
  };

以上!! Firestoreを使ったりすると、モデル定義の共通化がめんどくさいので、だいぶ楽になるかも(*´ω`*)

参考にしたサイトさま