package playwright import ( "encoding/base64" "encoding/json" "errors" "fmt" "os" "strings" ) type apiRequestImpl struct { *Playwright } func (r *apiRequestImpl) NewContext(options ...APIRequestNewContextOptions) (APIRequestContext, error) { overrides := map[string]interface{}{} if len(options) == 1 { if options[0].ClientCertificates != nil { certs, err := transformClientCertificate(options[0].ClientCertificates) if err != nil { return nil, err } overrides["clientCertificates"] = certs options[0].ClientCertificates = nil } if options[0].ExtraHttpHeaders != nil { overrides["extraHTTPHeaders"] = serializeMapToNameAndValue(options[0].ExtraHttpHeaders) options[0].ExtraHttpHeaders = nil } if options[0].StorageStatePath != nil { var storageState *StorageState storageString, err := os.ReadFile(*options[0].StorageStatePath) if err != nil { return nil, fmt.Errorf("could not read storage state file: %w", err) } err = json.Unmarshal(storageString, &storageState) if err != nil { return nil, fmt.Errorf("could not parse storage state file: %w", err) } options[0].StorageState = storageState options[0].StorageStatePath = nil } } channel, err := r.channel.Send("newRequest", options, overrides) if err != nil { return nil, err } return fromChannel(channel).(*apiRequestContextImpl), nil } func newApiRequestImpl(pw *Playwright) *apiRequestImpl { return &apiRequestImpl{pw} } type apiRequestContextImpl struct { channelOwner tracing *tracingImpl closeReason *string } func (r *apiRequestContextImpl) Dispose(options ...APIRequestContextDisposeOptions) error { if len(options) == 1 { r.closeReason = options[0].Reason } _, err := r.channel.Send("dispose", map[string]interface{}{ "reason": r.closeReason, }) if errors.Is(err, ErrTargetClosed) { return nil } return err } func (r *apiRequestContextImpl) Delete(url string, options ...APIRequestContextDeleteOptions) (APIResponse, error) { opts := APIRequestContextFetchOptions{ Method: String("DELETE"), } if len(options) == 1 { err := assignStructFields(&opts, options[0], false) if err != nil { return nil, err } } return r.Fetch(url, opts) } func (r *apiRequestContextImpl) Fetch(urlOrRequest interface{}, options ...APIRequestContextFetchOptions) (APIResponse, error) { switch v := urlOrRequest.(type) { case string: return r.innerFetch(v, nil, options...) case Request: return r.innerFetch("", v, options...) default: return nil, fmt.Errorf("urlOrRequest has unsupported type: %T", urlOrRequest) } } func (r *apiRequestContextImpl) innerFetch(url string, request Request, options ...APIRequestContextFetchOptions) (APIResponse, error) { if r.closeReason != nil { return nil, fmt.Errorf("%w: %s", ErrTargetClosed, *r.closeReason) } overrides := map[string]interface{}{} if url != "" { overrides["url"] = url } else if request != nil { overrides["url"] = request.URL() } if len(options) == 1 { if options[0].MaxRedirects != nil && *options[0].MaxRedirects < 0 { return nil, errors.New("maxRedirects must be non-negative") } if options[0].MaxRetries != nil && *options[0].MaxRetries < 0 { return nil, errors.New("maxRetries must be non-negative") } // only one of them can be specified if countNonNil(options[0].Data, options[0].Form, options[0].Multipart) > 1 { return nil, errors.New("only one of 'data', 'form' or 'multipart' can be specified") } if options[0].Method == nil { if request != nil { options[0].Method = String(request.Method()) } else { options[0].Method = String("GET") } } if options[0].Headers == nil { if request != nil { overrides["headers"] = serializeMapToNameAndValue(request.Headers()) } } else { overrides["headers"] = serializeMapToNameAndValue(options[0].Headers) options[0].Headers = nil } if options[0].Data != nil { switch v := options[0].Data.(type) { case string: headersArray, ok := overrides["headers"].([]map[string]string) if ok && isJsonContentType(headersArray) { if json.Valid([]byte(v)) { overrides["jsonData"] = v } else { data, err := json.Marshal(v) if err != nil { return nil, fmt.Errorf("could not marshal data: %w", err) } overrides["jsonData"] = string(data) } } else { overrides["postData"] = base64.StdEncoding.EncodeToString([]byte(v)) } case []byte: overrides["postData"] = base64.StdEncoding.EncodeToString(v) case interface{}: data, err := json.Marshal(v) if err != nil { return nil, fmt.Errorf("could not marshal data: %w", err) } overrides["jsonData"] = string(data) default: return nil, errors.New("data must be a string, []byte, or interface{} that can marshal to json") } options[0].Data = nil } else if options[0].Form != nil { form, ok := options[0].Form.(map[string]interface{}) if !ok { return nil, errors.New("form must be a map") } overrides["formData"] = serializeMapToNameValue(form) options[0].Form = nil } else if options[0].Multipart != nil { _, ok := options[0].Multipart.(map[string]interface{}) if !ok { return nil, errors.New("multipart must be a map") } multipartData := []map[string]interface{}{} for name, value := range options[0].Multipart.(map[string]interface{}) { switch v := value.(type) { case InputFile: multipartData = append(multipartData, map[string]interface{}{ "name": name, "file": map[string]string{ "name": v.Name, "mimeType": v.MimeType, "buffer": base64.StdEncoding.EncodeToString(v.Buffer), }, }) default: multipartData = append(multipartData, map[string]interface{}{ "name": name, "value": String(fmt.Sprintf("%v", v)), }) } } overrides["multipartData"] = multipartData options[0].Multipart = nil } else if request != nil { postDataBuf, err := request.PostDataBuffer() if err == nil { overrides["postData"] = base64.StdEncoding.EncodeToString(postDataBuf) } } if options[0].Params != nil { overrides["params"] = serializeMapToNameValue(options[0].Params) options[0].Params = nil } } response, err := r.channel.Send("fetch", options, overrides) if err != nil { return nil, err } return newAPIResponse(r, response.(map[string]interface{})), nil } func (r *apiRequestContextImpl) Get(url string, options ...APIRequestContextGetOptions) (APIResponse, error) { opts := APIRequestContextFetchOptions{ Method: String("GET"), } if len(options) == 1 { err := assignStructFields(&opts, options[0], false) if err != nil { return nil, err } } return r.Fetch(url, opts) } func (r *apiRequestContextImpl) Head(url string, options ...APIRequestContextHeadOptions) (APIResponse, error) { opts := APIRequestContextFetchOptions{ Method: String("HEAD"), } if len(options) == 1 { err := assignStructFields(&opts, options[0], false) if err != nil { return nil, err } } return r.Fetch(url, opts) } func (r *apiRequestContextImpl) Patch(url string, options ...APIRequestContextPatchOptions) (APIResponse, error) { opts := APIRequestContextFetchOptions{ Method: String("PATCH"), } if len(options) == 1 { err := assignStructFields(&opts, options[0], false) if err != nil { return nil, err } } return r.Fetch(url, opts) } func (r *apiRequestContextImpl) Put(url string, options ...APIRequestContextPutOptions) (APIResponse, error) { opts := APIRequestContextFetchOptions{ Method: String("PUT"), } if len(options) == 1 { err := assignStructFields(&opts, options[0], false) if err != nil { return nil, err } } return r.Fetch(url, opts) } func (r *apiRequestContextImpl) Post(url string, options ...APIRequestContextPostOptions) (APIResponse, error) { opts := APIRequestContextFetchOptions{ Method: String("POST"), } if len(options) == 1 { err := assignStructFields(&opts, options[0], false) if err != nil { return nil, err } } return r.Fetch(url, opts) } func (r *apiRequestContextImpl) StorageState(path ...string) (*StorageState, error) { result, err := r.channel.SendReturnAsDict("storageState") if err != nil { return nil, err } if len(path) == 1 { file, err := os.Create(path[0]) if err != nil { return nil, err } if err := json.NewEncoder(file).Encode(result); err != nil { return nil, err } if err := file.Close(); err != nil { return nil, err } } var storageState StorageState remapMapToStruct(result, &storageState) return &storageState, nil } func newAPIRequestContext(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *apiRequestContextImpl { rc := &apiRequestContextImpl{} rc.createChannelOwner(rc, parent, objectType, guid, initializer) rc.tracing = fromChannel(initializer["tracing"]).(*tracingImpl) return rc } type apiResponseImpl struct { request *apiRequestContextImpl initializer map[string]interface{} headers *rawHeaders } func (r *apiResponseImpl) Body() ([]byte, error) { result, err := r.request.channel.SendReturnAsDict("fetchResponseBody", []map[string]interface{}{ { "fetchUid": r.fetchUid(), }, }) if err != nil { if errors.Is(err, ErrTargetClosed) { return nil, errors.New("response has been disposed") } return nil, err } body := result["binary"] if body == nil { return nil, errors.New("response has been disposed") } return base64.StdEncoding.DecodeString(body.(string)) } func (r *apiResponseImpl) Dispose() error { _, err := r.request.channel.Send("disposeAPIResponse", []map[string]interface{}{ { "fetchUid": r.fetchUid(), }, }) return err } func (r *apiResponseImpl) Headers() map[string]string { return r.headers.Headers() } func (r *apiResponseImpl) HeadersArray() []NameValue { return r.headers.HeadersArray() } func (r *apiResponseImpl) JSON(v interface{}) error { body, err := r.Body() if err != nil { return err } return json.Unmarshal(body, &v) } func (r *apiResponseImpl) Ok() bool { return r.Status() == 0 || (r.Status() >= 200 && r.Status() <= 299) } func (r *apiResponseImpl) Status() int { return int(r.initializer["status"].(float64)) } func (r *apiResponseImpl) StatusText() string { return r.initializer["statusText"].(string) } func (r *apiResponseImpl) Text() (string, error) { body, err := r.Body() if err != nil { return "", err } return string(body), nil } func (r *apiResponseImpl) URL() string { return r.initializer["url"].(string) } func (r *apiResponseImpl) fetchUid() string { return r.initializer["fetchUid"].(string) } func (r *apiResponseImpl) fetchLog() ([]string, error) { ret, err := r.request.channel.Send("fetchLog", map[string]interface{}{ "fetchUid": r.fetchUid(), }) if err != nil { return nil, err } result := make([]string, len(ret.([]interface{}))) for i, v := range ret.([]interface{}) { result[i] = v.(string) } return result, nil } func newAPIResponse(context *apiRequestContextImpl, initializer map[string]interface{}) *apiResponseImpl { return &apiResponseImpl{ request: context, initializer: initializer, headers: newRawHeaders(initializer["headers"]), } } func countNonNil(args ...interface{}) int { count := 0 for _, v := range args { if v != nil { count++ } } return count } func isJsonContentType(headers []map[string]string) bool { if len(headers) > 0 { for _, v := range headers { if strings.ToLower(v["name"]) == "content-type" { if v["value"] == "application/json" { return true } } } } return false } func serializeMapToNameValue(data map[string]interface{}) []map[string]string { serialized := make([]map[string]string, 0, len(data)) for k, v := range data { serialized = append(serialized, map[string]string{ "name": k, "value": fmt.Sprintf("%v", v), }) } return serialized }