Search code examples
apirestgoyoutrackyoutrack-api

How to write such curl request in golang for youtrack rest api?


I am writing golang client with youtrack REST I have written the most most path of API. But faced problem with attaching files to an Issue. So, here there is small and good doc https://www.jetbrains.com/help/youtrack/devportal/api-usecase-attach-files.html

The using the commands from this and other doc pages are worked with curl(via terminal) I am newby in golang, but have to write in this language.

func createForm(form map[string]string) (string, io.Reader, error) {
    body := new(bytes.Buffer)
    mp := multipart.NewWriter(body)
    defer mp.Close()
    for key, val := range form {
        if strings.HasPrefix(val, "@") {
            val = val[1:]
            file, err := os.Open(val)
            if err != nil {
                return "", nil, err
            }
            defer file.Close()
            part, err := mp.CreateFormFile(key, val)
            if err != nil {
                return "", nil, err
            }
            io.Copy(part, file)
        } else {
            mp.WriteField(key, val)
        }
    }
    return mp.FormDataContentType(), body, nil
}

func AttachFileToIssue(path string, issueID string) {
    form := map[string]string{"image": "@image.jpeg", "key": "KEY"}
    _, body, err := createForm(form)
    if err != nil {
        panic(err)
    }

    req, err := http.NewRequest("POST", youTrackUrl+"/api/issues/"+issueID+"/attachments?fields=id,name", body)

    if err != nil {
        // handle err
    }
    req.Header.Set("Authorization", "Bearer "+youTrackToken)
    req.Header.Set("Content-Type", "multipart/form-data")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        // handle err
    }
    fmt.Println(resp)
    if resp.StatusCode == http.StatusOK {
        bodyBytes, err := io.ReadAll(resp.Body)
        if err != nil {
            log.Fatal(err)
        }
        bodyString := string(bodyBytes)
        fmt.Println(bodyString)
    }
    defer func(Body io.ReadCloser) {
        err := Body.Close()
        if err != nil {

        }
    }(resp.Body)
}

The error code is:

{400 Bad Request 400 HTTP/1.1 1 1 map[Access-Control-Expose-Headers:[Location] Cache-Control:[no-cache, no-store, no-transform, must-revalidate] Content-Length:[110] Content-Type:[application/json;charset=utf-8] Date:[Sun, 10 Jul 2022 10:28:24 GMT] Referrer-Policy:[same-origin] Server:[YouTrack] X-Content-Type-Options:[nosniff] X-Frame-Options:[SAMEORIGIN] X-Xss-Protection:[1; mode=block]] 0xc0001cc100 110 [] false false map[] 0xc000254000 <nil>}

I used wireshark to check what is wrong, the problem is with mime-part, something missing.

The curl REQUEST:

curl -v -i -F upload=@/Users/jetbrains/Downloads/youtrack.txt \
-F upload=@/Users/jetbrains/Downloads/youtrack_new.jpeg \
-H 'Authorization: Bearer perm:cm9vdA==.MjZGZWI=.WB02vjX0cM2ltLTJXUE3VOWHpJYYNx' \
-H 'Content-Type: multipart/form-data' \
-X POST 'https://example.youtrack.cloud/api/issues/99-500/attachments?fields=id,name'

Solution

  • Ana Bartasheva is right!

    The solution:

    func createForm(form map[string]string) (string, io.Reader, error) {
        body := new(bytes.Buffer)
        mp := multipart.NewWriter(body)
        defer func(mp *multipart.Writer) {
            err := mp.Close()
            if err != nil {
    
            }
        }(mp)
        var file *os.File
        for key, val := range form {
            if strings.HasPrefix(val, "@") {
                val = val[1:]
                file, err := os.Open(val)
                if err != nil {
                    return "", nil, err
                }
                part, err := mp.CreateFormFile(key, val)
                if err != nil {
                    return "", nil, err
                }
                _, err = io.Copy(part, file)
                if err != nil {
                    return "", nil, err
                }
            } else {
                err := mp.WriteField(key, val)
                if err != nil {
                    return "", nil, err
                }
            }
        }
        defer func(file *os.File) {
            err := file.Close()
            if err != nil {
    
            }
        }(file)
    
        return mp.FormDataContentType(), body, nil
    }
    
    func AttachFileToIssue(path string, issueID string) string {
        form := map[string]string{"path": "@" + path}
        mp, body, err := createForm(form)
        if err != nil {
            panic(err)
        }
        req, err := http.NewRequest("POST", youTrackUrl+"/api/issues/"+issueID+"/attachments?fields=id,name", body)
    
        if err != nil {
            // handle err
        }
    
        req.Header.Set("Authorization", "Bearer "+youTrackToken)
        req.Header.Set("Content-Type", mp)
    
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            // handle err
        }
        if resp.StatusCode == http.StatusOK {
            bodyBytes, err := io.ReadAll(resp.Body)
            if err != nil {
                log.Fatal(err)
            }
            bodyString := string(bodyBytes)
            return bodyString
        }
        defer func(Body io.ReadCloser) {
            err := Body.Close()
            if err != nil {
    
            }
        }(resp.Body)
        return ""
    }