| name | create-openapi-schemas-from-golang-models |
| description | Create OpenAPI schemas from Golang models in Meshery Cloud, generating schema artifacts in Meshery Schemas repository. Handles cross-repository schema creation workflow with proper naming conventions, Go code generation, and backwards compatibility validation. |
Create OpenAPI Schemas from Golang Models
Canonical naming contract โ see docs/identifier-naming-contributor-guide.md in meshery/schemas (https://github.com/meshery/schemas/blob/master/docs/identifier-naming-contributor-guide.md) for the full directory (26-row naming table with before/after and do/don't examples). The inline rules below remain the skill's authority for its workflow scope; the guide is the reader-friendly cross-repo reference.
Overview
This skill guides you through creating OpenAPI schemas from existing Golang models in Meshery Cloud (layer5io/meshery-cloud), with the generated schemas stored in Meshery Schemas (meshery/schemas). This cross-repository workflow ensures consistent API contracts between the two projects.
Prerequisites
Required Repositories
Both repositories must be locally cloned and available:
Required Knowledge
- Read the
meshery/schemas README.md and uphold all directives, including naming conventions
- Read AGENTS.md in both repositories
- Treat the Golang models in
layer5io/meshery-cloud as the source of truth
- Ignore any existing schemas in
meshery/schemas for constructs being worked on
- Ignore any existing tests in
layer5io/meshery-cloud for constructs being worked on
Naming Conventions
OpenAPI Schema Naming
| Element | Convention | Example |
|---|
| Property names | Preserve published wire-format casing: DB-backed fields use exact snake_case db names; new non-DB fields use camelCase | first_name, organization_id, schemaVersion |
| Identifier fields | camelCase + "Id" suffix | roleId, userId, keychainId |
| Enums | lowercase | admin, user, enabled |
| Object names | singular nouns | role, keychain, user |
| Schema components | PascalCase | Role, RolesPage, RoleHolderRequest |
| Files/folders | lowercase | role.yaml, api.yml |
Endpoint Naming
| Element | Convention | Example |
|---|
| Paths | /api + kebab-case plurals | /api/identity/orgs/{orgId}/roles |
| Operations | camelCase VerbNoun | GetAllRoles, UpsertRoles |
If a property has x-oapi-codegen-extra-tags.db and that db value is snake_case, the schema property name and JSON tag must use that exact snake_case name. Do not camelize DB-backed fields in-place within an existing API version. Pagination envelopes must use page, page_size, and total_count.
| Non-CRUD actions | append verb | .../keychains/{keychainId} |
Response descriptions and response message text must not include the word successfully. Use neutral wording such as Role deleted, Webhook processed, or Roles response.
Workflow
Task 1: Create Schema Artifacts
In meshery/schemas, create these files for the target construct:
Dual-Schema Pattern (required): Every entity needs two schemas โ <construct>.yaml (full response entity) and {Construct}Payload in api.yml (write request body). Never use the entity schema as a POST/PUT requestBody. See AGENTS.md ยง "The Dual-Schema Pattern" for full rules.
schemas/constructs/v1beta1/<construct_name>/
โโโ <construct_name>.yaml # Schema definition
โโโ api.yml # Endpoint definitions + page/payload schemas
โโโ templates/
โโโ <construct_name>_template.json # Example usage
Schema Definition (<construct_name>.yaml)
This is the response schema โ the full persisted object as the API returns it. It must:
- Have
additionalProperties: false at the top level
- Include all server-generated fields (
id, created_at, updated_at, deleted_at) in properties and required
Map Golang struct fields to OpenAPI properties:
type: object
additionalProperties: false
required:
- id
- roleName
- created_at
- updated_at
properties:
id:
type: string
format: uuid
x-oapi-codegen-extra-tags:
json: "id,omitempty"
yaml: "id,omitempty"
db: "id"
roleName:
type: string
x-oapi-codegen-extra-tags:
json: "role_name,omitempty"
yaml: "role_name,omitempty"
db: "role_name"
description:
type: string
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
deletedAt:
type: string
format: date-time
nullable: true
API Definition (api.yml)
Define endpoints matching the Meshery Cloud router. All POST/PUT operations must use a {Construct}Payload request body โ never the full entity schema.
openapi: 3.0.3
info:
title: Role API
version: v1beta1
paths:
/api/identity/orgs/{orgId}/roles:
get:
operationId: getAllRoles
parameters:
- name: orgId
in: path
required: true
schema:
type: string
format: uuid
- name: page
in: query
schema:
type: integer
- name: pagesize
in: query
schema:
type: integer
responses:
'200':
description: List of roles
content:
application/json:
schema:
$ref: '#/components/schemas/RolesPage'
post:
operationId: upsertRole
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RolePayload'
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Role'
components:
schemas:
Role:
$ref: './role.yaml'
RolePayload:
type: object
description: Payload for creating or updating a role.
required:
- roleName
properties:
id:
type: string
format: uuid
x-oapi-codegen-extra-tags:
json: "id,omitempty"
roleName:
type: string
description:
type: string
RolesPage:
type: object
properties:
page:
type: integer
roles:
type: array
items:
$ref: '#/components/schemas/Role'
totalCount:
type: integer
pageSize:
type: integer
Go Type Hints
For cross-schema references, use x-go-type to avoid redundant structs:
registrant:
x-go-type: "RegistrantReference"
x-go-import-path: "github.com/meshery/schemas/models/v1beta1/core"
$ref: "#/components/schemas/RegistrantReference"
Task 2: Generate Go Models
In the meshery/schemas repository:
cd /Users/l/code/schemas
make generate-golang
Verification Steps:
-
Ensure generated models are in models/v1beta1/<construct_name>/
-
If a schema type within the construct is stored as a JSON blob in a database column and has a dedicated schema definition with explicit properties, add x-generate-db-helpers: true at the schema component level in api.yml. This instructs the generator to produce Scan() and Value() SQL driver methods automatically in zz_generated.helpers.go โ no manual helpers.go is needed for that type.
Quiz:
x-generate-db-helpers: true
type: object
properties:
id:
$ref: "../../v1alpha1/core/api.yml#/components/schemas/uuid"
title:
type: string
For amorphous JSON blob fields that lack a fixed schema definition (e.g., a freeform metadata map), use x-go-type: "core.Map" on the property instead โ do not use x-generate-db-helpers for those.
-
If helpers are still needed for non-generated behavior (e.g., TableName(), custom business logic), create helpers.go manually. When implementing Scan/Value, follow these rules (see docs/schema-authoring-reference.md ยง "SQL Driver (Scan/Value) Implementation Rules"):
Value() must always marshal โ never return (nil, nil). A nil map produces JSON "null", not SQL NULL.
Scan() must zero the receiver (*m = nil) when src is nil, not silently return.
package role
import (
"database/sql/driver"
"encoding/json"
)
func (r Role) TableName() string {
return "roles"
}
func (r Role) Value() (driver.Value, error) {
b, err := json.Marshal(r)
if err != nil {
return nil, err
}
return string(b), nil
}
- Create/update tests:
go test ./models/v1beta1/<construct_name>/...
- Run full test suite:
go test ./...
Task 3: Validate Backwards Compatibility
Compare generated models with Meshery Cloud models:
Source: layer5io/meshery-cloud/server/models/<construct>.go
Generated: meshery/schemas/models/v1beta1/<construct_name>/<construct_name>.go
Validation Checklist:
Common Patterns
Pagination Response
<Construct>Page:
type: object
properties:
page:
type: integer
data:
type: array
items:
$ref: '#/components/schemas/<Construct>'
totalCount:
type: integer
pageSize:
type: integer
UUID Fields
id:
type: string
format: uuid
x-oapi-codegen-extra-tags:
json: "id,omitempty"
db: "id"
Nullable Time Fields
deletedAt:
type: string
format: date-time
nullable: true
x-go-type: "sql.NullTime"
x-go-import-path: "database/sql"
String Arrays (pq.StringArray)
roleNames:
type: array
items:
type: string
x-go-type: "pq.StringArray"
x-go-import-path: "github.com/lib/pq"
Exemplar: Academy Construct
The Academy construct in meshery/schemas serves as the primary exemplar for this workflow. Study these files:
Reference Files
Key Patterns from Academy
- Cross-schema references using
$ref with external files
- Custom Go type mappings with
x-go-type and x-go-type-import
- Nullable fields using
core.NullTime
- Enum definitions with proper Go type hints
- SQL Scanner/Valuer implementations in helpers.go
Source Repository
Example: Role Construct
Source Files in Meshery Cloud
- Model:
server/models/roles.go
- Handler:
server/handlers/roles.go
- DAO:
server/dao/roles_dao.go
- Routes:
server/router/router.go (search for "roles")
Key Endpoints
| Method | Path | Handler |
|---|
| GET | /api/identity/orgs/:orgID/roles | GetAllRoles |
| POST | /api/identity/orgs/:orgID/roles | UpsertRoles |
| GET | /api/identity/orgs/:orgID/roles/:roleID/keychains | GetKeychainsByRoleID |
| POST | /api/identity/orgs/:orgID/roles/:roleID/keychains/:keychainID | AssignKeychainToRole |
| DELETE | /api/identity/orgs/:orgID/roles/:roleID/keychains/:keychainID | UnAssignKeychainFromRole |
Key Models to Schema-ify
Role - Core role entity
RolesPage - Paginated response
UserWithRole - User with role assignments
RoleHolderRequest - Request payload for role assignment
Validation Commands
cd /Users/l/code/schemas
make validate-schemas
make build
go test ./models/v1beta1/<construct>/...
Tips
- Start with the Golang struct - It's the source of truth
- Match JSON tags exactly - Critical for API compatibility
- Use
x-oapi-codegen-extra-tags - Preserves db and yaml tags
- Check router for all endpoints - Don't miss any routes
- Run
make build - Validates everything at once