こんにちは。エンジニアの松下です。
最近は龍が如く 8 をクリアし、 FF7 リバースを進めているところです。
MP 消費なしで属性攻撃できるようになってて快適!
今回は Spring Boot で OpenAPI ドキュメントを自動生成するときの小技を紹介します。
また、 JSON ライブラリは Kotlin Serialization ドキュメント生成は springdoc-openapi を使うとします。
こんにちは、お久しぶりです。 気づけば入社して一年が経過していました。サカパ・iOSチームの東です。 筋トレを始めたので体がどんどん大きくなっていきます💪 WWDC2023で発表されたSwift Macrosを触ってみたので記事にします。
今回はCodingKeysのマクロを元に記事にしていきたいと思います。 普段馴染みのないSwiftSyntaxの知識も少し必要になります!
File -> New -> Package... -> Swift Macro
の流れです。NewからProjectを選択するとSwift Macroが選択できないので注意してください。
.
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
│ ├── Test
│ │ └── Test.swift
│ ├── TestClient
│ │ └── main.swift
│ └── TestMacros
│ ├── CodableKey.swift
│ └── CustomCodable.swift
└── Tests
└── TestTests
└── TestTests.swift
自立型と付属型の2種類があります。
// 自立型 @freestanding(引数) //付属型 @attached(引数)
それぞれ引数にマクロの種類が入ります。 引数によってマクロの動きが変わってきます。
引数として受け取った値を使用する。関数のようにどこにでも記述可能
| 変数 | 意味 |
|---|---|
| declaration | 宣言マクロ。struct, var, func... |
| expression | 式マクロ。グローバル関数とほぼ同じ意味 |
| codeItem | 宣言も式も生成できるマクロ。 |
宣言が対象です。class, struct, var等
| 変数 | 意味 |
|---|---|
| accessor | プロパティ宣言に付与可能。アクセサスコープを出力できる |
| member | 色々な型宣言に付与可能。スコープ内に別の宣言を出力可能 |
| memberAttribute | 色々な型宣言に付与可能。スコープ内の変数や関数にattributeを出力する |
| peer | 色々な宣言に付与可能。付与した宣言と同じスコープに別の宣言を出力する |
| extension | 型宣言に付与可能。extensionを出力します。 |
こちらのサイトより、Swiftの構文解析を行うことがきでます。 これで、Memberへのアクセスや、継承されている名前の参照を簡単に行えるようになります。
Test/Test.swift
@attached(peer) public macro CodableKey(name: String) = #externalMacro(module: "TestMacros", type: "CodableKey") @attached(member, names: arbitrary) public macro CustomCodable() = #externalMacro(module: "TestMacros", type: "CustomCodable")
moduleに指定するのは、typeに指定した構造体が存在するフォルダです。 CodableKeyの場合、TestMacrosフォルダ内のCodableKeyを使用するという意味です。
TestMacros/CodableKey.swift
import SwiftSyntax import SwiftSyntaxMacros public struct CodableKey: PeerMacro { // ① public static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [DeclSyntax] { [] } }
CodingKeysで書き換える対象を判別するだけのものなので
CodableKeyの実装はありません。
PeerMacroを継承していますが。これはマクロ宣言時のattachedの引数によって変わります。 memberならMemberMacro、accessorならAccessorMacroになります。
TestMacros/CustomCodable.swift
import SwiftSyntax import SwiftSyntaxMacros public struct CustomCodable: MemberMacro { public static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { let memberList = declaration.memberBlock.members // ① let types = declaration.inheritanceClause?.inheritedTypes // ② guard let types = types, !types.map({ $0.as(InheritedTypeSyntax.self)?.type.as(IdentifierTypeSyntax.self)?.name.text == "Codable" }).filter({ $0 == true }).isEmpty else { throw CustomError.message("Codableは必須です") // ③ } // ④ let cases = memberList.compactMap({ member -> String? in guard let propertyName = member.decl.as(VariableDeclSyntax.self)?.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text else { return nil } // ⑤ if let customeKeyMacro = member.decl.as(VariableDeclSyntax.self)?.attributes.first(where: { element in element.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.description == "CodableKey" }) { // ⑥ guard let customeKeyValue = customeKeyMacro.as(AttributeSyntax.self)!.arguments!.as(LabeledExprListSyntax.self)!.first?.expression.as(StringLiteralExprSyntax.self)?.segments.first?.as(StringSegmentSyntax.self)?.content.text else { return nil } return "case \(propertyName) = \"\(customeKeyValue)\"" } else { return "case \(propertyName)" } }) // ⑦ let codingKeys: DeclSyntax = """ enum CodingKeys: String, CodingKey { \(raw: cases.joined(separator: "\n")) } """ return [codingKeys] } }
構造体のメンバをリストで取得しています。 中身はMemberBlockItemSyntaxという構造体です。
CustomCodableをつけた構造体に継承されているものを取得しています。
guard節でCodableが継承されているかチェックし、ビルド前にエラーを表示させています。
今回の例ではCodingKeysを使用するのでCodableを必須にしています。以下のように即座にエラーが表示されます。

①で取得したmembersをCodingKeysのメンバに変換しています。
ここで、先ほど作成したCodableKeyが関係してきます。 attributeに指定されているかどうかをチェックし、変換を行うかを判断しています。
CodableKeyの引数の値を取得します。
今回は引数が1つなので単純に.arguments!.as(LabeledExprListSyntax.self)!.firstとしていますが、引数が複数ある場合は、適切に処理をしなければなりません。(引数名を取得する等)
最終的に結果として返す部分です。 ④では、構造体のメンバ分の数の文字列の配列が作成されるので、改行文字列で結合して返します。
TestMacros/Plugin.swift
import SwiftCompilerPlugin import SwiftSyntaxMacros @main struct TestPlugin: CompilerPlugin { let providingMacros: [Macro.Type] = [ CustomCodable.self, CodableKey.self ] }
こちらに型を登録しないと利用できないので注意が必要です。
マクロについても簡単にテストを書くことが可能です。 プロジェクトを作成した時点でTestsフォルダが作成されているかと思います。
TestTests/TestTests.swift
let testMacros: [String: Macro.Type] = [ "CodableKey": CodableKey.self, "CustomCodable": CustomCodable.self ]
こちらで登録を行わないとテスト実行時にマクロの実行ができないので忘れずに行ってください。
マクロのテストを書く際は、assertMacroExpansionを使用します。
(文字列ベースなので誤字、脱字などに注意して下さい)
今回のテスト例
TestTests/TestTests.swift
func testCodableKey() throws { #if canImport(TestMacros) assertMacroExpansion( """ @CustomCodable struct Test: Codable, Equatable { @CodableKey(name: "OtherName1") var propertyWithOtherName1: String var propertyWithSameName: String @CodableKey(name: "OtherName2") var propertyWithOtherName2: String } """, expandedSource: """ struct Test: Codable, Equatable { var propertyWithOtherName1: String var propertyWithSameName: String var propertyWithOtherName2: String enum CodingKeys: String, CodingKey { case propertyWithOtherName1 = "OtherName1" case propertyWithSameName case propertyWithOtherName2 = "OtherName2" } } """, macros: testMacros ) #else throw XCTSkip("macros are only supported when running tests for the host platform") #endif }
第一引数は実際のコードを書きます。
expandedSourceには、実際にマクロが展開された時に期待されるコードを書きます。
macorsにはtestMacrosにMacroを登録するで宣言したものを指定します。
TestTests/TestTests.swift
func testCodableKeyFiailureNotInheritedCodable() throws { #if canImport(CustomMacroLibMacros) assertMacroExpansion( """ @CustomCodable struct Test: Equatable { @CodableKey(name: "OtherName1") var propertyWithOtherName1: String var propertyWithSameName: String @CodableKey(name: "OtherName2") var propertyWithOtherName2: String } """, expandedSource: """ struct Test { var propertyWithOtherName1: String var propertyWithSameName: String var propertyWithOtherName2: String } """, diagnostics: [ DiagnosticSpec(message: "message(\"Codableは必須です\")", line: 1, column: 1) ], macros: testMacros ) #else throw XCTSkip("macros are only supported when running tests for the host platform") #endif }
エラーが表示されるかを確認するには、diagnostics引数を使用します。
Swift Macrosはまだ発表されたばかりで、情報も少ないですが使ってみるとかなり興味深いものだと思いました。 上手に使えば、開発の効率を上げることができますが、使いすぎるとかえってコードの可読性が悪くなってしまうので、取り扱いには気をつけたいところです。
弊社ではいっしょに働くアプリエンジニアを募集しています! ご興味のある方はぜひご応募いただけますと嬉しいです。
AIR Design for Marketing 事業部 バックエンドエンジニアの大田です。最近ボルダリングをサボっているせいで体重が高め安定してしまっています。久しぶりに会った知人から「ちょっとふっくらしました?」というお言葉を頂戴しました、控えめな表現に優しさが垣間見えますね 😇
タイトルの通り、 PyCon APAC 2023に参加してきましたので、簡単ですがレポート的な記事を書こうと思います 🐍
京都大学の喜多一教授による「なぜ大学教授がPythonの教科書を書いたのか 」という内容でした。 初学者が躓きやすいポイントなどについて話されていて、それに対してどのように対応していくのかをわかりやすく説明されていました。
やはりいちばんの壁はエラーメッセージで、正常系が動いているうちは前に進めるけれど、エラーが発生してしまうとメッセージを読みたくなくてドロップ・アウトしてしまうことがよくあるとのことでした。 なのであえてエラーを発生させることをして、それを解決することを体験してもらうことでエラーへの恐怖をなくすようなやり方をしているということで納得感のあるお話でした。
Kyoto University Research Information Repository: プログラミング演習 Python 2023
講義で使用するテキストと専用に開発したフォントを公開されているということや、配布しているコードにはテストコードが含まれているというところも印象的でした。
Python 3.10 からの新機能である構造的パターンマッチングの紹介で、何が嬉しいのかを見ていく内容でした。こういう新機能はなかなか使い始めるタイミングが無いので、こういった場で紹介されているとありがたいですね。

Python から PostgreSQL に Socket を使ってどの様にアクセスできるのかをやってみたという内容でした。
意外に(?)シンプルでわかりやすいプロトコルなのが印象的でした。
pyconapac2023-pep681-slide.ryu22e.dev
Pydantic などのいわゆる「データクラスっぽい」ライブラリに対して型チェックを強化するための標準仕様(Data Class Transforms (PEP681))についてでした。みんな大好き型のお話ですね。
この手のライブラリは IDE 側でも対応していないと補完などが効かなかったりするので、進化に期待ですね。
vscode で使える Dev Containers のお話ですね。最近は JetBrains IDE でも使えるらしいので、ちょっと導入してみたいと思っています1。
events/pycon.apac.20231026 at main · rhoboro/events · GitHub
UTF-8 の仕様のお話などされていました。「16進数はまずは 0,7,8,F だけ覚えておけば良い」2というのはなるほどと思いましたね。 最近業務で実装したID生成のことを思い出したりしました :)

20階の Unconference 会場でお弁当を頂いていたのですが、PyCon PH の方とお話させていただきました。 私の英語スキルは単語を羅列できる程度なのですが、フィリピンの学校でマイクロソフトの支援(?)でC#が教えられていることなど興味深いお話ができました。 (私が仕事でC#をやっていたことがあるという話から発展しました)


お菓子美味しかったです!
pipenv, Poetry, PDM, pip-tools, Hatch, pip(, Rye) の比較をされています。 まとめとしてはライブラリ開発者は Hatch 、アプリケーション開発者は PDM が良いのではということでした。3
PDM は pyproject.toml に記述する依存関係の標準4に対応していて依存性解決も速いということなので、早速 Poetry を使っているプロジェクトを PDM に切り替えてみて試しています。 使い勝手は Poetry と変わりなく、特に問題なく動いているようなのでこのまま正式に切り替えようかなと思っているところです :)
久しぶりのオフラインでのカンファレンス参加で正直疲れてしまった部分もありますが、やはり同じ開発者がリアルで話すという場のありがたさを感じました。 すぐに業務に取り入れることができるトピックもあり、刺激をもらえる2日間でした :)