API request to create a discussion on a line range

I’ve been trying to automate the creation of discussions on security related findings during a merge request. I’m unable to create a request via API (GraphQL or REST) that can refer to a line range. I believe the closest I got it something like this:

curl --request POST --header "PRIVATE-TOKEN: $APITOKEN" \                                                                          
--form "position[position_type]=text" \
--form "position[base_sha]=b43c2943324f3a641d6cbc12941980104d11e5c8" \
--form "position[head_sha]=78c44fe5181a6f8538befdea088f36086657d561" \
--form "position[start_sha]=b43c2943324f3a641d6cbc12941980104d11e5c8" \
--form "position[new_path]=Controller.java" \
--form "position[old_path]=Controller.java" \
--form "position[new_line]=7" \
--form "position[line_range][start][line_code]=71c8c635708dd177dc44033490f6562c3e6ae303_0_1" \
--form "position[line_range][start][type]=new" \
--form "position[line_range][start][new_line]=1" \
--form "position[line_range][end][line_code]=71c8c635708dd177dc44033490f6562c3e6ae303_0_1" \
--form "position[line_range][end][type]=new" \
--form "position[line_range][end][new_line]=7" \
--form "body=test comment body" https://gitlab.com/api/v4/projects/40276465/merge_requests/22/discussions

But this send me back this error message:

{"message":"400 Bad request - Note {:position=>[\"must be a valid json schema\"]}"}

Can anyone guide me into doing so via python or curl?

Typo on the above:
position[line_range][end][line_code]=71c8c635708dd177dc44033490f6562c3e6ae303_0_1

Should be:
position[line_range][end][line_code]=71c8c635708dd177dc44033490f6562c3e6ae303_0_7

Finally, I understand how it works:

position[old_line], position[new_line], and the file path are three elements to uniquely identify where to put the discussion(comment).
And for the line range, you only have to provide the start and end line_code. For the same, the line code here require three elements to compose:

  • A line code is of the form <SHA>_<old>_<new>, like this: adc83b19e793491b1c6ea0fd8b46cd9f32e292fc_5_5
  • <SHA> is the SHA1 hash of the file name.
  • <old> is the line number before the change.
  • <new> is the line number after the change.
    python code to get line code:
    f"{hashlib.sha1(filename.encode()).hexdigest()}_{oldline}_{newline}"

How to understand the old line and the new line, please refer to the image below(the diff view). There are two columns of line number, the left column is old_line, and the right column is new_line, these two line number, and the file name uniquely identify the code line on the diff view.
For the issue itself, try remove other attributes in line_range start and end, only keep the line_code attribute, and it will work (I had that same issue, and when I deleted other attributes, it worked).

The following python code worked. It is the same when call by curl post.

# coding: utf-8

from gitlab import Gitlab

gl = Gitlab('https://gitlab.xxxx.com', private_token='xxxxx')

project = gl.projects.get(0)

# get Merge Request
mr = project.mergerequests.get(1)

for diff in mr.diffs.list():
    head_commit_sha = diff.head_commit_sha
    base_commit_sha = diff.base_commit_sha
    start_commit_sha = diff.start_commit_sha

    # add comment to line 15-18 (it is one line identified by two line number)
    # range from line 12 to 18
    new_comment = {
        "position":
            {
                "position_type": "text",
                "base_sha": base_commit_sha,
                "head_sha": head_commit_sha,
                "start_sha": start_commit_sha,
                "new_path": "web_ui.py",
                "old_path": "web_ui.py",
                "old_line": 15,
                "new_line": 18,
                "line_range": {
                    "start": {
                        "line_code": "69b4bc2cf16ba42b8118274161223be57f1fb154_12_12",
                    },
                    "end": {
                        "line_code": "69b4bc2cf16ba42b8118274161223be57f1fb154_15_18",
                    }
                }
            },
        "body": "This is a new comment from python gitlab, multilines",
    }
    discussion = mr.discussions.create(new_comment)