669 lines
18 KiB
Go
669 lines
18 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"reanimator/internal/domain"
|
|
"reanimator/internal/repository/registry"
|
|
)
|
|
|
|
type RegistryDependencies struct {
|
|
Customers *registry.CustomerRepository
|
|
Projects *registry.ProjectRepository
|
|
Locations *registry.LocationRepository
|
|
Assets *registry.AssetRepository
|
|
Components *registry.ComponentRepository
|
|
Lots *registry.LotRepository
|
|
LotMappings *registry.LotModelMappingRepository
|
|
}
|
|
|
|
type registryHandlers struct {
|
|
deps RegistryDependencies
|
|
}
|
|
|
|
func RegisterRegistryRoutes(mux *http.ServeMux, deps RegistryDependencies) {
|
|
h := registryHandlers{deps: deps}
|
|
|
|
mux.HandleFunc("/customers", h.handleCustomers)
|
|
mux.HandleFunc("/customers/", h.handleCustomerByID)
|
|
|
|
mux.HandleFunc("/projects", h.handleProjects)
|
|
mux.HandleFunc("/projects/", h.handleProjectByID)
|
|
mux.HandleFunc("/locations", h.handleLocations)
|
|
mux.HandleFunc("/locations/", h.handleLocationByID)
|
|
|
|
mux.HandleFunc("/assets", h.handleAssets)
|
|
mux.HandleFunc("/registry/assets/", h.handleAssetByID)
|
|
mux.HandleFunc("/machines/dispatch", h.handleMachineDispatch)
|
|
mux.HandleFunc("/machines/return-to-stock", h.handleMachineReturnToStock)
|
|
|
|
mux.HandleFunc("/components", h.handleComponents)
|
|
mux.HandleFunc("/lots", h.handleLots)
|
|
mux.HandleFunc("/lot-mappings", h.handleLotMappings)
|
|
mux.HandleFunc("/lot-mappings/", h.handleLotMappingByModel)
|
|
}
|
|
|
|
func (h registryHandlers) handleCustomers(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
items, err := h.deps.Customers.List(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "list customers failed")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, items)
|
|
case http.MethodPost:
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
}
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Name) == "" {
|
|
writeError(w, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
|
|
item, err := h.deps.Customers.Create(r.Context(), strings.TrimSpace(req.Name))
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrConflict:
|
|
writeError(w, http.StatusConflict, "customer conflict")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "create customer failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, item)
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (h registryHandlers) handleCustomerByID(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
id, ok := parseID(r.URL.Path, "/customers/")
|
|
if !ok {
|
|
writeError(w, http.StatusNotFound, "customer not found")
|
|
return
|
|
}
|
|
|
|
item, err := h.deps.Customers.Get(r.Context(), id)
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrNotFound:
|
|
writeError(w, http.StatusNotFound, "customer not found")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "get customer failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, item)
|
|
}
|
|
|
|
func (h registryHandlers) handleProjects(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
items, err := h.deps.Projects.List(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "list projects failed")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, items)
|
|
case http.MethodPost:
|
|
var req struct {
|
|
CustomerID string `json:"customer_id"`
|
|
Name string `json:"name"`
|
|
}
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if req.CustomerID == "" {
|
|
writeError(w, http.StatusBadRequest, "customer_id is required")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Name) == "" {
|
|
writeError(w, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
|
|
item, err := h.deps.Projects.Create(r.Context(), req.CustomerID, strings.TrimSpace(req.Name))
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrConflict:
|
|
writeError(w, http.StatusConflict, "project conflict")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "create project failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, item)
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (h registryHandlers) handleProjectByID(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
id, ok := parseID(r.URL.Path, "/projects/")
|
|
if !ok {
|
|
writeError(w, http.StatusNotFound, "project not found")
|
|
return
|
|
}
|
|
|
|
item, err := h.deps.Projects.Get(r.Context(), id)
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrNotFound:
|
|
writeError(w, http.StatusNotFound, "project not found")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "get project failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, item)
|
|
}
|
|
|
|
func (h registryHandlers) handleLocations(w http.ResponseWriter, r *http.Request) {
|
|
if h.deps.Locations == nil {
|
|
writeError(w, http.StatusInternalServerError, "locations unavailable")
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
items, err := h.deps.Locations.List(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "list locations failed")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, items)
|
|
case http.MethodPost:
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
Kind *string `json:"kind"`
|
|
}
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
name := strings.TrimSpace(req.Name)
|
|
if name == "" {
|
|
writeError(w, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
item, err := h.deps.Locations.Create(r.Context(), name, normalizeOptionalString(req.Kind))
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrConflict:
|
|
writeError(w, http.StatusConflict, "location conflict")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "create location failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, item)
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (h registryHandlers) handleLocationByID(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if h.deps.Locations == nil {
|
|
writeError(w, http.StatusInternalServerError, "locations unavailable")
|
|
return
|
|
}
|
|
id, ok := parseID(r.URL.Path, "/locations/")
|
|
if !ok {
|
|
writeError(w, http.StatusNotFound, "location not found")
|
|
return
|
|
}
|
|
item, err := h.deps.Locations.Get(r.Context(), id)
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrNotFound:
|
|
writeError(w, http.StatusNotFound, "location not found")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "get location failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, item)
|
|
}
|
|
|
|
func (h registryHandlers) handleAssets(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
items, err := h.deps.Assets.List(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "list assets failed")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, items)
|
|
case http.MethodPost:
|
|
var req struct {
|
|
ProjectID *string `json:"project_id"`
|
|
CustomerID *string `json:"customer_id"`
|
|
LocationID *string `json:"location_id"`
|
|
Name string `json:"name"`
|
|
Vendor *string `json:"vendor"`
|
|
Model *string `json:"model"`
|
|
VendorSerial string `json:"vendor_serial"`
|
|
AssetTag *string `json:"asset_tag"`
|
|
}
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Name) == "" {
|
|
writeError(w, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.VendorSerial) == "" {
|
|
writeError(w, http.StatusBadRequest, "vendor_serial is required")
|
|
return
|
|
}
|
|
|
|
projectID := ""
|
|
if req.ProjectID != nil {
|
|
projectID = strings.TrimSpace(*req.ProjectID)
|
|
}
|
|
asset := domain.Asset{
|
|
ProjectID: projectID,
|
|
CustomerID: normalizeOptionalString(req.CustomerID),
|
|
LocationID: normalizeOptionalString(req.LocationID),
|
|
Name: strings.TrimSpace(req.Name),
|
|
Vendor: normalizeOptionalString(req.Vendor),
|
|
Model: normalizeOptionalString(req.Model),
|
|
VendorSerial: strings.TrimSpace(req.VendorSerial),
|
|
MachineTag: normalizeOptionalString(req.AssetTag),
|
|
}
|
|
|
|
item, err := h.deps.Assets.Create(r.Context(), asset)
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrConflict:
|
|
writeError(w, http.StatusConflict, "asset conflict")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "create asset failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, item)
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (h registryHandlers) handleAssetByID(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := parseID(r.URL.Path, "/registry/assets/")
|
|
if !ok {
|
|
writeError(w, http.StatusNotFound, "asset not found")
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
item, err := h.deps.Assets.Get(r.Context(), id)
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrNotFound:
|
|
writeError(w, http.StatusNotFound, "asset not found")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "get asset failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, item)
|
|
case http.MethodDelete:
|
|
result, err := h.deps.Assets.DeleteWithDetails(r.Context(), id)
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrNotFound:
|
|
writeError(w, http.StatusNotFound, "asset not found")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "delete asset failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"deleted": true,
|
|
"deleted_parts": result.DeletedParts,
|
|
})
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (h registryHandlers) handleMachineDispatch(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if h.deps.Assets == nil {
|
|
writeError(w, http.StatusInternalServerError, "machines unavailable")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
SerialNumbers []string `json:"serial_numbers"`
|
|
CustomerID string `json:"customer_id"`
|
|
LocationID *string `json:"location_id"`
|
|
}
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
serials := normalizeSerialInput(req.SerialNumbers)
|
|
if len(serials) == 0 {
|
|
writeError(w, http.StatusBadRequest, "serial_numbers is required")
|
|
return
|
|
}
|
|
customerID := strings.TrimSpace(req.CustomerID)
|
|
if customerID == "" {
|
|
writeError(w, http.StatusBadRequest, "customer_id is required")
|
|
return
|
|
}
|
|
|
|
locationID := normalizeOptionalString(req.LocationID)
|
|
result, err := h.deps.Assets.DispatchBySerials(r.Context(), serials, customerID, locationID, time.Now().UTC())
|
|
if err != nil {
|
|
if err == registry.ErrConflict {
|
|
writeError(w, http.StatusConflict, "invalid customer_id or location_id")
|
|
return
|
|
}
|
|
writeError(w, http.StatusInternalServerError, "dispatch failed")
|
|
return
|
|
}
|
|
if len(result.MissingSerials) > 0 {
|
|
writeJSON(w, http.StatusNotFound, map[string]any{
|
|
"error": "machines not found",
|
|
"missing_serials": result.MissingSerials,
|
|
})
|
|
return
|
|
}
|
|
if len(result.AlreadyAssignedSerials) > 0 {
|
|
writeJSON(w, http.StatusConflict, map[string]any{
|
|
"error": "machines already assigned",
|
|
"already_assigned_serials": result.AlreadyAssignedSerials,
|
|
})
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"updated": result.Updated,
|
|
})
|
|
}
|
|
|
|
func (h registryHandlers) handleMachineReturnToStock(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if h.deps.Assets == nil {
|
|
writeError(w, http.StatusInternalServerError, "machines unavailable")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
SerialNumbers []string `json:"serial_numbers"`
|
|
}
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
serials := normalizeSerialInput(req.SerialNumbers)
|
|
if len(serials) == 0 {
|
|
writeError(w, http.StatusBadRequest, "serial_numbers is required")
|
|
return
|
|
}
|
|
|
|
result, err := h.deps.Assets.ReturnToStockBySerials(r.Context(), serials, time.Now().UTC())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "return to stock failed")
|
|
return
|
|
}
|
|
if len(result.MissingSerials) > 0 {
|
|
writeJSON(w, http.StatusNotFound, map[string]any{
|
|
"error": "machines not found",
|
|
"missing_serials": result.MissingSerials,
|
|
})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"updated": result.Updated,
|
|
})
|
|
}
|
|
|
|
func (h registryHandlers) handleComponents(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
items, err := h.deps.Components.List(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "list components failed")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, items)
|
|
case http.MethodPost:
|
|
var req struct {
|
|
LotID *string `json:"lot_id"`
|
|
Vendor *string `json:"vendor"`
|
|
Model *string `json:"model"`
|
|
VendorSerial string `json:"vendor_serial"`
|
|
}
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.VendorSerial) == "" {
|
|
writeError(w, http.StatusBadRequest, "vendor_serial is required")
|
|
return
|
|
}
|
|
|
|
component := domain.Component{
|
|
LotID: req.LotID,
|
|
Vendor: req.Vendor,
|
|
Model: req.Model,
|
|
VendorSerial: strings.TrimSpace(req.VendorSerial),
|
|
}
|
|
|
|
item, err := h.deps.Components.Create(r.Context(), component)
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrConflict:
|
|
writeError(w, http.StatusConflict, "component conflict")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "create component failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, item)
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (h registryHandlers) handleLots(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if h.deps.Lots == nil {
|
|
writeError(w, http.StatusInternalServerError, "lots unavailable")
|
|
return
|
|
}
|
|
items, err := h.deps.Lots.List(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "list lots failed")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, items)
|
|
}
|
|
|
|
func (h registryHandlers) handleLotMappings(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if h.deps.LotMappings == nil {
|
|
writeError(w, http.StatusInternalServerError, "lot mappings unavailable")
|
|
return
|
|
}
|
|
items, err := h.deps.LotMappings.List(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "list lot mappings failed")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, items)
|
|
}
|
|
|
|
func (h registryHandlers) handleLotMappingByModel(w http.ResponseWriter, r *http.Request) {
|
|
if h.deps.Lots == nil || h.deps.LotMappings == nil {
|
|
writeError(w, http.StatusInternalServerError, "lot mapping dependencies unavailable")
|
|
return
|
|
}
|
|
|
|
model, ok := parseModelPathParam(r.URL.Path, "/lot-mappings/")
|
|
if !ok {
|
|
writeError(w, http.StatusBadRequest, "model is required")
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodPut:
|
|
var req struct {
|
|
LotCode string `json:"lot_code"`
|
|
}
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
lotCode := normalizeMappingValue(req.LotCode)
|
|
if lotCode == "" {
|
|
writeError(w, http.StatusBadRequest, "lot_code is required")
|
|
return
|
|
}
|
|
|
|
lot, err := h.deps.Lots.EnsureByCode(r.Context(), lotCode)
|
|
if err != nil {
|
|
if err == registry.ErrConflict {
|
|
writeError(w, http.StatusConflict, "lot conflict")
|
|
return
|
|
}
|
|
writeError(w, http.StatusInternalServerError, "ensure lot failed")
|
|
return
|
|
}
|
|
|
|
item, created, affectedParts, err := h.deps.LotMappings.UpsertByModelWithBackfill(r.Context(), model, lot.ID)
|
|
if err != nil {
|
|
if err == registry.ErrConflict {
|
|
writeError(w, http.StatusConflict, "lot mapping conflict")
|
|
return
|
|
}
|
|
writeError(w, http.StatusInternalServerError, "upsert lot mapping failed")
|
|
return
|
|
}
|
|
|
|
status := http.StatusOK
|
|
if created {
|
|
status = http.StatusCreated
|
|
}
|
|
writeJSON(w, status, map[string]any{
|
|
"mapping": item,
|
|
"affected_parts_count": affectedParts,
|
|
})
|
|
case http.MethodDelete:
|
|
affectedParts, err := h.deps.LotMappings.DeleteByModelWithReset(r.Context(), model)
|
|
if err != nil {
|
|
switch err {
|
|
case registry.ErrNotFound:
|
|
writeError(w, http.StatusNotFound, "lot mapping not found")
|
|
default:
|
|
writeError(w, http.StatusInternalServerError, "delete lot mapping failed")
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"deleted": true,
|
|
"affected_parts_count": affectedParts,
|
|
})
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func normalizeOptionalString(value *string) *string {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
trimmed := strings.TrimSpace(*value)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
return &trimmed
|
|
}
|
|
|
|
func normalizeSerialInput(values []string) []string {
|
|
seen := make(map[string]struct{}, len(values))
|
|
serials := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
trimmed := strings.TrimSpace(value)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
if _, exists := seen[trimmed]; exists {
|
|
continue
|
|
}
|
|
seen[trimmed] = struct{}{}
|
|
serials = append(serials, trimmed)
|
|
}
|
|
return serials
|
|
}
|
|
|
|
func parseModelPathParam(path, prefix string) (string, bool) {
|
|
if !strings.HasPrefix(path, prefix) {
|
|
return "", false
|
|
}
|
|
raw := strings.TrimPrefix(path, prefix)
|
|
if raw == "" || strings.Contains(raw, "/") {
|
|
return "", false
|
|
}
|
|
decoded, err := url.PathUnescape(raw)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
model := normalizeMappingValue(decoded)
|
|
if model == "" {
|
|
return "", false
|
|
}
|
|
return model, true
|
|
}
|
|
|
|
func normalizeMappingValue(value string) string {
|
|
trimmed := strings.TrimSpace(value)
|
|
if len(trimmed) >= 2 {
|
|
if (strings.HasPrefix(trimmed, "\"") && strings.HasSuffix(trimmed, "\"")) ||
|
|
(strings.HasPrefix(trimmed, "'") && strings.HasSuffix(trimmed, "'")) {
|
|
trimmed = strings.TrimSpace(trimmed[1 : len(trimmed)-1])
|
|
}
|
|
}
|
|
return trimmed
|
|
}
|