Vaporでp-x9/AliasMacroをContentやModelに使ってみる。

Last Updated on 2024年1月21日 by lemonade

@Mさん(Twitterアカウント)が作ったSwift Macro、AliasMacroという関数や変数の名前に日本語等のエイリアス名を作るマクロを使用して、VaporのORMであるFluent ModelやJSONに変換できるCodableな型、Contentに使用してみた結果を書いてみます。

環境

  • MacBook Pro 14inch M1 Pro
  • macOS Sonoma 14.2.1
  • Swift 5.9.2
  • Vapor Framework 4.91.1
  • Vapor toolbox 18.7.4

セットアップ

Swift Package ManagerでAliasMacroの依存を追加する

Package.swiftのdependenciesに.package(url: "https://github.com/p-x9/AliasMacro.git", from: "0.6.0"),.product(name: "Alias", package: "AliasMacro"),を追加します。

// swift-tools-version:5.9
import PackageDescription

let package = Package(
  name: "alias",
  platforms: [
    .macOS(.v13)
  ],
  dependencies: [
    // 💧 A server-side Swift web framework.
    .package(url: "https://github.com/vapor/vapor.git", from: "4.89.0"),
    // 🗄 An ORM for SQL and NoSQL databases.
    .package(url: "https://github.com/vapor/fluent.git", from: "4.8.0"),
    // 🐘 Fluent driver for Postgres.
    .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.7.2"),
    // Alias Macro
    .package(url: "https://github.com/p-x9/AliasMacro.git", from: "0.6.0"),
  ],
  targets: [
    .executableTarget(
      name: "App",
      dependencies: [
        .product(name: "Fluent", package: "fluent"),
        .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
        .product(name: "Vapor", package: "vapor"),
        // Alias Macro
        .product(name: "Alias", package: "AliasMacro"),
      ]
    ),
    .testTarget(
      name: "AppTests",
      dependencies: [
        .target(name: "App"),
        .product(name: "XCTVapor", package: "vapor"),

        // Workaround for https://github.com/apple/swift-package-manager/issues/6940
        .product(name: "Vapor", package: "vapor"),
        .product(name: "Fluent", package: "Fluent"),
        .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
      ]),
  ]
)

Aliasで変数名を日本語にする

Todoモデルにエイリアスをつける

import Alias
import Fluent
import Vapor

final class Todo: Model, Content {
  static let schema = "todos"

  @ID
  var id: UUID?

  @Alias("カテゴリ")
  @Parent(key: "category_id")
  var category: Category

  @Alias("タイトル")
  @Field(key: "title")
  var title: String

  @Alias("メモ")
  @Field(key: "note")
  var note: String

  @Alias("状態")
  @Enum(key: "state")
  var state: 状態

  @Timestamp(key: "created_at", on: .create)
  var createdAt: Date?

  @Timestamp(key: "updated_at", on: .update)
  var updatedAt: Date?

  init() {}

  init(id: UUID? = nil, categoryId: Category.IDValue, title: String, note: String, state: State) {
    self.id = id
    self.$category.id = categoryId
    self.title = title
    self.note = note
    self.state = state
  }
}

extension Todo {
  @Alias("状態")
  enum State: String, Hashable, Content, CaseIterable {
    @Alias("未完了")
    case todo
    @Alias("進行中")
    case doing
    @Alias("完了")
    case done
  }
}

Controllerで日本語エイリアスを使ってみる

TodoControllerのupdateメソッドを日本語を使ってみる

  /// Todoを変更する
  func update(req: Request) async throws -> HTTPStatus {
    let id = try req.parameters.require("id", as: UUID.self)
    guard let todo = try await Todo.find(id, on: req.db) else {
      throw Abort(.ok, reason: "The todo does not exist")
    }

    try EditTodoJSON.validate(content: req)
    let content = try req.content.decode(EditTodoJSON.self)
    guard let category = try await Category.find(content.categoryId, on: req.db) else {
      throw Abort(.ok, reason: "The category does not exist")
    }

    todo.$category.id = try category.requireID()
    todo.タイトル = content.title
    todo.メモ = content.note
    todo.状態 = content.state
    
    try await todo.update(on: req.db)

    return .noContent
  }

結果

AliasMacroはグローバルなクラスに対してつけるとエラーが出る。

READMEのClass/Struct/Enum/Actorにある通り、Warning PeerMacro with arbitrary specified in names cannot be used in global scope. でエラーが出るためモデル名に日本語でエイリアスをつけるのは難しそうです。ただ、Todo.Stateなどネストした定義のものであればエイリアスを貼ることはできました。

PropertyWrapperにはエイリアスが貼られない

todo.$category.id = try category.requireID()などもtodo.$カテゴリ.id = try category.requireID()とできないか試してみましたが、PropertyWrapperにはエイリアスが貼られないようです。

EncodeされたJSONにはエイリアスは関係ない

APIから返却されたJSONを見てみましたが、日本語エイリアスは反映されていませんでした。encodeする際にエイリアスは干渉しないようです。

感想

JSON値やORMに日本語エイリアスが干渉しないのがわかり嬉しかったですが、PropertyWrapperに日本語が貼られないとFluent ORMでプロパティに日本語エイリアスを貼る価値は薄いなとも思いました。SwiftUIでもPropertyWrapperに日本語エイリアスは欲しいでしょうし、PropertyWrapperに対応されれば最高ですね。

ソースコード

Leave a Comment

CAPTCHA