2730 lines
120 KiB
Go
2730 lines
120 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Hl7FhirHandlerData contains template data for HL7 FHIR handler generation
|
|
type Hl7FhirHandlerData 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
|
|
HasSearch bool
|
|
HasHistory bool
|
|
GetEndpoint string
|
|
PostEndpoint string
|
|
PutEndpoint string
|
|
PatchEndpoint string
|
|
DeleteEndpoint string
|
|
SearchEndpoint string
|
|
HistoryEndpoint string
|
|
Timestamp string
|
|
DirectoryDepth int
|
|
FhirResource string
|
|
FhirVersion string // R4, R5
|
|
Profile string // US Core, AU Base, UK Core, etc.
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
fmt.Println("Usage: go run generate-hl7fhir-handler.go [level1[/level2[/level3[/level4]]]]/entity [methods] [--version=R4|R5] [--profile=profile]")
|
|
fmt.Println("Examples:")
|
|
fmt.Println(" go run generate-hl7fhir-handler.go fhir/patient get post put patch --version=R4")
|
|
fmt.Println(" go run generate-hl7fhir-handler.go us-core/patient get post search --profile=USCore")
|
|
fmt.Println(" go run generate-hl7fhir-handler.go au-base/practitioner get post --profile=AUBase --version=R4")
|
|
fmt.Println(" go run generate-hl7fhir-handler.go api/v1/fhir/r4/observation get post put patch delete search history")
|
|
fmt.Println(" go run generate-hl7fhir-handler.go medication get")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Parse entity path (up to 4 levels + entity)
|
|
entityPath := os.Args[1]
|
|
methods := []string{}
|
|
fhirVersion := "R4" // Default to R4
|
|
profile := "" // Default to no specific profile
|
|
|
|
// Parse arguments
|
|
for i := 2; i < len(os.Args); i++ {
|
|
arg := os.Args[i]
|
|
if strings.HasPrefix(arg, "--version=") {
|
|
fhirVersion = strings.TrimPrefix(arg, "--version=")
|
|
} else if strings.HasPrefix(arg, "--profile=") {
|
|
profile = strings.TrimPrefix(arg, "--profile=")
|
|
} else {
|
|
methods = append(methods, arg)
|
|
}
|
|
}
|
|
|
|
if len(methods) == 0 {
|
|
// Default methods for FHIR resources
|
|
methods = []string{"get", "post", "put", "patch", "search"}
|
|
}
|
|
|
|
// 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 := Hl7FhirHandlerData{
|
|
Name: entityName,
|
|
NameLower: entityLower,
|
|
NameUpper: entityUpper,
|
|
Category: category,
|
|
CategoryPath: category,
|
|
CategoryParts: categoryParts,
|
|
ModuleName: "api-service",
|
|
DirectoryDepth: len(categoryParts),
|
|
FhirResource: fhirResource,
|
|
FhirVersion: fhirVersion,
|
|
Profile: profile,
|
|
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)
|
|
case "search":
|
|
data.HasSearch = true
|
|
data.SearchEndpoint = fhirResource
|
|
case "history":
|
|
data.HasHistory = true
|
|
data.HistoryEndpoint = fmt.Sprintf("%s/{id}/_history", fhirResource)
|
|
}
|
|
}
|
|
|
|
// Create directories with multi-level support
|
|
var handlerDir, modelDir string
|
|
if category != "" {
|
|
// Multi-level directory support
|
|
handlerDirParts := append([]string{"internal", "handlers", "hl7fhir"}, categoryParts...)
|
|
modelDirParts := append([]string{"internal", "models", "hl7fhir"}, categoryParts...)
|
|
|
|
handlerDir = filepath.Join(handlerDirParts...)
|
|
modelDir = filepath.Join(modelDirParts...)
|
|
} else {
|
|
// No category: direct internal/handlers/hl7fhir/
|
|
handlerDir = filepath.Join("internal", "handlers", "hl7fhir")
|
|
modelDir = filepath.Join("internal", "models", "hl7fhir")
|
|
}
|
|
|
|
// Create directories
|
|
for _, d := range []string{handlerDir, modelDir} {
|
|
if err := os.MkdirAll(d, 0755); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Generate files
|
|
generateOptimizedHl7FhirHandlerFile(data, handlerDir)
|
|
generateOptimizedHl7FhirModelFile(data, modelDir)
|
|
// updateOptimizedHl7FhirRoutesFile(data)
|
|
|
|
fmt.Printf("✅ Successfully generated optimized HL7 FHIR 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("📁 FHIR Version: %s\n", fhirVersion)
|
|
if profile != "" {
|
|
fmt.Printf("📁 FHIR Profile: %s\n", profile)
|
|
}
|
|
fmt.Printf("📁 Handler: %s\n", filepath.Join(handlerDir, entityLower+".go"))
|
|
fmt.Printf("📁 Model: %s\n", filepath.Join(modelDir, entityLower+".go"))
|
|
}
|
|
|
|
// ================= OPTIMIZED HANDLER GENERATION =====================
|
|
|
|
func generateOptimizedHl7FhirHandlerFile(data Hl7FhirHandlerData, handlerDir string) {
|
|
var modelsImportPath string
|
|
if data.Category != "" {
|
|
modelsImportPath = data.ModuleName + "/internal/models/hl7fhir/" + data.Category
|
|
} else {
|
|
modelsImportPath = data.ModuleName + "/internal/models/hl7fhir"
|
|
}
|
|
|
|
profileInfo := ""
|
|
if data.Profile != "" {
|
|
profileInfo = fmt.Sprintf("// FHIR Profile: %s\n", data.Profile)
|
|
}
|
|
|
|
handlerContent := `package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"` + data.ModuleName + `/internal/config"
|
|
"` + modelsImportPath + `"
|
|
services "` + data.ModuleName + `/internal/services/hl7fhir"
|
|
"` + data.ModuleName + `/pkg/logger"
|
|
"` + data.ModuleName + `/pkg/validator"
|
|
|
|
"api-service/pkg/utils"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/go-playground/validator/v10"
|
|
)
|
|
|
|
// ` + data.Name + `Handler handles ` + data.NameLower + ` HL7 FHIR services with multi-level organization
|
|
// Generated for FHIR Resource: ` + data.FhirResource + `
|
|
// FHIR Version: ` + data.FhirVersion + `
|
|
` + profileInfo + `// Path: ` + data.Category + `
|
|
// Directory depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + ` levels
|
|
type ` + data.Name + `Handler struct {
|
|
service services.Hl7FhirService
|
|
validator *validator.Validate
|
|
logger logger.Logger
|
|
config *config.Hl7FhirConfig
|
|
}
|
|
|
|
// HandlerConfig contains configuration for ` + data.Name + `Handler
|
|
type ` + data.Name + `HandlerConfig struct {
|
|
Hl7FhirConfig *config.Hl7FhirConfig
|
|
Logger logger.Logger
|
|
Validator *validator.Validate
|
|
}
|
|
|
|
// New` + data.Name + `Handler creates a new optimized ` + data.Name + `Handler for HL7 FHIR
|
|
func New` + data.Name + `Handler(cfg *` + data.Name + `HandlerConfig) *` + data.Name + `Handler {
|
|
return &` + data.Name + `Handler{
|
|
service: services.NewHl7FhirService(cfg.Hl7FhirConfig),
|
|
validator: cfg.Validator,
|
|
logger: cfg.Logger,
|
|
config: cfg.Hl7FhirConfig,
|
|
}
|
|
}`
|
|
|
|
// Add optimized methods based on flags
|
|
if data.HasPost {
|
|
handlerContent += generateOptimizedHl7FhirCreateMethod(data)
|
|
}
|
|
|
|
if data.HasPut {
|
|
handlerContent += generateOptimizedHl7FhirUpdateMethod(data)
|
|
}
|
|
|
|
if data.HasPatch {
|
|
handlerContent += generateOptimizedHl7FhirPatchMethod(data)
|
|
}
|
|
|
|
if data.HasDelete {
|
|
handlerContent += generateOptimizedHl7FhirDeleteMethod(data)
|
|
}
|
|
|
|
if data.HasGet {
|
|
handlerContent += generateOptimizedHl7FhirGetMethod(data)
|
|
}
|
|
|
|
if data.HasSearch {
|
|
handlerContent += generateOptimizedHl7FhirSearchMethod(data)
|
|
}
|
|
|
|
if data.HasHistory {
|
|
handlerContent += generateOptimizedHl7FhirHistoryMethod(data)
|
|
}
|
|
|
|
// Add helper methods
|
|
handlerContent += generateHl7FhirHelperMethods(data)
|
|
|
|
writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent)
|
|
}
|
|
|
|
func generateOptimizedHl7FhirCreateMethod(data Hl7FhirHandlerData) string {
|
|
var routePath, tagName string
|
|
if data.Category != "" {
|
|
routePath = "hl7fhir/" + data.Category + "/" + data.NameLower
|
|
tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...)
|
|
tagParts = append(tagParts, strings.Title(data.NameLower))
|
|
tagName = strings.Join(tagParts, "-")
|
|
} else {
|
|
routePath = "hl7fhir/" + data.NameLower
|
|
tagName = "HL7FHIR-" + strings.Title(data.NameLower)
|
|
}
|
|
|
|
profileTag := ""
|
|
if data.Profile != "" {
|
|
profileTag = fmt.Sprintf(" (%s Profile)", data.Profile)
|
|
}
|
|
|
|
return `
|
|
|
|
// Create` + data.Name + ` creates a new HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Summary Create a new HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Description Create a new ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + `
|
|
// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + `
|
|
// @Tags ` + tagName + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)"
|
|
// @Param Content-Type header string true "Request content type (application/fhir+json, application/json)"
|
|
// @Param Prefer header string false "Return preference (return=minimal, return=representation, return=OperationOutcome)"
|
|
// @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.Hl7FhirOperationOutcome "Bad request - validation error"
|
|
// @Failure 422 {object} models.Hl7FhirOperationOutcome "Unprocessable entity - FHIR validation error"
|
|
// @Failure 500 {object} models.Hl7FhirOperationOutcome "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 HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"timestamp": startTime,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"category_path": "` + data.Category + `",
|
|
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
|
|
"content_type": c.GetHeader("Content-Type"),
|
|
"accept": c.GetHeader("Accept"),
|
|
"prefer": c.GetHeader("Prefer"),
|
|
})
|
|
|
|
var req models.` + data.Name + `CreateRequest
|
|
req.RequestID = requestID
|
|
req.Timestamp = startTime
|
|
|
|
// Enhanced JSON binding with HL7 FHIR validation
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.logger.Error("Failed to bind HL7 FHIR JSON", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure",
|
|
"Invalid FHIR resource structure", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
// HL7 FHIR resource validation
|
|
if err := req.ValidateHl7Fhir(); err != nil {
|
|
h.logger.Error("HL7 FHIR validation failed", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule",
|
|
"HL7 FHIR resource validation failed", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
// Profile-specific validation
|
|
if "` + data.Profile + `" != "" {
|
|
if err := req.ValidateProfile("` + data.Profile + `"); err != nil {
|
|
h.logger.Error("Profile validation failed", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule",
|
|
"Profile 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.sendHl7FhirErrorResponse(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.Hl7FhirResponse
|
|
if err := h.service.CreateResource(ctx, "` + data.PostEndpoint + `", req, &fhirResponse); err != nil {
|
|
h.logger.Error("Failed to create HL7 FHIR resource", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"endpoint": "` + data.PostEndpoint + `",
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
statusCode, errorCode := h.categorizeHl7FhirError(err)
|
|
h.sendHl7FhirErrorResponse(c, statusCode, errorCode,
|
|
"Failed to create ` + data.FhirResource + ` resource", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
// Check for HL7 FHIR OperationOutcome
|
|
if fhirResponse.ResourceType == "OperationOutcome" {
|
|
h.logger.Warn("HL7 FHIR server returned OperationOutcome", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"path": "` + routePath + `",
|
|
"outcome": fhirResponse,
|
|
})
|
|
|
|
h.sendHl7FhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID)
|
|
return
|
|
}
|
|
|
|
duration := time.Since(startTime)
|
|
h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource created successfully", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"duration": duration.String(),
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": fhirResponse.ID,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
// Handle Prefer header for response
|
|
prefer := c.GetHeader("Prefer")
|
|
if strings.Contains(prefer, "return=minimal") {
|
|
c.Header("Location", fmt.Sprintf("%s/%s", "` + data.FhirResource + `", fhirResponse.ID))
|
|
c.Header("ETag", fmt.Sprintf("W/\"%s\"", fhirResponse.Meta.VersionId))
|
|
c.Header("Last-Modified", fhirResponse.Meta.LastUpdated)
|
|
c.Status(http.StatusCreated)
|
|
return
|
|
}
|
|
|
|
h.sendHl7FhirSuccessResponse(c, http.StatusCreated, "` + data.FhirResource + ` resource created successfully",
|
|
fhirResponse, requestID)
|
|
}`
|
|
}
|
|
|
|
func generateOptimizedHl7FhirSearchMethod(data Hl7FhirHandlerData) string {
|
|
var routePath, tagName string
|
|
if data.Category != "" {
|
|
routePath = "hl7fhir/" + data.Category + "/" + data.NameLower
|
|
tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...)
|
|
tagParts = append(tagParts, strings.Title(data.NameLower))
|
|
tagName = strings.Join(tagParts, "-")
|
|
} else {
|
|
routePath = "hl7fhir/" + data.NameLower
|
|
tagName = "HL7FHIR-" + strings.Title(data.NameLower)
|
|
}
|
|
|
|
profileTag := ""
|
|
if data.Profile != "" {
|
|
profileTag = fmt.Sprintf(" (%s Profile)", data.Profile)
|
|
}
|
|
|
|
return `
|
|
|
|
// Search` + data.Name + ` searches for HL7 FHIR ` + data.FhirResource + ` resources with parameters
|
|
// @Summary Search for HL7 FHIR ` + data.FhirResource + ` resources
|
|
// @Description Search for ` + data.FhirResource + ` resources compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + ` using search parameters
|
|
// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + `
|
|
// @Tags ` + tagName + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)"
|
|
// @Param _count query integer false "Number of results to return (default: 10, max: 50)"
|
|
// @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"
|
|
// @Param _sort query string false "Sort parameters (field1,-field2)"
|
|
// @Param _elements query string false "Elements to return (comma-separated)"
|
|
// @Param _summary query string false "Summary mode (true, text, data, count, false)"
|
|
// @Param _format query string false "Response format override (json, xml)"
|
|
// @Success 200 {object} models.Hl7FhirBundleResponse "` + data.FhirResource + ` resources search results"
|
|
// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - invalid search parameters"
|
|
// @Failure 500 {object} models.Hl7FhirOperationOutcome "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 HL7 FHIR ` + data.FhirResource + ` resources", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"timestamp": startTime,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"query_params": c.Request.URL.Query(),
|
|
"category_path": "` + data.Category + `",
|
|
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
|
|
"accept": c.GetHeader("Accept"),
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Parse and validate search parameters
|
|
searchParams := make(map[string]string)
|
|
for key, values := range c.Request.URL.Query() {
|
|
if len(values) > 0 {
|
|
searchParams[key] = values[0]
|
|
}
|
|
}
|
|
|
|
// Validate pagination parameters
|
|
count := 10 // default
|
|
if countStr := searchParams["_count"]; countStr != "" {
|
|
if parsedCount, err := strconv.Atoi(countStr); err == nil {
|
|
if parsedCount > 0 && parsedCount <= 50 {
|
|
count = parsedCount
|
|
}
|
|
}
|
|
}
|
|
|
|
page := 1 // default
|
|
if pageStr := searchParams["_page"]; pageStr != "" {
|
|
if parsedPage, err := strconv.Atoi(pageStr); err == nil && parsedPage > 0 {
|
|
page = parsedPage
|
|
}
|
|
}
|
|
|
|
// Add pagination info to search params
|
|
searchParams["_count"] = fmt.Sprintf("%d", count)
|
|
searchParams["_page"] = fmt.Sprintf("%d", page)
|
|
|
|
var fhirBundle models.Hl7FhirBundleResponse
|
|
if err := h.service.SearchResources(ctx, "` + data.FhirResource + `", searchParams, &fhirBundle); err != nil {
|
|
h.logger.Error("Failed to search HL7 FHIR resources", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"search_params": searchParams,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
statusCode, errorCode := h.categorizeHl7FhirError(err)
|
|
h.sendHl7FhirErrorResponse(c, statusCode, errorCode,
|
|
"Failed to search ` + data.FhirResource + ` resources", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
duration := time.Since(startTime)
|
|
h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resources search completed", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"duration": duration.String(),
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"total_results": fhirBundle.Total,
|
|
"search_params": searchParams,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
h.sendHl7FhirBundleResponse(c, http.StatusOK, "` + data.FhirResource + ` resources search completed",
|
|
fhirBundle, requestID)
|
|
}`
|
|
}
|
|
|
|
func generateHl7FhirHelperMethods(data Hl7FhirHandlerData) string {
|
|
return `
|
|
|
|
// Helper methods for ` + data.Name + `Handler with HL7 FHIR support
|
|
func (h *` + data.Name + `Handler) sendHl7FhirSuccessResponse(c *gin.Context, statusCode int, message string, data interface{}, requestID string) {
|
|
response := models.` + data.Name + `Response{
|
|
Hl7FhirResource: models.Hl7FhirResource{
|
|
ResourceType: "` + data.FhirResource + `",
|
|
Meta: models.Hl7FhirMeta{
|
|
LastUpdated: time.Now().Format(time.RFC3339),
|
|
VersionId: "1",
|
|
FhirVersion: "` + data.FhirVersion + `",
|
|
Profile: h.getProfileUrl("` + data.Profile + `"),
|
|
},
|
|
},
|
|
BaseResponse: models.BaseResponse{
|
|
Status: "success",
|
|
Message: message,
|
|
Data: data,
|
|
Metadata: &models.ResponseMetadata{
|
|
Timestamp: time.Now(),
|
|
Version: "HL7 FHIR ` + data.FhirVersion + `",
|
|
RequestID: requestID,
|
|
Path: "` + data.Category + `",
|
|
Depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
|
|
FhirResource: "` + data.FhirResource + `",
|
|
FhirVersion: "` + data.FhirVersion + `",
|
|
FhirProfile: "` + data.Profile + `",
|
|
},
|
|
},
|
|
}
|
|
|
|
// Set appropriate FHIR headers
|
|
c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `")
|
|
c.Header("ETag", fmt.Sprintf("W/\"%s\"", response.Hl7FhirResource.Meta.VersionId))
|
|
c.Header("Last-Modified", response.Hl7FhirResource.Meta.LastUpdated)
|
|
|
|
c.JSON(statusCode, response)
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) sendHl7FhirErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) {
|
|
operationOutcome := models.Hl7FhirOperationOutcome{
|
|
ResourceType: "OperationOutcome",
|
|
ID: requestID,
|
|
Meta: models.Hl7FhirMeta{
|
|
LastUpdated: time.Now().Format(time.RFC3339),
|
|
VersionId: "1",
|
|
FhirVersion: "` + data.FhirVersion + `",
|
|
},
|
|
Issue: []models.Hl7FhirOperationOutcomeIssue{{
|
|
Severity: h.mapHttpStatusToSeverity(statusCode),
|
|
Code: errorCode,
|
|
Details: models.Hl7FhirCodeableConcept{
|
|
Text: message,
|
|
Coding: []models.Hl7FhirCoding{{
|
|
System: "http://terminology.hl7.org/CodeSystem/operation-outcome",
|
|
Code: errorCode,
|
|
Display: message,
|
|
}},
|
|
},
|
|
Diagnostics: details,
|
|
Location: []string{fmt.Sprintf("` + data.FhirResource + `")},
|
|
Expression: []string{fmt.Sprintf("` + data.FhirResource + `")},
|
|
}},
|
|
}
|
|
|
|
c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `")
|
|
c.JSON(statusCode, operationOutcome)
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) sendHl7FhirOperationOutcome(c *gin.Context, statusCode int, outcome models.Hl7FhirResponse, requestID string) {
|
|
c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `")
|
|
c.JSON(statusCode, outcome)
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) sendHl7FhirBundleResponse(c *gin.Context, statusCode int, message string, bundle models.Hl7FhirBundleResponse, requestID string) {
|
|
bundle.Meta = models.Hl7FhirMeta{
|
|
LastUpdated: time.Now().Format(time.RFC3339),
|
|
VersionId: "1",
|
|
FhirVersion: "` + data.FhirVersion + `",
|
|
}
|
|
|
|
// Add self link
|
|
bundle.Link = append(bundle.Link, models.Hl7FhirBundleLink{
|
|
Relation: "self",
|
|
URL: fmt.Sprintf("%s?%s", "` + data.FhirResource + `", h.buildQueryString(bundle.SearchParams)),
|
|
})
|
|
|
|
c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `")
|
|
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()))
|
|
case "datetime":
|
|
messages = append(messages, fmt.Sprintf("Field '%s' must be a valid datetime", e.Field()))
|
|
default:
|
|
messages = append(messages, fmt.Sprintf("Field '%s' is invalid", e.Field()))
|
|
}
|
|
}
|
|
return fmt.Sprintf("HL7 FHIR validation failed: %v", messages)
|
|
}
|
|
return err.Error()
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) categorizeHl7FhirError(err error) (int, string) {
|
|
if err == nil {
|
|
return http.StatusOK, "informational"
|
|
}
|
|
|
|
errStr := err.Error()
|
|
|
|
// HL7 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 strings.Contains(errStr, "duplicate") || strings.Contains(errStr, "conflict") {
|
|
return http.StatusConflict, "duplicate"
|
|
}
|
|
|
|
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) getProfileUrl(profile string) []string {
|
|
profileUrls := map[string]string{
|
|
"USCore": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-` + strings.ToLower(data.FhirResource) + `",
|
|
"AUBase": "http://hl7.org.au/fhir/base/StructureDefinition/au-` + strings.ToLower(data.FhirResource) + `",
|
|
"UKCore": "https://fhir.hl7.org.uk/StructureDefinition/UKCore-` + data.FhirResource + `",
|
|
"CABaseline": "http://hl7.org/fhir/ca/baseline/StructureDefinition/profile-` + strings.ToLower(data.FhirResource) + `",
|
|
"InternationalPatientSummary": "http://hl7.org/fhir/uv/ips/StructureDefinition/` + data.FhirResource + `-uv-ips",
|
|
}
|
|
|
|
if profile != "" {
|
|
if url, exists := profileUrls[profile]; exists {
|
|
return []string{url}
|
|
}
|
|
}
|
|
return []string{}
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) buildQueryString(params map[string]string) string {
|
|
var parts []string
|
|
for key, value := range params {
|
|
parts = append(parts, fmt.Sprintf("%s=%s", key, value))
|
|
}
|
|
return strings.Join(parts, "&")
|
|
}
|
|
|
|
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 generateOptimizedHl7FhirModelFile(data Hl7FhirHandlerData, modelDir string) {
|
|
profileInfo := ""
|
|
if data.Profile != "" {
|
|
profileInfo = fmt.Sprintf("// FHIR Profile: %s\n", data.Profile)
|
|
}
|
|
|
|
modelContent := `package models
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
"regexp"
|
|
)
|
|
|
|
// ` + data.Name + ` HL7 FHIR ` + data.FhirVersion + ` Models with Enhanced Multi-Level Support
|
|
// Generated at: ` + data.Timestamp + `
|
|
// FHIR Resource: ` + data.FhirResource + `
|
|
// FHIR Version: ` + data.FhirVersion + `
|
|
` + profileInfo + `// Category Path: ` + data.Category + `
|
|
// Directory Depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + ` levels
|
|
|
|
// Base HL7 FHIR structures
|
|
type Hl7FhirResource struct {
|
|
ResourceType string ` + "`json:\"resourceType\"`" + `
|
|
ID string ` + "`json:\"id,omitempty\"`" + `
|
|
Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + `
|
|
ImplicitRules string ` + "`json:\"implicitRules,omitempty\"`" + `
|
|
Language string ` + "`json:\"language,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirMeta struct {
|
|
VersionId string ` + "`json:\"versionId,omitempty\"`" + `
|
|
LastUpdated string ` + "`json:\"lastUpdated,omitempty\"`" + `
|
|
Source string ` + "`json:\"source,omitempty\"`" + `
|
|
Profile []string ` + "`json:\"profile,omitempty\"`" + `
|
|
Security []Hl7FhirCoding ` + "`json:\"security,omitempty\"`" + `
|
|
Tag []Hl7FhirCoding ` + "`json:\"tag,omitempty\"`" + `
|
|
FhirVersion string ` + "`json:\"fhirVersion,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirCoding struct {
|
|
System string ` + "`json:\"system,omitempty\"`" + `
|
|
Version string ` + "`json:\"version,omitempty\"`" + `
|
|
Code string ` + "`json:\"code,omitempty\"`" + `
|
|
Display string ` + "`json:\"display,omitempty\"`" + `
|
|
UserSelected *bool ` + "`json:\"userSelected,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirCodeableConcept struct {
|
|
Coding []Hl7FhirCoding ` + "`json:\"coding,omitempty\"`" + `
|
|
Text string ` + "`json:\"text,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirReference struct {
|
|
Reference string ` + "`json:\"reference,omitempty\"`" + `
|
|
Type string ` + "`json:\"type,omitempty\"`" + `
|
|
Identifier Hl7FhirIdentifier ` + "`json:\"identifier,omitempty\"`" + `
|
|
Display string ` + "`json:\"display,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirIdentifier struct {
|
|
Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=usual official temp secondary old\"`" + `
|
|
Type Hl7FhirCodeableConcept ` + "`json:\"type,omitempty\"`" + `
|
|
System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Value string ` + "`json:\"value,omitempty\"`" + `
|
|
Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + `
|
|
Assigner Hl7FhirReference ` + "`json:\"assigner,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirPeriod struct {
|
|
Start string ` + "`json:\"start,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
End string ` + "`json:\"end,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
}
|
|
|
|
// HL7 FHIR OperationOutcome for error handling
|
|
type Hl7FhirOperationOutcome struct {
|
|
ResourceType string ` + "`json:\"resourceType\"`" + `
|
|
ID string ` + "`json:\"id,omitempty\"`" + `
|
|
Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + `
|
|
Issue []Hl7FhirOperationOutcomeIssue ` + "`json:\"issue\"`" + `
|
|
}
|
|
|
|
type Hl7FhirOperationOutcomeIssue struct {
|
|
Severity string ` + "`json:\"severity\" validate:\"required,oneof=fatal error warning information\"`" + `
|
|
Code string ` + "`json:\"code\" validate:\"required\"`" + `
|
|
Details Hl7FhirCodeableConcept ` + "`json:\"details,omitempty\"`" + `
|
|
Diagnostics string ` + "`json:\"diagnostics,omitempty\"`" + `
|
|
Location []string ` + "`json:\"location,omitempty\"`" + `
|
|
Expression []string ` + "`json:\"expression,omitempty\"`" + `
|
|
}
|
|
|
|
// HL7 FHIR Bundle for search results
|
|
type Hl7FhirBundleResponse struct {
|
|
ResourceType string ` + "`json:\"resourceType\"`" + `
|
|
ID string ` + "`json:\"id,omitempty\"`" + `
|
|
Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + `
|
|
Type string ` + "`json:\"type\" validate:\"required,oneof=document message transaction transaction-response batch batch-response history searchset collection\"`" + `
|
|
Timestamp string ` + "`json:\"timestamp,omitempty\"`" + `
|
|
Total int ` + "`json:\"total,omitempty\"`" + `
|
|
Link []Hl7FhirBundleLink ` + "`json:\"link,omitempty\"`" + `
|
|
Entry []Hl7FhirBundleEntry ` + "`json:\"entry,omitempty\"`" + `
|
|
Signature Hl7FhirSignature ` + "`json:\"signature,omitempty\"`" + `
|
|
SearchParams map[string]string ` + "`json:\"-\"`" + ` // For internal use
|
|
}
|
|
|
|
type Hl7FhirBundleLink struct {
|
|
Relation string ` + "`json:\"relation\" validate:\"required\"`" + `
|
|
URL string ` + "`json:\"url\" validate:\"required,url\"`" + `
|
|
}
|
|
|
|
type Hl7FhirBundleEntry struct {
|
|
Link []Hl7FhirBundleLink ` + "`json:\"link,omitempty\"`" + `
|
|
FullURL string ` + "`json:\"fullUrl,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Resource interface{} ` + "`json:\"resource,omitempty\"`" + `
|
|
Search Hl7FhirBundleEntrySearch ` + "`json:\"search,omitempty\"`" + `
|
|
Request Hl7FhirBundleEntryRequest ` + "`json:\"request,omitempty\"`" + `
|
|
Response Hl7FhirBundleEntryResponse` + "`json:\"response,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirBundleEntrySearch struct {
|
|
Mode string ` + "`json:\"mode,omitempty\" validate:\"omitempty,oneof=match include outcome\"`" + `
|
|
Score float64 ` + "`json:\"score,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirBundleEntryRequest struct {
|
|
Method string ` + "`json:\"method\" validate:\"required,oneof=GET HEAD POST PUT DELETE PATCH\"`" + `
|
|
URL string ` + "`json:\"url\" validate:\"required\"`" + `
|
|
IfNoneMatch string ` + "`json:\"ifNoneMatch,omitempty\"`" + `
|
|
IfModifiedSince string ` + "`json:\"ifModifiedSince,omitempty\"`" + `
|
|
IfMatch string ` + "`json:\"ifMatch,omitempty\"`" + `
|
|
IfNoneExist string ` + "`json:\"ifNoneExist,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirBundleEntryResponse struct {
|
|
Status string ` + "`json:\"status\" validate:\"required\"`" + `
|
|
Location string ` + "`json:\"location,omitempty\" validate:\"omitempty,url\"`" + `
|
|
ETag string ` + "`json:\"etag,omitempty\"`" + `
|
|
LastModified string ` + "`json:\"lastModified,omitempty\"`" + `
|
|
Outcome interface{} ` + "`json:\"outcome,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirSignature struct {
|
|
Type []Hl7FhirCoding ` + "`json:\"type\" validate:\"required,min=1\"`" + `
|
|
When string ` + "`json:\"when\" validate:\"required,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
Who Hl7FhirReference` + "`json:\"who\" validate:\"required\"`" + `
|
|
OnBehalfOf Hl7FhirReference` + "`json:\"onBehalfOf,omitempty\"`" + `
|
|
TargetFormat string ` + "`json:\"targetFormat,omitempty\"`" + `
|
|
SigFormat string ` + "`json:\"sigFormat,omitempty\"`" + `
|
|
Data string ` + "`json:\"data,omitempty\"`" + `
|
|
}
|
|
|
|
// Base request/response structures with HL7 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\"`" + `
|
|
FhirVersion string ` + "`json:\"fhir_version,omitempty\"`" + `
|
|
FhirProfile string ` + "`json:\"fhir_profile,omitempty\"`" + `
|
|
}
|
|
|
|
// ` + data.Name + ` Response Structure with HL7 FHIR integration
|
|
type ` + data.Name + `Response struct {
|
|
Hl7FhirResource
|
|
BaseResponse
|
|
}
|
|
|
|
// Generic HL7 FHIR Response
|
|
type Hl7FhirResponse struct {
|
|
ResourceType string ` + "`json:\"resourceType\"`" + `
|
|
ID string ` + "`json:\"id,omitempty\"`" + `
|
|
Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + `
|
|
Content map[string]interface{} ` + "`json:\"-\"`" + ` // For dynamic content
|
|
}`
|
|
|
|
// Add CRUD request structures based on methods
|
|
if data.HasPost {
|
|
modelContent += generateHl7FhirCreateRequestModel(data)
|
|
}
|
|
|
|
if data.HasPut {
|
|
modelContent += generateHl7FhirUpdateRequestModel(data)
|
|
}
|
|
|
|
if data.HasPatch {
|
|
modelContent += generateHl7FhirPatchRequestModel(data)
|
|
}
|
|
|
|
// Add common HL7 FHIR data types and validation helpers
|
|
modelContent += generateHl7FhirCommonDataTypes(data)
|
|
modelContent += generateHl7FhirValidationHelpers(data)
|
|
|
|
writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent)
|
|
}
|
|
|
|
func generateHl7FhirCreateRequestModel(data Hl7FhirHandlerData) string {
|
|
profileValidation := ""
|
|
if data.Profile != "" {
|
|
profileValidation = `
|
|
// Validate profile-specific constraints
|
|
if err := r.ValidateProfile("` + data.Profile + `"); err != nil {
|
|
return err
|
|
}`
|
|
}
|
|
|
|
return `
|
|
|
|
// ` + data.Name + ` CREATE Request Structure with HL7 FHIR ` + data.FhirVersion + ` Validation
|
|
type ` + data.Name + `CreateRequest struct {
|
|
BaseRequest
|
|
ResourceType string ` + "`json:\"resourceType\" binding:\"required\" validate:\"required,eq=` + data.FhirResource + `\"`" + `
|
|
|
|
// Core HL7 FHIR ` + data.FhirResource + ` fields - customize based on specific resource
|
|
// FHIR Version: ` + data.FhirVersion + `
|
|
// Path: ` + data.Category + `
|
|
Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + `
|
|
ImplicitRules string ` + "`json:\"implicitRules,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Language string ` + "`json:\"language,omitempty\" validate:\"omitempty,len=2\"`" + `
|
|
Text Hl7FhirNarrative ` + "`json:\"text,omitempty\"`" + `
|
|
Contained []interface{} ` + "`json:\"contained,omitempty\"`" + `
|
|
Extension []Hl7FhirExtension ` + "`json:\"extension,omitempty\"`" + `
|
|
ModifierExtension []Hl7FhirExtension ` + "`json:\"modifierExtension,omitempty\"`" + `
|
|
|
|
// Resource-specific fields (customize per resource type)
|
|
Identifier []Hl7FhirIdentifier ` + "`json:\"identifier,omitempty\" validate:\"dive\"`" + `
|
|
Active *bool ` + "`json:\"active,omitempty\"`" + `
|
|
Name []Hl7FhirHumanName ` + "`json:\"name,omitempty\" validate:\"dive\"`" + `
|
|
Telecom []Hl7FhirContactPoint ` + "`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 []Hl7FhirAddress ` + "`json:\"address,omitempty\" validate:\"dive\"`" + `
|
|
MaritalStatus Hl7FhirCodeableConcept ` + "`json:\"maritalStatus,omitempty\"`" + `
|
|
Contact []Hl7FhirPatientContact ` + "`json:\"contact,omitempty\"`" + `
|
|
Communication []Hl7FhirPatientCommunication ` + "`json:\"communication,omitempty\"`" + `
|
|
GeneralPractitioner []Hl7FhirReference ` + "`json:\"generalPractitioner,omitempty\"`" + `
|
|
ManagingOrganization Hl7FhirReference ` + "`json:\"managingOrganization,omitempty\"`" + `
|
|
}
|
|
|
|
// Additional HL7 FHIR data types for ` + data.FhirResource + `
|
|
type Hl7FhirNarrative struct {
|
|
Status string ` + "`json:\"status\" validate:\"required,oneof=generated extensions additional empty\"`" + `
|
|
Div string ` + "`json:\"div\" validate:\"required\"`" + `
|
|
}
|
|
|
|
type Hl7FhirExtension struct {
|
|
URL string ` + "`json:\"url\" validate:\"required,url\"`" + `
|
|
Value interface{} ` + "`json:\"value,omitempty\"`" + ` // Can be various types
|
|
}
|
|
|
|
type Hl7FhirHumanName 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 Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirContactPoint 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 Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirAddress 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\" validate:\"omitempty,len=2\"`" + `
|
|
Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirPatientContact struct {
|
|
Relationship []Hl7FhirCodeableConcept ` + "`json:\"relationship,omitempty\"`" + `
|
|
Name Hl7FhirHumanName ` + "`json:\"name,omitempty\"`" + `
|
|
Telecom []Hl7FhirContactPoint ` + "`json:\"telecom,omitempty\"`" + `
|
|
Address Hl7FhirAddress ` + "`json:\"address,omitempty\"`" + `
|
|
Gender string ` + "`json:\"gender,omitempty\" validate:\"omitempty,oneof=male female other unknown\"`" + `
|
|
Organization Hl7FhirReference ` + "`json:\"organization,omitempty\"`" + `
|
|
Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + `
|
|
}
|
|
|
|
type Hl7FhirPatientCommunication struct {
|
|
Language Hl7FhirCodeableConcept ` + "`json:\"language\" validate:\"required\"`" + `
|
|
Preferred *bool ` + "`json:\"preferred,omitempty\"`" + `
|
|
}
|
|
|
|
// ValidateHl7Fhir validates the ` + data.Name + `CreateRequest with HL7 FHIR ` + data.FhirVersion + ` business rules
|
|
func (r *` + data.Name + `CreateRequest) ValidateHl7Fhir() error {
|
|
if r.ResourceType != "` + data.FhirResource + `" {
|
|
return fmt.Errorf("invalid resourceType: expected ` + data.FhirResource + `, got %s", r.ResourceType)
|
|
}
|
|
|
|
// Validate narrative if present
|
|
if r.Text.Status != "" {
|
|
if r.Text.Div == "" {
|
|
return fmt.Errorf("narrative div is required when status is provided")
|
|
}
|
|
if !isValidXhtml(r.Text.Div) {
|
|
return fmt.Errorf("narrative div must be valid XHTML")
|
|
}
|
|
}
|
|
|
|
// Validate identifiers
|
|
for i, identifier := range r.Identifier {
|
|
if identifier.System != "" && identifier.Value == "" {
|
|
return fmt.Errorf("identifier[%d]: value is required when system is provided", i)
|
|
}
|
|
}
|
|
|
|
// Validate extensions
|
|
for i, ext := range r.Extension {
|
|
if ext.URL == "" {
|
|
return fmt.Errorf("extension[%d]: url is required", i)
|
|
}
|
|
}` + profileValidation + `
|
|
|
|
// Path: ` + data.Category + `
|
|
return nil
|
|
}
|
|
|
|
// ValidateProfile validates profile-specific constraints
|
|
func (r *` + data.Name + `CreateRequest) ValidateProfile(profile string) error {
|
|
switch profile {
|
|
case "USCore":
|
|
return r.validateUSCoreProfile()
|
|
case "AUBase":
|
|
return r.validateAUBaseProfile()
|
|
case "UKCore":
|
|
return r.validateUKCoreProfile()
|
|
case "CABaseline":
|
|
return r.validateCABaselineProfile()
|
|
case "InternationalPatientSummary":
|
|
return r.validateIPSProfile()
|
|
default:
|
|
return nil // No specific profile validation
|
|
}
|
|
}
|
|
|
|
func (r *` + data.Name + `CreateRequest) validateUSCoreProfile() error {
|
|
// US Core Patient profile requirements
|
|
if len(r.Identifier) == 0 {
|
|
return fmt.Errorf("US Core Patient requires at least one identifier")
|
|
}
|
|
|
|
if len(r.Name) == 0 {
|
|
return fmt.Errorf("US Core Patient requires at least one name")
|
|
}
|
|
|
|
if r.Gender == "" {
|
|
return fmt.Errorf("US Core Patient requires gender")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *` + data.Name + `CreateRequest) validateAUBaseProfile() error {
|
|
// AU Base Patient profile requirements
|
|
// Add Australian-specific validation rules
|
|
return nil
|
|
}
|
|
|
|
func (r *` + data.Name + `CreateRequest) validateUKCoreProfile() error {
|
|
// UK Core Patient profile requirements
|
|
// Add UK-specific validation rules
|
|
return nil
|
|
}
|
|
|
|
func (r *` + data.Name + `CreateRequest) validateCABaselineProfile() error {
|
|
// CA Baseline Patient profile requirements
|
|
// Add Canadian-specific validation rules
|
|
return nil
|
|
}
|
|
|
|
func (r *` + data.Name + `CreateRequest) validateIPSProfile() error {
|
|
// International Patient Summary profile requirements
|
|
// Add IPS-specific validation rules
|
|
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
|
|
}`
|
|
}
|
|
|
|
func generateHl7FhirValidationHelpers(data Hl7FhirHandlerData) string {
|
|
return `
|
|
|
|
// HL7 FHIR validation helper functions
|
|
func isValidXhtml(xhtml string) bool {
|
|
// Basic XHTML validation - in production, use proper XML parser
|
|
return strings.HasPrefix(xhtml, "<div") && strings.HasSuffix(xhtml, "</div>")
|
|
}
|
|
|
|
func isValidHl7FhirDate(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 isValidHl7FhirDateTime(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 isValidHl7FhirID(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
|
|
}
|
|
|
|
func isValidHl7FhirUri(uri string) bool {
|
|
// Basic URI validation - in production, use proper URI parser
|
|
pattern := "^[a-zA-Z][a-zA-Z0-9+.-]*:"
|
|
matched, _ := regexp.MatchString(pattern, uri)
|
|
return matched
|
|
}
|
|
|
|
func isValidHl7FhirCode(code string) bool {
|
|
// FHIR code: no whitespace, control chars
|
|
pattern := "^[^\\s\\x00-\\x1F\\x7F]+$"
|
|
matched, _ := regexp.MatchString(pattern, code)
|
|
return matched && len(code) > 0
|
|
}
|
|
|
|
func isValidLanguageCode(lang string) bool {
|
|
// BCP 47 language code (simplified validation)
|
|
if len(lang) < 2 || len(lang) > 8 {
|
|
return false
|
|
}
|
|
|
|
pattern := "^[a-z]{2,3}(-[A-Z]{2})?(-[a-z]{2,8})*$"
|
|
matched, _ := regexp.MatchString(pattern, lang)
|
|
return matched
|
|
}
|
|
|
|
// GetHl7FhirPathInfo returns information about the multi-level HL7 FHIR path
|
|
func GetHl7FhirPathInfo() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"path": "` + data.Category + `",
|
|
"depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
|
|
"parts": []string{` + `"` + strings.Join(data.CategoryParts, `", "`) + `"` + `},
|
|
"base_url": "http://hl7.org/fhir/` + strings.ToLower(data.FhirVersion) + `",
|
|
"profile_url": getStandardProfileUrl("` + data.Profile + `", "` + data.FhirResource + `"),
|
|
"specification": "http://hl7.org/fhir/` + strings.ToLower(data.FhirVersion) + `/` + strings.ToLower(data.FhirResource) + `.html",
|
|
}
|
|
}
|
|
|
|
func getStandardProfileUrl(profile, resource string) string {
|
|
profileUrls := map[string]string{
|
|
"USCore": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-" + strings.ToLower(resource),
|
|
"AUBase": "http://hl7.org.au/fhir/base/StructureDefinition/au-" + strings.ToLower(resource),
|
|
"UKCore": "https://fhir.hl7.org.uk/StructureDefinition/UKCore-" + resource,
|
|
"CABaseline": "http://hl7.org/fhir/ca/baseline/StructureDefinition/profile-" + strings.ToLower(resource),
|
|
"InternationalPatientSummary": "http://hl7.org/fhir/uv/ips/StructureDefinition/" + resource + "-uv-ips",
|
|
}
|
|
|
|
if url, exists := profileUrls[profile]; exists {
|
|
return url
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Common terminology system URLs
|
|
const (
|
|
SNOMED_CT_URL = "http://snomed.info/sct"
|
|
LOINC_URL = "http://loinc.org"
|
|
ICD10_URL = "http://hl7.org/fhir/sid/icd-10"
|
|
ICD11_URL = "http://id.who.int/icd/release/11/mms"
|
|
RXNORM_URL = "http://www.nlm.nih.gov/research/umls/rxnorm"
|
|
CPT_URL = "http://www.ama-assn.org/go/cpt"
|
|
UCUM_URL = "http://unitsofmeasure.org"
|
|
HL7_V3_URL = "http://terminology.hl7.org/CodeSystem/v3-"
|
|
HL7_V2_URL = "http://terminology.hl7.org/CodeSystem/v2-"
|
|
)`
|
|
}
|
|
func generateOptimizedHl7FhirUpdateMethod(data Hl7FhirHandlerData) string {
|
|
var routePath, tagName string
|
|
if data.Category != "" {
|
|
routePath = "hl7fhir/" + data.Category + "/" + data.NameLower
|
|
tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...)
|
|
tagParts = append(tagParts, strings.Title(data.NameLower))
|
|
tagName = strings.Join(tagParts, "-")
|
|
} else {
|
|
routePath = "hl7fhir/" + data.NameLower
|
|
tagName = "HL7FHIR-" + strings.Title(data.NameLower)
|
|
}
|
|
|
|
profileTag := ""
|
|
if data.Profile != "" {
|
|
profileTag = fmt.Sprintf(" (%s Profile)", data.Profile)
|
|
}
|
|
|
|
return `
|
|
|
|
// Update` + data.Name + ` updates (replaces) an existing HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Summary Update (replace) an existing HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Description Update an existing ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + `
|
|
// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + `
|
|
// @Tags ` + tagName + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)"
|
|
// @Param Content-Type header string true "Request content type (application/fhir+json, application/json)"
|
|
// @Param If-Match header string false "Version-aware update (ETag)"
|
|
// @Param Prefer header string false "Return preference (return=minimal, return=representation, return=OperationOutcome)"
|
|
// @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.Hl7FhirOperationOutcome "Bad request - validation error"
|
|
// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found"
|
|
// @Failure 409 {object} models.Hl7FhirOperationOutcome "Version conflict"
|
|
// @Failure 412 {object} models.Hl7FhirOperationOutcome "Precondition failed"
|
|
// @Failure 422 {object} models.Hl7FhirOperationOutcome "Unprocessable entity - FHIR validation error"
|
|
// @Failure 500 {object} models.Hl7FhirOperationOutcome "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 HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"timestamp": startTime,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"resource_id": id,
|
|
"category_path": "` + data.Category + `",
|
|
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
|
|
"content_type": c.GetHeader("Content-Type"),
|
|
"if_match": c.GetHeader("If-Match"),
|
|
"prefer": c.GetHeader("Prefer"),
|
|
})
|
|
|
|
if id == "" {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required",
|
|
"Resource ID is required", "", requestID)
|
|
return
|
|
}
|
|
|
|
if !isValidHl7FhirID(id) {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid",
|
|
"Invalid resource ID format", "", 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 HL7 FHIR JSON for update", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure",
|
|
"Invalid FHIR resource structure", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
// Validate resource ID matches URL parameter
|
|
if req.ID != "" && req.ID != id {
|
|
h.logger.Error("Resource ID mismatch", map[string]interface{}{
|
|
"url_id": id,
|
|
"resource_id": req.ID,
|
|
"request_id": requestID,
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid",
|
|
"Resource ID in body must match URL parameter", "", requestID)
|
|
return
|
|
}
|
|
req.ID = id // Ensure ID is set
|
|
|
|
if err := req.ValidateHl7Fhir(); err != nil {
|
|
h.logger.Error("HL7 FHIR update validation failed", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule",
|
|
"HL7 FHIR resource validation failed", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
// Profile-specific validation
|
|
if "` + data.Profile + `" != "" {
|
|
if err := req.ValidateProfile("` + data.Profile + `"); err != nil {
|
|
h.logger.Error("Profile validation failed", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule",
|
|
"Profile validation failed", err.Error(), requestID)
|
|
return
|
|
}
|
|
}
|
|
|
|
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 + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(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()
|
|
|
|
endpoint := fmt.Sprintf("` + strings.Replace(data.PutEndpoint, "{id}", "%s", 1) + `", id)
|
|
var fhirResponse models.Hl7FhirResponse
|
|
|
|
// Handle If-Match header for version-aware updates
|
|
ifMatch := c.GetHeader("If-Match")
|
|
if ifMatch != "" {
|
|
req.Meta.VersionId = parseETag(ifMatch)
|
|
}
|
|
|
|
if err := h.service.UpdateResource(ctx, endpoint, req, &fhirResponse); err != nil {
|
|
h.logger.Error("Failed to update HL7 FHIR resource", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"endpoint": endpoint,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
statusCode, errorCode := h.categorizeHl7FhirError(err)
|
|
h.sendHl7FhirErrorResponse(c, statusCode, errorCode,
|
|
"Failed to update ` + data.FhirResource + ` resource", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
if fhirResponse.ResourceType == "OperationOutcome" {
|
|
h.logger.Warn("HL7 FHIR server returned OperationOutcome for update", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
"outcome": fhirResponse,
|
|
})
|
|
|
|
h.sendHl7FhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID)
|
|
return
|
|
}
|
|
|
|
duration := time.Since(startTime)
|
|
h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource updated successfully", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"duration": duration.String(),
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
// Handle Prefer header for response
|
|
prefer := c.GetHeader("Prefer")
|
|
if strings.Contains(prefer, "return=minimal") {
|
|
c.Header("Location", fmt.Sprintf("%s/%s", "` + data.FhirResource + `", fhirResponse.ID))
|
|
c.Header("ETag", fmt.Sprintf("W/\"%s\"", fhirResponse.Meta.VersionId))
|
|
c.Header("Last-Modified", fhirResponse.Meta.LastUpdated)
|
|
c.Status(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
h.sendHl7FhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource updated successfully",
|
|
fhirResponse, requestID)
|
|
}`
|
|
}
|
|
|
|
func generateOptimizedHl7FhirPatchMethod(data Hl7FhirHandlerData) string {
|
|
var routePath, tagName string
|
|
if data.Category != "" {
|
|
routePath = "hl7fhir/" + data.Category + "/" + data.NameLower
|
|
tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...)
|
|
tagParts = append(tagParts, strings.Title(data.NameLower))
|
|
tagName = strings.Join(tagParts, "-")
|
|
} else {
|
|
routePath = "hl7fhir/" + data.NameLower
|
|
tagName = "HL7FHIR-" + strings.Title(data.NameLower)
|
|
}
|
|
|
|
profileTag := ""
|
|
if data.Profile != "" {
|
|
profileTag = fmt.Sprintf(" (%s Profile)", data.Profile)
|
|
}
|
|
|
|
return `
|
|
|
|
// Patch` + data.Name + ` partially updates an existing HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Summary Patch (partial update) an existing HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Description Partially update an existing ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + ` using JSON Patch
|
|
// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + `
|
|
// @Tags ` + tagName + `
|
|
// @Accept json-patch+json
|
|
// @Produce json
|
|
// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)"
|
|
// @Param Content-Type header string true "Request content type (application/json-patch+json)"
|
|
// @Param If-Match header string false "Version-aware patch (ETag)"
|
|
// @Param Prefer header string false "Return preference (return=minimal, return=representation, return=OperationOutcome)"
|
|
// @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.Hl7FhirOperationOutcome "Bad request - validation error"
|
|
// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found"
|
|
// @Failure 409 {object} models.Hl7FhirOperationOutcome "Version conflict"
|
|
// @Failure 412 {object} models.Hl7FhirOperationOutcome "Precondition failed"
|
|
// @Failure 422 {object} models.Hl7FhirOperationOutcome "Unprocessable entity - FHIR validation error"
|
|
// @Failure 500 {object} models.Hl7FhirOperationOutcome "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 HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"timestamp": startTime,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"resource_id": id,
|
|
"category_path": "` + data.Category + `",
|
|
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
|
|
"content_type": c.GetHeader("Content-Type"),
|
|
"if_match": c.GetHeader("If-Match"),
|
|
"prefer": c.GetHeader("Prefer"),
|
|
})
|
|
|
|
if id == "" {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required",
|
|
"Resource ID is required", "", requestID)
|
|
return
|
|
}
|
|
|
|
if !isValidHl7FhirID(id) {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid",
|
|
"Invalid resource ID format", "", requestID)
|
|
return
|
|
}
|
|
|
|
// Validate Content-Type for patch operations
|
|
contentType := c.GetHeader("Content-Type")
|
|
if !strings.Contains(contentType, "application/json-patch+json") &&
|
|
!strings.Contains(contentType, "application/json") {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "not-supported",
|
|
"Unsupported content type for patch operation", "", 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 HL7 FHIR patch JSON", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure",
|
|
"Invalid FHIR patch structure", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
if err := req.ValidateHl7FhirPatch(); err != nil {
|
|
h.logger.Error("HL7 FHIR patch validation failed", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule",
|
|
"HL7 FHIR patch validation failed", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
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 + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
})
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure",
|
|
"Patch structure validation failed", h.formatValidationError(err), 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.Hl7FhirResponse
|
|
|
|
// Handle If-Match header for version-aware patches
|
|
ifMatch := c.GetHeader("If-Match")
|
|
if ifMatch != "" {
|
|
req.VersionId = parseETag(ifMatch)
|
|
}
|
|
|
|
if err := h.service.PatchResource(ctx, endpoint, req, &fhirResponse); err != nil {
|
|
h.logger.Error("Failed to patch HL7 FHIR resource", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"endpoint": endpoint,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
statusCode, errorCode := h.categorizeHl7FhirError(err)
|
|
h.sendHl7FhirErrorResponse(c, statusCode, errorCode,
|
|
"Failed to patch ` + data.FhirResource + ` resource", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
if fhirResponse.ResourceType == "OperationOutcome" {
|
|
h.logger.Warn("HL7 FHIR server returned OperationOutcome for patch", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
"outcome": fhirResponse,
|
|
})
|
|
|
|
h.sendHl7FhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID)
|
|
return
|
|
}
|
|
|
|
duration := time.Since(startTime)
|
|
h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource patched successfully", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"duration": duration.String(),
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"patch_ops": len(req.Patches),
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
// Handle Prefer header for response
|
|
prefer := c.GetHeader("Prefer")
|
|
if strings.Contains(prefer, "return=minimal") {
|
|
c.Header("Location", fmt.Sprintf("%s/%s", "` + data.FhirResource + `", fhirResponse.ID))
|
|
c.Header("ETag", fmt.Sprintf("W/\"%s\"", fhirResponse.Meta.VersionId))
|
|
c.Header("Last-Modified", fhirResponse.Meta.LastUpdated)
|
|
c.Status(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
h.sendHl7FhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource patched successfully",
|
|
fhirResponse, requestID)
|
|
}`
|
|
}
|
|
|
|
func generateOptimizedHl7FhirDeleteMethod(data Hl7FhirHandlerData) string {
|
|
var routePath, tagName string
|
|
if data.Category != "" {
|
|
routePath = "hl7fhir/" + data.Category + "/" + data.NameLower
|
|
tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...)
|
|
tagParts = append(tagParts, strings.Title(data.NameLower))
|
|
tagName = strings.Join(tagParts, "-")
|
|
} else {
|
|
routePath = "hl7fhir/" + data.NameLower
|
|
tagName = "HL7FHIR-" + strings.Title(data.NameLower)
|
|
}
|
|
|
|
profileTag := ""
|
|
if data.Profile != "" {
|
|
profileTag = fmt.Sprintf(" (%s Profile)", data.Profile)
|
|
}
|
|
|
|
return `
|
|
|
|
// Delete` + data.Name + ` deletes an existing HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Summary Delete an existing HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Description Delete an existing ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + `
|
|
// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + `
|
|
// @Tags ` + tagName + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)"
|
|
// @Param If-Match header string false "Version-aware delete (ETag)"
|
|
// @Param id path string true "` + data.FhirResource + ` resource ID"
|
|
// @Success 204 "` + data.FhirResource + ` resource deleted successfully"
|
|
// @Success 200 {object} models.Hl7FhirOperationOutcome "Delete operation outcome"
|
|
// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - invalid ID"
|
|
// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found"
|
|
// @Failure 409 {object} models.Hl7FhirOperationOutcome "Version conflict"
|
|
// @Failure 412 {object} models.Hl7FhirOperationOutcome "Precondition failed"
|
|
// @Failure 500 {object} models.Hl7FhirOperationOutcome "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 HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"timestamp": startTime,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"resource_id": id,
|
|
"category_path": "` + data.Category + `",
|
|
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
|
|
"if_match": c.GetHeader("If-Match"),
|
|
})
|
|
|
|
if id == "" {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required",
|
|
"Resource ID is required", "", requestID)
|
|
return
|
|
}
|
|
|
|
if !isValidHl7FhirID(id) {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid",
|
|
"Invalid resource ID format", "", 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)
|
|
|
|
// Handle If-Match header for version-aware deletes
|
|
ifMatch := c.GetHeader("If-Match")
|
|
versionId := ""
|
|
if ifMatch != "" {
|
|
versionId = parseETag(ifMatch)
|
|
}
|
|
|
|
deleteResult, err := h.service.DeleteResource(ctx, endpoint, versionId)
|
|
if err != nil {
|
|
h.logger.Error("Failed to delete HL7 FHIR resource", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"endpoint": endpoint,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
statusCode, errorCode := h.categorizeHl7FhirError(err)
|
|
h.sendHl7FhirErrorResponse(c, statusCode, errorCode,
|
|
"Failed to delete ` + data.FhirResource + ` resource", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
duration := time.Since(startTime)
|
|
h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource deleted successfully", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"duration": duration.String(),
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"delete_result": deleteResult,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
// FHIR delete can return either 204 (No Content) or 200 with OperationOutcome
|
|
if deleteResult.OperationOutcome != nil {
|
|
// Server returned an OperationOutcome (successful delete with info)
|
|
c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `")
|
|
c.JSON(http.StatusOK, deleteResult.OperationOutcome)
|
|
return
|
|
}
|
|
|
|
// Standard successful delete - no content
|
|
c.Status(http.StatusNoContent)
|
|
}`
|
|
}
|
|
|
|
func generateOptimizedHl7FhirGetMethod(data Hl7FhirHandlerData) string {
|
|
var routePath, tagName string
|
|
if data.Category != "" {
|
|
routePath = "hl7fhir/" + data.Category + "/" + data.NameLower
|
|
tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...)
|
|
tagParts = append(tagParts, strings.Title(data.NameLower))
|
|
tagName = strings.Join(tagParts, "-")
|
|
} else {
|
|
routePath = "hl7fhir/" + data.NameLower
|
|
tagName = "HL7FHIR-" + strings.Title(data.NameLower)
|
|
}
|
|
|
|
profileTag := ""
|
|
if data.Profile != "" {
|
|
profileTag = fmt.Sprintf(" (%s Profile)", data.Profile)
|
|
}
|
|
|
|
return `
|
|
|
|
// Get` + data.Name + ` retrieves a specific HL7 FHIR ` + data.FhirResource + ` resource by ID
|
|
// @Summary Get a specific HL7 FHIR ` + data.FhirResource + ` resource by ID
|
|
// @Description Retrieve a specific ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + `
|
|
// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + `
|
|
// @Tags ` + tagName + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)"
|
|
// @Param If-None-Match header string false "Conditional read (ETag)"
|
|
// @Param If-Modified-Since header string false "Conditional read (last modified)"
|
|
// @Param _summary query string false "Summary mode (true, text, data, count, false)"
|
|
// @Param _elements query string false "Elements to return (comma-separated)"
|
|
// @Param _format query string false "Response format override (json, xml)"
|
|
// @Param id path string true "` + data.FhirResource + ` resource ID"
|
|
// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource retrieved successfully"
|
|
// @Success 304 "Not modified (conditional read)"
|
|
// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - invalid ID"
|
|
// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found"
|
|
// @Failure 410 {object} models.Hl7FhirOperationOutcome "Resource deleted"
|
|
// @Failure 500 {object} models.Hl7FhirOperationOutcome "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 HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"timestamp": startTime,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"resource_id": id,
|
|
"category_path": "` + data.Category + `",
|
|
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
|
|
"accept": c.GetHeader("Accept"),
|
|
"if_none_match": c.GetHeader("If-None-Match"),
|
|
"if_modified_since": c.GetHeader("If-Modified-Since"),
|
|
"summary": c.Query("_summary"),
|
|
"elements": c.Query("_elements"),
|
|
"format": c.Query("_format"),
|
|
})
|
|
|
|
if id == "" {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required",
|
|
"Resource ID is required", "", requestID)
|
|
return
|
|
}
|
|
|
|
if !isValidHl7FhirID(id) {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid",
|
|
"Invalid resource ID format", "", 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)
|
|
|
|
// Build query parameters for read options
|
|
queryParams := make(map[string]string)
|
|
if summary := c.Query("_summary"); summary != "" {
|
|
if summary == "true" || summary == "text" || summary == "data" || summary == "count" || summary == "false" {
|
|
queryParams["_summary"] = summary
|
|
}
|
|
}
|
|
|
|
if elements := c.Query("_elements"); elements != "" {
|
|
queryParams["_elements"] = elements
|
|
}
|
|
|
|
if format := c.Query("_format"); format != "" {
|
|
queryParams["_format"] = format
|
|
}
|
|
|
|
// Handle conditional read headers
|
|
ifNoneMatch := c.GetHeader("If-None-Match")
|
|
ifModifiedSince := c.GetHeader("If-Modified-Since")
|
|
|
|
var fhirResponse models.Hl7FhirResponse
|
|
readResult, err := h.service.GetResource(ctx, endpoint, queryParams, ifNoneMatch, ifModifiedSince, &fhirResponse)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get HL7 FHIR resource", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"endpoint": endpoint,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
statusCode, errorCode := h.categorizeHl7FhirError(err)
|
|
h.sendHl7FhirErrorResponse(c, statusCode, errorCode,
|
|
"Failed to get ` + data.FhirResource + ` resource", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
// Handle conditional read responses
|
|
if readResult.NotModified {
|
|
h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource not modified", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
})
|
|
c.Status(http.StatusNotModified)
|
|
return
|
|
}
|
|
|
|
if fhirResponse.ResourceType == "OperationOutcome" {
|
|
h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource not found", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"resource_id": id,
|
|
"path": "` + routePath + `",
|
|
"outcome": fhirResponse,
|
|
})
|
|
|
|
h.sendHl7FhirOperationOutcome(c, http.StatusNotFound, fhirResponse, requestID)
|
|
return
|
|
}
|
|
|
|
duration := time.Since(startTime)
|
|
h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource retrieved successfully", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"duration": duration.String(),
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"version_id": fhirResponse.Meta.VersionId,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
// Set response headers
|
|
if fhirResponse.Meta.VersionId != "" {
|
|
c.Header("ETag", fmt.Sprintf("W/\"%s\"", fhirResponse.Meta.VersionId))
|
|
}
|
|
|
|
if fhirResponse.Meta.LastUpdated != "" {
|
|
c.Header("Last-Modified", fhirResponse.Meta.LastUpdated)
|
|
}
|
|
|
|
h.sendHl7FhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource retrieved successfully",
|
|
fhirResponse, requestID)
|
|
}`
|
|
}
|
|
|
|
func generateOptimizedHl7FhirHistoryMethod(data Hl7FhirHandlerData) string {
|
|
var routePath, tagName string
|
|
if data.Category != "" {
|
|
routePath = "hl7fhir/" + data.Category + "/" + data.NameLower
|
|
tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...)
|
|
tagParts = append(tagParts, strings.Title(data.NameLower))
|
|
tagName = strings.Join(tagParts, "-")
|
|
} else {
|
|
routePath = "hl7fhir/" + data.NameLower
|
|
tagName = "HL7FHIR-" + strings.Title(data.NameLower)
|
|
}
|
|
|
|
profileTag := ""
|
|
if data.Profile != "" {
|
|
profileTag = fmt.Sprintf(" (%s Profile)", data.Profile)
|
|
}
|
|
|
|
return `
|
|
|
|
// History` + data.Name + ` retrieves the version history for a specific HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Summary Get version history for a specific HL7 FHIR ` + data.FhirResource + ` resource
|
|
// @Description Retrieve the version history for a specific ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + `
|
|
// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + `
|
|
// @Tags ` + tagName + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)"
|
|
// @Param _count query integer false "Number of history entries to return (default: 10, max: 50)"
|
|
// @Param _since query string false "Only return versions since this instant (ISO 8601)"
|
|
// @Param _at query string false "Only return versions that were current at this time (ISO 8601)"
|
|
// @Param _page query integer false "Page number for pagination (default: 1)"
|
|
// @Param _sort query string false "Sort order for history (_lastUpdated, -_lastUpdated)"
|
|
// @Param _format query string false "Response format override (json, xml)"
|
|
// @Param id path string true "` + data.FhirResource + ` resource ID"
|
|
// @Success 200 {object} models.Hl7FhirBundleResponse "` + data.FhirResource + ` resource history retrieved successfully"
|
|
// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - invalid parameters"
|
|
// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found"
|
|
// @Failure 500 {object} models.Hl7FhirOperationOutcome "Internal server error"
|
|
// @Router /api/v1/` + routePath + `/{id}/_history [get]
|
|
func (h *` + data.Name + `Handler) History` + data.Name + `(c *gin.Context) {
|
|
requestID := uuid.New().String()
|
|
startTime := time.Now()
|
|
id := c.Param("id")
|
|
|
|
h.logger.Info("Getting HL7 FHIR ` + data.FhirResource + ` resource history", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"timestamp": startTime,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"fhir_profile": "` + data.Profile + `",
|
|
"resource_id": id,
|
|
"category_path": "` + data.Category + `",
|
|
"directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `,
|
|
"query_params": c.Request.URL.Query(),
|
|
"accept": c.GetHeader("Accept"),
|
|
})
|
|
|
|
if id == "" {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required",
|
|
"Resource ID is required", "", requestID)
|
|
return
|
|
}
|
|
|
|
if !isValidHl7FhirID(id) {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid",
|
|
"Invalid resource ID format", "", requestID)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Parse and validate history parameters
|
|
historyParams := make(map[string]string)
|
|
|
|
// Count parameter (default: 10, max: 50)
|
|
count := 10
|
|
if countStr := c.Query("_count"); countStr != "" {
|
|
if parsedCount, err := strconv.Atoi(countStr); err == nil {
|
|
if parsedCount > 0 && parsedCount <= 50 {
|
|
count = parsedCount
|
|
}
|
|
}
|
|
}
|
|
historyParams["_count"] = fmt.Sprintf("%d", count)
|
|
|
|
// Page parameter
|
|
page := 1
|
|
if pageStr := c.Query("_page"); pageStr != "" {
|
|
if parsedPage, err := strconv.Atoi(pageStr); err == nil && parsedPage > 0 {
|
|
page = parsedPage
|
|
}
|
|
}
|
|
historyParams["_page"] = fmt.Sprintf("%d", page)
|
|
|
|
// Since parameter (ISO 8601 instant)
|
|
if since := c.Query("_since"); since != "" {
|
|
if isValidHl7FhirDateTime(since) {
|
|
historyParams["_since"] = since
|
|
} else {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid",
|
|
"Invalid _since parameter format (must be ISO 8601)", "", requestID)
|
|
return
|
|
}
|
|
}
|
|
|
|
// At parameter (ISO 8601 instant)
|
|
if at := c.Query("_at"); at != "" {
|
|
if isValidHl7FhirDateTime(at) {
|
|
historyParams["_at"] = at
|
|
} else {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid",
|
|
"Invalid _at parameter format (must be ISO 8601)", "", requestID)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Sort parameter
|
|
if sort := c.Query("_sort"); sort != "" {
|
|
if sort == "_lastUpdated" || sort == "-_lastUpdated" {
|
|
historyParams["_sort"] = sort
|
|
} else {
|
|
h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "not-supported",
|
|
"Only _lastUpdated and -_lastUpdated sort options are supported", "", requestID)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Format parameter
|
|
if format := c.Query("_format"); format != "" {
|
|
historyParams["_format"] = format
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("` + strings.Replace(data.HistoryEndpoint, "{id}", "%s", 1) + `", id)
|
|
var fhirBundle models.Hl7FhirBundleResponse
|
|
|
|
if err := h.service.GetHistory(ctx, endpoint, historyParams, &fhirBundle); err != nil {
|
|
h.logger.Error("Failed to get HL7 FHIR resource history", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"endpoint": endpoint,
|
|
"history_params": historyParams,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
statusCode, errorCode := h.categorizeHl7FhirError(err)
|
|
h.sendHl7FhirErrorResponse(c, statusCode, errorCode,
|
|
"Failed to get ` + data.FhirResource + ` resource history", err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
// Ensure bundle type is history
|
|
fhirBundle.Type = "history"
|
|
fhirBundle.ID = requestID
|
|
fhirBundle.Timestamp = startTime.Format(time.RFC3339)
|
|
|
|
duration := time.Since(startTime)
|
|
h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource history retrieved successfully", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"duration": duration.String(),
|
|
"fhir_resource": "` + data.FhirResource + `",
|
|
"fhir_version": "` + data.FhirVersion + `",
|
|
"resource_id": id,
|
|
"total_versions": fhirBundle.Total,
|
|
"returned_count": len(fhirBundle.Entry),
|
|
"history_params": historyParams,
|
|
"path": "` + routePath + `",
|
|
})
|
|
|
|
h.sendHl7FhirBundleResponse(c, http.StatusOK, "` + data.FhirResource + ` resource history retrieved successfully",
|
|
fhirBundle, requestID)
|
|
}`
|
|
}
|
|
|
|
func generateHl7FhirUpdateRequestModel(data Hl7FhirHandlerData) string {
|
|
return `
|
|
|
|
// ` + data.Name + ` UPDATE Request Structure with HL7 FHIR ` + data.FhirVersion + ` Validation
|
|
type ` + data.Name + `UpdateRequest struct {
|
|
BaseRequest
|
|
// Resource fields (same as create request but ID is required)
|
|
ResourceType string ` + "`json:\"resourceType\" binding:\"required\" validate:\"required,eq=` + data.FhirResource + `\"`" + `
|
|
ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + `
|
|
|
|
// Include all the same fields as CreateRequest
|
|
Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + `
|
|
ImplicitRules string ` + "`json:\"implicitRules,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Language string ` + "`json:\"language,omitempty\" validate:\"omitempty,len=2\"`" + `
|
|
Text Hl7FhirNarrative ` + "`json:\"text,omitempty\"`" + `
|
|
Contained []interface{} ` + "`json:\"contained,omitempty\"`" + `
|
|
Extension []Hl7FhirExtension ` + "`json:\"extension,omitempty\"`" + `
|
|
ModifierExtension []Hl7FhirExtension ` + "`json:\"modifierExtension,omitempty\"`" + `
|
|
|
|
// Resource-specific fields
|
|
Identifier []Hl7FhirIdentifier ` + "`json:\"identifier,omitempty\" validate:\"dive\"`" + `
|
|
Active *bool ` + "`json:\"active,omitempty\"`" + `
|
|
Name []Hl7FhirHumanName ` + "`json:\"name,omitempty\" validate:\"dive\"`" + `
|
|
Telecom []Hl7FhirContactPoint ` + "`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 []Hl7FhirAddress ` + "`json:\"address,omitempty\" validate:\"dive\"`" + `
|
|
MaritalStatus Hl7FhirCodeableConcept ` + "`json:\"maritalStatus,omitempty\"`" + `
|
|
Contact []Hl7FhirPatientContact ` + "`json:\"contact,omitempty\"`" + `
|
|
Communication []Hl7FhirPatientCommunication ` + "`json:\"communication,omitempty\"`" + `
|
|
GeneralPractitioner []Hl7FhirReference ` + "`json:\"generalPractitioner,omitempty\"`" + `
|
|
ManagingOrganization Hl7FhirReference ` + "`json:\"managingOrganization,omitempty\"`" + `
|
|
}
|
|
|
|
// ValidateHl7Fhir validates the ` + data.Name + `UpdateRequest
|
|
func (r *` + data.Name + `UpdateRequest) ValidateHl7Fhir() 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")
|
|
}
|
|
|
|
if !isValidHl7FhirID(r.ID) {
|
|
return fmt.Errorf("invalid resource ID format")
|
|
}
|
|
|
|
// Same validation as create request
|
|
return nil
|
|
}
|
|
|
|
// ValidateProfile validates profile-specific constraints for update
|
|
func (r *` + data.Name + `UpdateRequest) ValidateProfile(profile string) error {
|
|
// Same profile validation as create request
|
|
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
|
|
}`
|
|
}
|
|
|
|
func generateHl7FhirPatchRequestModel(data Hl7FhirHandlerData) string {
|
|
return `
|
|
|
|
// ` + data.Name + ` PATCH Request Structure with HL7 FHIR ` + data.FhirVersion + ` JSON Patch
|
|
type ` + data.Name + `PatchRequest struct {
|
|
BaseRequest
|
|
ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + `
|
|
VersionId string ` + "`json:\"versionId,omitempty\"`" + `
|
|
Patches []Hl7FhirJsonPatch ` + "`json:\"patches\" binding:\"required\" validate:\"required,dive\"`" + `
|
|
}
|
|
|
|
type Hl7FhirJsonPatch 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\"`" + `
|
|
}
|
|
|
|
// ValidateHl7FhirPatch validates the ` + data.Name + `PatchRequest
|
|
func (r *` + data.Name + `PatchRequest) ValidateHl7FhirPatch() error {
|
|
if r.ID == "" {
|
|
return fmt.Errorf("resource ID is required for patch operation")
|
|
}
|
|
|
|
if !isValidHl7FhirID(r.ID) {
|
|
return fmt.Errorf("invalid resource ID format")
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// Validate path format (JSON Pointer)
|
|
if !strings.HasPrefix(patch.Path, "/") {
|
|
return fmt.Errorf("patch[%d]: path must be a valid JSON Pointer", 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 !strings.HasPrefix(patch.From, "/") {
|
|
return fmt.Errorf("patch[%d]: from must be a valid JSON Pointer", i)
|
|
}
|
|
}
|
|
|
|
if patch.Op != "remove" && patch.Op != "test" && patch.Value == nil {
|
|
return fmt.Errorf("patch[%d]: value is required for %s operation", i, patch.Op)
|
|
}
|
|
}
|
|
|
|
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
|
|
}`
|
|
}
|
|
|
|
func generateHl7FhirCommonDataTypes(data Hl7FhirHandlerData) string {
|
|
return `
|
|
|
|
// ============================================================================
|
|
// Common HL7 FHIR Data Types for ` + data.FhirVersion + `
|
|
// These are reusable data types used across multiple FHIR resources
|
|
// Generated for: ` + data.FhirResource + ` | Path: ` + data.Category + `
|
|
// ============================================================================
|
|
|
|
// Narrative - Human-readable summary of the resource
|
|
type Hl7FhirNarrative struct {
|
|
Status string ` + "`json:\"status\" validate:\"required,oneof=generated extensions additional empty\"`" + `
|
|
Div string ` + "`json:\"div\" validate:\"required\"`" + ` // Limited XHTML content
|
|
}
|
|
|
|
// Extension - Additional content defined by implementations
|
|
type Hl7FhirExtension struct {
|
|
URL string ` + "`json:\"url\" validate:\"required,url\"`" + `
|
|
ValueBase64Binary *string ` + "`json:\"valueBase64Binary,omitempty\"`" + `
|
|
ValueBoolean *bool ` + "`json:\"valueBoolean,omitempty\"`" + `
|
|
ValueCanonical *string ` + "`json:\"valueCanonical,omitempty\" validate:\"omitempty,url\"`" + `
|
|
ValueCode *string ` + "`json:\"valueCode,omitempty\"`" + `
|
|
ValueDate *string ` + "`json:\"valueDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + `
|
|
ValueDateTime *string ` + "`json:\"valueDateTime,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
ValueDecimal *float64 ` + "`json:\"valueDecimal,omitempty\"`" + `
|
|
ValueId *string ` + "`json:\"valueId,omitempty\"`" + `
|
|
ValueInstant *string ` + "`json:\"valueInstant,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
ValueInteger *int ` + "`json:\"valueInteger,omitempty\"`" + `
|
|
ValueMarkdown *string ` + "`json:\"valueMarkdown,omitempty\"`" + `
|
|
ValueOid *string ` + "`json:\"valueOid,omitempty\"`" + `
|
|
ValuePositiveInt *int ` + "`json:\"valuePositiveInt,omitempty\" validate:\"omitempty,min=1\"`" + `
|
|
ValueString *string ` + "`json:\"valueString,omitempty\"`" + `
|
|
ValueTime *string ` + "`json:\"valueTime,omitempty\" validate:\"omitempty,datetime=15:04:05\"`" + `
|
|
ValueUnsignedInt *int ` + "`json:\"valueUnsignedInt,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
ValueUri *string ` + "`json:\"valueUri,omitempty\" validate:\"omitempty,url\"`" + `
|
|
ValueUrl *string ` + "`json:\"valueUrl,omitempty\" validate:\"omitempty,url\"`" + `
|
|
ValueUuid *string ` + "`json:\"valueUuid,omitempty\" validate:\"omitempty,uuid\"`" + `
|
|
|
|
// Complex data types
|
|
ValueAddress *Hl7FhirAddress ` + "`json:\"valueAddress,omitempty\"`" + `
|
|
ValueAge *Hl7FhirAge ` + "`json:\"valueAge,omitempty\"`" + `
|
|
ValueAnnotation *Hl7FhirAnnotation ` + "`json:\"valueAnnotation,omitempty\"`" + `
|
|
ValueAttachment *Hl7FhirAttachment ` + "`json:\"valueAttachment,omitempty\"`" + `
|
|
ValueCodeableConcept *Hl7FhirCodeableConcept ` + "`json:\"valueCodeableConcept,omitempty\"`" + `
|
|
ValueCoding *Hl7FhirCoding ` + "`json:\"valueCoding,omitempty\"`" + `
|
|
ValueContactPoint *Hl7FhirContactPoint ` + "`json:\"valueContactPoint,omitempty\"`" + `
|
|
ValueCount *Hl7FhirCount ` + "`json:\"valueCount,omitempty\"`" + `
|
|
ValueDistance *Hl7FhirDistance ` + "`json:\"valueDistance,omitempty\"`" + `
|
|
ValueDuration *Hl7FhirDuration ` + "`json:\"valueDuration,omitempty\"`" + `
|
|
ValueHumanName *Hl7FhirHumanName ` + "`json:\"valueHumanName,omitempty\"`" + `
|
|
ValueIdentifier *Hl7FhirIdentifier ` + "`json:\"valueIdentifier,omitempty\"`" + `
|
|
ValueMoney *Hl7FhirMoney ` + "`json:\"valueMoney,omitempty\"`" + `
|
|
ValuePeriod *Hl7FhirPeriod ` + "`json:\"valuePeriod,omitempty\"`" + `
|
|
ValueQuantity *Hl7FhirQuantity ` + "`json:\"valueQuantity,omitempty\"`" + `
|
|
ValueRange *Hl7FhirRange ` + "`json:\"valueRange,omitempty\"`" + `
|
|
ValueRatio *Hl7FhirRatio ` + "`json:\"valueRatio,omitempty\"`" + `
|
|
ValueReference *Hl7FhirReference ` + "`json:\"valueReference,omitempty\"`" + `
|
|
ValueSampledData *Hl7FhirSampledData ` + "`json:\"valueSampledData,omitempty\"`" + `
|
|
ValueSignature *Hl7FhirSignature ` + "`json:\"valueSignature,omitempty\"`" + `
|
|
ValueTiming *Hl7FhirTiming ` + "`json:\"valueTiming,omitempty\"`" + `
|
|
ValueContactDetail *Hl7FhirContactDetail ` + "`json:\"valueContactDetail,omitempty\"`" + `
|
|
ValueContributor *Hl7FhirContributor ` + "`json:\"valueContributor,omitempty\"`" + `
|
|
ValueDataRequirement *Hl7FhirDataRequirement ` + "`json:\"valueDataRequirement,omitempty\"`" + `
|
|
ValueExpression *Hl7FhirExpression ` + "`json:\"valueExpression,omitempty\"`" + `
|
|
ValueParameterDefinition *Hl7FhirParameterDefinition ` + "`json:\"valueParameterDefinition,omitempty\"`" + `
|
|
ValueRelatedArtifact *Hl7FhirRelatedArtifact ` + "`json:\"valueRelatedArtifact,omitempty\"`" + `
|
|
ValueTriggerDefinition *Hl7FhirTriggerDefinition ` + "`json:\"valueTriggerDefinition,omitempty\"`" + `
|
|
ValueUsageContext *Hl7FhirUsageContext ` + "`json:\"valueUsageContext,omitempty\"`" + `
|
|
ValueDosage *Hl7FhirDosage ` + "`json:\"valueDosage,omitempty\"`" + `
|
|
}
|
|
|
|
// Coding - Reference to a code defined by a terminology system
|
|
type Hl7FhirCoding struct {
|
|
System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Version string ` + "`json:\"version,omitempty\"`" + `
|
|
Code string ` + "`json:\"code,omitempty\"`" + `
|
|
Display string ` + "`json:\"display,omitempty\"`" + `
|
|
UserSelected *bool ` + "`json:\"userSelected,omitempty\"`" + `
|
|
}
|
|
|
|
// CodeableConcept - Concept defined by Coding(s) and/or text
|
|
type Hl7FhirCodeableConcept struct {
|
|
Coding []Hl7FhirCoding ` + "`json:\"coding,omitempty\" validate:\"dive\"`" + `
|
|
Text string ` + "`json:\"text,omitempty\"`" + `
|
|
}
|
|
|
|
// Reference - Reference to another resource
|
|
type Hl7FhirReference struct {
|
|
Reference string ` + "`json:\"reference,omitempty\"`" + `
|
|
Type string ` + "`json:\"type,omitempty\"`" + `
|
|
Identifier Hl7FhirIdentifier ` + "`json:\"identifier,omitempty\"`" + `
|
|
Display string ` + "`json:\"display,omitempty\"`" + `
|
|
}
|
|
|
|
// Identifier - Identifier for a resource
|
|
type Hl7FhirIdentifier struct {
|
|
Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=usual official temp secondary old\"`" + `
|
|
Type Hl7FhirCodeableConcept ` + "`json:\"type,omitempty\"`" + `
|
|
System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Value string ` + "`json:\"value,omitempty\"`" + `
|
|
Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + `
|
|
Assigner Hl7FhirReference ` + "`json:\"assigner,omitempty\"`" + `
|
|
}
|
|
|
|
// Period - Time range defined by start and end date/time
|
|
type Hl7FhirPeriod struct {
|
|
Start string ` + "`json:\"start,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
End string ` + "`json:\"end,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
}
|
|
|
|
// Quantity - Measured amount (or range of measured amounts)
|
|
type Hl7FhirQuantity struct {
|
|
Value float64 ` + "`json:\"value,omitempty\"`" + `
|
|
Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= > ad\"`" + `
|
|
Unit string ` + "`json:\"unit,omitempty\"`" + `
|
|
System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Code string ` + "`json:\"code,omitempty\"`" + `
|
|
}
|
|
|
|
// Range - Set of values bounded by low and high
|
|
type Hl7FhirRange struct {
|
|
Low Hl7FhirQuantity ` + "`json:\"low,omitempty\"`" + `
|
|
High Hl7FhirQuantity ` + "`json:\"high,omitempty\"`" + `
|
|
}
|
|
|
|
// Ratio - Relationship between two quantities
|
|
type Hl7FhirRatio struct {
|
|
Numerator Hl7FhirQuantity ` + "`json:\"numerator,omitempty\"`" + `
|
|
Denominator Hl7FhirQuantity ` + "`json:\"denominator,omitempty\"`" + `
|
|
}
|
|
|
|
// SampledData - Data that comes from observations
|
|
type Hl7FhirSampledData struct {
|
|
Origin Hl7FhirQuantity ` + "`json:\"origin\" validate:\"required\"`" + `
|
|
Period float64 ` + "`json:\"period\" validate:\"required,min=0\"`" + `
|
|
Factor float64 ` + "`json:\"factor,omitempty\"`" + `
|
|
LowerLimit float64 ` + "`json:\"lowerLimit,omitempty\"`" + `
|
|
UpperLimit float64 ` + "`json:\"upperLimit,omitempty\"`" + `
|
|
Dimensions int ` + "`json:\"dimensions\" validate:\"required,min=1\"`" + `
|
|
Data string ` + "`json:\"data,omitempty\"`" + `
|
|
}
|
|
|
|
// Attachment - Content in a format defined elsewhere
|
|
type Hl7FhirAttachment struct {
|
|
ContentType string ` + "`json:\"contentType,omitempty\"`" + `
|
|
Language string ` + "`json:\"language,omitempty\" validate:\"omitempty,len=2\"`" + `
|
|
Data string ` + "`json:\"data,omitempty\"`" + `
|
|
Url string ` + "`json:\"url,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Size int ` + "`json:\"size,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
Hash string ` + "`json:\"hash,omitempty\"`" + `
|
|
Title string ` + "`json:\"title,omitempty\"`" + `
|
|
Creation string ` + "`json:\"creation,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
Height int ` + "`json:\"height,omitempty\" validate:\"omitempty,min=1\"`" + `
|
|
Width int ` + "`json:\"width,omitempty\" validate:\"omitempty,min=1\"`" + `
|
|
Frames int ` + "`json:\"frames,omitempty\" validate:\"omitempty,min=1\"`" + `
|
|
Duration float64` + "`json:\"duration,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
Pages int ` + "`json:\"pages,omitempty\" validate:\"omitempty,min=1\"`" + `
|
|
}
|
|
|
|
// ContactPoint - Details for contacting
|
|
type Hl7FhirContactPoint 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 Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + `
|
|
}
|
|
|
|
// HumanName - Name of a human or other living entity
|
|
type Hl7FhirHumanName 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 Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + `
|
|
}
|
|
|
|
// Address - Postal address
|
|
type Hl7FhirAddress 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\" validate:\"omitempty,len=2\"`" + `
|
|
Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + `
|
|
}
|
|
|
|
// Age - Duration with context
|
|
type Hl7FhirAge struct {
|
|
Value float64 ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= >\"`" + `
|
|
Unit string ` + "`json:\"unit,omitempty\"`" + `
|
|
System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Code string ` + "`json:\"code,omitempty\"`" + `
|
|
}
|
|
|
|
// Count - Integer with units
|
|
type Hl7FhirCount struct {
|
|
Value int ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= >\"`" + `
|
|
Unit string ` + "`json:\"unit,omitempty\"`" + `
|
|
System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Code string ` + "`json:\"code,omitempty\"`" + `
|
|
}
|
|
|
|
// Distance - Length with units
|
|
type Hl7FhirDistance struct {
|
|
Value float64 ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= >\"`" + `
|
|
Unit string ` + "`json:\"unit,omitempty\"`" + `
|
|
System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Code string ` + "`json:\"code,omitempty\"`" + `
|
|
}
|
|
|
|
// Duration - Length of time
|
|
type Hl7FhirDuration struct {
|
|
Value float64 ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= >\"`" + `
|
|
Unit string ` + "`json:\"unit,omitempty\"`" + `
|
|
System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Code string ` + "`json:\"code,omitempty\"`" + `
|
|
}
|
|
|
|
// Money - Amount of economic utility
|
|
type Hl7FhirMoney struct {
|
|
Value float64 ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
Currency string ` + "`json:\"currency,omitempty\" validate:\"omitempty,len=3\"`" + `
|
|
}
|
|
|
|
// Annotation - Text node with attribution
|
|
type Hl7FhirAnnotation struct {
|
|
AuthorReference Hl7FhirReference ` + "`json:\"authorReference,omitempty\"`" + `
|
|
AuthorString string ` + "`json:\"authorString,omitempty\"`" + `
|
|
Time string ` + "`json:\"time,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
Text string ` + "`json:\"text\" validate:\"required\"`" + `
|
|
}
|
|
|
|
// Timing - When something happens
|
|
type Hl7FhirTiming struct {
|
|
Event []string ` + "`json:\"event,omitempty\"`" + `
|
|
Repeat Hl7FhirTimingRepeat ` + "`json:\"repeat,omitempty\"`" + `
|
|
Code Hl7FhirCodeableConcept` + "`json:\"code,omitempty\"`" + `
|
|
}
|
|
|
|
// TimingRepeat - When events repeat
|
|
type Hl7FhirTimingRepeat struct {
|
|
BoundsDuration Hl7FhirDuration ` + "`json:\"boundsDuration,omitempty\"`" + `
|
|
BoundsRange Hl7FhirRange ` + "`json:\"boundsRange,omitempty\"`" + `
|
|
BoundsPeriod Hl7FhirPeriod ` + "`json:\"boundsPeriod,omitempty\"`" + `
|
|
Count int ` + "`json:\"count,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
CountMax int ` + "`json:\"countMax,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
Duration float64 ` + "`json:\"duration,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
DurationMax float64 ` + "`json:\"durationMax,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
DurationUnit string ` + "`json:\"durationUnit,omitempty\" validate:\"omitempty,oneof=s min h d wk mo a\"`" + `
|
|
Frequency int ` + "`json:\"frequency,omitempty\" validate:\"omitempty,min=1\"`" + `
|
|
FrequencyMax int ` + "`json:\"frequencyMax,omitempty\" validate:\"omitempty,min=1\"`" + `
|
|
Period float64 ` + "`json:\"period,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
PeriodMax float64 ` + "`json:\"periodMax,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
PeriodUnit string ` + "`json:\"periodUnit,omitempty\" validate:\"omitempty,oneof=s min h d wk mo a\"`" + `
|
|
DayOfWeek []string ` + "`json:\"dayOfWeek,omitempty\"`" + `
|
|
TimeOfDay []string ` + "`json:\"timeOfDay,omitempty\"`" + `
|
|
When []string ` + "`json:\"when,omitempty\"`" + `
|
|
Offset int ` + "`json:\"offset,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
}
|
|
|
|
// ContactDetail - Contact details (See also ContactPoint)
|
|
type Hl7FhirContactDetail struct {
|
|
Name string ` + "`json:\"name,omitempty\"`" + `
|
|
Telecom []Hl7FhirContactPoint ` + "`json:\"telecom,omitempty\" validate:\"dive\"`" + `
|
|
}
|
|
|
|
// Contributor - Contributor information
|
|
type Hl7FhirContributor struct {
|
|
Type string ` + "`json:\"type\" validate:\"required,oneof=author editor reviewer endorser\"`" + `
|
|
Name string ` + "`json:\"name\" validate:\"required\"`" + `
|
|
Contact []Hl7FhirContactDetail ` + "`json:\"contact,omitempty\" validate:\"dive\"`" + `
|
|
}
|
|
|
|
// DataRequirement - Describes a required data item
|
|
type Hl7FhirDataRequirement struct {
|
|
Type string ` + "`json:\"type\" validate:\"required\"`" + `
|
|
Profile []string ` + "`json:\"profile,omitempty\"`" + `
|
|
SubjectCodeableConcept Hl7FhirCodeableConcept ` + "`json:\"subjectCodeableConcept,omitempty\"`" + `
|
|
SubjectReference Hl7FhirReference ` + "`json:\"subjectReference,omitempty\"`" + `
|
|
MustSupport []string ` + "`json:\"mustSupport,omitempty\"`" + `
|
|
CodeFilter []Hl7FhirDataRequirementCodeFilter ` + "`json:\"codeFilter,omitempty\"`" + `
|
|
DateFilter []Hl7FhirDataRequirementDateFilter ` + "`json:\"dateFilter,omitempty\"`" + `
|
|
Limit int ` + "`json:\"limit,omitempty\" validate:\"omitempty,min=1\"`" + `
|
|
Sort []Hl7FhirDataRequirementSort ` + "`json:\"sort,omitempty\"`" + `
|
|
}
|
|
|
|
// DataRequirementCodeFilter - Code filters specify additional constraints
|
|
type Hl7FhirDataRequirementCodeFilter struct {
|
|
Path string ` + "`json:\"path,omitempty\"`" + `
|
|
SearchParam string ` + "`json:\"searchParam,omitempty\"`" + `
|
|
ValueSet string ` + "`json:\"valueSet,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Code []Hl7FhirCoding ` + "`json:\"code,omitempty\" validate:\"dive\"`" + `
|
|
}
|
|
|
|
// DataRequirementDateFilter - Date filters specify additional constraints
|
|
type Hl7FhirDataRequirementDateFilter struct {
|
|
Path string ` + "`json:\"path,omitempty\"`" + `
|
|
SearchParam string ` + "`json:\"searchParam,omitempty\"`" + `
|
|
ValueDateTime string ` + "`json:\"valueDateTime,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
ValuePeriod Hl7FhirPeriod ` + "`json:\"valuePeriod,omitempty\"`" + `
|
|
ValueDuration Hl7FhirDuration ` + "`json:\"valueDuration,omitempty\"`" + `
|
|
}
|
|
|
|
// DataRequirementSort - Specifies the order of the results
|
|
type Hl7FhirDataRequirementSort struct {
|
|
Path string ` + "`json:\"path\" validate:\"required\"`" + `
|
|
Direction string ` + "`json:\"direction\" validate:\"required,oneof=ascending descending\"`" + `
|
|
}
|
|
|
|
// Expression - Expression language
|
|
type Hl7FhirExpression struct {
|
|
Description string ` + "`json:\"description,omitempty\"`" + `
|
|
Name string ` + "`json:\"name,omitempty\"`" + `
|
|
Language string ` + "`json:\"language\" validate:\"required\"`" + `
|
|
Expression string ` + "`json:\"expression,omitempty\"`" + `
|
|
Reference string ` + "`json:\"reference,omitempty\" validate:\"omitempty,url\"`" + `
|
|
}
|
|
|
|
// ParameterDefinition - Definition of a parameter to a module
|
|
type Hl7FhirParameterDefinition struct {
|
|
Name string ` + "`json:\"name,omitempty\"`" + `
|
|
Use string ` + "`json:\"use\" validate:\"required,oneof=in out\"`" + `
|
|
Min int ` + "`json:\"min,omitempty\" validate:\"omitempty,min=0\"`" + `
|
|
Max string ` + "`json:\"max,omitempty\"`" + `
|
|
Documentation string ` + "`json:\"documentation,omitempty\"`" + `
|
|
Type string ` + "`json:\"type\" validate:\"required\"`" + `
|
|
Profile string ` + "`json:\"profile,omitempty\" validate:\"omitempty,url\"`" + `
|
|
}
|
|
|
|
// RelatedArtifact - Related artifacts such as additional documentation
|
|
type Hl7FhirRelatedArtifact struct {
|
|
Type string ` + "`json:\"type\" validate:\"required,oneof=documentation justification citation predecessor successor derived-from depends-on composed-of\"`" + `
|
|
Label string ` + "`json:\"label,omitempty\"`" + `
|
|
Display string ` + "`json:\"display,omitempty\"`" + `
|
|
Citation string ` + "`json:\"citation,omitempty\"`" + `
|
|
Url string ` + "`json:\"url,omitempty\" validate:\"omitempty,url\"`" + `
|
|
Document Hl7FhirAttachment ` + "`json:\"document,omitempty\"`" + `
|
|
Resource string ` + "`json:\"resource,omitempty\" validate:\"omitempty,url\"`" + `
|
|
}
|
|
|
|
// TriggerDefinition - Defines an expected trigger for a module
|
|
type Hl7FhirTriggerDefinition struct {
|
|
Type string ` + "`json:\"type\" validate:\"required,oneof=named-event periodic data-changed data-added data-modified data-removed data-accessed data-access-ended\"`" + `
|
|
Name string ` + "`json:\"name,omitempty\"`" + `
|
|
TimingTiming Hl7FhirTiming ` + "`json:\"timingTiming,omitempty\"`" + `
|
|
TimingReference Hl7FhirReference ` + "`json:\"timingReference,omitempty\"`" + `
|
|
TimingDate string ` + "`json:\"timingDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + `
|
|
TimingDateTime string ` + "`json:\"timingDateTime,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + `
|
|
Data []Hl7FhirDataRequirement ` + "`json:\"data,omitempty\" validate:\"dive\"`" + `
|
|
Condition Hl7FhirExpression ` + "`json:\"condition,omitempty\"`" + `
|
|
}
|
|
|
|
// UsageContext - Describes the context of use for a conformance or knowledge resource
|
|
type Hl7FhirUsageContext struct {
|
|
Code Hl7FhirCoding ` + "`json:\"code\" validate:\"required\"`" + `
|
|
ValueCodeableConcept Hl7FhirCodeableConcept ` + "`json:\"valueCodeableConcept,omitempty\"`" + `
|
|
ValueQuantity Hl7FhirQuantity ` + "`json:\"valueQuantity,omitempty\"`" + `
|
|
ValueRange Hl7FhirRange ` + "`json:\"valueRange,omitempty\"`" + `
|
|
ValueReference Hl7FhirReference ` + "`json:\"valueReference,omitempty\"`" + `
|
|
}
|
|
|
|
// Dosage - How medication is/was taken or should be taken
|
|
type Hl7FhirDosage struct {
|
|
Sequence int ` + "`json:\"sequence,omitempty\" validate:\"omitempty,min=1\"`" + `
|
|
Text string ` + "`json:\"text,omitempty\"`" + `
|
|
AdditionalInstruction []Hl7FhirCodeableConcept ` + "`json:\"additionalInstruction,omitempty\" validate:\"dive\"`" + `
|
|
PatientInstruction string ` + "`json:\"patientInstruction,omitempty\"`" + `
|
|
Timing Hl7FhirTiming ` + "`json:\"timing,omitempty\"`" + `
|
|
AsNeededBoolean *bool ` + "`json:\"asNeededBoolean,omitempty\"`" + `
|
|
AsNeededCodeableConcept Hl7FhirCodeableConcept ` + "`json:\"asNeededCodeableConcept,omitempty\"`" + `
|
|
Site Hl7FhirCodeableConcept ` + "`json:\"site,omitempty\"`" + `
|
|
Route Hl7FhirCodeableConcept ` + "`json:\"route,omitempty\"`" + `
|
|
Method Hl7FhirCodeableConcept ` + "`json:\"method,omitempty\"`" + `
|
|
DoseAndRate []Hl7FhirDosageDoseAndRate ` + "`json:\"doseAndRate,omitempty\" validate:\"dive\"`" + `
|
|
MaxDosePerPeriod []Hl7FhirRatio ` + "`json:\"maxDosePerPeriod,omitempty\" validate:\"dive\"`" + `
|
|
MaxDosePerAdministration Hl7FhirQuantity ` + "`json:\"maxDosePerAdministration,omitempty\"`" + `
|
|
MaxDosePerLifetime Hl7FhirQuantity ` + "`json:\"maxDosePerLifetime,omitempty\"`" + `
|
|
}
|
|
|
|
// DosageDoseAndRate - Amount of medication administered
|
|
type Hl7FhirDosageDoseAndRate struct {
|
|
Type Hl7FhirCodeableConcept ` + "`json:\"type,omitempty\"`" + `
|
|
DoseRange Hl7FhirRange ` + "`json:\"doseRange,omitempty\"`" + `
|
|
DoseQuantity Hl7FhirQuantity ` + "`json:\"doseQuantity,omitempty\"`" + `
|
|
RateRatio Hl7FhirRatio ` + "`json:\"rateRatio,omitempty\"`" + `
|
|
RateRange Hl7FhirRange ` + "`json:\"rateRange,omitempty\"`" + `
|
|
RateQuantity Hl7FhirQuantity ` + "`json:\"rateQuantity,omitempty\"`" + `
|
|
}
|
|
|
|
// ============================================================================
|
|
// End of Common HL7 FHIR Data Types
|
|
// ============================================================================
|
|
`
|
|
}
|
|
|
|
// Continue with additional methods and model generation...
|
|
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 HL7 FHIR file: %s\n", filename)
|
|
}
|