Files
antrean-anjungan/tools/satusehat/generate

1443 lines
56 KiB
Plaintext

package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// SatuSehatHandlerData contains template data for Satu Sehat handler generation
type SatuSehatHandlerData struct {
Name string
NameLower string
NameUpper string
Category string
CategoryPath string
CategoryParts []string
ModuleName string
HasGet bool
HasPost bool
HasPut bool
HasPatch bool
HasDelete bool
GetEndpoint string
PostEndpoint string
PutEndpoint string
PatchEndpoint string
DeleteEndpoint string
Timestamp string
DirectoryDepth int
FhirResource string
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run generate-satusehat-handler.go [level1[/level2[/level3[/level4]]]]/entity [methods]")
fmt.Println("Examples:")
fmt.Println(" go run generate-satusehat-handler.go fhir/patient get post put patch")
fmt.Println(" go run generate-satusehat-handler.go master/organization get post")
fmt.Println(" go run generate-satusehat-handler.go integration/encounter/observation get post")
fmt.Println(" go run generate-satusehat-handler.go api/v1/fhir/r4/patient get post put patch delete")
fmt.Println(" go run generate-satusehat-handler.go location get")
os.Exit(1)
}
// Parse entity path (up to 4 levels + entity)
entityPath := os.Args[1]
methods := []string{}
if len(os.Args) > 2 {
methods = os.Args[2:]
} else {
// Default methods for FHIR resources
methods = []string{"get", "post", "put", "patch"}
}
// Parse multi-level category and entity
var categoryParts []string
var entityName string
var category string
parts := strings.Split(entityPath, "/")
if len(parts) > 1 && len(parts) <= 5 { // Up to 4 levels + entity
categoryParts = parts[:len(parts)-1]
entityName = parts[len(parts)-1]
category = strings.Join(categoryParts, "/")
} else if len(parts) == 1 {
category = ""
entityName = parts[0]
categoryParts = []string{}
} else {
fmt.Println("❌ Error: Invalid path format. Use up to 4 levels like 'level1/level2/level3/level4/entity' or just 'entity'")
fmt.Printf("❌ You provided %d levels, maximum is 4 levels + entity\n", len(parts)-1)
os.Exit(1)
}
// Format names
entityName = strings.Title(entityName) // PascalCase entity name
entityLower := strings.ToLower(entityName)
entityUpper := strings.ToUpper(entityName)
// FHIR Resource name (capitalize first letter only)
fhirResource := strings.Title(strings.ToLower(entityName))
data := SatuSehatHandlerData{
Name: entityName,
NameLower: entityLower,
NameUpper: entityUpper,
Category: category,
CategoryPath: category,
CategoryParts: categoryParts,
ModuleName: "api-service",
DirectoryDepth: len(categoryParts),
FhirResource: fhirResource,
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
}
// Set methods and endpoints for FHIR resources
for _, m := range methods {
switch strings.ToLower(m) {
case "get":
data.HasGet = true
data.GetEndpoint = fmt.Sprintf("%s/{id}", fhirResource)
case "post":
data.HasPost = true
data.PostEndpoint = fhirResource
case "put":
data.HasPut = true
data.PutEndpoint = fmt.Sprintf("%s/{id}", fhirResource)
case "patch":
data.HasPatch = true
data.PatchEndpoint = fmt.Sprintf("%s/{id}", fhirResource)
case "delete":
data.HasDelete = true
data.DeleteEndpoint = fmt.Sprintf("%s/{id}", fhirResource)
}
}
// Create directories with multi-level support
var handlerDir, modelDir string
if category != "" {
// Multi-level directory support
handlerDirParts := append([]string{"internal", "handlers", "satusehat"}, categoryParts...)
modelDirParts := append([]string{"internal", "models", "satusehat"}, categoryParts...)
handlerDir = filepath.Join(handlerDirParts...)
modelDir = filepath.Join(modelDirParts...)
} else {
// No category: direct internal/handlers/satusehat/
handlerDir = filepath.Join("internal", "handlers", "satusehat")
modelDir = filepath.Join("internal", "models", "satusehat")
}
// Create directories
for _, d := range []string{handlerDir, modelDir} {
if err := os.MkdirAll(d, 0755); err != nil {
panic(err)
}
}
// Generate files
generateOptimizedSatuSehatHandlerFile(data, handlerDir)
generateOptimizedSatuSehatModelFile(data, modelDir)
// updateOptimizedSatuSehatRoutesFile(data)
fmt.Printf("✅ Successfully generated optimized Satu Sehat handler: %s\n", entityName)
if category != "" {
fmt.Printf("📁 Category Path: %s (%d levels deep)\n", category, data.DirectoryDepth)
}
fmt.Printf("📁 FHIR Resource: %s\n", fhirResource)
fmt.Printf("📁 Handler: %s\n", filepath.Join(handlerDir, entityLower+".go"))
fmt.Printf("📁 Model: %s\n", filepath.Join(modelDir, entityLower+".go"))
}
// ================= OPTIMIZED HANDLER GENERATION =====================
func generateOptimizedSatuSehatHandlerFile(data SatuSehatHandlerData, handlerDir string) {
var modelsImportPath string
if data.Category != "" {
modelsImportPath = data.ModuleName + "/internal/models/satusehat/" + data.Category
} else {
modelsImportPath = data.ModuleName + "/internal/models/satusehat"
}
handlerContent := `package handlers
import (
"context"
"fmt"
"net/http"
"time"
"` + data.ModuleName + `/internal/config"
"` + modelsImportPath + `"
services "` + data.ModuleName + `/internal/services/satusehat"
"` + data.ModuleName + `/pkg/logger"
"` + data.ModuleName + `/pkg/validator"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/go-playground/validator/v10"
)
// ` + data.Name + `Handler handles ` + data.NameLower + ` Satu Sehat FHIR services with multi-level organization
// Generated for FHIR Resource: ` + data.FhirResource + `
// Path: ` + data.Category + `
// Directory depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + ` levels
type ` + data.Name + `Handler struct {
service services.SatuSehatService
validator *validator.Validate
logger logger.Logger
config *config.SatuSehatConfig
}
// HandlerConfig contains configuration for ` + data.Name + `Handler
type ` + data.Name + `HandlerConfig struct {
SatuSehatConfig *config.SatuSehatConfig
Logger logger.Logger
Validator *validator.Validate
}
// New` + data.Name + `Handler creates a new optimized ` + data.Name + `Handler for Satu Sehat FHIR
func New` + data.Name + `Handler(cfg *` + data.Name + `HandlerConfig) *` + data.Name + `Handler {
return &` + data.Name + `Handler{
service: services.NewSatuSehatService(cfg.SatuSehatConfig),
validator: cfg.Validator,
logger: cfg.Logger,
config: cfg.SatuSehatConfig,
}
}`
// Add optimized methods based on flags
if data.HasPost {
handlerContent += generateOptimizedSatuSehatCreateMethod(data)
}
if data.HasPut {
handlerContent += generateOptimizedSatuSehatUpdateMethod(data)
}
if data.HasPatch {
handlerContent += generateOptimizedSatuSehatPatchMethod(data)
}
if data.HasDelete {
handlerContent += generateOptimizedSatuSehatDeleteMethod(data)
}
if data.HasGet {
handlerContent += generateOptimizedSatuSehatGetMethod(data)
handlerContent += generateOptimizedSatuSehatSearchMethod(data)
}
// Add helper methods
handlerContent += generateSatuSehatHelperMethods(data)
writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent)
}
func generateOptimizedSatuSehatCreateMethod(data SatuSehatHandlerData) string {
var routePath, tagName string
if data.Category != "" {
routePath = "satusehat/" + data.Category + "/" + data.NameLower
tagParts := append([]string{"SatuSehat"}, data.CategoryParts...)
tagParts = append(tagParts, strings.Title(data.NameLower))
tagName = strings.Join(tagParts, "-")
} else {
routePath = "satusehat/" + data.NameLower
tagName = "SatuSehat-" + strings.Title(data.NameLower)
}
return `
// Create` + data.Name + ` creates a new FHIR ` + data.FhirResource + ` resource
// @Summary Create a new FHIR ` + data.FhirResource + ` resource
// @Description Create a new ` + data.FhirResource + ` resource in Satu Sehat ecosystem with FHIR R4 compliance
// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + `
// @Tags ` + tagName + `
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2"
// @Param request body models.` + data.Name + `CreateRequest true "` + data.FhirResource + ` FHIR resource creation request"
// @Success 201 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource created successfully"
// @Failure 400 {object} models.FhirOperationOutcome "Bad request - validation error"
// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token"
// @Failure 422 {object} models.FhirOperationOutcome "Unprocessable entity - FHIR validation error"
// @Failure 500 {object} models.FhirOperationOutcome "Internal server error"
// @Router /api/v1/` + routePath + ` [post]
func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
h.logger.Info("Creating FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
"fhir_resource": "` + data.FhirResource + `",
"category_path": "` + data.Category + `",
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
})
var req models.` + data.Name + `CreateRequest
req.RequestID = requestID
req.Timestamp = startTime
// Enhanced JSON binding with FHIR validation
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind FHIR JSON", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"path": "` + routePath + `",
})
h.sendFhirErrorResponse(c, http.StatusBadRequest, "structure",
"Invalid FHIR resource structure", err.Error(), requestID)
return
}
// FHIR resource validation
if err := req.ValidateFhir(); err != nil {
h.logger.Error("FHIR validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"path": "` + routePath + `",
})
h.sendFhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule",
"FHIR resource validation failed", err.Error(), requestID)
return
}
// Struct validation
if err := h.validator.Struct(&req); err != nil {
h.logger.Error("Struct validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"path": "` + routePath + `",
})
h.sendFhirErrorResponse(c, http.StatusBadRequest, "structure",
"Resource structure validation failed", h.formatValidationError(err), requestID)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
var fhirResponse models.FhirResponse
if err := h.service.CreateResource(ctx, "` + data.PostEndpoint + `", req, &fhirResponse); err != nil {
h.logger.Error("Failed to create FHIR resource", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"endpoint": "` + data.PostEndpoint + `",
"path": "` + routePath + `",
})
statusCode, errorCode := h.categorizeFhirError(err)
h.sendFhirErrorResponse(c, statusCode, errorCode,
"Failed to create ` + data.FhirResource + ` resource", err.Error(), requestID)
return
}
// Check for FHIR OperationOutcome
if fhirResponse.ResourceType == "OperationOutcome" {
h.logger.Warn("Satu Sehat returned OperationOutcome", map[string]interface{}{
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"path": "` + routePath + `",
"outcome": fhirResponse,
})
h.sendFhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("FHIR ` + data.FhirResource + ` resource created successfully", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
"fhir_resource": "` + data.FhirResource + `",
"resource_id": fhirResponse.ID,
"path": "` + routePath + `",
})
h.sendFhirSuccessResponse(c, http.StatusCreated, "` + data.FhirResource + ` resource created successfully",
fhirResponse, requestID)
}`
}
func generateOptimizedSatuSehatUpdateMethod(data SatuSehatHandlerData) string {
var routePath, tagName string
if data.Category != "" {
routePath = "satusehat/" + data.Category + "/" + data.NameLower
tagParts := append([]string{"SatuSehat"}, data.CategoryParts...)
tagParts = append(tagParts, strings.Title(data.NameLower))
tagName = strings.Join(tagParts, "-")
} else {
routePath = "satusehat/" + data.NameLower
tagName = "SatuSehat-" + strings.Title(data.NameLower)
}
return `
// Update` + data.Name + ` updates an existing FHIR ` + data.FhirResource + ` resource
// @Summary Update (replace) an existing FHIR ` + data.FhirResource + ` resource
// @Description Update an existing ` + data.FhirResource + ` resource in Satu Sehat ecosystem with FHIR R4 compliance
// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + `
// @Tags ` + tagName + `
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2"
// @Param id path string true "` + data.FhirResource + ` resource ID"
// @Param request body models.` + data.Name + `UpdateRequest true "` + data.FhirResource + ` FHIR resource update request"
// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource updated successfully"
// @Failure 400 {object} models.FhirOperationOutcome "Bad request - validation error"
// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token"
// @Failure 404 {object} models.FhirOperationOutcome "Resource not found"
// @Failure 422 {object} models.FhirOperationOutcome "Unprocessable entity - FHIR validation error"
// @Failure 500 {object} models.FhirOperationOutcome "Internal server error"
// @Router /api/v1/` + routePath + `/{id} [put]
func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
id := c.Param("id")
h.logger.Info("Updating FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"category_path": "` + data.Category + `",
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
})
if id == "" {
h.sendFhirErrorResponse(c, http.StatusBadRequest, "required",
"Resource ID is required", "", requestID)
return
}
var req models.` + data.Name + `UpdateRequest
req.RequestID = requestID
req.Timestamp = startTime
req.ID = id
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind FHIR JSON for update", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
h.sendFhirErrorResponse(c, http.StatusBadRequest, "structure",
"Invalid FHIR resource structure", err.Error(), requestID)
return
}
if err := req.ValidateFhir(); err != nil {
h.logger.Error("FHIR update validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
h.sendFhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule",
"FHIR resource validation failed", err.Error(), requestID)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
endpoint := fmt.Sprintf("` + strings.Replace(data.PutEndpoint, "{id}", "%s", 1) + `", id)
var fhirResponse models.FhirResponse
if err := h.service.UpdateResource(ctx, endpoint, req, &fhirResponse); err != nil {
h.logger.Error("Failed to update FHIR resource", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
statusCode, errorCode := h.categorizeFhirError(err)
h.sendFhirErrorResponse(c, statusCode, errorCode,
"Failed to update ` + data.FhirResource + ` resource", err.Error(), requestID)
return
}
if fhirResponse.ResourceType == "OperationOutcome" {
h.logger.Warn("Satu Sehat returned OperationOutcome for update", map[string]interface{}{
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
"outcome": fhirResponse,
})
h.sendFhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("FHIR ` + data.FhirResource + ` resource updated successfully", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
h.sendFhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource updated successfully",
fhirResponse, requestID)
}`
}
func generateOptimizedSatuSehatPatchMethod(data SatuSehatHandlerData) string {
var routePath, tagName string
if data.Category != "" {
routePath = "satusehat/" + data.Category + "/" + data.NameLower
tagParts := append([]string{"SatuSehat"}, data.CategoryParts...)
tagParts = append(tagParts, strings.Title(data.NameLower))
tagName = strings.Join(tagParts, "-")
} else {
routePath = "satusehat/" + data.NameLower
tagName = "SatuSehat-" + strings.Title(data.NameLower)
}
return `
// Patch` + data.Name + ` partially updates an existing FHIR ` + data.FhirResource + ` resource
// @Summary Patch (partial update) an existing FHIR ` + data.FhirResource + ` resource
// @Description Partially update an existing ` + data.FhirResource + ` resource in Satu Sehat ecosystem with FHIR R4 compliance
// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + `
// @Tags ` + tagName + `
// @Accept json-patch+json
// @Produce json
// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2"
// @Param id path string true "` + data.FhirResource + ` resource ID"
// @Param request body models.` + data.Name + `PatchRequest true "` + data.FhirResource + ` FHIR resource patch request"
// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource patched successfully"
// @Failure 400 {object} models.FhirOperationOutcome "Bad request - validation error"
// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token"
// @Failure 404 {object} models.FhirOperationOutcome "Resource not found"
// @Failure 422 {object} models.FhirOperationOutcome "Unprocessable entity - FHIR validation error"
// @Failure 500 {object} models.FhirOperationOutcome "Internal server error"
// @Router /api/v1/` + routePath + `/{id} [patch]
func (h *` + data.Name + `Handler) Patch` + data.Name + `(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
id := c.Param("id")
h.logger.Info("Patching FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"category_path": "` + data.Category + `",
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
})
if id == "" {
h.sendFhirErrorResponse(c, http.StatusBadRequest, "required",
"Resource ID is required", "", requestID)
return
}
var req models.` + data.Name + `PatchRequest
req.RequestID = requestID
req.Timestamp = startTime
req.ID = id
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind FHIR patch JSON", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
h.sendFhirErrorResponse(c, http.StatusBadRequest, "structure",
"Invalid FHIR patch structure", err.Error(), requestID)
return
}
if err := req.ValidateFhirPatch(); err != nil {
h.logger.Error("FHIR patch validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
h.sendFhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule",
"FHIR patch validation failed", err.Error(), requestID)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
endpoint := fmt.Sprintf("` + strings.Replace(data.PatchEndpoint, "{id}", "%s", 1) + `", id)
var fhirResponse models.FhirResponse
if err := h.service.PatchResource(ctx, endpoint, req, &fhirResponse); err != nil {
h.logger.Error("Failed to patch FHIR resource", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
statusCode, errorCode := h.categorizeFhirError(err)
h.sendFhirErrorResponse(c, statusCode, errorCode,
"Failed to patch ` + data.FhirResource + ` resource", err.Error(), requestID)
return
}
if fhirResponse.ResourceType == "OperationOutcome" {
h.logger.Warn("Satu Sehat returned OperationOutcome for patch", map[string]interface{}{
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
"outcome": fhirResponse,
})
h.sendFhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("FHIR ` + data.FhirResource + ` resource patched successfully", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
h.sendFhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource patched successfully",
fhirResponse, requestID)
}`
}
func generateOptimizedSatuSehatDeleteMethod(data SatuSehatHandlerData) string {
var routePath, tagName string
if data.Category != "" {
routePath = "satusehat/" + data.Category + "/" + data.NameLower
tagParts := append([]string{"SatuSehat"}, data.CategoryParts...)
tagParts = append(tagParts, strings.Title(data.NameLower))
tagName = strings.Join(tagParts, "-")
} else {
routePath = "satusehat/" + data.NameLower
tagName = "SatuSehat-" + strings.Title(data.NameLower)
}
return `
// Delete` + data.Name + ` deletes an existing FHIR ` + data.FhirResource + ` resource
// @Summary Delete an existing FHIR ` + data.FhirResource + ` resource
// @Description Delete an existing ` + data.FhirResource + ` resource in Satu Sehat ecosystem with FHIR R4 compliance
// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + `
// @Tags ` + tagName + `
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2"
// @Param id path string true "` + data.FhirResource + ` resource ID"
// @Success 204 "` + data.FhirResource + ` resource deleted successfully"
// @Failure 400 {object} models.FhirOperationOutcome "Bad request - invalid ID"
// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token"
// @Failure 404 {object} models.FhirOperationOutcome "Resource not found"
// @Failure 500 {object} models.FhirOperationOutcome "Internal server error"
// @Router /api/v1/` + routePath + `/{id} [delete]
func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
id := c.Param("id")
h.logger.Info("Deleting FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"category_path": "` + data.Category + `",
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
})
if id == "" {
h.sendFhirErrorResponse(c, http.StatusBadRequest, "required",
"Resource ID is required", "", requestID)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
endpoint := fmt.Sprintf("` + strings.Replace(data.DeleteEndpoint, "{id}", "%s", 1) + `", id)
if err := h.service.DeleteResource(ctx, endpoint); err != nil {
h.logger.Error("Failed to delete FHIR resource", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
statusCode, errorCode := h.categorizeFhirError(err)
h.sendFhirErrorResponse(c, statusCode, errorCode,
"Failed to delete ` + data.FhirResource + ` resource", err.Error(), requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("FHIR ` + data.FhirResource + ` resource deleted successfully", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
c.Status(http.StatusNoContent)
}`
}
func generateOptimizedSatuSehatGetMethod(data SatuSehatHandlerData) string {
var routePath, tagName string
if data.Category != "" {
routePath = "satusehat/" + data.Category + "/" + data.NameLower
tagParts := append([]string{"SatuSehat"}, data.CategoryParts...)
tagParts = append(tagParts, strings.Title(data.NameLower))
tagName = strings.Join(tagParts, "-")
} else {
routePath = "satusehat/" + data.NameLower
tagName = "SatuSehat-" + strings.Title(data.NameLower)
}
return `
// Get` + data.Name + ` retrieves a specific FHIR ` + data.FhirResource + ` resource by ID
// @Summary Get a specific FHIR ` + data.FhirResource + ` resource by ID
// @Description Retrieve a specific ` + data.FhirResource + ` resource from Satu Sehat ecosystem with FHIR R4 compliance
// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + `
// @Tags ` + tagName + `
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2"
// @Param id path string true "` + data.FhirResource + ` resource ID"
// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource retrieved successfully"
// @Failure 400 {object} models.FhirOperationOutcome "Bad request - invalid ID"
// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token"
// @Failure 404 {object} models.FhirOperationOutcome "Resource not found"
// @Failure 500 {object} models.FhirOperationOutcome "Internal server error"
// @Router /api/v1/` + routePath + `/{id} [get]
func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
id := c.Param("id")
h.logger.Info("Getting FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"category_path": "` + data.Category + `",
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
})
if id == "" {
h.sendFhirErrorResponse(c, http.StatusBadRequest, "required",
"Resource ID is required", "", requestID)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
endpoint := fmt.Sprintf("` + strings.Replace(data.GetEndpoint, "{id}", "%s", 1) + `", id)
var fhirResponse models.FhirResponse
if err := h.service.GetResource(ctx, endpoint, &fhirResponse); err != nil {
h.logger.Error("Failed to get FHIR resource", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
statusCode, errorCode := h.categorizeFhirError(err)
h.sendFhirErrorResponse(c, statusCode, errorCode,
"Failed to get ` + data.FhirResource + ` resource", err.Error(), requestID)
return
}
if fhirResponse.ResourceType == "OperationOutcome" {
h.logger.Info("FHIR ` + data.FhirResource + ` resource not found", map[string]interface{}{
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
"outcome": fhirResponse,
})
h.sendFhirOperationOutcome(c, http.StatusNotFound, fhirResponse, requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("FHIR ` + data.FhirResource + ` resource retrieved successfully", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
"fhir_resource": "` + data.FhirResource + `",
"resource_id": id,
"path": "` + routePath + `",
})
h.sendFhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource retrieved successfully",
fhirResponse, requestID)
}`
}
func generateOptimizedSatuSehatSearchMethod(data SatuSehatHandlerData) string {
var routePath, tagName string
if data.Category != "" {
routePath = "satusehat/" + data.Category + "/" + data.NameLower
tagParts := append([]string{"SatuSehat"}, data.CategoryParts...)
tagParts = append(tagParts, strings.Title(data.NameLower))
tagName = strings.Join(tagParts, "-")
} else {
routePath = "satusehat/" + data.NameLower
tagName = "SatuSehat-" + strings.Title(data.NameLower)
}
return `
// Search` + data.Name + ` searches for FHIR ` + data.FhirResource + ` resources with parameters
// @Summary Search for FHIR ` + data.FhirResource + ` resources
// @Description Search for ` + data.FhirResource + ` resources in Satu Sehat ecosystem with FHIR R4 search parameters
// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + `
// @Tags ` + tagName + `
// @Accept json
// @Produce json
// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2"
// @Param _count query integer false "Number of results to return (default: 10)"
// @Param _page query integer false "Page number for pagination (default: 1)"
// @Param _include query string false "Include referenced resources"
// @Param _revinclude query string false "Reverse include referenced resources"
// @Success 200 {object} models.FhirBundleResponse "` + data.FhirResource + ` resources search results"
// @Failure 400 {object} models.FhirOperationOutcome "Bad request - invalid search parameters"
// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token"
// @Failure 500 {object} models.FhirOperationOutcome "Internal server error"
// @Router /api/v1/` + routePath + ` [get]
func (h *` + data.Name + `Handler) Search` + data.Name + `(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
h.logger.Info("Searching FHIR ` + data.FhirResource + ` resources", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
"fhir_resource": "` + data.FhirResource + `",
"query_params": c.Request.URL.Query(),
"category_path": "` + data.Category + `",
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
})
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
// Build search parameters
searchParams := make(map[string]string)
for key, values := range c.Request.URL.Query() {
if len(values) > 0 {
searchParams[key] = values[0]
}
}
var fhirBundle models.FhirBundleResponse
if err := h.service.SearchResources(ctx, "` + data.FhirResource + `", searchParams, &fhirBundle); err != nil {
h.logger.Error("Failed to search FHIR resources", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"fhir_resource": "` + data.FhirResource + `",
"search_params": searchParams,
"path": "` + routePath + `",
})
statusCode, errorCode := h.categorizeFhirError(err)
h.sendFhirErrorResponse(c, statusCode, errorCode,
"Failed to search ` + data.FhirResource + ` resources", err.Error(), requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("FHIR ` + data.FhirResource + ` resources search completed", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
"fhir_resource": "` + data.FhirResource + `",
"total_results": fhirBundle.Total,
"search_params": searchParams,
"path": "` + routePath + `",
})
h.sendFhirBundleResponse(c, http.StatusOK, "` + data.FhirResource + ` resources search completed",
fhirBundle, requestID)
}`
}
func generateSatuSehatHelperMethods(data SatuSehatHandlerData) string {
return `
// Helper methods for ` + data.Name + `Handler with FHIR support
func (h *` + data.Name + `Handler) sendFhirSuccessResponse(c *gin.Context, statusCode int, message string, data interface{}, requestID string) {
response := models.` + data.Name + `Response{
FhirResponse: models.FhirResponse{
ResourceType: "` + data.FhirResource + `",
Meta: models.FhirMeta{
LastUpdated: time.Now().Format(time.RFC3339),
VersionId: "1",
},
},
BaseResponse: models.BaseResponse{
Status: "success",
Message: message,
Data: data,
Metadata: &models.ResponseMetadata{
Timestamp: time.Now(),
Version: "FHIR R4",
RequestID: requestID,
Path: "` + data.Category + `",
Depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
FhirResource: "` + data.FhirResource + `",
},
},
}
c.JSON(statusCode, response)
}
func (h *` + data.Name + `Handler) sendFhirErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) {
operationOutcome := models.FhirOperationOutcome{
ResourceType: "OperationOutcome",
ID: requestID,
Meta: models.FhirMeta{
LastUpdated: time.Now().Format(time.RFC3339),
VersionId: "1",
},
Issue: []models.FhirOperationOutcomeIssue{{
Severity: h.mapHttpStatusToSeverity(statusCode),
Code: errorCode,
Details: models.FhirCodeableConcept{
Text: message,
},
Diagnostics: details,
}},
}
c.JSON(statusCode, operationOutcome)
}a
func (h *` + data.Name + `Handler) sendFhirOperationOutcome(c *gin.Context, statusCode int, outcome models.FhirResponse, requestID string) {
c.JSON(statusCode, outcome)
}
func (h *` + data.Name + `Handler) sendFhirBundleResponse(c *gin.Context, statusCode int, message string, bundle models.FhirBundleResponse, requestID string) {
bundle.Meta = models.FhirMeta{
LastUpdated: time.Now().Format(time.RFC3339),
VersionId: "1",
}
c.JSON(statusCode, bundle)
}
func (h *` + data.Name + `Handler) formatValidationError(err error) string {
if validationErrors, ok := err.(validator.ValidationErrors); ok {
var messages []string
for _, e := range validationErrors {
switch e.Tag() {
case "required":
messages = append(messages, fmt.Sprintf("Field '%s' is required", e.Field()))
case "min":
messages = append(messages, fmt.Sprintf("Field '%s' must be at least %s characters", e.Field(), e.Param()))
case "max":
messages = append(messages, fmt.Sprintf("Field '%s' must be at most %s characters", e.Field(), e.Param()))
case "oneof":
messages = append(messages, fmt.Sprintf("Field '%s' must be one of: %s", e.Field(), e.Param()))
case "url":
messages = append(messages, fmt.Sprintf("Field '%s' must be a valid URL", e.Field()))
case "uuid":
messages = append(messages, fmt.Sprintf("Field '%s' must be a valid UUID", e.Field()))
default:
messages = append(messages, fmt.Sprintf("Field '%s' is invalid", e.Field()))
}
}
return fmt.Sprintf("FHIR validation failed: %v", messages)
}
return err.Error()
}
func (h *` + data.Name + `Handler) categorizeFhirError(err error) (int, string) {
if err == nil {
return http.StatusOK, "informational"
}
errStr := err.Error()
// FHIR-specific error categorization
if strings.Contains(errStr, "unauthorized") || strings.Contains(errStr, "invalid token") {
return http.StatusUnauthorized, "security"
}
if strings.Contains(errStr, "not found") || strings.Contains(errStr, "404") {
return http.StatusNotFound, "not-found"
}
if strings.Contains(errStr, "validation") || strings.Contains(errStr, "invalid") {
return http.StatusUnprocessableEntity, "business-rule"
}
if h.isTimeoutError(err) {
return http.StatusRequestTimeout, "timeout"
}
if h.isNetworkError(err) {
return http.StatusBadGateway, "transient"
}
return http.StatusInternalServerError, "exception"
}
func (h *` + data.Name + `Handler) mapHttpStatusToSeverity(statusCode int) string {
switch {
case statusCode >= 500:
return "fatal"
case statusCode >= 400:
return "error"
case statusCode >= 300:
return "warning"
default:
return "information"
}
}
func (h *` + data.Name + `Handler) isTimeoutError(err error) bool {
return err != nil && (strings.Contains(err.Error(), "timeout") ||
strings.Contains(err.Error(), "deadline exceeded"))
}
func (h *` + data.Name + `Handler) isNetworkError(err error) bool {
return err != nil && (strings.Contains(err.Error(), "connection refused") ||
strings.Contains(err.Error(), "no such host") ||
strings.Contains(err.Error(), "network unreachable"))
}`
}
func generateOptimizedSatuSehatModelFile(data SatuSehatHandlerData, modelDir string) {
modelContent := `package models
import (
"encoding/json"
"fmt"
"time"
"regexp"
)
// ` + data.Name + ` Satu Sehat FHIR R4 Models with Enhanced Multi-Level Support
// Generated at: ` + data.Timestamp + `
// FHIR Resource: ` + data.FhirResource + `
// Category Path: ` + data.Category + `
// Directory Depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + ` levels
// Base FHIR structures
type FhirResource struct {
ResourceType string ` + "`json:\"resourceType\"`" + `
ID string ` + "`json:\"id,omitempty\"`" + `
Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + `
}
type FhirMeta struct {
VersionId string ` + "`json:\"versionId,omitempty\"`" + `
LastUpdated string ` + "`json:\"lastUpdated,omitempty\"`" + `
Profile []string ` + "`json:\"profile,omitempty\"`" + `
Security []FhirCoding ` + "`json:\"security,omitempty\"`" + `
Tag []FhirCoding ` + "`json:\"tag,omitempty\"`" + `
}
type FhirCoding struct {
System string ` + "`json:\"system,omitempty\"`" + `
Version string ` + "`json:\"version,omitempty\"`" + `
Code string ` + "`json:\"code,omitempty\"`" + `
Display string ` + "`json:\"display,omitempty\"`" + `
}
type FhirCodeableConcept struct {
Coding []FhirCoding ` + "`json:\"coding,omitempty\"`" + `
Text string ` + "`json:\"text,omitempty\"`" + `
}
type FhirReference struct {
Reference string ` + "`json:\"reference,omitempty\"`" + `
Type string ` + "`json:\"type,omitempty\"`" + `
Identifier FhirIdentifier ` + "`json:\"identifier,omitempty\"`" + `
Display string ` + "`json:\"display,omitempty\"`" + `
}
type FhirIdentifier struct {
Use string ` + "`json:\"use,omitempty\"`" + `
Type FhirCodeableConcept ` + "`json:\"type,omitempty\"`" + `
System string ` + "`json:\"system,omitempty\"`" + `
Value string ` + "`json:\"value,omitempty\"`" + `
Period FhirPeriod ` + "`json:\"period,omitempty\"`" + `
Assigner FhirReference ` + "`json:\"assigner,omitempty\"`" + `
}
type FhirPeriod struct {
Start string ` + "`json:\"start,omitempty\"`" + `
End string ` + "`json:\"end,omitempty\"`" + `
}
// FHIR OperationOutcome for error handling
type FhirOperationOutcome struct {
ResourceType string ` + "`json:\"resourceType\"`" + `
ID string ` + "`json:\"id,omitempty\"`" + `
Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + `
Issue []FhirOperationOutcomeIssue ` + "`json:\"issue\"`" + `
}
type FhirOperationOutcomeIssue struct {
Severity string ` + "`json:\"severity\"`" + `
Code string ` + "`json:\"code\"`" + `
Details FhirCodeableConcept ` + "`json:\"details,omitempty\"`" + `
Diagnostics string ` + "`json:\"diagnostics,omitempty\"`" + `
Location []string ` + "`json:\"location,omitempty\"`" + `
Expression []string ` + "`json:\"expression,omitempty\"`" + `
}
// FHIR Bundle for search results
type FhirBundleResponse struct {
ResourceType string ` + "`json:\"resourceType\"`" + `
ID string ` + "`json:\"id,omitempty\"`" + `
Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + `
Type string ` + "`json:\"type\"`" + `
Total int ` + "`json:\"total,omitempty\"`" + `
Link []FhirBundleLink ` + "`json:\"link,omitempty\"`" + `
Entry []FhirBundleEntry ` + "`json:\"entry,omitempty\"`" + `
}
type FhirBundleLink struct {
Relation string ` + "`json:\"relation\"`" + `
URL string ` + "`json:\"url\"`" + `
}
type FhirBundleEntry struct {
FullURL string ` + "`json:\"fullUrl,omitempty\"`" + `
Resource interface{} ` + "`json:\"resource,omitempty\"`" + `
Search FhirBundleEntrySearch ` + "`json:\"search,omitempty\"`" + `
}
type FhirBundleEntrySearch struct {
Mode string ` + "`json:\"mode,omitempty\"`" + `
Score string ` + "`json:\"score,omitempty\"`" + `
}
// Base request/response structures with FHIR integration
type BaseRequest struct {
RequestID string ` + "`json:\"request_id,omitempty\"`" + `
Timestamp time.Time ` + "`json:\"timestamp,omitempty\"`" + `
}
type BaseResponse struct {
Status string ` + "`json:\"status\"`" + `
Message string ` + "`json:\"message\"`" + `
Data interface{} ` + "`json:\"data,omitempty\"`" + `
Error *ErrorResponse ` + "`json:\"error,omitempty\"`" + `
Metadata *ResponseMetadata ` + "`json:\"metadata,omitempty\"`" + `
}
type ErrorResponse struct {
Code string ` + "`json:\"code\"`" + `
Message string ` + "`json:\"message\"`" + `
Details string ` + "`json:\"details,omitempty\"`" + `
}
type ResponseMetadata struct {
Timestamp time.Time ` + "`json:\"timestamp\"`" + `
Version string ` + "`json:\"version\"`" + `
RequestID string ` + "`json:\"request_id,omitempty\"`" + `
Path string ` + "`json:\"path,omitempty\"`" + `
Depth int ` + "`json:\"depth,omitempty\"`" + `
FhirResource string ` + "`json:\"fhir_resource,omitempty\"`" + `
}
// ` + data.Name + ` Response Structure with FHIR integration
type ` + data.Name + `Response struct {
FhirResource
BaseResponse
}
// Generic FHIR Response
type FhirResponse struct {
ResourceType string ` + "`json:\"resourceType\"`" + `
ID string ` + "`json:\"id,omitempty\"`" + `
Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + `
Content map[string]interface{} ` + "`json:\"-\"`" + ` // For dynamic content
}`
// Add CRUD request structures based on methods
if data.HasPost {
modelContent += `
// ` + data.Name + ` CREATE Request Structure with FHIR R4 Validation
type ` + data.Name + `CreateRequest struct {
BaseRequest
ResourceType string ` + "`json:\"resourceType\" binding:\"required\" validate:\"required,eq=` + data.FhirResource + `\"`" + `
// Core FHIR ` + data.FhirResource + ` fields - customize based on specific resource
// Path: ` + data.Category + `
Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + `
Identifier []FhirIdentifier ` + "`json:\"identifier,omitempty\" validate:\"dive\"`" + `
Active *bool ` + "`json:\"active,omitempty\"`" + `
Name []FhirHumanName ` + "`json:\"name,omitempty\" validate:\"dive\"`" + `
Telecom []FhirContactPoint ` + "`json:\"telecom,omitempty\" validate:\"dive\"`" + `
Gender string ` + "`json:\"gender,omitempty\" validate:\"omitempty,oneof=male female other unknown\"`" + `
BirthDate string ` + "`json:\"birthDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + `
Address []FhirAddress ` + "`json:\"address,omitempty\" validate:\"dive\"`" + `
}
// Additional FHIR data types for ` + data.FhirResource + `
type FhirHumanName struct {
Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=usual official temp nickname anonymous old maiden\"`" + `
Text string ` + "`json:\"text,omitempty\"`" + `
Family string ` + "`json:\"family,omitempty\"`" + `
Given []string ` + "`json:\"given,omitempty\"`" + `
Prefix []string ` + "`json:\"prefix,omitempty\"`" + `
Suffix []string ` + "`json:\"suffix,omitempty\"`" + `
Period FhirPeriod ` + "`json:\"period,omitempty\"`" + `
}
type FhirContactPoint struct {
System string ` + "`json:\"system,omitempty\" validate:\"omitempty,oneof=phone fax email pager url sms other\"`" + `
Value string ` + "`json:\"value,omitempty\"`" + `
Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=home work temp old mobile\"`" + `
Rank int ` + "`json:\"rank,omitempty\" validate:\"omitempty,min=1\"`" + `
Period FhirPeriod ` + "`json:\"period,omitempty\"`" + `
}
type FhirAddress struct {
Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=home work temp old billing\"`" + `
Type string ` + "`json:\"type,omitempty\" validate:\"omitempty,oneof=postal physical both\"`" + `
Text string ` + "`json:\"text,omitempty\"`" + `
Line []string ` + "`json:\"line,omitempty\"`" + `
City string ` + "`json:\"city,omitempty\"`" + `
District string ` + "`json:\"district,omitempty\"`" + `
State string ` + "`json:\"state,omitempty\"`" + `
PostalCode string ` + "`json:\"postalCode,omitempty\"`" + `
Country string ` + "`json:\"country,omitempty\"`" + `
Period FhirPeriod ` + "`json:\"period,omitempty\"`" + `
}
// ValidateFhir validates the ` + data.Name + `CreateRequest with FHIR R4 business rules
func (r *` + data.Name + `CreateRequest) ValidateFhir() error {
if r.ResourceType != "` + data.FhirResource + `" {
return fmt.Errorf("invalid resourceType: expected ` + data.FhirResource + `, got %s", r.ResourceType)
}
// Validate required identifiers for Indonesian context
if len(r.Identifier) == 0 {
return fmt.Errorf("at least one identifier is required for ` + data.FhirResource + ` resource")
}
// Validate NIK if present
for _, identifier := range r.Identifier {
if identifier.System == "https://fhir.kemkes.go.id/id/nik" {
if !isValidNIK(identifier.Value) {
return fmt.Errorf("invalid NIK format: %s", identifier.Value)
}
}
}
// Path: ` + data.Category + `
return nil
}
// ToJSON converts struct to JSON string
func (r *` + data.Name + `CreateRequest) ToJSON() (string, error) {
data, err := json.Marshal(r)
return string(data), err
}`
}
if data.HasPut {
modelContent += `
// ` + data.Name + ` UPDATE Request Structure with FHIR R4 Validation
type ` + data.Name + `UpdateRequest struct {
BaseRequest
ID string ` + "`json:\"id\" binding:\"required\" validate:\"required,uuid\"`" + `
ResourceType string ` + "`json:\"resourceType\" binding:\"required\" validate:\"required,eq=` + data.FhirResource + `\"`" + `
Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + `
Identifier []FhirIdentifier ` + "`json:\"identifier,omitempty\" validate:\"dive\"`" + `
Active *bool ` + "`json:\"active,omitempty\"`" + `
Name []FhirHumanName ` + "`json:\"name,omitempty\" validate:\"dive\"`" + `
Telecom []FhirContactPoint ` + "`json:\"telecom,omitempty\" validate:\"dive\"`" + `
Gender string ` + "`json:\"gender,omitempty\" validate:\"omitempty,oneof=male female other unknown\"`" + `
BirthDate string ` + "`json:\"birthDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + `
Address []FhirAddress ` + "`json:\"address,omitempty\" validate:\"dive\"`" + `
}
// ValidateFhir validates the ` + data.Name + `UpdateRequest with FHIR R4 business rules
func (r *` + data.Name + `UpdateRequest) ValidateFhir() error {
if r.ResourceType != "` + data.FhirResource + `" {
return fmt.Errorf("invalid resourceType: expected ` + data.FhirResource + `, got %s", r.ResourceType)
}
if r.ID == "" {
return fmt.Errorf("resource ID is required for update operation")
}
// Path: ` + data.Category + `
return nil
}
// ToJSON converts struct to JSON string
func (r *` + data.Name + `UpdateRequest) ToJSON() (string, error) {
data, err := json.Marshal(r)
return string(data), err
}`
}
if data.HasPatch {
modelContent += `
// ` + data.Name + ` PATCH Request Structure with FHIR R4 JSON Patch
type ` + data.Name + `PatchRequest struct {
BaseRequest
ID string ` + "`json:\"id\" binding:\"required\" validate:\"required,uuid\"`" + `
Patches []FhirJsonPatch ` + "`json:\"patches\" binding:\"required\" validate:\"required,dive\"`" + `
}
type FhirJsonPatch struct {
Op string ` + "`json:\"op\" binding:\"required\" validate:\"required,oneof=add remove replace move copy test\"`" + `
Path string ` + "`json:\"path\" binding:\"required\" validate:\"required\"`" + `
Value interface{} ` + "`json:\"value,omitempty\"`" + `
From string ` + "`json:\"from,omitempty\"`" + `
}
// ValidateFhirPatch validates the ` + data.Name + `PatchRequest with FHIR R4 patch rules
func (r *` + data.Name + `PatchRequest) ValidateFhirPatch() error {
if r.ID == "" {
return fmt.Errorf("resource ID is required for patch operation")
}
if len(r.Patches) == 0 {
return fmt.Errorf("at least one patch operation is required")
}
// Validate each patch operation
for i, patch := range r.Patches {
if patch.Path == "" {
return fmt.Errorf("patch[%d]: path is required", i)
}
if patch.Op == "move" || patch.Op == "copy" {
if patch.From == "" {
return fmt.Errorf("patch[%d]: from is required for %s operation", i, patch.Op)
}
}
if patch.Op != "remove" && patch.Op != "test" && patch.Value == nil {
return fmt.Errorf("patch[%d]: value is required for %s operation", i, patch.Op)
}
}
// Path: ` + data.Category + `
return nil
}
// ToJSON converts struct to JSON string
func (r *` + data.Name + `PatchRequest) ToJSON() (string, error) {
data, err := json.Marshal(r)
return string(data), err
}`
}
// Add validation helper functions
modelContent += `
// FHIR validation helper functions for Indonesian context
func isValidNIK(nik string) bool {
if len(nik) != 16 {
return false
}
// Check if all characters are digits
matched, _ := regexp.MatchString("^[0-9]{16}$", nik)
return matched
}
func isValidFhirDate(date string) bool {
// FHIR date format: YYYY, YYYY-MM, or YYYY-MM-DD
patterns := []string{
"^[0-9]{4}$", // Year only
"^[0-9]{4}-[0-9]{2}$", // Year-Month
"^[0-9]{4}-[0-9]{2}-[0-9]{2}$", // Full date
}
for _, pattern := range patterns {
matched, _ := regexp.MatchString(pattern, date)
if matched {
return true
}
}
return false
}
func isValidFhirDateTime(datetime string) bool {
// FHIR datetime format with timezone
pattern := "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$"
matched, _ := regexp.MatchString(pattern, datetime)
return matched
}
func isValidFhirID(id string) bool {
// FHIR ID: 1-64 characters, alphanumeric, dash, dot
if len(id) < 1 || len(id) > 64 {
return false
}
pattern := "^[A-Za-z0-9\\-\\.]{1,64}$"
matched, _ := regexp.MatchString(pattern, id)
return matched
}
// GetPathInfo returns information about the multi-level FHIR path
func GetFhirPathInfo() map[string]interface{} {
return map[string]interface{}{
"fhir_resource": "` + data.FhirResource + `",
"path": "` + data.Category + `",
"depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
"parts": []string{` + `"` + strings.Join(data.CategoryParts, `", "`) + `"` + `},
"base_url": "https://api-satusehat-stg.dto.kemkes.go.id/fhir-r4/v1",
"profile_url": "https://fhir.kemkes.go.id/r4/StructureDefinition/` + data.FhirResource + `",
}
}`
writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent)
}
// Continue with model generation, routes generation, and utility functions...
// [Model generation and other functions would continue here following the same pattern]
func writeFile(filename, content string) {
if err := os.WriteFile(filename, []byte(content), 0644); err != nil {
fmt.Printf("❌ Error creating file %s: %v\n", filename, err)
return
}
fmt.Printf("✅ Generated Satu Sehat FHIR file: %s\n", filename)
}