agentic examples

The 422 Generator

The 422 Generator: turn a ValidationError into the API error document your frontend actually wants - message, allowed values, bounds - using ONLY what the exception carries (new this round: #expectations). The renderer has zero knowledge of the contract; the exception brings the contract with it.

Data & Pipelines Round 5 Piotr Solnica exit 0

source on github

bundle exec ruby examples/form_errors.rb

a real captured run

submission #1: {:email=>"ada@example.com", :plan=>"team", :seats=>12}
  201 CREATED ord-team-12

submission #2: {:email=>"", :plan=>"premium", :seats=>0}
  {
    "status": 422,
    "capability": "checkout",
    "errors": [
      {
        "field": "email",
        "messages": [
          "size cannot be less than 1"
        ],
        "type": "string"
      },
      {
        "field": "plan",
        "messages": [
          "must be one of: starter, team, enterprise"
        ],
        "allowed": [
          "starter",
          "team",
          "enterprise"
        ],
        "type": "string"
      },
      {
        "field": "seats",
        "messages": [
          "must be greater than or equal to 1"
        ],
        "minimum": 1,
        "maximum": 500,
        "type": "number"
      }
    ],
    "policy_violations": []
  }

submission #3: {:email=>"joan@example.com", :plan=>"starter", :seats=>9}
  {
    "status": 422,
    "capability": "checkout",
    "errors": [],
    "policy_violations": [
      {
        "rule": "starter_seat_limit",
        "message": "starter plan is limited to 5 seats",
        "highlight_fields": [
          "plan",
          "seats"
        ]
      }
    ]
  }

the renderer never saw the checkout contract - 'allowed', 'minimum',
and 'maximum' all traveled inside the exception. one renderer serves
every capability in the app, current and future.

source

# frozen_string_literal: true

# The 422 Generator: turn a ValidationError into the API error document
# your frontend actually wants - message, allowed values, bounds - using
# ONLY what the exception carries (new this round: #expectations). The
# renderer has zero knowledge of the contract; the exception brings the
# contract with it.
#
#   bundle exec ruby examples/form_errors.rb
#
# Runs offline; prints the JSON your form would receive.

require class="s">"bundler/setup"
require class="s">"agentic"
require class="s">"json"

spec = Agentic:class="y">:CapabilitySpecification.new(
  name: class="s">"checkout",
  description: class="s">"Process a checkout form",
  version: class="s">"1.0.0",
  inputs: {
    email: {type: class="s">"string", required: true, non_empty: true},
    plan: {type: class="s">"string", required: true, enum: %w[starter team enterprise]},
    seats: {type: class="s">"number", required: true, min: 1, max: 500},
    coupon: {type: class="s">"string"}
  },
  rules: {
    starter_seat_limit: {
      message: class="s">"starter plan is limited to 5 seats",
      fields: [class="y">:plan, class="y">:seats],
      check: ->(i) { i[class="y">:plan] != class="s">"starter" || i[class="y">:seats] <= 5 }
    }
  }
)
Agentic.register_capability(spec, Agentic:class="y">:CapabilityProvider.new(
  capability: spec, implementation: ->(i) { {order_id: class="s">"ord-#{i[class="y">:plan]}-#{i[class="y">:seats]}"} }
))

# The renderer: exception in, error document out. Note what it does NOT
# have: any reference to the checkout contract.
def error_document(error)
  field_errors = error.violations.filter_map do |field, messages|
    next if field == class="y">:base

    declared = error.expectations[field] || {}
    detail = {field: field, messages: Array(messages)}
    detail[class="y">:allowed] = declared[class="y">:enum] if declared[class="y">:enum]
    detail[class="y">:minimum] = declared[class="y">:min] if declared[class="y">:min]
    detail[class="y">:maximum] = declared[class="y">:max] if declared[class="y">:max]
    detail[class="y">:type] = declared[class="y">:type] if declared[class="y">:type]
    detail
  end

  {
    status: 422,
    capability: error.capability,
    errors: field_errors,
    # Structured rule violations point at the widgets they involve
    policy_violations: error.rule_violations.map { |v|
      {rule: v[class="y">:rule], message: v[class="y">:message], highlight_fields: v[class="y">:fields]}
    }
  }
end

checkout = Agentic:class="y">:AgentCapabilityRegistry.instance.get_provider(class="s">"checkout")

SUBMISSIONS = [
  {email: class="s">"ada@example.com", plan: class="s">"team", seats: 12},
  {email: class="s">"", plan: class="s">"premium", seats: 0},
  {email: class="s">"joan@example.com", plan: class="s">"starter", seats: 9}
].freeze

SUBMISSIONS.each_with_index do |form, index|
  puts class="s">"submission ##{index + 1}: #{form.inspect}"
  begin
    result = checkout.execute(form)
    puts class="s">"  201 CREATED #{result[class="y">:order_id]}"
  rescue Agentic:class="y">:Errors:class="y">:ValidationError => e
    puts JSON.pretty_generate(error_document(e)).gsub(/^/, class="s">"  ")
  end
  puts
end

puts class="s">"the renderer never saw the checkout contract - 'allowed', 'minimum',"
puts class="s">"and 'maximum' all traveled inside the exception. one renderer serves"
puts class="s">"every capability in the app, current and future."