mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-15 04:19:28 +08:00
642 lines
18 KiB
Go
642 lines
18 KiB
Go
package uixt
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/base64"
|
||
builtinJSON "encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"mime"
|
||
"mime/multipart"
|
||
"net"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/pkg/errors"
|
||
"github.com/rs/zerolog/log"
|
||
|
||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||
)
|
||
|
||
const (
|
||
defaultWDAPort = 8100
|
||
defaultMjpegPort = 9100
|
||
)
|
||
|
||
const (
|
||
// Changes the value of maximum depth for traversing elements source tree.
|
||
// It may help to prevent out of memory or timeout errors while getting the elements source tree,
|
||
// but it might restrict the depth of source tree.
|
||
// A part of elements source tree might be lost if the value was too small. Defaults to 50
|
||
snapshotMaxDepth = 10
|
||
// Allows to customize accept/dismiss alert button selector.
|
||
// It helps you to handle an arbitrary element as accept button in accept alert command.
|
||
// The selector should be a valid class chain expression, where the search root is the alert element itself.
|
||
// The default button location algorithm is used if the provided selector is wrong or does not match any element.
|
||
// e.g. **/XCUIElementTypeButton[`label CONTAINS[c] ‘accept’`]
|
||
acceptAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'允许','好','仅在使用应用期间','稍后再说'}`]"
|
||
dismissAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'不允许','暂不'}`]"
|
||
)
|
||
|
||
type IOSDeviceOption func(*IOSDevice)
|
||
|
||
func WithUDID(udid string) IOSDeviceOption {
|
||
return func(device *IOSDevice) {
|
||
device.UDID = udid
|
||
}
|
||
}
|
||
|
||
func WithWDAPort(port int) IOSDeviceOption {
|
||
return func(device *IOSDevice) {
|
||
device.Port = port
|
||
}
|
||
}
|
||
|
||
func WithWDAMjpegPort(port int) IOSDeviceOption {
|
||
return func(device *IOSDevice) {
|
||
device.MjpegPort = port
|
||
}
|
||
}
|
||
|
||
func WithLogOn(logOn bool) IOSDeviceOption {
|
||
return func(device *IOSDevice) {
|
||
device.LogOn = logOn
|
||
}
|
||
}
|
||
|
||
func WithResetHomeOnStartup(reset bool) IOSDeviceOption {
|
||
return func(device *IOSDevice) {
|
||
device.ResetHomeOnStartup = reset
|
||
}
|
||
}
|
||
|
||
func WithSnapshotMaxDepth(depth int) IOSDeviceOption {
|
||
return func(device *IOSDevice) {
|
||
device.SnapshotMaxDepth = depth
|
||
}
|
||
}
|
||
|
||
func WithAcceptAlertButtonSelector(selector string) IOSDeviceOption {
|
||
return func(device *IOSDevice) {
|
||
device.AcceptAlertButtonSelector = selector
|
||
}
|
||
}
|
||
|
||
func WithDismissAlertButtonSelector(selector string) IOSDeviceOption {
|
||
return func(device *IOSDevice) {
|
||
device.DismissAlertButtonSelector = selector
|
||
}
|
||
}
|
||
|
||
func WithPerfOptions(options ...gidevice.PerfOption) IOSDeviceOption {
|
||
return func(device *IOSDevice) {
|
||
device.PerfOptions = &gidevice.PerfOptions{}
|
||
for _, option := range options {
|
||
option(device.PerfOptions)
|
||
}
|
||
}
|
||
}
|
||
|
||
func IOSDevices(udid ...string) (devices []gidevice.Device, err error) {
|
||
var usbmux gidevice.Usbmux
|
||
if usbmux, err = gidevice.NewUsbmux(); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceConnectionError,
|
||
fmt.Sprintf("init usbmux failed: %v", err))
|
||
}
|
||
|
||
if devices, err = usbmux.Devices(); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceConnectionError,
|
||
fmt.Sprintf("list ios devices failed: %v", err))
|
||
}
|
||
|
||
// filter by udid
|
||
var deviceList []gidevice.Device
|
||
for _, d := range devices {
|
||
for _, u := range udid {
|
||
if u != "" && u != d.Properties().SerialNumber {
|
||
continue
|
||
}
|
||
deviceList = append(deviceList, d)
|
||
}
|
||
}
|
||
|
||
return deviceList, nil
|
||
}
|
||
|
||
func GetIOSDeviceOptions(dev *IOSDevice) (deviceOptions []IOSDeviceOption) {
|
||
if dev.UDID != "" {
|
||
deviceOptions = append(deviceOptions, WithUDID(dev.UDID))
|
||
}
|
||
if dev.Port != 0 {
|
||
deviceOptions = append(deviceOptions, WithWDAPort(dev.Port))
|
||
}
|
||
if dev.MjpegPort != 0 {
|
||
deviceOptions = append(deviceOptions, WithWDAMjpegPort(dev.MjpegPort))
|
||
}
|
||
if dev.LogOn {
|
||
deviceOptions = append(deviceOptions, WithLogOn(true))
|
||
}
|
||
if dev.PerfOptions != nil {
|
||
deviceOptions = append(deviceOptions, WithPerfOptions(dev.perfOpitons()...))
|
||
}
|
||
if dev.ResetHomeOnStartup {
|
||
deviceOptions = append(deviceOptions, WithResetHomeOnStartup(true))
|
||
}
|
||
if dev.SnapshotMaxDepth != 0 {
|
||
deviceOptions = append(deviceOptions, WithSnapshotMaxDepth(dev.SnapshotMaxDepth))
|
||
}
|
||
if dev.AcceptAlertButtonSelector != "" {
|
||
deviceOptions = append(deviceOptions, WithAcceptAlertButtonSelector(dev.AcceptAlertButtonSelector))
|
||
}
|
||
if dev.DismissAlertButtonSelector != "" {
|
||
deviceOptions = append(deviceOptions, WithDismissAlertButtonSelector(dev.DismissAlertButtonSelector))
|
||
}
|
||
return
|
||
}
|
||
|
||
func NewIOSDevice(options ...IOSDeviceOption) (device *IOSDevice, err error) {
|
||
device = &IOSDevice{
|
||
Port: defaultWDAPort,
|
||
MjpegPort: defaultMjpegPort,
|
||
SnapshotMaxDepth: snapshotMaxDepth,
|
||
AcceptAlertButtonSelector: acceptAlertButtonSelector,
|
||
DismissAlertButtonSelector: dismissAlertButtonSelector,
|
||
// switch to iOS springboard before init WDA session
|
||
// avoid getting stuck when some super app is active such as douyin or wexin
|
||
ResetHomeOnStartup: true,
|
||
}
|
||
for _, option := range options {
|
||
option(device)
|
||
}
|
||
|
||
deviceList, err := IOSDevices(device.UDID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if len(deviceList) > 0 {
|
||
device.UDID = deviceList[0].Properties().SerialNumber
|
||
log.Info().Str("udid", device.UDID).Msg("select device")
|
||
device.d = deviceList[0]
|
||
return device, nil
|
||
}
|
||
|
||
return nil, errors.Wrap(code.IOSDeviceConnectionError,
|
||
fmt.Sprintf("device %s not found", device.UDID))
|
||
}
|
||
|
||
type IOSDevice struct {
|
||
d gidevice.Device
|
||
PerfOptions *gidevice.PerfOptions `json:"perf_options,omitempty" yaml:"perf_options,omitempty"`
|
||
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"`
|
||
Port int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port
|
||
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
|
||
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
|
||
|
||
// switch to iOS springboard before init WDA session
|
||
ResetHomeOnStartup bool `json:"reset_home_on_startup,omitempty" yaml:"reset_home_on_startup,omitempty"`
|
||
|
||
// config appium settings
|
||
SnapshotMaxDepth int `json:"snapshot_max_depth,omitempty" yaml:"snapshot_max_depth,omitempty"`
|
||
AcceptAlertButtonSelector string `json:"accept_alert_button_selector,omitempty" yaml:"accept_alert_button_selector,omitempty"`
|
||
DismissAlertButtonSelector string `json:"dismiss_alert_button_selector,omitempty" yaml:"dismiss_alert_button_selector,omitempty"`
|
||
}
|
||
|
||
func (dev *IOSDevice) UUID() string {
|
||
return dev.UDID
|
||
}
|
||
|
||
func (dev *IOSDevice) NewDriver(capabilities Capabilities) (driverExt *DriverExt, err error) {
|
||
// init WDA driver
|
||
if capabilities == nil {
|
||
capabilities = NewCapabilities()
|
||
capabilities.WithDefaultAlertAction(AlertActionAccept)
|
||
}
|
||
|
||
var driver WebDriver
|
||
if env.WDA_USB_DRIVER == "" {
|
||
// default use http driver
|
||
driver, err = dev.NewHTTPDriver(capabilities)
|
||
} else {
|
||
driver, err = dev.NewUSBDriver(capabilities)
|
||
}
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "failed to init WDA driver")
|
||
}
|
||
|
||
if dev.ResetHomeOnStartup {
|
||
log.Info().Msg("go back to home screen")
|
||
if err = driver.Homescreen(); err != nil {
|
||
return nil, errors.Wrap(code.MobileUIDriverError,
|
||
fmt.Sprintf("go back to home screen failed: %v", err))
|
||
}
|
||
}
|
||
|
||
driverExt, err = Extend(driver)
|
||
if err != nil {
|
||
return nil, errors.Wrap(code.MobileUIDriverError,
|
||
fmt.Sprintf("extend WebDriver failed: %v", err))
|
||
}
|
||
settings, err := driverExt.Driver.SetAppiumSettings(map[string]interface{}{
|
||
"snapshotMaxDepth": dev.SnapshotMaxDepth,
|
||
"acceptAlertButtonSelector": dev.AcceptAlertButtonSelector,
|
||
})
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "failed to set appium WDA settings")
|
||
}
|
||
log.Info().Interface("appiumWDASettings", settings).Msg("set appium WDA settings")
|
||
|
||
if dev.LogOn {
|
||
err = driverExt.Driver.StartCaptureLog("hrp_wda_log")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
if dev.PerfOptions != nil {
|
||
data, err := dev.d.PerfStart(dev.perfOpitons()...)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
driverExt.perfStop = make(chan struct{})
|
||
// start performance monitor
|
||
go func() {
|
||
for {
|
||
select {
|
||
case <-driverExt.perfStop:
|
||
dev.d.PerfStop()
|
||
return
|
||
case d := <-data:
|
||
fmt.Println(string(d))
|
||
driverExt.perfData = append(driverExt.perfData, string(d))
|
||
}
|
||
}
|
||
}()
|
||
}
|
||
|
||
driverExt.UUID = dev.UUID()
|
||
return driverExt, nil
|
||
}
|
||
|
||
func (dev *IOSDevice) forward(localPort, remotePort int) error {
|
||
log.Info().Int("localPort", localPort).Int("remotePort", remotePort).
|
||
Str("udid", dev.UDID).Msg("forward tcp port")
|
||
|
||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort))
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("listen tcp error")
|
||
return err
|
||
}
|
||
|
||
go func(listener net.Listener, device gidevice.Device) {
|
||
for {
|
||
accept, err := listener.Accept()
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("accept error")
|
||
continue
|
||
}
|
||
|
||
rInnerConn, err := device.NewConnect(remotePort)
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("connect to ios device failed")
|
||
os.Exit(code.GetErrorCode(code.IOSDeviceConnectionError))
|
||
}
|
||
|
||
rConn := rInnerConn.RawConn()
|
||
_ = rConn.SetDeadline(time.Time{})
|
||
|
||
go func(lConn net.Conn) {
|
||
go func(lConn, rConn net.Conn) {
|
||
if _, err := io.Copy(lConn, rConn); err != nil {
|
||
log.Error().Err(err).Msg("copy local -> remote")
|
||
}
|
||
}(lConn, rConn)
|
||
go func(lConn, rConn net.Conn) {
|
||
if _, err := io.Copy(rConn, lConn); err != nil {
|
||
log.Error().Err(err).Msg("copy local <- remote")
|
||
}
|
||
}(lConn, rConn)
|
||
}(accept)
|
||
}
|
||
}(listener, dev.d)
|
||
|
||
return nil
|
||
}
|
||
|
||
func (dev *IOSDevice) perfOpitons() (perfOptions []gidevice.PerfOption) {
|
||
if dev.PerfOptions == nil {
|
||
return
|
||
}
|
||
|
||
// system
|
||
if dev.PerfOptions.SysCPU {
|
||
perfOptions = append(perfOptions, gidevice.WithPerfSystemCPU(true))
|
||
}
|
||
if dev.PerfOptions.SysMem {
|
||
perfOptions = append(perfOptions, gidevice.WithPerfSystemMem(true))
|
||
}
|
||
if dev.PerfOptions.SysDisk {
|
||
perfOptions = append(perfOptions, gidevice.WithPerfSystemDisk(true))
|
||
}
|
||
if dev.PerfOptions.SysNetwork {
|
||
perfOptions = append(perfOptions, gidevice.WithPerfSystemNetwork(true))
|
||
}
|
||
if dev.PerfOptions.FPS {
|
||
perfOptions = append(perfOptions, gidevice.WithPerfFPS(true))
|
||
}
|
||
if dev.PerfOptions.Network {
|
||
perfOptions = append(perfOptions, gidevice.WithPerfNetwork(true))
|
||
}
|
||
|
||
// process
|
||
if dev.PerfOptions.BundleID != "" {
|
||
perfOptions = append(perfOptions,
|
||
gidevice.WithPerfBundleID(dev.PerfOptions.BundleID))
|
||
}
|
||
if dev.PerfOptions.Pid != 0 {
|
||
perfOptions = append(perfOptions,
|
||
gidevice.WithPerfPID(dev.PerfOptions.Pid))
|
||
}
|
||
|
||
// config
|
||
if dev.PerfOptions.OutputInterval != 0 {
|
||
perfOptions = append(perfOptions,
|
||
gidevice.WithPerfOutputInterval(dev.PerfOptions.OutputInterval))
|
||
}
|
||
if dev.PerfOptions.SystemAttributes != nil {
|
||
perfOptions = append(perfOptions,
|
||
gidevice.WithPerfSystemAttributes(dev.PerfOptions.SystemAttributes...))
|
||
}
|
||
if dev.PerfOptions.ProcessAttributes != nil {
|
||
perfOptions = append(perfOptions,
|
||
gidevice.WithPerfProcessAttributes(dev.PerfOptions.ProcessAttributes...))
|
||
}
|
||
return
|
||
}
|
||
|
||
// NewHTTPDriver creates new remote HTTP client, this will also start a new session.
|
||
func (dev *IOSDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver, err error) {
|
||
var localPort int
|
||
localPort, err = strconv.Atoi(env.WDA_LOCAL_PORT)
|
||
if err != nil {
|
||
localPort, err = getFreePort()
|
||
if err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError,
|
||
fmt.Sprintf("get free port failed: %v", err))
|
||
}
|
||
|
||
if err = dev.forward(localPort, dev.Port); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError,
|
||
fmt.Sprintf("forward tcp port failed: %v", err))
|
||
}
|
||
} else {
|
||
log.Info().Int("WDA_LOCAL_PORT", localPort).Msg("reuse WDA local port")
|
||
}
|
||
|
||
var localMjpegPort int
|
||
localMjpegPort, err = strconv.Atoi(env.WDA_LOCAL_MJPEG_PORT)
|
||
if err != nil {
|
||
localMjpegPort, err = getFreePort()
|
||
if err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError,
|
||
fmt.Sprintf("get free port failed: %v", err))
|
||
}
|
||
if err = dev.forward(localMjpegPort, dev.MjpegPort); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError,
|
||
fmt.Sprintf("forward tcp port failed: %v", err))
|
||
}
|
||
} else {
|
||
log.Info().Int("WDA_LOCAL_MJPEG_PORT", localMjpegPort).
|
||
Msg("reuse WDA local mjpeg port")
|
||
}
|
||
|
||
log.Info().Interface("capabilities", capabilities).
|
||
Int("localPort", localPort).Int("localMjpegPort", localMjpegPort).
|
||
Msg("init WDA HTTP driver")
|
||
|
||
wd := new(wdaDriver)
|
||
wd.client = http.DefaultClient
|
||
|
||
host := "127.0.0.1"
|
||
if wd.urlPrefix, err = url.Parse(fmt.Sprintf("http://%s:%d", host, localPort)); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, err.Error())
|
||
}
|
||
var sessionInfo SessionInfo
|
||
if sessionInfo, err = wd.NewSession(capabilities); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, err.Error())
|
||
}
|
||
wd.sessionId = sessionInfo.SessionId
|
||
|
||
if wd.mjpegHTTPConn, err = net.Dial(
|
||
"tcp",
|
||
fmt.Sprintf("%s:%d", host, localMjpegPort),
|
||
); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, err.Error())
|
||
}
|
||
wd.mjpegClient = convertToHTTPClient(wd.mjpegHTTPConn)
|
||
|
||
return wd, nil
|
||
}
|
||
|
||
// NewUSBDriver creates new client via USB connected device, this will also start a new session.
|
||
func (dev *IOSDevice) NewUSBDriver(capabilities Capabilities) (driver WebDriver, err error) {
|
||
log.Info().Interface("capabilities", capabilities).
|
||
Str("udid", dev.UDID).Msg("init WDA USB driver")
|
||
|
||
wd := new(wdaDriver)
|
||
if wd.defaultConn, err = dev.d.NewConnect(dev.Port, 0); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceUSBDriverError,
|
||
fmt.Sprintf("connect port %d failed: %v", dev.Port, err))
|
||
}
|
||
wd.client = convertToHTTPClient(wd.defaultConn.RawConn())
|
||
|
||
if wd.mjpegUSBConn, err = dev.d.NewConnect(dev.MjpegPort, 0); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceUSBDriverError,
|
||
fmt.Sprintf("connect MJPEG port %d failed: %v", dev.MjpegPort, err))
|
||
}
|
||
wd.mjpegClient = convertToHTTPClient(wd.mjpegUSBConn.RawConn())
|
||
|
||
if wd.urlPrefix, err = url.Parse("http://" + dev.UDID); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceUSBDriverError, err.Error())
|
||
}
|
||
if _, err = wd.NewSession(capabilities); err != nil {
|
||
return nil, errors.Wrap(code.IOSDeviceUSBDriverError, err.Error())
|
||
}
|
||
|
||
return wd, nil
|
||
}
|
||
|
||
func (dExt *DriverExt) ConnectMjpegStream(httpClient *http.Client) (err error) {
|
||
if httpClient == nil {
|
||
return errors.New(`'httpClient' can't be nil`)
|
||
}
|
||
|
||
var req *http.Request
|
||
if req, err = http.NewRequest(http.MethodGet, "http://*", nil); err != nil {
|
||
return err
|
||
}
|
||
|
||
var resp *http.Response
|
||
if resp, err = httpClient.Do(req); err != nil {
|
||
return err
|
||
}
|
||
// defer func() { _ = resp.Body.Close() }()
|
||
|
||
var boundary string
|
||
if _, param, err := mime.ParseMediaType(resp.Header.Get("Content-Type")); err != nil {
|
||
return err
|
||
} else {
|
||
boundary = strings.Trim(param["boundary"], "-")
|
||
}
|
||
|
||
mjpegReader := multipart.NewReader(resp.Body, boundary)
|
||
|
||
go func() {
|
||
for {
|
||
select {
|
||
case <-dExt.doneMjpegStream:
|
||
_ = resp.Body.Close()
|
||
return
|
||
default:
|
||
var part *multipart.Part
|
||
if part, err = mjpegReader.NextPart(); err != nil {
|
||
dExt.frame = nil
|
||
continue
|
||
}
|
||
|
||
raw := new(bytes.Buffer)
|
||
if _, err = raw.ReadFrom(part); err != nil {
|
||
dExt.frame = nil
|
||
continue
|
||
}
|
||
dExt.frame = raw
|
||
}
|
||
}
|
||
}()
|
||
|
||
return
|
||
}
|
||
|
||
func (dExt *DriverExt) CloseMjpegStream() {
|
||
dExt.doneMjpegStream <- true
|
||
}
|
||
|
||
type rawResponse []byte
|
||
|
||
func (r rawResponse) checkErr() (err error) {
|
||
reply := new(struct {
|
||
Value struct {
|
||
Err string `json:"error"`
|
||
Message string `json:"message"`
|
||
Traceback string `json:"traceback"` // wda
|
||
Stacktrace string `json:"stacktrace"` // uia
|
||
}
|
||
})
|
||
if err = json.Unmarshal(r, reply); err != nil {
|
||
return err
|
||
}
|
||
if reply.Value.Err != "" {
|
||
errText := reply.Value.Message
|
||
re := regexp.MustCompile(`{.+?=(.+?)}`)
|
||
if re.MatchString(reply.Value.Message) {
|
||
subMatch := re.FindStringSubmatch(reply.Value.Message)
|
||
errText = subMatch[len(subMatch)-1]
|
||
}
|
||
return fmt.Errorf("%s: %s", reply.Value.Err, errText)
|
||
}
|
||
return
|
||
}
|
||
|
||
func (r rawResponse) valueConvertToString() (s string, err error) {
|
||
reply := new(struct{ Value string })
|
||
if err = json.Unmarshal(r, reply); err != nil {
|
||
return "", errors.Wrapf(err, "json.Unmarshal failed, rawResponse: %s", string(r))
|
||
}
|
||
s = reply.Value
|
||
return
|
||
}
|
||
|
||
func (r rawResponse) valueConvertToBool() (b bool, err error) {
|
||
reply := new(struct{ Value bool })
|
||
if err = json.Unmarshal(r, reply); err != nil {
|
||
return false, err
|
||
}
|
||
b = reply.Value
|
||
return
|
||
}
|
||
|
||
func (r rawResponse) valueConvertToSessionInfo() (sessionInfo SessionInfo, err error) {
|
||
reply := new(struct{ Value struct{ SessionInfo } })
|
||
if err = json.Unmarshal(r, reply); err != nil {
|
||
return SessionInfo{}, err
|
||
}
|
||
sessionInfo = reply.Value.SessionInfo
|
||
return
|
||
}
|
||
|
||
func (r rawResponse) valueConvertToJsonRawMessage() (raw builtinJSON.RawMessage, err error) {
|
||
reply := new(struct{ Value builtinJSON.RawMessage })
|
||
if err = json.Unmarshal(r, reply); err != nil {
|
||
return nil, err
|
||
}
|
||
raw = reply.Value
|
||
return
|
||
}
|
||
|
||
func (r rawResponse) valueDecodeAsBase64() (raw *bytes.Buffer, err error) {
|
||
str, err := r.valueConvertToString()
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "failed to convert value to string")
|
||
}
|
||
decodeString, err := base64.StdEncoding.DecodeString(str)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "failed to decode base64 string")
|
||
}
|
||
raw = bytes.NewBuffer(decodeString)
|
||
return
|
||
}
|
||
|
||
var errNoSuchElement = errors.New("no such element")
|
||
|
||
func (r rawResponse) valueConvertToElementID() (id string, err error) {
|
||
reply := new(struct{ Value map[string]string })
|
||
if err = json.Unmarshal(r, reply); err != nil {
|
||
return "", err
|
||
}
|
||
if len(reply.Value) == 0 {
|
||
return "", errNoSuchElement
|
||
}
|
||
if id = elementIDFromValue(reply.Value); id == "" {
|
||
return "", fmt.Errorf("invalid element returned: %+v", reply)
|
||
}
|
||
return
|
||
}
|
||
|
||
func (r rawResponse) valueConvertToElementIDs() (IDs []string, err error) {
|
||
reply := new(struct{ Value []map[string]string })
|
||
if err = json.Unmarshal(r, reply); err != nil {
|
||
return nil, err
|
||
}
|
||
if len(reply.Value) == 0 {
|
||
return nil, errNoSuchElement
|
||
}
|
||
IDs = make([]string, len(reply.Value))
|
||
for i, elem := range reply.Value {
|
||
var id string
|
||
if id = elementIDFromValue(elem); id == "" {
|
||
return nil, fmt.Errorf("invalid element returned: %+v", reply)
|
||
}
|
||
IDs[i] = id
|
||
}
|
||
return
|
||
}
|