コンテンツにスキップ

`rigor-dry-validation` — スライシング決定

ステータス:設計ノート。rigor-dry-typesスライス4コミットで2026-05-17に著作。rigor-dry-typesrigor-dry-struct(両方ともv0.1.6で着地)を超えた次のdry-rbアダプタのスライス順序を決定する。

docs/design/20260509-dry-plugins-roadmap.md §「dry-validation」がgemの3つのプラグイン関連DSLサーフェスを記述する;ユーザー向けプログラミング形は次のいずれか:

class NewUserContract < Dry::Validation::Contract
params do # (1) params { ... }アダプター — dry-schemaに委譲
required(:email).filled(:string)
required(:age).value(:integer)
end
rule(:email) do # (2) rule { ... }ブロック — 型寄与なし
key.failure('has invalid format') unless EMAIL_RE.match?(value)
end
end
result = contract.call(...) # (3) Contract#call → Dry::Validation::Result
result.success? # Resultをナローイング
result.to_h # 型付きparamsハッシュを表面化

プラグインは、Rigorが次に答えられるときにユーザー価値を得る:

  • contract.call(...)の型は何か? → contract形に関係なくDry::Validation::Result
  • 成功時のresult.to_hの型付き形は何か? → dry-schemaから派生したHashShape。dry-schemaが必要。
  • 与えられたContractにどのparamsキーが存在するか?params { ... }ブロック(これは変装したdry-schema)からの:email / :age / …のセット。dry-schemaが必要。
  • rule(:email)は型付けに何をするか? → 何もしない;純粋なビジネスルール。
┌──── rigor-dry-types (v0.1.6 — スライス1+2+3+4)
rigor-dry-schema (まだ) ←── :dry_type_aliasesを消費
rigor-dry-validation (まだ) ←── dry-schemaのparams形を消費

スタンドアロンのrigor-dry-validation(dry-schemaなし)はContract#call → Resultファクトのみを寄与できる。リッチなペイロード — 型付きresult.to_h、paramsキー、キーごとの型 — はdry-schemaから流れる。dry-schemaなしでは、dry-validationは1行のRBS寄与:

module Dry
module Validation
class Contract
def call: (Hash[Symbol, untyped]) -> Result
end
class Result
def success?: () -> bool
def failure?: () -> bool
def to_h: () -> Hash[Symbol, untyped]
end
end
end

それは10行のRBSオーバーレイ。専用プラグインスライスの価値はない — 類似の境界と並んで将来の「dry-rbコアRBSバンドル」に畳み込む。

決定: rigor-dry-validation前にrigor-dry-schemaをスライスする。スキーマ認識なしのvalidationプラグインは非常に少ししか寄与しない;それを伴うと、価値はユーザーコード内のスキーマ使用でスケールする。

dry-pluginsロードマップ §「dry-schema」エントリーに従い:

NewUserSchema = Dry::Schema.Params do
required(:email).filled(:string)
required(:age).value(:integer)
end
result = NewUserSchema.call(input)
result.to_h # => HashShape[{email: String, age: Integer}]
result.errors.to_h # => Hash[Symbol, Array[String]]

プラグイン契約(提案):

  • プロジェクトのトップレベルでまたはクラスレベル定数としてFoo = Dry::Schema.{Params,JSON,define} { ... }を認識。
  • ブロック本体をrequired(:key).<predicates>optional(:key).<predicates>呼び出しで歩く。
  • 各述語サフィックス(filled(:string)value(:integer)value(:date) …)をrigor-dry-typesが使うのと同じCANONICAL_ALIASESテーブル経由で基底クラスにマップする(:stringString:integerInteger、……)。ユーザー著作の参照(value(Types::Email)はクロスプラグインファクトチャネルを通じて解決される)に対しては:dry_type_aliasesファクトを消費する。
  • :dry_schema_tableファクトを公開: {schema_const_fqn => {required: {key => underlying_class}, optional: {...}}}
  • ADR-16基板(:dry_schema_tableを消費するreturns_from_arg:を持つTier C HeredocTemplate)または基板のパラメータ化された戻り値がその時点でスコープ外の場合はカスタムウォーカーのいずれかを経由してresult.to_hの型付き戻りを合成する。

rigor-dry-schemaのスライス1フロア: 認識 + ファクト公開(まだ診断なし)、rigor-dry-typesスライス1の形をミラー。

rigor-dry-validationのスライシング — 提案された3つのスライス

Section titled “rigor-dry-validationのスライシング — 提案された3つのスライス”

rigor-dry-schemaが基底の形を提供したら、validationプラグインは基板にクリーンにマップする。

スライス1 — Contract認識 + Resultキャリア

Section titled “スライス1 — Contract認識 + Resultキャリア”
  • プロジェクトをclass X < Dry::Validation::Contractサブクラスのために歩く。
  • X#call(Hash[Symbol, untyped]) → Resultを合成する(ジェネリックなResult、まだスキーマ認識なし)。
  • Dry::Validation::Result#{success?, failure?, to_h}の手書きRBSオーバーレイ、そのためcontract.call(...).to_hチェーンがHash[Symbol, untyped]に解決する。

フロア: すべてのcontract呼び出しサイトは下流のメソッドチェーン推論のために型付きのResultレシーバーを持つ。

スライス2 — params { ... }のdry-schemaとの統合

Section titled “スライス2 — params { ... }のdry-schemaとの統合”
  • contractボディ内のparams do ... endブロックを認識。
  • それをdry-schema宣言として扱う(rigor-dry-schemaのウォーカーに委譲するか、プラグインカップリングが問題なら関連サブセットを複製する)。
  • :dry_validation_paramsファクトを公開: {contract_const_fqn => HashShape}
  • Contract#callの戻りを精緻化し、result.to_huntyped値ではなくcontractごとの形に対して型付けされるように。

フロア: NewUserContract.new.call(email: "x@y", age: 17).to_hHashShape[{email: String, age: Integer}]に解決する。

スライス3 — json { ... }アダプタパリティ

Section titled “スライス3 — json { ... }アダプタパリティ”

json { ... }ブロックはparamsと同じ形だがより厳密な型期待を適用する(文字列から整数への強制なし)。同じウォーカーを適用;同じファクトを別のキー(:dry_validation_jsonまたはkind:判別子を持つ共有された:dry_validation_schema)で発行。

フロア: paramsとのパリティ。プロジェクトがjson { ... }を使わない場合は需要駆動。

上記のスライシングに対しては何もなし。dry-validationはrigor-dry-monadsが必要とするResult[T, E]キャリア修正を必要としない(下記 §「オープン観察」を参照) — Dry::Validation::Resultは直和型ではなくジェネリッククラス。その#to_hペイロードが型付き形そのものであり、#success? / #failure?述語は既存のboolフローファクトを通じて下流チェーンをナローイングする。

オープン観察 — rigor-dry-monadsは別途ブロックされている

Section titled “オープン観察 — rigor-dry-monadsは別途ブロックされている”

ロードマップは両方とも次のティアのdry-rbプラグインであるため、rigor-dry-validationrigor-dry-monadsをグループ化する。しかしdry-monadsは別の軸でブロックされている: メソッドごとの戻り型ラッピング(def x; Success(42); end → Result[Integer, untyped])を望む。ラップされたResult[T, E] / Maybe[T]キャリアは今日のRigor::Type::*階層には存在しない。

2つのルート:

  • (a) Result[T, E] / Maybe[T]キャリアを新しいRigor::Type::*値クラスとして実装する。ADR-3修正レベルの作業(新しい型種別、正規化ルール、RBS消去、表示契約、等価性 / 確実性サーフェス)。
  • (b) Result[T, E]Union[T, E]として、Maybe[T]Union[T, NilClass]として表現する。Success(v)Failure(e)を精密にする「タグ」曖昧性解消を失うが、フロアとして機能するかもしれない。

決定: 2つのルートの少なくとも1つが具体的になるまでdry-monadsを先送り。dry-validationはmonadsなしで出荷可能 — 依存は逆方向に行く(dry-validationはdry-monadsではなくdry-types + dry-schemaを使う)。

作業順(キュー、需要駆動):

  1. rigor-dry-schemaスライス1(認識 + ファクト公開)
  2. rigor-dry-schemaスライス2+(スキーマごとの形合成)
  3. rigor-dry-validationスライス1(Contract認識 + Resultキャリア)
  4. rigor-dry-validationスライス2(paramsのdry-schema統合)
  5. rigor-dry-validationスライス3(jsonアダプタパリティ)
  6. rigor-dry-monads — (a)または(b)がResult / Maybeキャリア質問を解決した後にのみ

合計: 5〜6の小〜中スライス。特定の層に対する具体的なユーザー需要はそれを前倒しすることを正当化する。

© 2026 TypedDuck. Licensed under CC BY-SA 4.0.