Integrate BACKBEAT SDK and resolve KACHING license validation
Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
211
vendor/github.com/huin/goupnp/soap/soap.go
generated
vendored
Normal file
211
vendor/github.com/huin/goupnp/soap/soap.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
// Definition for the SOAP structure required for UPnP's SOAP usage.
|
||||
|
||||
package soap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||
soapPrefix = xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
|
||||
soapSuffix = `</s:Body></s:Envelope>`
|
||||
)
|
||||
|
||||
type SOAPClient struct {
|
||||
EndpointURL url.URL
|
||||
HTTPClient http.Client
|
||||
}
|
||||
|
||||
func NewSOAPClient(endpointURL url.URL) *SOAPClient {
|
||||
return &SOAPClient{
|
||||
EndpointURL: endpointURL,
|
||||
}
|
||||
}
|
||||
|
||||
// PerformSOAPAction makes a SOAP request, with the given action.
|
||||
// inAction and outAction must both be pointers to structs with string fields
|
||||
// only.
|
||||
func (client *SOAPClient) PerformActionCtx(ctx context.Context, actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
|
||||
requestBytes, err := encodeRequestAction(actionNamespace, actionName, inAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: "POST",
|
||||
URL: &client.EndpointURL,
|
||||
Header: http.Header{
|
||||
"SOAPACTION": []string{`"` + actionNamespace + "#" + actionName + `"`},
|
||||
"CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""},
|
||||
},
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(requestBytes)),
|
||||
// Set ContentLength to avoid chunked encoding - some servers might not support it.
|
||||
ContentLength: int64(len(requestBytes)),
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
response, err := client.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("goupnp: error performing SOAP HTTP request: %v", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 && response.ContentLength == 0 {
|
||||
return fmt.Errorf("goupnp: SOAP request got HTTP %s", response.Status)
|
||||
}
|
||||
|
||||
responseEnv := newSOAPEnvelope()
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
if err := decoder.Decode(responseEnv); err != nil {
|
||||
return fmt.Errorf("goupnp: error decoding response body: %v", err)
|
||||
}
|
||||
|
||||
if responseEnv.Body.Fault != nil {
|
||||
return responseEnv.Body.Fault
|
||||
} else if response.StatusCode != 200 {
|
||||
return fmt.Errorf("goupnp: SOAP request got HTTP %s", response.Status)
|
||||
}
|
||||
|
||||
if outAction != nil {
|
||||
if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
|
||||
return fmt.Errorf("goupnp: error unmarshalling out action: %v, %v", err, responseEnv.Body.RawAction)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PerformAction is the legacy version of PerformActionCtx, which uses
|
||||
// context.Background.
|
||||
func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
|
||||
return client.PerformActionCtx(context.Background(), actionNamespace, actionName, inAction, outAction)
|
||||
}
|
||||
|
||||
// newSOAPAction creates a soapEnvelope with the given action and arguments.
|
||||
func newSOAPEnvelope() *soapEnvelope {
|
||||
return &soapEnvelope{
|
||||
EncodingStyle: soapEncodingStyle,
|
||||
}
|
||||
}
|
||||
|
||||
// encodeRequestAction is a hacky way to create an encoded SOAP envelope
|
||||
// containing the given action. Experiments with one router have shown that it
|
||||
// 500s for requests where the outer default xmlns is set to the SOAP
|
||||
// namespace, and then reassigning the default namespace within that to the
|
||||
// service namespace. Hand-coding the outer XML to work-around this.
|
||||
func encodeRequestAction(actionNamespace, actionName string, inAction interface{}) ([]byte, error) {
|
||||
requestBuf := new(bytes.Buffer)
|
||||
requestBuf.WriteString(soapPrefix)
|
||||
requestBuf.WriteString(`<u:`)
|
||||
xml.EscapeText(requestBuf, []byte(actionName))
|
||||
requestBuf.WriteString(` xmlns:u="`)
|
||||
xml.EscapeText(requestBuf, []byte(actionNamespace))
|
||||
requestBuf.WriteString(`">`)
|
||||
if inAction != nil {
|
||||
if err := encodeRequestArgs(requestBuf, inAction); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
requestBuf.WriteString(`</u:`)
|
||||
xml.EscapeText(requestBuf, []byte(actionName))
|
||||
requestBuf.WriteString(`>`)
|
||||
requestBuf.WriteString(soapSuffix)
|
||||
return requestBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
func encodeRequestArgs(w *bytes.Buffer, inAction interface{}) error {
|
||||
in := reflect.Indirect(reflect.ValueOf(inAction))
|
||||
if in.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("goupnp: SOAP inAction is not a struct but of type %v", in.Type())
|
||||
}
|
||||
enc := xml.NewEncoder(w)
|
||||
nFields := in.NumField()
|
||||
inType := in.Type()
|
||||
for i := 0; i < nFields; i++ {
|
||||
field := inType.Field(i)
|
||||
argName := field.Name
|
||||
if nameOverride := field.Tag.Get("soap"); nameOverride != "" {
|
||||
argName = nameOverride
|
||||
}
|
||||
value := in.Field(i)
|
||||
if value.Kind() != reflect.String {
|
||||
return fmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", argName, value.Type())
|
||||
}
|
||||
elem := xml.StartElement{Name: xml.Name{Space: "", Local: argName}, Attr: nil}
|
||||
if err := enc.EncodeToken(elem); err != nil {
|
||||
return fmt.Errorf("goupnp: error encoding start element for SOAP arg %q: %v", argName, err)
|
||||
}
|
||||
if err := enc.Flush(); err != nil {
|
||||
return fmt.Errorf("goupnp: error flushing start element for SOAP arg %q: %v", argName, err)
|
||||
}
|
||||
if _, err := w.Write([]byte(escapeXMLText(value.Interface().(string)))); err != nil {
|
||||
return fmt.Errorf("goupnp: error writing value for SOAP arg %q: %v", argName, err)
|
||||
}
|
||||
if err := enc.EncodeToken(elem.End()); err != nil {
|
||||
return fmt.Errorf("goupnp: error encoding end element for SOAP arg %q: %v", argName, err)
|
||||
}
|
||||
}
|
||||
enc.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
var xmlCharRx = regexp.MustCompile("[<>&]")
|
||||
|
||||
// escapeXMLText is used by generated code to escape text in XML, but only
|
||||
// escaping the characters `<`, `>`, and `&`.
|
||||
//
|
||||
// This is provided in order to work around SOAP server implementations that
|
||||
// fail to decode XML correctly, specifically failing to decode `"`, `'`. Note
|
||||
// that this can only be safely used for injecting into XML text, but not into
|
||||
// attributes or other contexts.
|
||||
func escapeXMLText(s string) string {
|
||||
return xmlCharRx.ReplaceAllStringFunc(s, replaceEntity)
|
||||
}
|
||||
|
||||
func replaceEntity(s string) string {
|
||||
switch s {
|
||||
case "<":
|
||||
return "<"
|
||||
case ">":
|
||||
return ">"
|
||||
case "&":
|
||||
return "&"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type soapEnvelope struct {
|
||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
||||
EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
|
||||
Body soapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
|
||||
}
|
||||
|
||||
type soapBody struct {
|
||||
Fault *SOAPFaultError `xml:"Fault"`
|
||||
RawAction []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// SOAPFaultError implements error, and contains SOAP fault information.
|
||||
type SOAPFaultError struct {
|
||||
FaultCode string `xml:"faultcode"`
|
||||
FaultString string `xml:"faultstring"`
|
||||
Detail struct {
|
||||
UPnPError struct {
|
||||
Errorcode int `xml:"errorCode"`
|
||||
ErrorDescription string `xml:"errorDescription"`
|
||||
} `xml:"UPnPError"`
|
||||
Raw []byte `xml:",innerxml"`
|
||||
} `xml:"detail"`
|
||||
}
|
||||
|
||||
func (err *SOAPFaultError) Error() string {
|
||||
return fmt.Sprintf("SOAP fault. Code: %s | Explanation: %s | Detail: %s",
|
||||
err.FaultCode, err.FaultString, string(err.Detail.Raw))
|
||||
}
|
||||
578
vendor/github.com/huin/goupnp/soap/types.go
generated
vendored
Normal file
578
vendor/github.com/huin/goupnp/soap/types.go
generated
vendored
Normal file
@@ -0,0 +1,578 @@
|
||||
package soap
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
// localLoc acts like time.Local for this package, but is faked out by the
|
||||
// unit tests to ensure that things stay constant (especially when running
|
||||
// this test in a place where local time is UTC which might mask bugs).
|
||||
localLoc = time.Local
|
||||
)
|
||||
|
||||
func MarshalUi1(v uint8) (string, error) {
|
||||
return strconv.FormatUint(uint64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi1(s string) (uint8, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 8)
|
||||
return uint8(v), err
|
||||
}
|
||||
|
||||
func MarshalUi2(v uint16) (string, error) {
|
||||
return strconv.FormatUint(uint64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi2(s string) (uint16, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 16)
|
||||
return uint16(v), err
|
||||
}
|
||||
|
||||
func MarshalUi4(v uint32) (string, error) {
|
||||
return strconv.FormatUint(uint64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi4(s string) (uint32, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 32)
|
||||
return uint32(v), err
|
||||
}
|
||||
|
||||
func MarshalUi8(v uint64) (string, error) {
|
||||
return strconv.FormatUint(v, 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi8(s string) (uint64, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 64)
|
||||
return uint64(v), err
|
||||
}
|
||||
|
||||
func MarshalI1(v int8) (string, error) {
|
||||
return strconv.FormatInt(int64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalI1(s string) (int8, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 8)
|
||||
return int8(v), err
|
||||
}
|
||||
|
||||
func MarshalI2(v int16) (string, error) {
|
||||
return strconv.FormatInt(int64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalI2(s string) (int16, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 16)
|
||||
return int16(v), err
|
||||
}
|
||||
|
||||
func MarshalI4(v int32) (string, error) {
|
||||
return strconv.FormatInt(int64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalI4(s string) (int32, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 32)
|
||||
return int32(v), err
|
||||
}
|
||||
|
||||
func MarshalInt(v int64) (string, error) {
|
||||
return strconv.FormatInt(v, 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalInt(s string) (int64, error) {
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
}
|
||||
|
||||
func MarshalR4(v float32) (string, error) {
|
||||
return strconv.FormatFloat(float64(v), 'G', -1, 32), nil
|
||||
}
|
||||
|
||||
func UnmarshalR4(s string) (float32, error) {
|
||||
v, err := strconv.ParseFloat(s, 32)
|
||||
return float32(v), err
|
||||
}
|
||||
|
||||
func MarshalR8(v float64) (string, error) {
|
||||
return strconv.FormatFloat(v, 'G', -1, 64), nil
|
||||
}
|
||||
|
||||
func UnmarshalR8(s string) (float64, error) {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
return float64(v), err
|
||||
}
|
||||
|
||||
// MarshalFixed14_4 marshals float64 to SOAP "fixed.14.4" type.
|
||||
func MarshalFixed14_4(v float64) (string, error) {
|
||||
if v >= 1e14 || v <= -1e14 {
|
||||
return "", fmt.Errorf("soap fixed14.4: value %v out of bounds", v)
|
||||
}
|
||||
return strconv.FormatFloat(v, 'f', 4, 64), nil
|
||||
}
|
||||
|
||||
// UnmarshalFixed14_4 unmarshals float64 from SOAP "fixed.14.4" type.
|
||||
func UnmarshalFixed14_4(s string) (float64, error) {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if v >= 1e14 || v <= -1e14 {
|
||||
return 0, fmt.Errorf("soap fixed14.4: value %q out of bounds", s)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// MarshalChar marshals rune to SOAP "char" type.
|
||||
func MarshalChar(v rune) (string, error) {
|
||||
if v == 0 {
|
||||
return "", errors.New("soap char: rune 0 is not allowed")
|
||||
}
|
||||
return string(v), nil
|
||||
}
|
||||
|
||||
// UnmarshalChar unmarshals rune from SOAP "char" type.
|
||||
func UnmarshalChar(s string) (rune, error) {
|
||||
if len(s) == 0 {
|
||||
return 0, errors.New("soap char: got empty string")
|
||||
}
|
||||
r, n := utf8.DecodeRune([]byte(s))
|
||||
if n != len(s) {
|
||||
return 0, fmt.Errorf("soap char: value %q is not a single rune", s)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func MarshalString(v string) (string, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func UnmarshalString(v string) (string, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseInt(s string, err *error) int {
|
||||
v, parseErr := strconv.ParseInt(s, 10, 64)
|
||||
if parseErr != nil {
|
||||
*err = parseErr
|
||||
}
|
||||
return int(v)
|
||||
}
|
||||
|
||||
var dateRegexps = []*regexp.Regexp{
|
||||
// yyyy[-mm[-dd]]
|
||||
regexp.MustCompile(`^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?$`),
|
||||
// yyyy[mm[dd]]
|
||||
regexp.MustCompile(`^(\d{4})(?:(\d{2})(?:(\d{2}))?)?$`),
|
||||
}
|
||||
|
||||
func parseDateParts(s string) (year, month, day int, err error) {
|
||||
var parts []string
|
||||
for _, re := range dateRegexps {
|
||||
parts = re.FindStringSubmatch(s)
|
||||
if parts != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap date: value %q is not in a recognized ISO8601 date format", s)
|
||||
return
|
||||
}
|
||||
|
||||
year = parseInt(parts[1], &err)
|
||||
month = 1
|
||||
day = 1
|
||||
if len(parts[2]) != 0 {
|
||||
month = parseInt(parts[2], &err)
|
||||
if len(parts[3]) != 0 {
|
||||
day = parseInt(parts[3], &err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("soap date: %q: %v", s, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var timeRegexps = []*regexp.Regexp{
|
||||
// hh[:mm[:ss]]
|
||||
regexp.MustCompile(`^(\d{2})(?::(\d{2})(?::(\d{2}))?)?$`),
|
||||
// hh[mm[ss]]
|
||||
regexp.MustCompile(`^(\d{2})(?:(\d{2})(?:(\d{2}))?)?$`),
|
||||
}
|
||||
|
||||
func parseTimeParts(s string) (hour, minute, second int, err error) {
|
||||
var parts []string
|
||||
for _, re := range timeRegexps {
|
||||
parts = re.FindStringSubmatch(s)
|
||||
if parts != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap time: value %q is not in ISO8601 time format", s)
|
||||
return
|
||||
}
|
||||
|
||||
hour = parseInt(parts[1], &err)
|
||||
if len(parts[2]) != 0 {
|
||||
minute = parseInt(parts[2], &err)
|
||||
if len(parts[3]) != 0 {
|
||||
second = parseInt(parts[3], &err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("soap time: %q: %v", s, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// (+|-)hh[[:]mm]
|
||||
var timezoneRegexp = regexp.MustCompile(`^([+-])(\d{2})(?::?(\d{2}))?$`)
|
||||
|
||||
func parseTimezone(s string) (offset int, err error) {
|
||||
if s == "Z" {
|
||||
return 0, nil
|
||||
}
|
||||
parts := timezoneRegexp.FindStringSubmatch(s)
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap timezone: value %q is not in ISO8601 timezone format", s)
|
||||
return
|
||||
}
|
||||
|
||||
offset = parseInt(parts[2], &err) * 3600
|
||||
if len(parts[3]) != 0 {
|
||||
offset += parseInt(parts[3], &err) * 60
|
||||
}
|
||||
if parts[1] == "-" {
|
||||
offset = -offset
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("soap timezone: %q: %v", s, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var completeDateTimeZoneRegexp = regexp.MustCompile(`^([^T]+)(?:T([^-+Z]+)(.+)?)?$`)
|
||||
|
||||
// splitCompleteDateTimeZone splits date, time and timezone apart from an
|
||||
// ISO8601 string. It does not ensure that the contents of each part are
|
||||
// correct, it merely splits on certain delimiters.
|
||||
// e.g "2010-09-08T12:15:10+0700" => "2010-09-08", "12:15:10", "+0700".
|
||||
// Timezone can only be present if time is also present.
|
||||
func splitCompleteDateTimeZone(s string) (dateStr, timeStr, zoneStr string, err error) {
|
||||
parts := completeDateTimeZoneRegexp.FindStringSubmatch(s)
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap date/time/zone: value %q is not in ISO8601 datetime format", s)
|
||||
return
|
||||
}
|
||||
dateStr = parts[1]
|
||||
timeStr = parts[2]
|
||||
zoneStr = parts[3]
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalDate marshals time.Time to SOAP "date" type. Note that this converts
|
||||
// to local time, and discards the time-of-day components.
|
||||
func MarshalDate(v time.Time) (string, error) {
|
||||
return v.In(localLoc).Format("2006-01-02"), nil
|
||||
}
|
||||
|
||||
var dateFmts = []string{"2006-01-02", "20060102"}
|
||||
|
||||
// UnmarshalDate unmarshals time.Time from SOAP "date" type. This outputs the
|
||||
// date as midnight in the local time zone.
|
||||
func UnmarshalDate(s string) (time.Time, error) {
|
||||
year, month, day, err := parseDateParts(s)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, localLoc), nil
|
||||
}
|
||||
|
||||
// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
|
||||
type TimeOfDay struct {
|
||||
// Duration of time since midnight.
|
||||
FromMidnight time.Duration
|
||||
|
||||
// Set to true if Offset is specified. If false, then the timezone is
|
||||
// unspecified (and by ISO8601 - implies some "local" time).
|
||||
HasOffset bool
|
||||
|
||||
// Offset is non-zero only if time.tz is used. It is otherwise ignored. If
|
||||
// non-zero, then it is regarded as a UTC offset in seconds. Note that the
|
||||
// sub-minutes is ignored by the marshal function.
|
||||
Offset int
|
||||
}
|
||||
|
||||
// MarshalTimeOfDay marshals TimeOfDay to the "time" type.
|
||||
func MarshalTimeOfDay(v TimeOfDay) (string, error) {
|
||||
d := int64(v.FromMidnight / time.Second)
|
||||
hour := d / 3600
|
||||
d = d % 3600
|
||||
minute := d / 60
|
||||
second := d % 60
|
||||
|
||||
return fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), nil
|
||||
}
|
||||
|
||||
// UnmarshalTimeOfDay unmarshals TimeOfDay from the "time" type.
|
||||
func UnmarshalTimeOfDay(s string) (TimeOfDay, error) {
|
||||
t, err := UnmarshalTimeOfDayTz(s)
|
||||
if err != nil {
|
||||
return TimeOfDay{}, err
|
||||
} else if t.HasOffset {
|
||||
return TimeOfDay{}, fmt.Errorf("soap time: value %q contains unexpected timezone", s)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// MarshalTimeOfDayTz marshals TimeOfDay to the "time.tz" type.
|
||||
func MarshalTimeOfDayTz(v TimeOfDay) (string, error) {
|
||||
d := int64(v.FromMidnight / time.Second)
|
||||
hour := d / 3600
|
||||
d = d % 3600
|
||||
minute := d / 60
|
||||
second := d % 60
|
||||
|
||||
tz := ""
|
||||
if v.HasOffset {
|
||||
if v.Offset == 0 {
|
||||
tz = "Z"
|
||||
} else {
|
||||
offsetMins := v.Offset / 60
|
||||
sign := '+'
|
||||
if offsetMins < 1 {
|
||||
offsetMins = -offsetMins
|
||||
sign = '-'
|
||||
}
|
||||
tz = fmt.Sprintf("%c%02d:%02d", sign, offsetMins/60, offsetMins%60)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%02d:%02d:%02d%s", hour, minute, second, tz), nil
|
||||
}
|
||||
|
||||
// UnmarshalTimeOfDayTz unmarshals TimeOfDay from the "time.tz" type.
|
||||
func UnmarshalTimeOfDayTz(s string) (tod TimeOfDay, err error) {
|
||||
zoneIndex := strings.IndexAny(s, "Z+-")
|
||||
var timePart string
|
||||
var hasOffset bool
|
||||
var offset int
|
||||
if zoneIndex == -1 {
|
||||
hasOffset = false
|
||||
timePart = s
|
||||
} else {
|
||||
hasOffset = true
|
||||
timePart = s[:zoneIndex]
|
||||
if offset, err = parseTimezone(s[zoneIndex:]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
hour, minute, second, err := parseTimeParts(timePart)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fromMidnight := time.Duration(hour*3600+minute*60+second) * time.Second
|
||||
|
||||
// ISO8601 special case - values up to 24:00:00 are allowed, so using
|
||||
// strictly greater-than for the maximum value.
|
||||
if fromMidnight > 24*time.Hour || minute >= 60 || second >= 60 {
|
||||
return TimeOfDay{}, fmt.Errorf("soap time.tz: value %q has value(s) out of range", s)
|
||||
}
|
||||
|
||||
return TimeOfDay{
|
||||
FromMidnight: time.Duration(hour*3600+minute*60+second) * time.Second,
|
||||
HasOffset: hasOffset,
|
||||
Offset: offset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalDateTime marshals time.Time to SOAP "dateTime" type. Note that this
|
||||
// converts to local time.
|
||||
func MarshalDateTime(v time.Time) (string, error) {
|
||||
return v.In(localLoc).Format("2006-01-02T15:04:05"), nil
|
||||
}
|
||||
|
||||
// UnmarshalDateTime unmarshals time.Time from the SOAP "dateTime" type. This
|
||||
// returns a value in the local timezone.
|
||||
func UnmarshalDateTime(s string) (result time.Time, err error) {
|
||||
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(zoneStr) != 0 {
|
||||
err = fmt.Errorf("soap datetime: unexpected timezone in %q", s)
|
||||
return
|
||||
}
|
||||
|
||||
year, month, day, err := parseDateParts(dateStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var hour, minute, second int
|
||||
if len(timeStr) != 0 {
|
||||
hour, minute, second, err = parseTimeParts(timeStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result = time.Date(year, time.Month(month), day, hour, minute, second, 0, localLoc)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalDateTimeTz marshals time.Time to SOAP "dateTime.tz" type.
|
||||
func MarshalDateTimeTz(v time.Time) (string, error) {
|
||||
return v.Format("2006-01-02T15:04:05-07:00"), nil
|
||||
}
|
||||
|
||||
// UnmarshalDateTimeTz unmarshals time.Time from the SOAP "dateTime.tz" type.
|
||||
// This returns a value in the local timezone when the timezone is unspecified.
|
||||
func UnmarshalDateTimeTz(s string) (result time.Time, err error) {
|
||||
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
year, month, day, err := parseDateParts(dateStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var hour, minute, second int
|
||||
var location *time.Location = localLoc
|
||||
if len(timeStr) != 0 {
|
||||
hour, minute, second, err = parseTimeParts(timeStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(zoneStr) != 0 {
|
||||
var offset int
|
||||
offset, err = parseTimezone(zoneStr)
|
||||
if offset == 0 {
|
||||
location = time.UTC
|
||||
} else {
|
||||
location = time.FixedZone("", offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = time.Date(year, time.Month(month), day, hour, minute, second, 0, location)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalBoolean marshals bool to SOAP "boolean" type.
|
||||
func MarshalBoolean(v bool) (string, error) {
|
||||
if v {
|
||||
return "1", nil
|
||||
}
|
||||
return "0", nil
|
||||
}
|
||||
|
||||
// UnmarshalBoolean unmarshals bool from the SOAP "boolean" type.
|
||||
func UnmarshalBoolean(s string) (bool, error) {
|
||||
switch s {
|
||||
case "0", "false", "no":
|
||||
return false, nil
|
||||
case "1", "true", "yes":
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("soap boolean: %q is not a valid boolean value", s)
|
||||
}
|
||||
|
||||
// MarshalBinBase64 marshals []byte to SOAP "bin.base64" type.
|
||||
func MarshalBinBase64(v []byte) (string, error) {
|
||||
return base64.StdEncoding.EncodeToString(v), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinBase64 unmarshals []byte from the SOAP "bin.base64" type.
|
||||
func UnmarshalBinBase64(s string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// MarshalBinHex marshals []byte to SOAP "bin.hex" type.
|
||||
func MarshalBinHex(v []byte) (string, error) {
|
||||
return hex.EncodeToString(v), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinHex unmarshals []byte from the SOAP "bin.hex" type.
|
||||
func UnmarshalBinHex(s string) ([]byte, error) {
|
||||
return hex.DecodeString(s)
|
||||
}
|
||||
|
||||
// MarshalURI marshals *url.URL to SOAP "uri" type.
|
||||
func MarshalURI(v *url.URL) (string, error) {
|
||||
return v.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalURI unmarshals *url.URL from the SOAP "uri" type.
|
||||
func UnmarshalURI(s string) (*url.URL, error) {
|
||||
return url.Parse(s)
|
||||
}
|
||||
|
||||
// TypeData provides metadata about for marshalling and unmarshalling a SOAP
|
||||
// type.
|
||||
type TypeData struct {
|
||||
funcSuffix string
|
||||
goType string
|
||||
}
|
||||
|
||||
// GoTypeName returns the name of the Go type.
|
||||
func (td TypeData) GoTypeName() string {
|
||||
return td.goType
|
||||
}
|
||||
|
||||
// MarshalFunc returns the name of the function that marshals the type.
|
||||
func (td TypeData) MarshalFunc() string {
|
||||
return fmt.Sprintf("Marshal%s", td.funcSuffix)
|
||||
}
|
||||
|
||||
// UnmarshalFunc returns the name of the function that unmarshals the type.
|
||||
func (td TypeData) UnmarshalFunc() string {
|
||||
return fmt.Sprintf("Unmarshal%s", td.funcSuffix)
|
||||
}
|
||||
|
||||
// TypeDataMap maps from a SOAP type (e.g "fixed.14.4") to its type data.
|
||||
var TypeDataMap = map[string]TypeData{
|
||||
"ui1": {"Ui1", "uint8"},
|
||||
"ui2": {"Ui2", "uint16"},
|
||||
"ui4": {"Ui4", "uint32"},
|
||||
"ui8": {"Ui8", "uint64"},
|
||||
"i1": {"I1", "int8"},
|
||||
"i2": {"I2", "int16"},
|
||||
"i4": {"I4", "int32"},
|
||||
"int": {"Int", "int64"},
|
||||
"r4": {"R4", "float32"},
|
||||
"r8": {"R8", "float64"},
|
||||
"number": {"R8", "float64"}, // Alias for r8.
|
||||
"fixed.14.4": {"Fixed14_4", "float64"},
|
||||
"float": {"R8", "float64"},
|
||||
"char": {"Char", "rune"},
|
||||
"string": {"String", "string"},
|
||||
"date": {"Date", "time.Time"},
|
||||
"dateTime": {"DateTime", "time.Time"},
|
||||
"dateTime.tz": {"DateTimeTz", "time.Time"},
|
||||
"time": {"TimeOfDay", "soap.TimeOfDay"},
|
||||
"time.tz": {"TimeOfDayTz", "soap.TimeOfDay"},
|
||||
"boolean": {"Boolean", "bool"},
|
||||
"bin.base64": {"BinBase64", "[]byte"},
|
||||
"bin.hex": {"BinHex", "[]byte"},
|
||||
"uri": {"URI", "*url.URL"},
|
||||
}
|
||||
Reference in New Issue
Block a user