Documentation
¶
Overview ¶
Package soap provides primitives for SOAP (Simple Object Access Protocol).
Index ¶
- Constants
- func DefaultCheckRetry(ctx context.Context, err error, request *http.Request, response *http.Response) bool
- func UnmarshalHeaderEntry(entry HeaderEntry, dest any) error
- type Body
- type Client
- type ClientOption
- func WithCheckRetry(checkRetry func(context.Context, error, *http.Request, *http.Response) bool) ClientOption
- func WithDebug() ClientOption
- func WithEndpoint(endpoint string) ClientOption
- func WithHTTPClient(client *http.Client) ClientOption
- func WithInterceptor(interceptor func(http.RoundTripper) http.RoundTripper) ClientOption
- func WithMaxRetries(retries int) ClientOption
- func WithTimeout(timeout time.Duration) ClientOption
- func WithXMLDeclaration(include bool) ClientOption
- type DebugTransport
- type Detail
- type Envelope
- type EnvelopeOption
- type Error
- type Fault
- type Header
- type HeaderEntry
- type XSDDateTime
Examples ¶
Constants ¶
const Namespace = "http://schemas.xmlsoap.org/soap/envelope/"
Namespace is the standard SOAP 1.1 envelope namespace
Variables ¶
This section is empty.
Functions ¶
func DefaultCheckRetry ¶
func DefaultCheckRetry(ctx context.Context, err error, request *http.Request, response *http.Response) bool
DefaultCheckRetry implements the default retry logic for HTTP requests.
func UnmarshalHeaderEntry ¶
func UnmarshalHeaderEntry(entry HeaderEntry, dest any) error
UnmarshalHeaderEntry decodes a HeaderEntry back into a typed value — the response-side counterpart of WithHeaderContent. It marshals the entry (preserving the entry's element name, attributes and inner XML) and unmarshals the resulting element into dest.
dest must be a non-nil pointer to a struct whose xml:"..." tag on its XMLName field matches the entry's element name and namespace; otherwise encoding/xml will return an UnmarshalError. Fields on the entry that dest does not declare are silently ignored, matching encoding/xml's usual relaxed behaviour.
Types ¶
type Body ¶
type Body struct {
XMLName xml.Name
// Content as raw XML for maximum flexibility
// This allows both simple payloads and complex nested structures
Content []byte `xml:",innerxml"`
// Additional attributes for extensibility
Attrs []xml.Attr `xml:",any,attr"`
}
Body represents a SOAP body containing the main message payload. As per SOAP 1.1 spec section 4.3, it contains body entries.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client represents a generic SOAP HTTP client that can handle any type of SOAP request. It works with Envelope types and provides a clean abstraction over HTTP transport.
func NewClient ¶
func NewClient(opts ...ClientOption) (*Client, error)
NewClient creates a new SOAP client with the specified options. Returns an error if the configuration is invalid.
type ClientOption ¶
type ClientOption func(*clientConfig)
ClientOption configures a Client using the functional options pattern. Can be used both during client creation and per-call.
func WithCheckRetry ¶
func WithCheckRetry(checkRetry func(context.Context, error, *http.Request, *http.Response) bool) ClientOption
WithCheckRetry sets a custom retry check function. If not provided, uses DefaultCheckRetry for generic HTTP retry logic.
func WithDebug ¶
func WithDebug() ClientOption
WithDebug enables debug output, dumping HTTP requests and responses to stderr. This is a convenience shorthand for:
soap.WithHTTPClient(&http.Client{
Transport: &soap.DebugTransport{Enabled: &alwaysTrue, Next: http.DefaultTransport},
})
For lazy evaluation (e.g. a --debug flag parsed after client construction), use WithHTTPClient with a DebugTransport directly.
func WithEndpoint ¶
func WithEndpoint(endpoint string) ClientOption
WithEndpoint sets the default SOAP endpoint URL. This can be overridden per call if needed in the future.
func WithHTTPClient ¶
func WithHTTPClient(client *http.Client) ClientOption
WithHTTPClient sets the base HTTP client whose transport is used as the innermost layer of the transport chain (interceptors and retry wrap it). If the client has no Transport, http.DefaultTransport is used. The client's Timeout is ignored — use WithTimeout instead.
func WithInterceptor ¶
func WithInterceptor(interceptor func(http.RoundTripper) http.RoundTripper) ClientOption
WithInterceptor adds a request interceptor for the Client.
func WithMaxRetries ¶
func WithMaxRetries(retries int) ClientOption
WithMaxRetries sets the maximum number of retries for a request. Defaults to 3.
func WithTimeout ¶
func WithTimeout(timeout time.Duration) ClientOption
WithTimeout sets the timeout for the SOAP client.
func WithXMLDeclaration ¶
func WithXMLDeclaration(include bool) ClientOption
WithXMLDeclaration controls whether XML declaration is automatically added to requests. Defaults to true. Set to false if your SOAP service doesn't expect or allow XML declarations.
type DebugTransport ¶
type DebugTransport struct {
// Enabled controls whether debug output is produced. Checked at
// request time, so a flag parsed after construction still works.
Enabled *bool
// Next is the underlying transport. If nil, [http.DefaultTransport] is used.
Next http.RoundTripper
}
DebugTransport is an http.RoundTripper that dumps requests and responses to stderr. When Enabled is nil or points to false, requests pass through to Next unchanged.
Standalone CLIs can wire a --debug flag to Enabled:
var debug bool
t := &soap.DebugTransport{Enabled: &debug, Next: http.DefaultTransport}
client, _ := soap.NewClient(soap.WithHTTPClient(&http.Client{Transport: t}))
flag.BoolVar(&debug, "debug", false, "enable debug logging")
func (*DebugTransport) RoundTrip ¶
RoundTrip implements http.RoundTripper.
type Detail ¶
type Detail struct {
// Content as raw XML to accommodate any application-specific fault data
Content []byte `xml:",innerxml"`
// Additional attributes for extensibility
Attrs []xml.Attr `xml:",any,attr"`
}
Detail represents application-specific fault detail information.
type Envelope ¶
type Envelope struct {
XMLName xml.Name
// Optional encoding style as per SOAP 1.1 spec section 4.1.1
EncodingStyle string `xml:"encodingStyle,attr,omitempty"`
// Optional header as per SOAP 1.1 spec section 4.2
Header *Header `xml:"Header,omitempty"`
// Mandatory body as per SOAP 1.1 spec section 4.3
Body Body `xml:"Body"`
// Additional attributes for extensibility as per SOAP 1.1 spec section 4.1
Attrs []xml.Attr `xml:",any,attr"`
}
Envelope represents a SOAP envelope with flexible namespace support. It can handle any namespace prefix and URI, making it compatible with various SOAP implementations. The XMLName field determines the actual element name and namespace used in marshaling/unmarshaling.
Example (Basic) ¶
ExampleEnvelope_basic demonstrates creating a basic SOAP envelope with just a body.
// Create a simple SOAP envelope using the new API
envelope, err := soap.NewEnvelope(soap.WithBody([]byte(`<GetWeather><city>London</city></GetWeather>`)))
if err != nil {
log.Fatal(err)
}
xmlData, err := xml.MarshalIndent(envelope, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(xmlData))
Output: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body><GetWeather><city>London</city></GetWeather></soapenv:Body> </soapenv:Envelope>
Example (Extensibility) ¶
ExampleEnvelope_extensibility demonstrates using custom attributes for extensibility.
envelope, err := soap.NewEnvelope(soap.WithBody([]byte(`<ProcessOrder><orderId>12345</orderId></ProcessOrder>`)))
if err != nil {
log.Fatal(err)
}
// Add custom attributes to the body for extensibility
envelope.Body.Attrs = []xml.Attr{
{Name: xml.Name{Local: "priority"}, Value: "high"},
}
// Add custom attributes to the envelope for extensibility
envelope.Attrs = append(envelope.Attrs, []xml.Attr{
{Name: xml.Name{Local: "version"}, Value: "1.2"},
{Name: xml.Name{Local: "trace", Space: "http://example.com/trace"}, Value: "enabled"},
}...)
xmlData, err := xml.MarshalIndent(envelope, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(xmlData))
Output: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" version="1.2" xmlns:trace="http://example.com/trace" trace:trace="enabled"> <soapenv:Body priority="high"><ProcessOrder><orderId>12345</orderId></ProcessOrder></soapenv:Body> </soapenv:Envelope>
Example (RealWorld) ¶
ExampleEnvelope_realWorld demonstrates creating a SOAP envelope for real-world use.
// Create a SOAP envelope for a weather service request using the new API
envelope, err := soap.NewEnvelope(
soap.WithBody(
[]byte(`<GetTemperature xmlns="http://weather.example.com/"><city>Paris</city></GetTemperature>`),
),
)
if err != nil {
log.Fatal(err)
}
// Marshal to XML (this is what gets sent to SOAP services)
xmlData, err := xml.MarshalIndent(envelope, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(xmlData))
Output: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body><GetTemperature xmlns="http://weather.example.com/"><city>Paris</city></GetTemperature></soapenv:Body> </soapenv:Envelope>
Example (WithEncodingStyle) ¶
ExampleEnvelope_withEncodingStyle demonstrates setting encoding style.
envelope, err := soap.NewEnvelope(soap.WithBody([]byte(`<GetStockPrice><symbol>AAPL</symbol></GetStockPrice>`)))
if err != nil {
log.Fatal(err)
}
// Set encoding style for advanced use cases
envelope.EncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
xmlData, err := xml.MarshalIndent(envelope, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(xmlData))
Output: <soapenv:Envelope encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body><GetStockPrice><symbol>AAPL</symbol></GetStockPrice></soapenv:Body> </soapenv:Envelope>
Example (WithHeader) ¶
ExampleEnvelope_withHeader demonstrates creating a SOAP envelope with headers.
// Create header entries
mustUnderstand := true
authHeader := soap.HeaderEntry{
XMLName: xml.Name{Local: "Authentication", Space: "http://example.com/auth"},
MustUnderstand: &mustUnderstand,
Actor: "http://example.com/gateway",
Content: []byte(`<token>abc123xyz</token><user>john.doe</user>`),
}
transactionHeader := soap.HeaderEntry{
XMLName: xml.Name{Local: "Transaction", Space: "http://example.com/tx"},
Content: []byte(`<id>tx-456</id>`),
}
// Create envelope with headers using the new API
envelope, err := soap.NewEnvelope(soap.WithBody([]byte(`<GetUserProfile><userId>12345</userId></GetUserProfile>`)))
if err != nil {
log.Fatal(err)
}
// Add headers manually for advanced use cases
envelope.Header = &soap.Header{
XMLName: xml.Name{Local: "soapenv:Header"},
Entries: []soap.HeaderEntry{authHeader, transactionHeader},
}
xmlData, err := xml.MarshalIndent(envelope, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(xmlData))
Output: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <Authentication xmlns="http://example.com/auth" mustUnderstand="true" actor="http://example.com/gateway"><token>abc123xyz</token><user>john.doe</user></Authentication> <Transaction xmlns="http://example.com/tx"><id>tx-456</id></Transaction> </soapenv:Header> <soapenv:Body><GetUserProfile><userId>12345</userId></GetUserProfile></soapenv:Body> </soapenv:Envelope>
func NewEnvelope ¶
func NewEnvelope(opts ...EnvelopeOption) (*Envelope, error)
NewEnvelope creates a new SOAP envelope with the specified options.
type EnvelopeOption ¶
type EnvelopeOption func(*envelopeConfig)
EnvelopeOption is a function that configures an Envelope.
func WithHeader ¶
func WithHeader(h *Header) EnvelopeOption
WithHeader sets a pre-constructed SOAP Header on the Envelope. Use this when you need full control over the header (multiple entries, mustUnderstand or actor attributes, etc.). For the common case of a single typed header entry, prefer WithHeaderContent.
func WithHeaderContent ¶
func WithHeaderContent(v any) EnvelopeOption
WithHeaderContent adds a single typed value as a SOAP Header entry. The value is marshalled to XML; its root element's name becomes the HeaderEntry's XMLName and its children become the HeaderEntry's inner content.
Typical usage with generated SOAP types whose XMLName carries the correct namespace:
soap.NewEnvelope(
soap.WithHeaderContent(&MySoapHeader{Field1: "a", Field2: "b"}),
soap.WithBody(&MyRequest{}),
)
Multiple WithHeaderContent (and WithHeader) options may be combined to construct a header with several entries; entries are appended in the order the options are applied.
Limitations: attributes on the value's root element (other than xmlns declarations) are not preserved on the HeaderEntry. If you need custom attributes, mustUnderstand, or actor, construct a HeaderEntry directly and pass it via WithHeader.
If marshalling fails, the error is deferred and returned from NewEnvelope. The first such error wins; subsequent header errors are ignored to keep diagnostics focused on the original failure.
func WithNamespace ¶
func WithNamespace(prefix, namespace string) EnvelopeOption
WithNamespace sets the namespace for the Envelope.
type Error ¶
type Error struct {
// StatusCode is the HTTP status code of the response.
StatusCode int
// ResponseBody is the raw HTTP response body.
ResponseBody []byte
// Envelope is the SOAP envelope that was received, nil if parsing failed.
Envelope *Envelope
// Fault is the SOAP fault, nil if no fault was present.
Fault *Fault
}
Error contains HTTP and SOAP fault information.
type Fault ¶
type Fault struct {
XMLName xml.Name
// FaultCode is mandatory and provides algorithmic fault identification
FaultCode string `xml:"faultcode"`
// FaultString is mandatory and provides human-readable fault description
FaultString string `xml:"faultstring"`
// FaultActor is optional and identifies the fault source
FaultActor string `xml:"faultactor,omitempty"`
// Detail is optional and contains application-specific error information
Detail *Detail `xml:"detail,omitempty"`
}
Fault represents a SOAP fault element as per SOAP 1.1 spec section 4.4.
Example ¶
ExampleFault demonstrates creating and handling SOAP faults.
// Create a fault
fault := soap.Fault{
FaultCode: "Client",
FaultString: "Invalid authentication credentials",
FaultActor: "http://example.com/auth-service",
Detail: &soap.Detail{
Content: []byte(`<error><code>AUTH001</code><message>Token expired</message></error>`),
},
}
faultXMLData, _ := xml.Marshal(fault)
envelope, err := soap.NewEnvelope(soap.WithBody(faultXMLData))
if err != nil {
log.Fatal(err)
}
xmlData, err := xml.MarshalIndent(envelope, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(xmlData))
Output: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body><Fault><faultcode>Client</faultcode><faultstring>Invalid authentication credentials</faultstring><faultactor>http://example.com/auth-service</faultactor><detail><error><code>AUTH001</code><message>Token expired</message></error></detail></Fault></soapenv:Body> </soapenv:Envelope>
type Header ¶
type Header struct {
XMLName xml.Name
// Header entries - flexible content allowing any XML
Entries []HeaderEntry `xml:",any"`
// Additional attributes for extensibility
Attrs []xml.Attr `xml:",any,attr"`
}
Header represents a SOAP header containing header entries. Each header entry can have mustUnderstand and actor attributes as per SOAP 1.1 spec section 4.2.
type HeaderEntry ¶
type HeaderEntry struct {
XMLName xml.Name
// MustUnderstand attribute as per SOAP 1.1 spec section 4.2.3
// Values: true (1) means mandatory, false (0) or nil means optional
MustUnderstand *bool `xml:"mustUnderstand,attr,omitempty"`
// Actor attribute as per SOAP 1.1 spec section 4.2.2
// Specifies the intended recipient of this header entry
Actor string `xml:"actor,attr,omitempty"`
// Content as raw XML for maximum flexibility
Content []byte `xml:",innerxml"`
// Additional attributes for extensibility
Attrs []xml.Attr `xml:",any,attr"`
}
HeaderEntry represents a single header entry with SOAP-specific attributes. Implements the mustUnderstand and actor semantics from SOAP 1.1 spec sections 4.2.2 and 4.2.3.
Example (MustUnderstand) ¶
ExampleHeaderEntry_mustUnderstand demonstrates the mustUnderstand attribute usage.
// Header that MUST be understood by the receiver
mustUnderstand := true
criticalHeader := soap.HeaderEntry{
XMLName: xml.Name{Local: "Security", Space: "http://example.com/security"},
MustUnderstand: &mustUnderstand,
Content: []byte(`<signature>digital_signature_here</signature>`),
}
// Header that's optional
optionalHeader := soap.HeaderEntry{
XMLName: xml.Name{Local: "Metadata", Space: "http://example.com/meta"},
Content: []byte(`<version>2.0</version>`),
// MustUnderstand is nil, so it's optional
}
envelope, err := soap.NewEnvelope(
soap.WithBody([]byte(`<SecureOperation><data>sensitive</data></SecureOperation>`)),
)
if err != nil {
log.Fatal(err)
}
// Add headers manually for advanced use cases
envelope.Header = &soap.Header{
XMLName: xml.Name{Local: "soapenv:Header"},
Entries: []soap.HeaderEntry{criticalHeader, optionalHeader},
}
xmlData, err := xml.MarshalIndent(envelope, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(xmlData))
Output: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <Security xmlns="http://example.com/security" mustUnderstand="true"><signature>digital_signature_here</signature></Security> <Metadata xmlns="http://example.com/meta"><version>2.0</version></Metadata> </soapenv:Header> <soapenv:Body><SecureOperation><data>sensitive</data></SecureOperation></soapenv:Body> </soapenv:Envelope>
type XSDDateTime ¶
XSDDateTime carries an xs:dateTime value while tolerating timezone-less inputs, which the XSD spec permits as "unspecified". Timezone-less inputs are interpreted as UTC. Marshalling emits RFC 3339 with nanosecond precision.
func (XSDDateTime) MarshalText ¶
func (d XSDDateTime) MarshalText() ([]byte, error)
func (*XSDDateTime) UnmarshalText ¶
func (d *XSDDateTime) UnmarshalText(text []byte) error