2025-05-17 11:36:26 -04:00

201 lines
4.6 KiB
Go

package soap
import (
"bytes"
"context"
"encoding/xml"
"io"
"net/http"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
)
// Client es la interfaz para un cliente SOAP
type Client interface {
Call(ctx context.Context, operation string, request interface{}, response interface{}) error
}
// ClientOptions contiene opciones para el cliente SOAP
type ClientOptions struct {
Endpoint string
Username string
Password string
Timeout time.Duration
Headers map[string]string
}
// RequestIDKey es la clave para el ID de solicitud en el contexto
type RequestIDKey struct{}
// client implementa la interfaz Client
//type client struct {
// endpoint string
// httpClient *http.Client
// credentials *credentials
// headers map[string]string
//}
// credentials contiene las credenciales para autenticación
type credentials struct {
username string
password string
}
// Añadir este método al final del archivo pkg/soap/soap.go
// En la definición del cliente:
type client struct {
endpoint string
httpClient *http.Client
credentials *credentials
headers map[string]string
}
// Endpoint retorna el endpoint actual del cliente
func (c *client) Endpoint() string {
return c.endpoint
}
// NewClient crea un nuevo cliente SOAP
func NewClient(options ClientOptions) Client {
httpClient := &http.Client{
Timeout: options.Timeout,
}
var creds *credentials
if options.Username != "" && options.Password != "" {
creds = &credentials{
username: options.Username,
password: options.Password,
}
}
return &client{
endpoint: options.Endpoint,
httpClient: httpClient,
credentials: creds,
headers: options.Headers,
}
}
// Envelope es la estructura para el envelope SOAP
type Envelope struct {
XMLName xml.Name `xml:"Envelope"`
XMLNS string `xml:"xmlns,attr"`
Header interface{} `xml:"Header,omitempty"`
Body Body `xml:"Body"`
}
// Body es la estructura para el cuerpo SOAP
type Body struct {
XMLName xml.Name `xml:"Body"`
Content interface{} `xml:",omitempty"`
Fault *Fault `xml:"Fault,omitempty"`
}
// Fault es la estructura para errores SOAP
type Fault struct {
XMLName xml.Name `xml:"Fault"`
FaultCode string `xml:"faultcode,omitempty"`
FaultString string `xml:"faultstring,omitempty"`
Detail string `xml:"detail,omitempty"`
}
// Call realiza una llamada SOAP
func (c *client) Call(ctx context.Context, operation string, request interface{}, response interface{}) error {
// Obtener ID de solicitud del contexto o generar uno nuevo
var requestID string
if id, ok := ctx.Value(RequestIDKey{}).(string); ok {
requestID = id
} else {
requestID = uuid.New().String()
}
// Crear envelope SOAP
envelope := Envelope{
XMLNS: "http://schemas.xmlsoap.org/soap/envelope/",
Body: Body{
Content: request,
},
}
// Serializar envelope a XML
requestBuffer := &bytes.Buffer{}
enc := xml.NewEncoder(requestBuffer)
enc.Indent("", " ")
if err := enc.Encode(envelope); err != nil {
return errors.Wrap(err, "error encoding SOAP request")
}
// Crear solicitud HTTP
httpReq, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
c.endpoint,
requestBuffer,
)
if err != nil {
return errors.Wrap(err, "error creating HTTP request")
}
// Establecer cabeceras HTTP
httpReq.Header.Set("Content-Type", "text/xml; charset=utf-8")
httpReq.Header.Set("SOAPAction", operation)
httpReq.Header.Set("X-Request-ID", requestID)
// Agregar cabeceras adicionales
for key, value := range c.headers {
httpReq.Header.Set(key, value)
}
// Agregar autenticación si es necesario
if c.credentials != nil {
httpReq.SetBasicAuth(c.credentials.username, c.credentials.password)
}
// Realizar solicitud HTTP
httpResp, err := c.httpClient.Do(httpReq)
if err != nil {
return errors.Wrap(err, "error sending HTTP request")
}
defer httpResp.Body.Close()
// Leer respuesta
respBody, err := io.ReadAll(httpResp.Body)
if err != nil {
return errors.Wrap(err, "error reading HTTP response")
}
// Verificar código de estado HTTP
if httpResp.StatusCode != http.StatusOK {
return errors.Errorf("HTTP error: %d - %s, body: %s",
httpResp.StatusCode,
httpResp.Status,
string(respBody),
)
}
// Deserializar respuesta
respEnvelope := &Envelope{
Body: Body{
Content: response,
},
}
if err := xml.Unmarshal(respBody, respEnvelope); err != nil {
return errors.Wrap(err, "error unmarshalling SOAP response")
}
// Verificar si hay un SOAP Fault
if respEnvelope.Body.Fault != nil {
return errors.Errorf("SOAP Fault: %s - %s, detail: %s",
respEnvelope.Body.Fault.FaultCode,
respEnvelope.Body.Fault.FaultString,
respEnvelope.Body.Fault.Detail,
)
}
return nil
}