mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-13 17:10:36 +00:00
500 lines
18 KiB
Go
500 lines
18 KiB
Go
package openapi
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
// generateRestheadSpecPaths generates OpenAPI paths for RestheadSpec endpoints
|
|
func (g *Generator) generateRestheadSpecPaths(spec *OpenAPISpec, schema, entity, schemaName string) {
|
|
basePath := fmt.Sprintf("/%s/%s", schema, entity)
|
|
idPath := fmt.Sprintf("/%s/%s/{id}", schema, entity)
|
|
metaPath := fmt.Sprintf("/%s/%s/metadata", schema, entity)
|
|
|
|
// Collection endpoint: GET (list), POST (create)
|
|
spec.Paths[basePath] = PathItem{
|
|
Get: &Operation{
|
|
Summary: fmt.Sprintf("List %s records", entity),
|
|
Description: fmt.Sprintf("Retrieve a list of %s records with optional filtering, sorting, and pagination via headers", entity),
|
|
OperationID: fmt.Sprintf("listRestheadSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (RestheadSpec)", entity)},
|
|
Parameters: g.getRestheadSpecHeaders(),
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Successful response",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"success": {Type: "boolean"},
|
|
"data": {Type: "array", Items: &Schema{Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)}},
|
|
"metadata": {Ref: "#/components/schemas/Metadata"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
Post: &Operation{
|
|
Summary: fmt.Sprintf("Create %s record", entity),
|
|
Description: fmt.Sprintf("Create a new %s record", entity),
|
|
OperationID: fmt.Sprintf("createRestheadSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (RestheadSpec)", entity)},
|
|
RequestBody: &RequestBody{
|
|
Required: true,
|
|
Description: fmt.Sprintf("%s object to create", entity),
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)},
|
|
},
|
|
},
|
|
},
|
|
Responses: map[string]Response{
|
|
"201": {
|
|
Description: "Record created successfully",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"success": {Type: "boolean"},
|
|
"data": {Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"400": g.errorResponse("Bad request"),
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
Options: &Operation{
|
|
Summary: "CORS preflight",
|
|
Description: "Handle CORS preflight requests",
|
|
OperationID: fmt.Sprintf("optionsRestheadSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (RestheadSpec)", entity)},
|
|
Responses: map[string]Response{
|
|
"204": {Description: "No content"},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Single record endpoint: GET (read), PUT/PATCH (update), DELETE
|
|
spec.Paths[idPath] = PathItem{
|
|
Get: &Operation{
|
|
Summary: fmt.Sprintf("Get %s record by ID", entity),
|
|
Description: fmt.Sprintf("Retrieve a single %s record by its ID", entity),
|
|
OperationID: fmt.Sprintf("getRestheadSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (RestheadSpec)", entity)},
|
|
Parameters: []Parameter{
|
|
{Name: "id", In: "path", Required: true, Description: "Record ID", Schema: &Schema{Type: "integer"}},
|
|
},
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Successful response",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"success": {Type: "boolean"},
|
|
"data": {Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"404": g.errorResponse("Record not found"),
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
Put: &Operation{
|
|
Summary: fmt.Sprintf("Update %s record", entity),
|
|
Description: fmt.Sprintf("Update an existing %s record by ID", entity),
|
|
OperationID: fmt.Sprintf("updateRestheadSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (RestheadSpec)", entity)},
|
|
Parameters: []Parameter{
|
|
{Name: "id", In: "path", Required: true, Description: "Record ID", Schema: &Schema{Type: "integer"}},
|
|
},
|
|
RequestBody: &RequestBody{
|
|
Required: true,
|
|
Description: fmt.Sprintf("Updated %s object", entity),
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)},
|
|
},
|
|
},
|
|
},
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Record updated successfully",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"success": {Type: "boolean"},
|
|
"data": {Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"400": g.errorResponse("Bad request"),
|
|
"404": g.errorResponse("Record not found"),
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
Patch: &Operation{
|
|
Summary: fmt.Sprintf("Partially update %s record", entity),
|
|
Description: fmt.Sprintf("Partially update an existing %s record by ID", entity),
|
|
OperationID: fmt.Sprintf("patchRestheadSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (RestheadSpec)", entity)},
|
|
Parameters: []Parameter{
|
|
{Name: "id", In: "path", Required: true, Description: "Record ID", Schema: &Schema{Type: "integer"}},
|
|
},
|
|
RequestBody: &RequestBody{
|
|
Required: true,
|
|
Description: fmt.Sprintf("Partial %s object", entity),
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)},
|
|
},
|
|
},
|
|
},
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Record updated successfully",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"success": {Type: "boolean"},
|
|
"data": {Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"400": g.errorResponse("Bad request"),
|
|
"404": g.errorResponse("Record not found"),
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
Delete: &Operation{
|
|
Summary: fmt.Sprintf("Delete %s record", entity),
|
|
Description: fmt.Sprintf("Delete a %s record by ID", entity),
|
|
OperationID: fmt.Sprintf("deleteRestheadSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (RestheadSpec)", entity)},
|
|
Parameters: []Parameter{
|
|
{Name: "id", In: "path", Required: true, Description: "Record ID", Schema: &Schema{Type: "integer"}},
|
|
},
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Record deleted successfully",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"success": {Type: "boolean"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"404": g.errorResponse("Record not found"),
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
}
|
|
|
|
// Metadata endpoint
|
|
spec.Paths[metaPath] = PathItem{
|
|
Get: &Operation{
|
|
Summary: fmt.Sprintf("Get %s metadata", entity),
|
|
Description: fmt.Sprintf("Retrieve metadata information for %s table", entity),
|
|
OperationID: fmt.Sprintf("metadataRestheadSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (RestheadSpec)", entity)},
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Metadata retrieved successfully",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"success": {Type: "boolean"},
|
|
"data": {
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"schema": {Type: "string"},
|
|
"table": {Type: "string"},
|
|
"columns": {Type: "array", Items: &Schema{Type: "object"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
}
|
|
}
|
|
|
|
// generateResolveSpecPaths generates OpenAPI paths for ResolveSpec endpoints
|
|
func (g *Generator) generateResolveSpecPaths(spec *OpenAPISpec, schema, entity, schemaName string) {
|
|
basePath := fmt.Sprintf("/resolve/%s/%s", schema, entity)
|
|
idPath := fmt.Sprintf("/resolve/%s/%s/{id}", schema, entity)
|
|
|
|
// Collection endpoint: POST (operations)
|
|
spec.Paths[basePath] = PathItem{
|
|
Post: &Operation{
|
|
Summary: fmt.Sprintf("Perform operation on %s", entity),
|
|
Description: fmt.Sprintf("Execute read, create, or meta operations on %s records", entity),
|
|
OperationID: fmt.Sprintf("operateResolveSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (ResolveSpec)", entity)},
|
|
RequestBody: &RequestBody{
|
|
Required: true,
|
|
Description: "Operation request with operation type and options",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{Ref: "#/components/schemas/ResolveSpecRequest"},
|
|
Example: map[string]interface{}{
|
|
"operation": "read",
|
|
"options": map[string]interface{}{
|
|
"limit": 10,
|
|
"filters": []map[string]interface{}{
|
|
{"column": "status", "operator": "eq", "value": "active"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Operation completed successfully",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"success": {Type: "boolean"},
|
|
"data": {Type: "array", Items: &Schema{Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)}},
|
|
"metadata": {Ref: "#/components/schemas/Metadata"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"400": g.errorResponse("Bad request"),
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
Get: &Operation{
|
|
Summary: fmt.Sprintf("Get %s metadata", entity),
|
|
Description: fmt.Sprintf("Retrieve metadata for %s", entity),
|
|
OperationID: fmt.Sprintf("metadataResolveSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (ResolveSpec)", entity)},
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Metadata retrieved successfully",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{Ref: "#/components/schemas/Response"},
|
|
},
|
|
},
|
|
},
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
Options: &Operation{
|
|
Summary: "CORS preflight",
|
|
Description: "Handle CORS preflight requests",
|
|
OperationID: fmt.Sprintf("optionsResolveSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (ResolveSpec)", entity)},
|
|
Responses: map[string]Response{
|
|
"204": {Description: "No content"},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Single record endpoint: POST (update/delete)
|
|
spec.Paths[idPath] = PathItem{
|
|
Post: &Operation{
|
|
Summary: fmt.Sprintf("Update or delete %s record", entity),
|
|
Description: fmt.Sprintf("Execute update or delete operation on a specific %s record", entity),
|
|
OperationID: fmt.Sprintf("modifyResolveSpec%s%s", formatSchemaName(schema, ""), formatSchemaName("", entity)),
|
|
Tags: []string{fmt.Sprintf("%s (ResolveSpec)", entity)},
|
|
Parameters: []Parameter{
|
|
{Name: "id", In: "path", Required: true, Description: "Record ID", Schema: &Schema{Type: "integer"}},
|
|
},
|
|
RequestBody: &RequestBody{
|
|
Required: true,
|
|
Description: "Operation request (update or delete)",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{Ref: "#/components/schemas/ResolveSpecRequest"},
|
|
Example: map[string]interface{}{
|
|
"operation": "update",
|
|
"data": map[string]interface{}{
|
|
"status": "inactive",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Operation completed successfully",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{
|
|
Type: "object",
|
|
Properties: map[string]*Schema{
|
|
"success": {Type: "boolean"},
|
|
"data": {Ref: fmt.Sprintf("#/components/schemas/%s", schemaName)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"400": g.errorResponse("Bad request"),
|
|
"404": g.errorResponse("Record not found"),
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
},
|
|
}
|
|
}
|
|
|
|
// generateFuncSpecPaths generates OpenAPI paths for FuncSpec endpoints
|
|
func (g *Generator) generateFuncSpecPaths(spec *OpenAPISpec) {
|
|
for path, endpoint := range g.config.FuncSpecEndpoints {
|
|
operation := &Operation{
|
|
Summary: endpoint.Summary,
|
|
Description: endpoint.Description,
|
|
OperationID: fmt.Sprintf("funcSpec%s", sanitizeOperationID(path)),
|
|
Tags: []string{"FuncSpec"},
|
|
Parameters: g.extractFuncSpecParameters(endpoint.Parameters),
|
|
Responses: map[string]Response{
|
|
"200": {
|
|
Description: "Query executed successfully",
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{Ref: "#/components/schemas/Response"},
|
|
},
|
|
},
|
|
},
|
|
"400": g.errorResponse("Bad request"),
|
|
"401": g.errorResponse("Unauthorized"),
|
|
"500": g.errorResponse("Internal server error"),
|
|
},
|
|
Security: g.securityRequirements(),
|
|
}
|
|
|
|
pathItem := spec.Paths[path]
|
|
switch endpoint.Method {
|
|
case "GET":
|
|
pathItem.Get = operation
|
|
case "POST":
|
|
pathItem.Post = operation
|
|
case "PUT":
|
|
pathItem.Put = operation
|
|
case "DELETE":
|
|
pathItem.Delete = operation
|
|
}
|
|
spec.Paths[path] = pathItem
|
|
}
|
|
}
|
|
|
|
// getRestheadSpecHeaders returns all RestheadSpec header parameters
|
|
func (g *Generator) getRestheadSpecHeaders() []Parameter {
|
|
return []Parameter{
|
|
{Name: "X-Filters", In: "header", Description: "JSON array of filter conditions", Schema: &Schema{Type: "string"}},
|
|
{Name: "X-Columns", In: "header", Description: "Comma-separated list of columns to select", Schema: &Schema{Type: "string"}},
|
|
{Name: "X-Sort", In: "header", Description: "JSON array of sort specifications", Schema: &Schema{Type: "string"}},
|
|
{Name: "X-Limit", In: "header", Description: "Maximum number of records to return", Schema: &Schema{Type: "integer"}},
|
|
{Name: "X-Offset", In: "header", Description: "Number of records to skip", Schema: &Schema{Type: "integer"}},
|
|
{Name: "X-Preload", In: "header", Description: "Relations to eager load (comma-separated)", Schema: &Schema{Type: "string"}},
|
|
{Name: "X-Expand", In: "header", Description: "Relations to expand with LEFT JOIN (comma-separated)", Schema: &Schema{Type: "string"}},
|
|
{Name: "X-Distinct", In: "header", Description: "Enable DISTINCT query (true/false)", Schema: &Schema{Type: "boolean"}},
|
|
{Name: "X-Response-Format", In: "header", Description: "Response format", Schema: &Schema{Type: "string", Enum: []interface{}{"detail", "simple", "syncfusion"}}},
|
|
{Name: "X-Clean-JSON", In: "header", Description: "Remove null/empty fields from response (true/false)", Schema: &Schema{Type: "boolean"}},
|
|
{Name: "X-Custom-SQL-Where", In: "header", Description: "Custom SQL WHERE clause (AND)", Schema: &Schema{Type: "string"}},
|
|
{Name: "X-Custom-SQL-Or", In: "header", Description: "Custom SQL WHERE clause (OR)", Schema: &Schema{Type: "string"}},
|
|
}
|
|
}
|
|
|
|
// extractFuncSpecParameters creates OpenAPI parameters from parameter names
|
|
func (g *Generator) extractFuncSpecParameters(paramNames []string) []Parameter {
|
|
params := []Parameter{}
|
|
for _, name := range paramNames {
|
|
params = append(params, Parameter{
|
|
Name: name,
|
|
In: "query",
|
|
Description: fmt.Sprintf("Parameter: %s", name),
|
|
Schema: &Schema{Type: "string"},
|
|
})
|
|
}
|
|
return params
|
|
}
|
|
|
|
// errorResponse creates a standard error response
|
|
func (g *Generator) errorResponse(description string) Response {
|
|
return Response{
|
|
Description: description,
|
|
Content: map[string]MediaType{
|
|
"application/json": {
|
|
Schema: &Schema{Ref: "#/components/schemas/APIError"},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// securityRequirements returns all security options (user can use any)
|
|
func (g *Generator) securityRequirements() []map[string][]string {
|
|
return []map[string][]string{
|
|
{"BearerAuth": {}},
|
|
{"SessionToken": {}},
|
|
{"CookieAuth": {}},
|
|
{"HeaderAuth": {}},
|
|
}
|
|
}
|
|
|
|
// sanitizeOperationID removes invalid characters from operation IDs
|
|
func sanitizeOperationID(path string) string {
|
|
result := ""
|
|
for _, char := range path {
|
|
if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') {
|
|
result += string(char)
|
|
}
|
|
}
|
|
return result
|
|
}
|