Validation Errors
ActiveRecord validation errors captured and presented in a unified format
API Definition
config/apis/happy_zebra.rb
# frozen_string_literal: true
Apiwork::API.define '/happy_zebra' do
key_format :camel
export :openapi
export :typescript
export :zod
resources :users
resources :posts, only: %i[index]
resources :comments, only: %i[index]
endModels
app/models/happy_zebra/user.rb
# frozen_string_literal: true
module HappyZebra
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_one :profile, dependent: :destroy
accepts_nested_attributes_for :posts
accepts_nested_attributes_for :profile
validates :email, format: { with: /@/ }, presence: true
validates :username, length: { maximum: 20, minimum: 3 }, presence: true
validate :validate_username_differs_from_email
private
def validate_username_differs_from_email
return unless username.present? && email.present?
return unless username == email
errors.add(:base, :conflict)
end
end
endDatabase Table
| Column | Type | Nullable | Default |
|---|---|---|---|
| id | string | ||
| created_at | datetime | ||
| string | |||
| updated_at | datetime | ||
| username | string |
app/models/happy_zebra/profile.rb
# frozen_string_literal: true
module HappyZebra
class Profile < ApplicationRecord
belongs_to :user
validates :bio, length: { maximum: 500 }
end
endDatabase Table
| Column | Type | Nullable | Default |
|---|---|---|---|
| id | string | ||
| bio | text | ✓ | |
| created_at | datetime | ||
| updated_at | datetime | ||
| user_id | string | ||
| website | string | ✓ |
app/models/happy_zebra/post.rb
# frozen_string_literal: true
module HappyZebra
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
accepts_nested_attributes_for :comments
validates :title, length: { maximum: 100 }, presence: true
end
endDatabase Table
| Column | Type | Nullable | Default |
|---|---|---|---|
| id | string | ||
| created_at | datetime | ||
| title | string | ||
| updated_at | datetime | ||
| user_id | string |
app/models/happy_zebra/comment.rb
# frozen_string_literal: true
module HappyZebra
class Comment < ApplicationRecord
belongs_to :post
validates :body, length: { maximum: 500 }, presence: true
validates :author, presence: true
end
endDatabase Table
| Column | Type | Nullable | Default |
|---|---|---|---|
| id | string | ||
| author | string | ||
| body | string | ||
| created_at | datetime | ||
| post_id | string | ||
| updated_at | datetime |
Representations
app/representations/happy_zebra/user_representation.rb
# frozen_string_literal: true
module HappyZebra
class UserRepresentation < Apiwork::Representation::Base
attribute :id
attribute :email, filterable: true, writable: true
attribute :username, filterable: true, writable: true
attribute :created_at
attribute :updated_at
has_one :profile, include: :always, writable: true
has_many :posts, include: :always, writable: true
end
endapp/representations/happy_zebra/profile_representation.rb
# frozen_string_literal: true
module HappyZebra
class ProfileRepresentation < Apiwork::Representation::Base
attribute :id
attribute :bio, writable: true
attribute :website, writable: true
attribute :created_at
attribute :updated_at
belongs_to :user
end
endapp/representations/happy_zebra/post_representation.rb
# frozen_string_literal: true
module HappyZebra
class PostRepresentation < Apiwork::Representation::Base
attribute :id
attribute :title, writable: true
has_many :comments, include: :always, writable: true
end
endapp/representations/happy_zebra/comment_representation.rb
# frozen_string_literal: true
module HappyZebra
class CommentRepresentation < Apiwork::Representation::Base
attribute :id
attribute :body, writable: true
attribute :author, writable: true
end
endContracts
app/contracts/happy_zebra/user_contract.rb
# frozen_string_literal: true
module HappyZebra
class UserContract < Apiwork::Contract::Base
representation UserRepresentation
end
endapp/contracts/happy_zebra/post_contract.rb
# frozen_string_literal: true
module HappyZebra
class PostContract < Apiwork::Contract::Base
representation PostRepresentation
end
endapp/contracts/happy_zebra/comment_contract.rb
# frozen_string_literal: true
module HappyZebra
class CommentContract < Apiwork::Contract::Base
representation CommentRepresentation
end
endControllers
app/controllers/happy_zebra/users_controller.rb
# frozen_string_literal: true
module HappyZebra
class UsersController < ApplicationController
before_action :set_user, only: %i[show update destroy]
def index
users = User.all
expose users
end
def show
expose user
end
def create
user = User.create(contract.body[:user])
expose user
end
def update
user.update(contract.body[:user])
expose user
end
def destroy
user.destroy
expose user
end
private
attr_reader :user
def set_user
@user = User.find(params[:id])
end
end
endapp/controllers/happy_zebra/posts_controller.rb
# frozen_string_literal: true
module HappyZebra
class PostsController < ApplicationController
def index
posts = Post.all
expose posts
end
end
endapp/controllers/happy_zebra/comments_controller.rb
# frozen_string_literal: true
module HappyZebra
class CommentsController < ApplicationController
def index
comments = Comment.all
expose comments
end
end
endRequest Examples
Create valid user
Request
POST /happy_zebra/users
Content-Type: application/json
{
"user": {
"email": "john@example.com",
"username": "johndoe",
"profile": {
"bio": "Software developer",
"website": "https://example.com"
}
}
}Response 400
{
"issues": [
{
"code": "field_missing",
"detail": "Required",
"meta": {
"field": "OP"
},
"path": [
"user",
"profile",
"OP"
],
"pointer": "/user/profile/OP"
}
],
"layer": "contract"
}Invalid email format
Request
POST /happy_zebra/users
Content-Type: application/json
{
"user": {
"email": "not-an-email",
"username": "johndoe"
}
}Response 422
{
"issues": [
{
"code": "invalid",
"detail": "Invalid",
"meta": {},
"path": [
"user",
"email"
],
"pointer": "/user/email"
}
],
"layer": "domain"
}Empty required fields
Request
POST /happy_zebra/users
Content-Type: application/json
{
"user": {
"email": "",
"username": ""
}
}Response 422
{
"issues": [
{
"code": "invalid",
"detail": "Invalid",
"meta": {},
"path": [
"user",
"email"
],
"pointer": "/user/email"
},
{
"code": "required",
"detail": "Required",
"meta": {},
"path": [
"user",
"email"
],
"pointer": "/user/email"
},
{
"code": "min",
"detail": "Too short",
"meta": {
"min": 3
},
"path": [
"user",
"username"
],
"pointer": "/user/username"
},
{
"code": "required",
"detail": "Required",
"meta": {},
"path": [
"user",
"username"
],
"pointer": "/user/username"
}
],
"layer": "domain"
}Invalid nested profile
Request
POST /happy_zebra/users
Content-Type: application/json
{
"user": {
"email": "john@example.com",
"username": "johndoe",
"profile": {
"bio": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Extra text to push this well over the five hundred character limit that exists."
}
}
}Response 400
{
"issues": [
{
"code": "field_missing",
"detail": "Required",
"meta": {
"field": "OP"
},
"path": [
"user",
"profile",
"OP"
],
"pointer": "/user/profile/OP"
}
],
"layer": "contract"
}Create with deep nesting
Request
POST /happy_zebra/users
Content-Type: application/json
{
"user": {
"email": "deep@example.com",
"username": "deepuser",
"posts": [
{
"title": "My First Post",
"comments": [
{
"body": "Great post!",
"author": "Jane"
},
{
"body": "Thanks for sharing",
"author": "Bob"
}
]
}
]
}
}Response 400
{
"issues": [
{
"code": "field_missing",
"detail": "Required",
"meta": {
"field": "OP"
},
"path": [
"user",
"posts",
0,
"OP"
],
"pointer": "/user/posts/0/OP"
}
],
"layer": "contract"
}Custom base error
Request
POST /happy_zebra/users
Content-Type: application/json
{
"user": {
"email": "same@example.com",
"username": "same@example.com"
}
}Response 422
{
"issues": [
{
"code": "conflict",
"detail": "Conflict",
"meta": {},
"path": [
"user"
],
"pointer": "/user"
}
],
"layer": "domain"
}Deep nested validation error
Request
POST /happy_zebra/users
Content-Type: application/json
{
"user": {
"email": "deep@example.com",
"username": "deepuser",
"posts": [
{
"title": "My Post",
"comments": [
{
"body": "",
"author": ""
}
]
}
]
}
}Response 400
{
"issues": [
{
"code": "field_missing",
"detail": "Required",
"meta": {
"field": "OP"
},
"path": [
"user",
"posts",
0,
"OP"
],
"pointer": "/user/posts/0/OP"
}
],
"layer": "contract"
}Generated Output
Introspection
{
"base_path": "/happy_zebra",
"fingerprint": "dd110a4ef0a4a1fb",
"locales": [],
"enums": {
"error_layer": {
"deprecated": false,
"description": null,
"example": null,
"values": [
"http",
"contract",
"domain"
]
}
},
"error_codes": {
"unprocessable_entity": {
"description": "Unprocessable Entity",
"status": 422
}
},
"info": null,
"resources": {
"users": {
"actions": {
"index": {
"deprecated": false,
"description": null,
"method": "get",
"operation_id": null,
"path": "/users",
"raises": [],
"request": {
"description": null,
"body": {},
"query": {
"filter": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "union",
"discriminator": null,
"variants": [
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user_filter"
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user_filter"
},
"shape": {}
}
]
},
"include": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "user_include"
},
"page": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "user_page"
}
}
},
"response": {
"description": null,
"body": {
"deprecated": null,
"description": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"meta": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "object",
"partial": false,
"shape": {}
},
"pagination": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "offset_pagination"
},
"users": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user"
},
"shape": {}
}
}
},
"no_content": false
},
"summary": null,
"tags": []
},
"show": {
"deprecated": false,
"description": null,
"method": "get",
"operation_id": null,
"path": "/users/:id",
"raises": [],
"request": {
"description": null,
"body": {},
"query": {
"include": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "user_include"
}
}
},
"response": {
"description": null,
"body": {
"deprecated": null,
"description": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"meta": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "object",
"partial": false,
"shape": {}
},
"user": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user"
}
}
},
"no_content": false
},
"summary": null,
"tags": []
},
"create": {
"deprecated": false,
"description": null,
"method": "post",
"operation_id": null,
"path": "/users",
"raises": [
"unprocessable_entity"
],
"request": {
"description": null,
"body": {
"user": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user_create_payload"
}
},
"query": {
"include": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "user_include"
}
}
},
"response": {
"description": null,
"body": {
"deprecated": null,
"description": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"meta": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "object",
"partial": false,
"shape": {}
},
"user": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user"
}
}
},
"no_content": false
},
"summary": null,
"tags": []
},
"update": {
"deprecated": false,
"description": null,
"method": "patch",
"operation_id": null,
"path": "/users/:id",
"raises": [
"unprocessable_entity"
],
"request": {
"description": null,
"body": {
"user": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user_update_payload"
}
},
"query": {
"include": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "user_include"
}
}
},
"response": {
"description": null,
"body": {
"deprecated": null,
"description": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"meta": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "object",
"partial": false,
"shape": {}
},
"user": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user"
}
}
},
"no_content": false
},
"summary": null,
"tags": []
},
"destroy": {
"deprecated": false,
"description": null,
"method": "delete",
"operation_id": null,
"path": "/users/:id",
"raises": [],
"request": {
"description": null,
"body": {},
"query": {
"include": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "user_include"
}
}
},
"response": {
"description": null,
"body": null,
"no_content": true
},
"summary": null,
"tags": []
}
},
"identifier": "users",
"parent_identifiers": [],
"path": "users",
"resources": {}
},
"posts": {
"actions": {
"index": {
"deprecated": false,
"description": null,
"method": "get",
"operation_id": null,
"path": "/posts",
"raises": [],
"request": {
"description": null,
"body": {},
"query": {
"page": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "post_page"
}
}
},
"response": {
"description": null,
"body": {
"deprecated": null,
"description": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"meta": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "object",
"partial": false,
"shape": {}
},
"pagination": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "offset_pagination"
},
"posts": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "post"
},
"shape": {}
}
}
},
"no_content": false
},
"summary": null,
"tags": []
}
},
"identifier": "posts",
"parent_identifiers": [],
"path": "posts",
"resources": {}
},
"comments": {
"actions": {
"index": {
"deprecated": false,
"description": null,
"method": "get",
"operation_id": null,
"path": "/comments",
"raises": [],
"request": {
"description": null,
"body": {},
"query": {
"page": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "comment_page"
}
}
},
"response": {
"description": null,
"body": {
"deprecated": null,
"description": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"comments": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "comment"
},
"shape": {}
},
"meta": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "object",
"partial": false,
"shape": {}
},
"pagination": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "offset_pagination"
}
}
},
"no_content": false
},
"summary": null,
"tags": []
}
},
"identifier": "comments",
"parent_identifiers": [],
"path": "comments",
"resources": {}
}
},
"types": {
"comment": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"author": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"body": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"comment_nested_create_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"OP": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "literal",
"value": "create"
},
"author": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"body": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"comment_nested_delete_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"OP": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "literal",
"value": "delete"
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"comment_nested_payload": {
"deprecated": false,
"description": null,
"discriminator": "OP",
"example": null,
"extends": [],
"shape": {},
"type": "union",
"variants": [
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "comment_nested_create_payload"
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "comment_nested_update_payload"
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "comment_nested_delete_payload"
}
]
},
"comment_nested_update_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"OP": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "literal",
"value": "update"
},
"author": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"body": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"comment_page": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"number": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": null,
"min": 1
},
"size": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": 100,
"min": 1
}
},
"type": "object",
"variants": []
},
"error": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"issues": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "error_issue"
},
"shape": {}
},
"layer": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "error_layer"
}
},
"type": "object",
"variants": []
},
"error_issue": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"code": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"detail": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"meta": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "object",
"partial": false,
"shape": {}
},
"path": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "union",
"discriminator": null,
"variants": [
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
]
},
"shape": {}
},
"pointer": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"offset_pagination": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"current": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"items": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"next": {
"deprecated": false,
"description": null,
"nullable": true,
"optional": true,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"prev": {
"deprecated": false,
"description": null,
"nullable": true,
"optional": true,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"total": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"post": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"comments": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "comment"
},
"shape": {}
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"title": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"post_nested_create_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"OP": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "literal",
"value": "create"
},
"comments": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "comment_nested_payload"
},
"shape": {}
},
"title": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"post_nested_delete_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"OP": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "literal",
"value": "delete"
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"post_nested_payload": {
"deprecated": false,
"description": null,
"discriminator": "OP",
"example": null,
"extends": [],
"shape": {},
"type": "union",
"variants": [
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "post_nested_create_payload"
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "post_nested_update_payload"
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "post_nested_delete_payload"
}
]
},
"post_nested_update_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"OP": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "literal",
"value": "update"
},
"comments": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "comment_nested_payload"
},
"shape": {}
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"title": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"post_page": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"number": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": null,
"min": 1
},
"size": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": 100,
"min": 1
}
},
"type": "object",
"variants": []
},
"profile": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"bio": {
"deprecated": false,
"description": null,
"nullable": true,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"created_at": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "datetime",
"default": null,
"example": null
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"updated_at": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "datetime",
"default": null,
"example": null
},
"user": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "user"
},
"website": {
"deprecated": false,
"description": null,
"nullable": true,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"profile_include": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"user": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "boolean",
"default": null,
"example": null
}
},
"type": "object",
"variants": []
},
"profile_nested_create_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"OP": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "literal",
"value": "create"
},
"bio": {
"deprecated": false,
"description": null,
"nullable": true,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"website": {
"deprecated": false,
"description": null,
"nullable": true,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"profile_nested_delete_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"OP": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "literal",
"value": "delete"
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"profile_nested_payload": {
"deprecated": false,
"description": null,
"discriminator": "OP",
"example": null,
"extends": [],
"shape": {},
"type": "union",
"variants": [
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "profile_nested_create_payload"
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "profile_nested_update_payload"
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "profile_nested_delete_payload"
}
]
},
"profile_nested_update_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"OP": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "literal",
"value": "update"
},
"bio": {
"deprecated": false,
"description": null,
"nullable": true,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"website": {
"deprecated": false,
"description": null,
"nullable": true,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"string_filter": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"contains": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"ends_with": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"eq": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"in": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"shape": {}
},
"starts_with": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"user": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"created_at": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "datetime",
"default": null,
"example": null
},
"email": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"id": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"posts": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "post"
},
"shape": {}
},
"profile": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "profile"
},
"updated_at": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "datetime",
"default": null,
"example": null
},
"username": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"user_create_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"email": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"posts": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "post_nested_payload"
},
"shape": {}
},
"profile": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "profile_nested_payload"
},
"username": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"user_filter": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"AND": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user_filter"
},
"shape": {}
},
"NOT": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "user_filter"
},
"OR": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "user_filter"
},
"shape": {}
},
"email": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "union",
"discriminator": null,
"variants": [
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "string_filter"
}
]
},
"username": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "union",
"discriminator": null,
"variants": [
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
{
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "string_filter"
}
]
}
},
"type": "object",
"variants": []
},
"user_include": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"profile": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "profile_include"
}
},
"type": "object",
"variants": []
},
"user_page": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"number": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": null,
"min": 1
},
"size": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "integer",
"default": null,
"example": null,
"format": null,
"max": 100,
"min": 1
}
},
"type": "object",
"variants": []
},
"user_update_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"email": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
},
"posts": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "array",
"default": null,
"example": null,
"max": null,
"min": null,
"of": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "post_nested_payload"
},
"shape": {}
},
"profile": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "reference",
"reference": "profile_nested_payload"
},
"username": {
"deprecated": false,
"description": null,
"nullable": false,
"optional": true,
"type": "string",
"default": null,
"example": null,
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
}
}
}TypeScript
export interface Comment {
author: string;
body: string;
id: string;
}
export interface CommentNestedCreatePayload {
OP: 'create';
author: string;
body: string;
}
export interface CommentNestedDeletePayload {
OP: 'delete';
id: string;
}
export type CommentNestedPayload = CommentNestedCreatePayload | CommentNestedUpdatePayload | CommentNestedDeletePayload;
export interface CommentNestedUpdatePayload {
OP: 'update';
author?: string;
body?: string;
id?: string;
}
export interface CommentPage {
number?: number;
size?: number;
}
export interface CommentsIndexRequest {
query: CommentsIndexRequestQuery;
}
export interface CommentsIndexRequestQuery {
page?: CommentPage;
}
export type CommentsIndexResponse = { status: 200; body: CommentsIndexResponseBody };
export type CommentsIndexResponseBody = { comments: Comment[]; meta?: Record<string, unknown>; pagination: OffsetPagination };
export interface Error {
issues: ErrorIssue[];
layer: ErrorLayer;
}
export interface ErrorIssue {
code: string;
detail: string;
meta: Record<string, unknown>;
path: (number | string)[];
pointer: string;
}
export type ErrorLayer = 'contract' | 'domain' | 'http';
export interface OffsetPagination {
current: number;
items: number;
next: null | number;
prev: null | number;
total: number;
}
export interface Post {
comments: Comment[];
id: string;
title: string;
}
export interface PostNestedCreatePayload {
OP: 'create';
comments?: CommentNestedPayload[];
title: string;
}
export interface PostNestedDeletePayload {
OP: 'delete';
id: string;
}
export type PostNestedPayload = PostNestedCreatePayload | PostNestedUpdatePayload | PostNestedDeletePayload;
export interface PostNestedUpdatePayload {
OP: 'update';
comments?: CommentNestedPayload[];
id?: string;
title?: string;
}
export interface PostPage {
number?: number;
size?: number;
}
export interface PostsIndexRequest {
query: PostsIndexRequestQuery;
}
export interface PostsIndexRequestQuery {
page?: PostPage;
}
export type PostsIndexResponse = { status: 200; body: PostsIndexResponseBody };
export type PostsIndexResponseBody = { meta?: Record<string, unknown>; pagination: OffsetPagination; posts: Post[] };
export interface Profile {
bio: null | string;
createdAt: string;
id: string;
updatedAt: string;
user?: User;
website: null | string;
}
export interface ProfileInclude {
user?: boolean;
}
export interface ProfileNestedCreatePayload {
OP: 'create';
bio: null | string;
website: null | string;
}
export interface ProfileNestedDeletePayload {
OP: 'delete';
id: string;
}
export type ProfileNestedPayload = ProfileNestedCreatePayload | ProfileNestedUpdatePayload | ProfileNestedDeletePayload;
export interface ProfileNestedUpdatePayload {
OP: 'update';
bio: null | string;
id?: string;
website: null | string;
}
export interface StringFilter {
contains?: string;
endsWith?: string;
eq?: string;
in?: string[];
startsWith?: string;
}
export interface User {
createdAt: string;
email: string;
id: string;
posts: Post[];
profile: Profile;
updatedAt: string;
username: string;
}
export interface UserCreatePayload {
email: string;
posts?: PostNestedPayload[];
profile?: ProfileNestedPayload;
username: string;
}
export interface UserFilter {
AND?: UserFilter[];
NOT?: UserFilter;
OR?: UserFilter[];
email?: StringFilter | string;
username?: StringFilter | string;
}
export interface UserInclude {
profile?: ProfileInclude;
}
export interface UserPage {
number?: number;
size?: number;
}
export interface UserUpdatePayload {
email?: string;
posts?: PostNestedPayload[];
profile?: ProfileNestedPayload;
username?: string;
}
export interface UsersCreateRequest {
query: UsersCreateRequestQuery;
body: UsersCreateRequestBody;
}
export interface UsersCreateRequestBody {
user: UserCreatePayload;
}
export interface UsersCreateRequestQuery {
include?: UserInclude;
}
export type UsersCreateResponse =
| { status: 200; body: UsersCreateResponseBody }
| { status: 422; body: Error };
export type UsersCreateResponseBody = { meta?: Record<string, unknown>; user: User };
export interface UsersDestroyRequest {
query: UsersDestroyRequestQuery;
}
export interface UsersDestroyRequestQuery {
include?: UserInclude;
}
export type UsersDestroyResponse = { status: 204 };
export interface UsersIndexRequest {
query: UsersIndexRequestQuery;
}
export interface UsersIndexRequestQuery {
filter?: UserFilter | UserFilter[];
include?: UserInclude;
page?: UserPage;
}
export type UsersIndexResponse = { status: 200; body: UsersIndexResponseBody };
export type UsersIndexResponseBody = { meta?: Record<string, unknown>; pagination: OffsetPagination; users: User[] };
export interface UsersShowRequest {
query: UsersShowRequestQuery;
}
export interface UsersShowRequestQuery {
include?: UserInclude;
}
export type UsersShowResponse = { status: 200; body: UsersShowResponseBody };
export type UsersShowResponseBody = { meta?: Record<string, unknown>; user: User };
export interface UsersUpdateRequest {
query: UsersUpdateRequestQuery;
body: UsersUpdateRequestBody;
}
export interface UsersUpdateRequestBody {
user: UserUpdatePayload;
}
export interface UsersUpdateRequestQuery {
include?: UserInclude;
}
export type UsersUpdateResponse =
| { status: 200; body: UsersUpdateResponseBody }
| { status: 422; body: Error };
export type UsersUpdateResponseBody = { meta?: Record<string, unknown>; user: User };Zod
import { z } from 'zod';
export const ErrorLayerSchema = z.enum(['contract', 'domain', 'http']);
export const ProfileSchema: z.ZodType<Profile> = z.lazy(() => z.object({
bio: z.string().nullable(),
createdAt: z.iso.datetime(),
id: z.string(),
updatedAt: z.iso.datetime(),
user: UserSchema.optional(),
website: z.string().nullable()
}));
export const UserFilterSchema: z.ZodType<UserFilter> = z.lazy(() => z.object({
AND: z.array(UserFilterSchema).optional(),
NOT: UserFilterSchema.optional(),
OR: z.array(UserFilterSchema).optional(),
email: z.union([z.string(), StringFilterSchema]).optional(),
username: z.union([z.string(), StringFilterSchema]).optional()
}));
export const CommentSchema = z.object({
author: z.string(),
body: z.string(),
id: z.string()
});
export const CommentNestedCreatePayloadSchema = z.object({
OP: z.literal('create'),
author: z.string(),
body: z.string()
});
export const CommentNestedDeletePayloadSchema = z.object({
OP: z.literal('delete'),
id: z.string()
});
export const CommentNestedUpdatePayloadSchema = z.object({
OP: z.literal('update'),
author: z.string().optional(),
body: z.string().optional(),
id: z.string().optional()
});
export const CommentPageSchema = z.object({
number: z.number().int().min(1).optional(),
size: z.number().int().min(1).max(100).optional()
});
export const ErrorIssueSchema = z.object({
code: z.string(),
detail: z.string(),
meta: z.record(z.string(), z.unknown()),
path: z.array(z.union([z.string(), z.number().int()])),
pointer: z.string()
});
export const OffsetPaginationSchema = z.object({
current: z.number().int(),
items: z.number().int(),
next: z.number().int().nullable().default(null),
prev: z.number().int().nullable().default(null),
total: z.number().int()
});
export const PostNestedDeletePayloadSchema = z.object({
OP: z.literal('delete'),
id: z.string()
});
export const PostPageSchema = z.object({
number: z.number().int().min(1).optional(),
size: z.number().int().min(1).max(100).optional()
});
export const ProfileIncludeSchema = z.object({
user: z.boolean().optional()
});
export const ProfileNestedCreatePayloadSchema = z.object({
OP: z.literal('create'),
bio: z.string().nullable().default(null),
website: z.string().nullable().default(null)
});
export const ProfileNestedDeletePayloadSchema = z.object({
OP: z.literal('delete'),
id: z.string()
});
export const ProfileNestedUpdatePayloadSchema = z.object({
OP: z.literal('update'),
bio: z.string().nullable().default(null),
id: z.string().optional(),
website: z.string().nullable().default(null)
});
export const StringFilterSchema = z.object({
contains: z.string().optional(),
endsWith: z.string().optional(),
eq: z.string().optional(),
in: z.array(z.string()).optional(),
startsWith: z.string().optional()
});
export const UserPageSchema = z.object({
number: z.number().int().min(1).optional(),
size: z.number().int().min(1).max(100).optional()
});
export const CommentNestedPayloadSchema = z.discriminatedUnion('OP', [
CommentNestedCreatePayloadSchema,
CommentNestedUpdatePayloadSchema,
CommentNestedDeletePayloadSchema
]);
export const ErrorSchema = z.object({
issues: z.array(ErrorIssueSchema),
layer: ErrorLayerSchema
});
export const PostSchema = z.object({
comments: z.array(CommentSchema),
id: z.string(),
title: z.string()
});
export const ProfileNestedPayloadSchema = z.discriminatedUnion('OP', [
ProfileNestedCreatePayloadSchema,
ProfileNestedUpdatePayloadSchema,
ProfileNestedDeletePayloadSchema
]);
export const UserIncludeSchema = z.object({
profile: ProfileIncludeSchema.optional()
});
export const PostNestedCreatePayloadSchema = z.object({
OP: z.literal('create'),
comments: z.array(CommentNestedPayloadSchema).optional(),
title: z.string()
});
export const PostNestedUpdatePayloadSchema = z.object({
OP: z.literal('update'),
comments: z.array(CommentNestedPayloadSchema).optional(),
id: z.string().optional(),
title: z.string().optional()
});
export const UserSchema = z.object({
createdAt: z.iso.datetime(),
email: z.string(),
id: z.string(),
posts: z.array(PostSchema),
profile: ProfileSchema,
updatedAt: z.iso.datetime(),
username: z.string()
});
export const PostNestedPayloadSchema = z.discriminatedUnion('OP', [
PostNestedCreatePayloadSchema,
PostNestedUpdatePayloadSchema,
PostNestedDeletePayloadSchema
]);
export const UserCreatePayloadSchema = z.object({
email: z.string(),
posts: z.array(PostNestedPayloadSchema).optional(),
profile: ProfileNestedPayloadSchema.optional(),
username: z.string()
});
export const UserUpdatePayloadSchema = z.object({
email: z.string().optional(),
posts: z.array(PostNestedPayloadSchema).optional(),
profile: ProfileNestedPayloadSchema.optional(),
username: z.string().optional()
});
export const UsersIndexRequestQuerySchema = z.object({
filter: z.union([UserFilterSchema, z.array(UserFilterSchema)]).optional(),
include: UserIncludeSchema.optional(),
page: UserPageSchema.optional()
});
export const UsersIndexRequestSchema = z.object({
query: UsersIndexRequestQuerySchema
});
export const UsersIndexResponseBodySchema = z.object({ meta: z.record(z.string(), z.unknown()).optional(), pagination: OffsetPaginationSchema, users: z.array(UserSchema) });
export const UsersShowRequestQuerySchema = z.object({
include: UserIncludeSchema.optional()
});
export const UsersShowRequestSchema = z.object({
query: UsersShowRequestQuerySchema
});
export const UsersShowResponseBodySchema = z.object({ meta: z.record(z.string(), z.unknown()).optional(), user: UserSchema });
export const UsersCreateRequestQuerySchema = z.object({
include: UserIncludeSchema.optional()
});
export const UsersCreateRequestBodySchema = z.object({
user: UserCreatePayloadSchema
});
export const UsersCreateRequestSchema = z.object({
query: UsersCreateRequestQuerySchema,
body: UsersCreateRequestBodySchema
});
export const UsersCreateResponseBodySchema = z.object({ meta: z.record(z.string(), z.unknown()).optional(), user: UserSchema });
export const UsersUpdateRequestQuerySchema = z.object({
include: UserIncludeSchema.optional()
});
export const UsersUpdateRequestBodySchema = z.object({
user: UserUpdatePayloadSchema
});
export const UsersUpdateRequestSchema = z.object({
query: UsersUpdateRequestQuerySchema,
body: UsersUpdateRequestBodySchema
});
export const UsersUpdateResponseBodySchema = z.object({ meta: z.record(z.string(), z.unknown()).optional(), user: UserSchema });
export const UsersDestroyRequestQuerySchema = z.object({
include: UserIncludeSchema.optional()
});
export const UsersDestroyRequestSchema = z.object({
query: UsersDestroyRequestQuerySchema
});
export const PostsIndexRequestQuerySchema = z.object({
page: PostPageSchema.optional()
});
export const PostsIndexRequestSchema = z.object({
query: PostsIndexRequestQuerySchema
});
export const PostsIndexResponseBodySchema = z.object({ meta: z.record(z.string(), z.unknown()).optional(), pagination: OffsetPaginationSchema, posts: z.array(PostSchema) });
export const CommentsIndexRequestQuerySchema = z.object({
page: CommentPageSchema.optional()
});
export const CommentsIndexRequestSchema = z.object({
query: CommentsIndexRequestQuerySchema
});
export const CommentsIndexResponseBodySchema = z.object({ comments: z.array(CommentSchema), meta: z.record(z.string(), z.unknown()).optional(), pagination: OffsetPaginationSchema });
export const UsersIndexResponseSchema = z.object({ status: z.literal(200), body: UsersIndexResponseBodySchema });
export const UsersShowResponseSchema = z.object({ status: z.literal(200), body: UsersShowResponseBodySchema });
export const UsersCreateResponseSchema = z.discriminatedUnion('status', [
z.object({ status: z.literal(200), body: UsersCreateResponseBodySchema }),
z.object({ status: z.literal(422), body: ErrorSchema })
]);
export const UsersUpdateResponseSchema = z.discriminatedUnion('status', [
z.object({ status: z.literal(200), body: UsersUpdateResponseBodySchema }),
z.object({ status: z.literal(422), body: ErrorSchema })
]);
export const UsersDestroyResponseSchema = z.object({ status: z.literal(204) });
export const PostsIndexResponseSchema = z.object({ status: z.literal(200), body: PostsIndexResponseBodySchema });
export const CommentsIndexResponseSchema = z.object({ status: z.literal(200), body: CommentsIndexResponseBodySchema });
export interface Comment {
author: string;
body: string;
id: string;
}
export interface CommentNestedCreatePayload {
OP: 'create';
author: string;
body: string;
}
export interface CommentNestedDeletePayload {
OP: 'delete';
id: string;
}
export type CommentNestedPayload = CommentNestedCreatePayload | CommentNestedUpdatePayload | CommentNestedDeletePayload;
export interface CommentNestedUpdatePayload {
OP: 'update';
author?: string;
body?: string;
id?: string;
}
export interface CommentPage {
number?: number;
size?: number;
}
export interface CommentsIndexRequest {
query: CommentsIndexRequestQuery;
}
export interface CommentsIndexRequestQuery {
page?: CommentPage;
}
export type CommentsIndexResponse = { status: 200; body: CommentsIndexResponseBody };
export type CommentsIndexResponseBody = { comments: Comment[]; meta?: Record<string, unknown>; pagination: OffsetPagination };
export interface Error {
issues: ErrorIssue[];
layer: ErrorLayer;
}
export interface ErrorIssue {
code: string;
detail: string;
meta: Record<string, unknown>;
path: (number | string)[];
pointer: string;
}
export type ErrorLayer = 'contract' | 'domain' | 'http';
export interface OffsetPagination {
current: number;
items: number;
next: null | number;
prev: null | number;
total: number;
}
export interface Post {
comments: Comment[];
id: string;
title: string;
}
export interface PostNestedCreatePayload {
OP: 'create';
comments?: CommentNestedPayload[];
title: string;
}
export interface PostNestedDeletePayload {
OP: 'delete';
id: string;
}
export type PostNestedPayload = PostNestedCreatePayload | PostNestedUpdatePayload | PostNestedDeletePayload;
export interface PostNestedUpdatePayload {
OP: 'update';
comments?: CommentNestedPayload[];
id?: string;
title?: string;
}
export interface PostPage {
number?: number;
size?: number;
}
export interface PostsIndexRequest {
query: PostsIndexRequestQuery;
}
export interface PostsIndexRequestQuery {
page?: PostPage;
}
export type PostsIndexResponse = { status: 200; body: PostsIndexResponseBody };
export type PostsIndexResponseBody = { meta?: Record<string, unknown>; pagination: OffsetPagination; posts: Post[] };
export interface Profile {
bio: null | string;
createdAt: string;
id: string;
updatedAt: string;
user?: User;
website: null | string;
}
export interface ProfileInclude {
user?: boolean;
}
export interface ProfileNestedCreatePayload {
OP: 'create';
bio: null | string;
website: null | string;
}
export interface ProfileNestedDeletePayload {
OP: 'delete';
id: string;
}
export type ProfileNestedPayload = ProfileNestedCreatePayload | ProfileNestedUpdatePayload | ProfileNestedDeletePayload;
export interface ProfileNestedUpdatePayload {
OP: 'update';
bio: null | string;
id?: string;
website: null | string;
}
export interface StringFilter {
contains?: string;
endsWith?: string;
eq?: string;
in?: string[];
startsWith?: string;
}
export interface User {
createdAt: string;
email: string;
id: string;
posts: Post[];
profile: Profile;
updatedAt: string;
username: string;
}
export interface UserCreatePayload {
email: string;
posts?: PostNestedPayload[];
profile?: ProfileNestedPayload;
username: string;
}
export interface UserFilter {
AND?: UserFilter[];
NOT?: UserFilter;
OR?: UserFilter[];
email?: StringFilter | string;
username?: StringFilter | string;
}
export interface UserInclude {
profile?: ProfileInclude;
}
export interface UserPage {
number?: number;
size?: number;
}
export interface UserUpdatePayload {
email?: string;
posts?: PostNestedPayload[];
profile?: ProfileNestedPayload;
username?: string;
}
export interface UsersCreateRequest {
query: UsersCreateRequestQuery;
body: UsersCreateRequestBody;
}
export interface UsersCreateRequestBody {
user: UserCreatePayload;
}
export interface UsersCreateRequestQuery {
include?: UserInclude;
}
export type UsersCreateResponse =
| { status: 200; body: UsersCreateResponseBody }
| { status: 422; body: Error };
export type UsersCreateResponseBody = { meta?: Record<string, unknown>; user: User };
export interface UsersDestroyRequest {
query: UsersDestroyRequestQuery;
}
export interface UsersDestroyRequestQuery {
include?: UserInclude;
}
export type UsersDestroyResponse = { status: 204 };
export interface UsersIndexRequest {
query: UsersIndexRequestQuery;
}
export interface UsersIndexRequestQuery {
filter?: UserFilter | UserFilter[];
include?: UserInclude;
page?: UserPage;
}
export type UsersIndexResponse = { status: 200; body: UsersIndexResponseBody };
export type UsersIndexResponseBody = { meta?: Record<string, unknown>; pagination: OffsetPagination; users: User[] };
export interface UsersShowRequest {
query: UsersShowRequestQuery;
}
export interface UsersShowRequestQuery {
include?: UserInclude;
}
export type UsersShowResponse = { status: 200; body: UsersShowResponseBody };
export type UsersShowResponseBody = { meta?: Record<string, unknown>; user: User };
export interface UsersUpdateRequest {
query: UsersUpdateRequestQuery;
body: UsersUpdateRequestBody;
}
export interface UsersUpdateRequestBody {
user: UserUpdatePayload;
}
export interface UsersUpdateRequestQuery {
include?: UserInclude;
}
export type UsersUpdateResponse =
| { status: 200; body: UsersUpdateResponseBody }
| { status: 422; body: Error };
export type UsersUpdateResponseBody = { meta?: Record<string, unknown>; user: User };OpenAPI
---
openapi: 3.1.0
info:
title: "/happy_zebra"
version: 1.0.0
paths:
"/users":
get:
operationId: usersIndex
parameters:
- in: query
name: filter
required: false
schema:
oneOf:
- "$ref": "#/components/schemas/userFilter"
- items:
"$ref": "#/components/schemas/userFilter"
type: array
- in: query
name: include
required: false
schema:
"$ref": "#/components/schemas/userInclude"
- in: query
name: page
required: false
schema:
"$ref": "#/components/schemas/userPage"
responses:
'200':
content:
application/json:
schema:
properties:
meta:
properties: {}
type: object
pagination:
"$ref": "#/components/schemas/offsetPagination"
users:
items:
"$ref": "#/components/schemas/user"
type: array
type: object
required:
- pagination
- users
description: ''
post:
operationId: usersCreate
parameters:
- in: query
name: include
required: false
schema:
"$ref": "#/components/schemas/userInclude"
requestBody:
content:
application/json:
schema:
properties:
user:
"$ref": "#/components/schemas/userCreatePayload"
type: object
required:
- user
required: true
responses:
'200':
content:
application/json:
schema:
properties:
meta:
properties: {}
type: object
user:
"$ref": "#/components/schemas/user"
type: object
required:
- user
description: ''
'422':
description: Unprocessable Entity
content:
application/json:
schema:
properties:
issues:
items:
"$ref": "#/components/schemas/error"
type: array
required:
- issues
type: object
"/users/{id}":
get:
operationId: usersShow
parameters:
- in: path
name: id
required: true
schema:
type: string
- in: query
name: include
required: false
schema:
"$ref": "#/components/schemas/userInclude"
responses:
'200':
content:
application/json:
schema:
properties:
meta:
properties: {}
type: object
user:
"$ref": "#/components/schemas/user"
type: object
required:
- user
description: ''
patch:
operationId: usersUpdate
parameters:
- in: path
name: id
required: true
schema:
type: string
- in: query
name: include
required: false
schema:
"$ref": "#/components/schemas/userInclude"
requestBody:
content:
application/json:
schema:
properties:
user:
"$ref": "#/components/schemas/userUpdatePayload"
type: object
required:
- user
required: true
responses:
'200':
content:
application/json:
schema:
properties:
meta:
properties: {}
type: object
user:
"$ref": "#/components/schemas/user"
type: object
required:
- user
description: ''
'422':
description: Unprocessable Entity
content:
application/json:
schema:
properties:
issues:
items:
"$ref": "#/components/schemas/error"
type: array
required:
- issues
type: object
delete:
operationId: usersDestroy
parameters:
- in: path
name: id
required: true
schema:
type: string
- in: query
name: include
required: false
schema:
"$ref": "#/components/schemas/userInclude"
responses:
'204':
description: ''
"/posts":
get:
operationId: postsIndex
parameters:
- in: query
name: page
required: false
schema:
"$ref": "#/components/schemas/postPage"
responses:
'200':
content:
application/json:
schema:
properties:
meta:
properties: {}
type: object
pagination:
"$ref": "#/components/schemas/offsetPagination"
posts:
items:
"$ref": "#/components/schemas/post"
type: array
type: object
required:
- pagination
- posts
description: ''
"/comments":
get:
operationId: commentsIndex
parameters:
- in: query
name: page
required: false
schema:
"$ref": "#/components/schemas/commentPage"
responses:
'200':
content:
application/json:
schema:
properties:
comments:
items:
"$ref": "#/components/schemas/comment"
type: array
meta:
properties: {}
type: object
pagination:
"$ref": "#/components/schemas/offsetPagination"
type: object
required:
- comments
- pagination
description: ''
components:
schemas:
comment:
properties:
author:
type: string
body:
type: string
id:
type: string
type: object
required:
- author
- body
- id
commentNestedCreatePayload:
properties:
OP:
const: create
type: string
author:
type: string
body:
type: string
type: object
required:
- OP
- author
- body
commentNestedDeletePayload:
properties:
OP:
const: delete
type: string
id:
type: string
type: object
required:
- OP
- id
commentNestedPayload:
oneOf:
- "$ref": "#/components/schemas/commentNestedCreatePayload"
- "$ref": "#/components/schemas/commentNestedUpdatePayload"
- "$ref": "#/components/schemas/commentNestedDeletePayload"
discriminator:
mapping:
create: "#/components/schemas/commentNestedCreatePayload"
update: "#/components/schemas/commentNestedUpdatePayload"
delete: "#/components/schemas/commentNestedDeletePayload"
propertyName: OP
commentNestedUpdatePayload:
properties:
OP:
const: update
type: string
author:
type: string
body:
type: string
id:
type: string
type: object
required:
- OP
commentPage:
properties:
number:
type: integer
minimum: 1
size:
type: integer
minimum: 1
maximum: 100
type: object
error:
properties:
issues:
items:
"$ref": "#/components/schemas/errorIssue"
type: array
layer:
enum:
- http
- contract
- domain
type: string
type: object
required:
- issues
- layer
errorIssue:
properties:
code:
type: string
detail:
type: string
meta:
properties: {}
type: object
path:
items:
oneOf:
- type: string
- type: integer
type: array
pointer:
type: string
type: object
required:
- code
- detail
- meta
- path
- pointer
offsetPagination:
properties:
current:
type: integer
items:
type: integer
next:
type:
- integer
- 'null'
default:
prev:
type:
- integer
- 'null'
default:
total:
type: integer
type: object
required:
- current
- items
- total
post:
properties:
comments:
items:
"$ref": "#/components/schemas/comment"
type: array
id:
type: string
title:
type: string
type: object
required:
- comments
- id
- title
postNestedCreatePayload:
properties:
OP:
const: create
type: string
comments:
items:
"$ref": "#/components/schemas/commentNestedPayload"
type: array
title:
type: string
type: object
required:
- OP
- title
postNestedDeletePayload:
properties:
OP:
const: delete
type: string
id:
type: string
type: object
required:
- OP
- id
postNestedPayload:
oneOf:
- "$ref": "#/components/schemas/postNestedCreatePayload"
- "$ref": "#/components/schemas/postNestedUpdatePayload"
- "$ref": "#/components/schemas/postNestedDeletePayload"
discriminator:
mapping:
create: "#/components/schemas/postNestedCreatePayload"
update: "#/components/schemas/postNestedUpdatePayload"
delete: "#/components/schemas/postNestedDeletePayload"
propertyName: OP
postNestedUpdatePayload:
properties:
OP:
const: update
type: string
comments:
items:
"$ref": "#/components/schemas/commentNestedPayload"
type: array
id:
type: string
title:
type: string
type: object
required:
- OP
postPage:
properties:
number:
type: integer
minimum: 1
size:
type: integer
minimum: 1
maximum: 100
type: object
profile:
properties:
bio:
type:
- string
- 'null'
createdAt:
type: string
format: date-time
id:
type: string
updatedAt:
type: string
format: date-time
user:
"$ref": "#/components/schemas/user"
website:
type:
- string
- 'null'
type: object
required:
- bio
- createdAt
- id
- updatedAt
- website
profileInclude:
properties:
user:
type: boolean
type: object
profileNestedCreatePayload:
properties:
OP:
const: create
type: string
bio:
type:
- string
- 'null'
default:
website:
type:
- string
- 'null'
default:
type: object
required:
- OP
profileNestedDeletePayload:
properties:
OP:
const: delete
type: string
id:
type: string
type: object
required:
- OP
- id
profileNestedPayload:
oneOf:
- "$ref": "#/components/schemas/profileNestedCreatePayload"
- "$ref": "#/components/schemas/profileNestedUpdatePayload"
- "$ref": "#/components/schemas/profileNestedDeletePayload"
discriminator:
mapping:
create: "#/components/schemas/profileNestedCreatePayload"
update: "#/components/schemas/profileNestedUpdatePayload"
delete: "#/components/schemas/profileNestedDeletePayload"
propertyName: OP
profileNestedUpdatePayload:
properties:
OP:
const: update
type: string
bio:
type:
- string
- 'null'
default:
id:
type: string
website:
type:
- string
- 'null'
default:
type: object
required:
- OP
stringFilter:
properties:
contains:
type: string
endsWith:
type: string
eq:
type: string
in:
items:
type: string
type: array
startsWith:
type: string
type: object
user:
properties:
createdAt:
type: string
format: date-time
email:
type: string
id:
type: string
posts:
items:
"$ref": "#/components/schemas/post"
type: array
profile:
"$ref": "#/components/schemas/profile"
updatedAt:
type: string
format: date-time
username:
type: string
type: object
required:
- createdAt
- email
- id
- posts
- profile
- updatedAt
- username
userCreatePayload:
properties:
email:
type: string
posts:
items:
"$ref": "#/components/schemas/postNestedPayload"
type: array
profile:
"$ref": "#/components/schemas/profileNestedPayload"
username:
type: string
type: object
required:
- email
- username
userFilter:
properties:
AND:
items:
"$ref": "#/components/schemas/userFilter"
type: array
NOT:
"$ref": "#/components/schemas/userFilter"
OR:
items:
"$ref": "#/components/schemas/userFilter"
type: array
email:
oneOf:
- type: string
- "$ref": "#/components/schemas/stringFilter"
username:
oneOf:
- type: string
- "$ref": "#/components/schemas/stringFilter"
type: object
userInclude:
properties:
profile:
"$ref": "#/components/schemas/profileInclude"
type: object
userPage:
properties:
number:
type: integer
minimum: 1
size:
type: integer
minimum: 1
maximum: 100
type: object
userUpdatePayload:
properties:
email:
type: string
posts:
items:
"$ref": "#/components/schemas/postNestedPayload"
type: array
profile:
"$ref": "#/components/schemas/profileNestedPayload"
username:
type: string
type: object