Manual Contracts
Define contracts manually without representations
API Definition
config/apis/funny_snake.rb
rb
# frozen_string_literal: true
Apiwork::API.define '/funny_snake' do
key_format :camel
export :openapi
export :typescript
export :zod
resources :invoices
endModels
app/models/funny_snake/invoice.rb
rb
# frozen_string_literal: true
module FunnySnake
class Invoice < ApplicationRecord
enum :status, { draft: 0, sent: 1, paid: 2 }
validates :number, presence: true
end
endDatabase Table
| Column | Type | Nullable | Default |
|---|---|---|---|
| id | string | ||
| created_at | datetime | ||
| issued_on | date | ✓ | |
| notes | string | ✓ | |
| number | string | ||
| status | integer | 0 | |
| updated_at | datetime |
Contracts
app/contracts/funny_snake/invoice_contract.rb
rb
# frozen_string_literal: true
module FunnySnake
class InvoiceContract < Apiwork::Contract::Base
enum :status, values: %w[draft sent paid]
object :invoice do
uuid :id
datetime :created_at
datetime :updated_at
string :number
date :issued_on
string :status, enum: :status
string :notes
end
object :create_payload do
string :number
date :issued_on
string :status, enum: :status
string :notes
end
object :update_payload do
string? :number
date? :issued_on
string? :status, enum: :status
string? :notes
end
action :index do
response do
body do
array :invoices do
reference :invoice
end
end
end
end
action :show do
response do
body do
reference :invoice
end
end
end
action :create do
request do
body do
reference :invoice, to: :create_payload
end
end
response do
body do
reference :invoice
end
end
end
action :update do
request do
body do
reference :invoice, to: :update_payload
end
end
response do
body do
reference :invoice
end
end
end
action :destroy do
response { no_content! }
end
end
endControllers
app/controllers/funny_snake/invoices_controller.rb
rb
# frozen_string_literal: true
module FunnySnake
class InvoicesController < ApplicationController
before_action :set_invoice, only: %i[show update destroy]
def index
expose({ invoices: Invoice.all.map(&:as_json) })
end
def show
expose({ invoice: invoice.as_json })
end
def create
invoice = Invoice.create(contract.body[:invoice])
expose({ invoice: invoice.as_json })
end
def update
invoice.update(contract.body[:invoice])
expose({ invoice: invoice.as_json })
end
def destroy
invoice.destroy
expose({ invoice: invoice.as_json })
end
private
attr_reader :invoice
def set_invoice
@invoice = Invoice.find(params[:id])
end
end
endRequest Examples
List all invoices
Request
http
GET /funny_snake/invoicesResponse 200
json
{
"invoices": [
{
"id": "657000e8-1cd9-5b78-9ca2-dd399ce78cb4",
"createdAt": "2024-01-01T12:00:00.000Z",
"issuedOn": null,
"notes": null,
"number": "INV-001",
"status": "draft",
"updatedAt": "2024-01-01T12:00:00.000Z"
},
{
"id": "a791666c-73b8-5dd1-b737-31ca691383ca",
"createdAt": "2024-01-01T12:00:00.000Z",
"issuedOn": null,
"notes": null,
"number": "INV-002",
"status": "sent",
"updatedAt": "2024-01-01T12:00:00.000Z"
}
]
}Get invoice details
Request
http
GET /funny_snake/invoices/657000e8-1cd9-5b78-9ca2-dd399ce78cb4Response 200
json
{
"invoice": {
"id": "657000e8-1cd9-5b78-9ca2-dd399ce78cb4",
"createdAt": "2024-01-01T12:00:00.000Z",
"issuedOn": null,
"notes": null,
"number": "INV-001",
"status": "draft",
"updatedAt": "2024-01-01T12:00:00.000Z"
}
}Create a new invoice
Request
http
POST /funny_snake/invoices
Content-Type: application/json
{
"invoice": {
"number": "INV-001",
"issuedOn": "2024-01-15",
"status": "draft",
"notes": "First invoice"
}
}Response 201
json
{
"invoice": {
"id": "657000e8-1cd9-5b78-9ca2-dd399ce78cb4",
"createdAt": "2024-01-01T12:00:00.000Z",
"issuedOn": "2024-01-15",
"notes": "First invoice",
"number": "INV-001",
"status": "draft",
"updatedAt": "2024-01-01T12:00:00.000Z"
}
}Update an invoice
Request
http
PATCH /funny_snake/invoices/657000e8-1cd9-5b78-9ca2-dd399ce78cb4
Content-Type: application/json
{
"invoice": {
"status": "sent",
"notes": "Updated invoice"
}
}Response 200
json
{
"invoice": {
"status": "sent",
"notes": "Updated invoice",
"id": "657000e8-1cd9-5b78-9ca2-dd399ce78cb4",
"createdAt": "2024-01-01T12:00:00.000Z",
"issuedOn": null,
"number": "INV-001",
"updatedAt": "2024-01-01T12:00:00.000Z"
}
}Delete an invoice
Request
http
DELETE /funny_snake/invoices/657000e8-1cd9-5b78-9ca2-dd399ce78cb4Response 204
Generated Output
Introspection
json
{
"base_path": "/funny_snake",
"enums": {
"invoice_status": {
"deprecated": false,
"description": null,
"example": null,
"values": [
"draft",
"sent",
"paid"
]
},
"layer": {
"deprecated": false,
"description": null,
"example": null,
"values": [
"http",
"contract",
"domain"
]
}
},
"error_codes": {},
"info": null,
"resources": {
"invoices": {
"actions": {
"index": {
"deprecated": false,
"description": null,
"method": "get",
"operation_id": null,
"path": "/invoices",
"raises": [],
"request": {
"body": {},
"query": {}
},
"response": {
"body": {
"default": null,
"deprecated": null,
"description": null,
"example": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"invoices": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "array",
"max": null,
"min": null,
"of": {
"default": null,
"deprecated": null,
"description": null,
"example": null,
"nullable": null,
"optional": null,
"type": "reference",
"reference": "invoice"
},
"shape": {}
}
}
},
"no_content": false
},
"summary": null,
"tags": []
},
"show": {
"deprecated": false,
"description": null,
"method": "get",
"operation_id": null,
"path": "/invoices/:id",
"raises": [],
"request": {
"body": {},
"query": {}
},
"response": {
"body": {
"default": null,
"deprecated": null,
"description": null,
"example": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"invoice": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "invoice"
}
}
},
"no_content": false
},
"summary": null,
"tags": []
},
"create": {
"deprecated": false,
"description": null,
"method": "post",
"operation_id": null,
"path": "/invoices",
"raises": [],
"request": {
"body": {
"invoice": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "invoice_create_payload"
}
},
"query": {}
},
"response": {
"body": {
"default": null,
"deprecated": null,
"description": null,
"example": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"invoice": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "invoice"
}
}
},
"no_content": false
},
"summary": null,
"tags": []
},
"update": {
"deprecated": false,
"description": null,
"method": "patch",
"operation_id": null,
"path": "/invoices/:id",
"raises": [],
"request": {
"body": {
"invoice": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "invoice_update_payload"
}
},
"query": {}
},
"response": {
"body": {
"default": null,
"deprecated": null,
"description": null,
"example": null,
"nullable": null,
"optional": null,
"type": "object",
"partial": null,
"shape": {
"invoice": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "invoice"
}
}
},
"no_content": false
},
"summary": null,
"tags": []
},
"destroy": {
"deprecated": false,
"description": null,
"method": "delete",
"operation_id": null,
"path": "/invoices/:id",
"raises": [],
"request": {
"body": {},
"query": {}
},
"response": {
"body": {
"default": null,
"deprecated": null,
"description": null,
"example": null,
"nullable": null,
"optional": null,
"type": null
},
"no_content": true
},
"summary": null,
"tags": []
}
},
"identifier": "invoices",
"parent_identifiers": [],
"path": "invoices",
"resources": {}
}
},
"types": {
"error": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"issues": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "array",
"max": null,
"min": null,
"of": {
"default": null,
"deprecated": null,
"description": null,
"example": null,
"nullable": null,
"optional": null,
"type": "reference",
"reference": "issue"
},
"shape": {}
},
"layer": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "reference",
"reference": "layer"
}
},
"type": "object",
"variants": []
},
"error_response_body": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [
"error"
],
"shape": {},
"type": "object",
"variants": []
},
"invoice": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"created_at": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "datetime"
},
"id": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "uuid"
},
"issued_on": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "date"
},
"notes": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "string",
"format": null,
"max": null,
"min": null
},
"number": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "string",
"format": null,
"max": null,
"min": null
},
"status": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "string",
"enum": "invoice_status",
"format": null,
"max": null,
"min": null
},
"updated_at": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "datetime"
}
},
"type": "object",
"variants": []
},
"invoice_create_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"issued_on": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "date"
},
"notes": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "string",
"format": null,
"max": null,
"min": null
},
"number": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "string",
"format": null,
"max": null,
"min": null
},
"status": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "string",
"enum": "invoice_status",
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"invoice_update_payload": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"issued_on": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": true,
"type": "date"
},
"notes": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": true,
"type": "string",
"format": null,
"max": null,
"min": null
},
"number": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": true,
"type": "string",
"format": null,
"max": null,
"min": null
},
"status": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": true,
"type": "string",
"enum": "invoice_status",
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
},
"issue": {
"deprecated": false,
"description": null,
"discriminator": null,
"example": null,
"extends": [],
"shape": {
"code": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "string",
"format": null,
"max": null,
"min": null
},
"detail": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "string",
"format": null,
"max": null,
"min": null
},
"meta": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "object",
"partial": false,
"shape": {}
},
"path": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "array",
"max": null,
"min": null,
"of": {
"default": null,
"deprecated": null,
"description": null,
"example": null,
"nullable": null,
"optional": null,
"type": "string",
"format": null,
"max": null,
"min": null
},
"shape": {}
},
"pointer": {
"default": null,
"deprecated": false,
"description": null,
"example": null,
"nullable": false,
"optional": false,
"type": "string",
"format": null,
"max": null,
"min": null
}
},
"type": "object",
"variants": []
}
}
}TypeScript
ts
export interface Invoice {
createdAt: string;
id: string;
issuedOn: string;
notes: string;
number: string;
status: InvoiceStatus;
updatedAt: string;
}
export interface InvoiceCreatePayload {
issuedOn: string;
notes: string;
number: string;
status: InvoiceStatus;
}
export type InvoiceStatus = 'draft' | 'paid' | 'sent';
export interface InvoiceUpdatePayload {
issuedOn?: string;
notes?: string;
number?: string;
status?: InvoiceStatus;
}
export interface InvoicesCreateRequest {
body: InvoicesCreateRequestBody;
}
export interface InvoicesCreateRequestBody {
invoice: InvoiceCreatePayload;
}
export interface InvoicesCreateResponse {
body: InvoicesCreateResponseBody;
}
export type InvoicesCreateResponseBody = { invoice: Invoice };
export type InvoicesDestroyResponse = never;
export interface InvoicesIndexResponse {
body: InvoicesIndexResponseBody;
}
export type InvoicesIndexResponseBody = { invoices: Invoice[] };
export interface InvoicesShowResponse {
body: InvoicesShowResponseBody;
}
export type InvoicesShowResponseBody = { invoice: Invoice };
export interface InvoicesUpdateRequest {
body: InvoicesUpdateRequestBody;
}
export interface InvoicesUpdateRequestBody {
invoice: InvoiceUpdatePayload;
}
export interface InvoicesUpdateResponse {
body: InvoicesUpdateResponseBody;
}
export type InvoicesUpdateResponseBody = { invoice: Invoice };Zod
ts
import { z } from 'zod';
export const InvoiceStatusSchema = z.enum(['draft', 'paid', 'sent']);
export const InvoiceSchema = z.object({
createdAt: z.iso.datetime(),
id: z.uuid(),
issuedOn: z.iso.date(),
notes: z.string(),
number: z.string(),
status: InvoiceStatusSchema,
updatedAt: z.iso.datetime()
});
export const InvoiceCreatePayloadSchema = z.object({
issuedOn: z.iso.date(),
notes: z.string(),
number: z.string(),
status: InvoiceStatusSchema
});
export const InvoiceUpdatePayloadSchema = z.object({
issuedOn: z.iso.date().optional(),
notes: z.string().optional(),
number: z.string().optional(),
status: InvoiceStatusSchema.optional()
});
export const InvoicesIndexResponseBodySchema = z.object({ invoices: z.array(InvoiceSchema) });
export const InvoicesIndexResponseSchema = z.object({
body: InvoicesIndexResponseBodySchema
});
export const InvoicesShowResponseBodySchema = z.object({ invoice: InvoiceSchema });
export const InvoicesShowResponseSchema = z.object({
body: InvoicesShowResponseBodySchema
});
export const InvoicesCreateRequestBodySchema = z.object({
invoice: InvoiceCreatePayloadSchema
});
export const InvoicesCreateRequestSchema = z.object({
body: InvoicesCreateRequestBodySchema
});
export const InvoicesCreateResponseBodySchema = z.object({ invoice: InvoiceSchema });
export const InvoicesCreateResponseSchema = z.object({
body: InvoicesCreateResponseBodySchema
});
export const InvoicesUpdateRequestBodySchema = z.object({
invoice: InvoiceUpdatePayloadSchema
});
export const InvoicesUpdateRequestSchema = z.object({
body: InvoicesUpdateRequestBodySchema
});
export const InvoicesUpdateResponseBodySchema = z.object({ invoice: InvoiceSchema });
export const InvoicesUpdateResponseSchema = z.object({
body: InvoicesUpdateResponseBodySchema
});
export const InvoicesDestroyResponseSchema = z.never();
export interface Invoice {
createdAt: string;
id: string;
issuedOn: string;
notes: string;
number: string;
status: InvoiceStatus;
updatedAt: string;
}
export interface InvoiceCreatePayload {
issuedOn: string;
notes: string;
number: string;
status: InvoiceStatus;
}
export type InvoiceStatus = 'draft' | 'paid' | 'sent';
export interface InvoiceUpdatePayload {
issuedOn?: string;
notes?: string;
number?: string;
status?: InvoiceStatus;
}
export interface InvoicesCreateRequest {
body: InvoicesCreateRequestBody;
}
export interface InvoicesCreateRequestBody {
invoice: InvoiceCreatePayload;
}
export interface InvoicesCreateResponse {
body: InvoicesCreateResponseBody;
}
export type InvoicesCreateResponseBody = { invoice: Invoice };
export type InvoicesDestroyResponse = never;
export interface InvoicesIndexResponse {
body: InvoicesIndexResponseBody;
}
export type InvoicesIndexResponseBody = { invoices: Invoice[] };
export interface InvoicesShowResponse {
body: InvoicesShowResponseBody;
}
export type InvoicesShowResponseBody = { invoice: Invoice };
export interface InvoicesUpdateRequest {
body: InvoicesUpdateRequestBody;
}
export interface InvoicesUpdateRequestBody {
invoice: InvoiceUpdatePayload;
}
export interface InvoicesUpdateResponse {
body: InvoicesUpdateResponseBody;
}
export type InvoicesUpdateResponseBody = { invoice: Invoice };OpenAPI
yml
---
openapi: 3.1.0
info:
title: "/funny_snake"
version: 1.0.0
paths:
"/invoices":
get:
operationId: invoicesIndex
responses:
'200':
content:
application/json:
schema:
properties:
invoices:
items:
"$ref": "#/components/schemas/invoice"
type: array
type: object
required:
- invoices
description: Successful response
post:
operationId: invoicesCreate
requestBody:
content:
application/json:
schema:
properties:
invoice:
"$ref": "#/components/schemas/invoiceCreatePayload"
type: object
required:
- invoice
required: true
responses:
'200':
content:
application/json:
schema:
properties:
invoice:
"$ref": "#/components/schemas/invoice"
type: object
required:
- invoice
description: Successful response
"/invoices/{id}":
get:
operationId: invoicesShow
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
properties:
invoice:
"$ref": "#/components/schemas/invoice"
type: object
required:
- invoice
description: Successful response
patch:
operationId: invoicesUpdate
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
properties:
invoice:
"$ref": "#/components/schemas/invoiceUpdatePayload"
type: object
required:
- invoice
required: true
responses:
'200':
content:
application/json:
schema:
properties:
invoice:
"$ref": "#/components/schemas/invoice"
type: object
required:
- invoice
description: Successful response
delete:
operationId: invoicesDestroy
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'204':
description: No content
components:
schemas:
invoice:
properties:
createdAt:
type: string
format: date-time
id:
type: string
format: uuid
issuedOn:
type: string
format: date
notes:
type: string
number:
type: string
status:
enum:
- draft
- sent
- paid
type: string
updatedAt:
type: string
format: date-time
type: object
required:
- createdAt
- id
- issuedOn
- notes
- number
- status
- updatedAt
invoiceCreatePayload:
properties:
issuedOn:
type: string
format: date
notes:
type: string
number:
type: string
status:
enum:
- draft
- sent
- paid
type: string
type: object
required:
- issuedOn
- notes
- number
- status
invoiceUpdatePayload:
properties:
issuedOn:
type: string
format: date
notes:
type: string
number:
type: string
status:
enum:
- draft
- sent
- paid
type: string
type: object