Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RailsのValidatesをSwift Macrosで再現してみた

RailsのValidatesをSwift Macrosで再現してみた

Avatar for Takuma Shimizu

Takuma Shimizu

March 06, 2026
Tweet

More Decks by Takuma Shimizu

Other Decks in Programming

Transcript

  1. "DUJWF3FDPSE 3VCZPO3BJMT ͷόϦσʔγϣϯ class Article < ApplicationRecord validates :title, :body,

    presence: true validates :status, inclusion: { in: %w[draft published] } validates :title, length: { maximum: 100 } validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } end > article.valid? #=> true or false
  2. 4XJGU.BDSPTͰ࠶ݱ @Validatable struct Article { var title: String var body:

    String var status: Status var email: String? #Validation(\Self.title, \.body, presence: .required) #Validation(\Self.status, inclusion: [.draft, .published]) #Validation(\Self.title, countWithin: ...100) #Validation(\Self.email, format: /^[a-zA-Z0-9._%+-]+@.+\..{2,}$/) } > article.isValid > try article.validate() IUUQTHJUIVCDPNIPLVSPOTXJGUWBMJEBUJPOT IPLVSPOTXJGUWBMJEBUJPOT
  3. "DUJWF3FDPSE 4XJGU7BMJEBUJPOT validates :title, :body, presence: true #Validation(\Self.title, \.body, presence:

    .required) validates :status, inclusion: { in: %w[…] } #Validation(\Self.status, inclusion: […]) validates :title, length: { maximum: 100 } #Validation(\Self.title, countWithin: ...100) validates :email, format: { with: /…/} #Validation(\Self.email, format: /…/) ΠϯλϑΣʔε͸֓Ͷಉ͡ʹͭͭ͠ɺࡉ͔͍෦෼͸4XJGU΍"QQMFϑϨʔϜϫʔΫͷޠኮΛ࢖༻ɻ
  4. ܕ҆શੑ "DUJWF3FDPSE validates :age, length: { minimum: 16 } //

    ⚠ ܻ਺νΣοΫͱͳΓҙਤ͠ͳ͍ݕূ݁ՌʹͳΔɻ͜ͷ৔߹numericalityݕূΛ͢Δͷ ͕ਖ਼͍͠ɻ 4XJGU7BMJEBUJPOT #Validation(\Self.age, countWithin: 16…) // ❌ Macro 'Validation(_:countWithin:where:)' requires that 'Int' conform to 'Collection'
  5. 4XJGUͳΒͰ͸ͷ੍໿ w ݕূର৅Λ,FZ1BUIͰࢦఆ͢Δඞཁ͕͋Δ w 'SFFTUBOEJOHNBDSPͳͷͰɺपลͷίϯςΩετΛ֬ೝͰ͖ͳ͍ w Φʔόʔϩʔυ͕૿͕͑ͪ w ੩తܕ෇͚ͳͷͰɺݕূର৅ͱݕূ஋ͷܕͷؔ܎ੑ͝ͱʹఆ͕ٛඞཁ w

    ઌͷ੍໿ʢ,FZ1BUIࢦఆʣͷͨΊɺ0QUJPOBM൛ͱඇ0QUJPOBM൛ͷ྆ํͷఆ͕ٛඞཁ w ͜ͷ੍໿ʹΑΓɺఆٛ਺͕୯७ʹഒʹͳΔ w IUUQTHJUIVCDPNIPLVSPOTXJGUWBMJEBUJPOT4PVSDFT7BMJEBUJPOT.BDSPTTXJGU ޙऀ͸࣮૷ଆͷ౎߹ͳͷͰɺར༻ऀʹӨڹ͕͋Δͷ͸લऀͱͳΔɻ
  6. .BDSPͷ࣮૷ w #Validationࣗମ͸Կ΋ੜ੒͠ͳ͍ struct ValidationMacro: DeclarationMacro { public static func

    expansion(...) throws -> [DeclSyntax] { return [] // ۭ഑ྻΛฦ͠ίʔυੜ੒΋ͳ͍ } } w @ValidatableϚΫϩଆϝϯόʔ಺ͷ#ValidationͷଘࡏΛ֬ೝ͠ίʔυੜ੒ Λߦ͏ w IUUQTHJUIVCDPNIPLVSPOTXJGUWBMJEBUJPOT4PVSDFT7BMJEBUJPOT.BDSPT7BMJEBUBCMF.BDSPTXJGU
  7. @Validatable struct User { var name: String var age: UInt

    var email: String? var bio: String? var password: String #Validation(\Self.name, presence: .required) #Validation(\Self.age, comparison: .greaterThan(16)) #Validation(\Self.email, format: /…/, presence: .required(allowsNil: true)) #Validation(\Self.bio, presence: .none) #Validation(\Self.password, presence: .required(allowsEmpty: true)) }
  8. @Validatable struct User { var name: String var age: UInt

    var email: String? var bio: String? var password: String #Validation(\Self.name, presence: .required) #Validation(\Self.age, comparison: .greaterThan(16)) #Validation(\Self.email, format: /…/, presence: .required(allowsNil: true)) #Validation(\Self.bio, presence: .none) #Validation(\Self.password, presence: .required(allowsEmpty: true)) } ˣੜ੒ίʔυ SFTVMUCVJMEFS  extension User: Validations.Validatable { var validation: some Validator { Presence(of: self[keyPath: \Self.name]).presence(.required) .errorKey(Self.self, \Self.name) Comparison(of: self[keyPath: \Self.age], .greaterThan(16)) .errorKey(Self.self, \Self.age) Format(of: self[keyPath: \Self.email], with: /…/).presence(.required(allowsNil: true)) .errorKey(Self.self, \Self.email) Presence(of: self[keyPath: \Self.bio]).presence(.none) .errorKey(Self.self, \Self.bio) Presence(of: self[keyPath: \Self.password]).presence(.required(allowsEmpty: true)) .errorKey(Self.self, \Self.password) } }