microsoftgraph/msgraph-sdk-java

Can't get/patch fields for SharePoint drive item by path

Opened this issue · 4 comments

Describe the bug

I am trying to get and patch fields for SharePoint drive item by path.

In v5 of SDK I was able to do something like:

graphClient.sites(siteId).lists(libraryId).drive().root().itemWithPath(path)
.listItem().fields().buildRequest().patch(sourceFieldValueSet);

which leads to:
https://graph.microsoft.com/v1.0/sites/<siteId>/lists/<libraryId>/drive/root:/<filename>:/listItem/fields

In v6 of SDK this endpoint is not accessible. I found 2 way to get fields:

graphClient.sites().bySiteId(siteId).lists().byListId(libraryId).items().byListItemId(path).fields().get();
graphClient.drives().byDriveId(driveId).list().items().byListItemId(path).fields().get();

As I've read in #1961 (comment) it should be easy to use root:/folder/file: instead of item-id, but it is not working.

Expected behavior

Easy way to call https://graph.microsoft.com/v1.0/sites/<siteId>/lists/<libraryId>/drive/root:/<filename>:/listItem/fields and other similar endpoints

How to reproduce

I've written some example code:

    public void fileTest() {
        try {
            final String siteId = "<removed>";
            final String libraryId = "<removed>";
            final String filename = "doc.txt";

            final List<String> paths = List.of( //
                    filename, //
                    "/" + filename, //
                    "root:/" + filename, //
                    "root:/" + filename + ":" //
            );

            System.out.println("by sites");
            for (final String path : paths) {
                try {
                    graphServiceClient.sites().bySiteId(siteId).lists().byListId(libraryId).items().byListItemId(path)
                            .fields().get();
                    System.out.println("OK for " + path);
                } catch (final ODataError e) {
                    System.out.println("Error for " + path + " " + e.getMessage());
                }
            }

            System.out.println("\nby drives");
            final Drive drive = graphServiceClient.sites().bySiteId(siteId).lists().byListId(libraryId).drive().get();
            for (final String path : paths) {
                try {
                    graphServiceClient.drives().byDriveId(drive.getId()).list().items().byListItemId(path).fields()
                            .get();
                    System.out.println("OK for " + path);
                } catch (final ODataError e) {
                    System.out.println("Error for " + path + " " + e.getMessage());
                }
            }

            System.out.println("\nProof it's there:");
            final var driveItem = graphServiceClient.drives().byDriveId(drive.getId()).items()
                    .byDriveItemId("root:/" + filename + ":").get();
            System.out.println(driveItem.getId() + " " + driveItem.getName());
        } catch (final Exception e) {
            System.out.println("Other Exception:");
            e.printStackTrace();
        }
    }

which leads to output:

by sites
Error for doc.txt Item not found
Error for /doc.txt Item not found
Error for root:/doc.txt Resource not found for the segment 'getByPath'.
Error for root:/doc.txt: Resource not found for the segment 'getByPath'.

by drives
Error for doc.txt Item not found
Error for /doc.txt Item not found
Error for root:/doc.txt Item not found
Error for root:/doc.txt: Item not found

Proof it's there:
014ICIUEH23MWGBJOMZRF3RFLCCU7KXWAI doc.txt

SDK Version

6.13.0

Latest version known to work for scenario above?

5.73.0 / 5.80.0

Known Workarounds

No response

Debug output

Click to expand log ```
</details>


### Configuration

- OS: Linux
- architecture x64

### Other information

_No response_

I would appreciate it if someone could help me. I also tried to use the ID I got from driveItem.getId() in my "proof" section (014ICIUEH23MWGBJOMZRF3RFLCCU7KXWAI) to make the request instead of the path:

String driveItemId = "014ICIUEH23MWGBJOMZRF3RFLCCU7KXWAI";
graphServiceClient.sites().bySiteId(siteId).lists().byListId(libraryId).items().byListItemId(driveItemId).fields().get();
graphServiceClient.drives().byDriveId(drive.getId()).list().items().byListItemId(driveItemId).fields().get();

But in both cases I get this error: Provided list item identifer is not in an allowed format

Hi @poschi3.

Sorry for the poor experience here.

Please try the withUrl method as a workaround as I investigate this further:

client.sites().bySiteId("").lists().byListId("").items().byListItemId("").fields().withUrl(
    "https://graph.microsoft.com/v1.0/sites/<siteId>/lists/<libraryId>/drive/root:/<filename>:/listItem/fields"
).get();

withUrl overrides all the values passed into the fluent request builder methods. Trick is to call it on an object who's HTTP method deserializes the response to an object you expect.

Please let me know if this helps.

Hi @Ndiritu

thank you for your reply. In the meantime I've done something like this:

    private FieldValueSet sendListItemFieldsPatch(final String siteId, final String listId,
            final String unescapedPath, final FieldValueSet body) {
        final HashMap<String, Object> pathParameters = new HashMap<>();
        pathParameters.put("site%2Did", siteId);
        pathParameters.put("list%2Did", listId);
        pathParameters.put("path", "root:/" + unescapedPath + ":");

        final RequestInformation requestInfo = new RequestInformation();
        requestInfo.httpMethod = HttpMethod.PATCH;
        requestInfo.urlTemplate = "{+baseurl}/sites/{site%2Did}/lists/{list%2Did}/drive/{path}/listItem/fields";
        requestInfo.pathParameters = pathParameters;
        requestInfo.headers.tryAdd("Accept", "application/json");
        requestInfo.setContentFromParsable(getGraphClient().getRequestAdapter(), "application/json", body);

        final HashMap<String, ParsableFactory<? extends Parsable>> errorMapping = new HashMap<>();
        errorMapping.put("XXX", ODataError::createFromDiscriminatorValue);

        return getGraphClient().getRequestAdapter().send(requestInfo, errorMapping,
                FieldValueSet::createFromDiscriminatorValue);
    }

But now I'm rewriting it to use .withUrl() but still using RequestInformation with .getUri().toURL().toString() to get urlTemplate with escaping of parameter values.

Please also reconsider to not remove non canonical request path. With the SDK v6 I have to double the number of requests because I have to retrieve the drive to get the drive ID to put it into the next request which was before just one request total.

Now I'm doing something like this. It's may be helpful for someone.

    private String urlGenerator(final String urlTemplate, final Map<String, Object> pathParameters,
            final Map<String, Object> queryParameters) {

        final RequestInformation requestInfo = new RequestInformation();
        requestInfo.urlTemplate = urlTemplate;

        requestInfo.pathParameters = new HashMap<>(pathParameters);
        final String baseUrl = getGraphClient().getRequestAdapter().getBaseUrl();
        requestInfo.pathParameters.put("baseurl", baseUrl);
        if (queryParameters != null) {
            for (final Entry<String, Object> parameter : queryParameters.entrySet()) {
                requestInfo.addQueryParameter(parameter.getKey(), parameter.getValue());
            }
        }

        try {
            return requestInfo.getUri().toURL().toString();
        } catch (final URISyntaxException | MalformedURLException | IllegalArgumentException
                | IllegalStateException e) {
            throw new RuntimeException("Error building url", e);
        }
    }

    private FieldValueSet sendListItemFieldsPatch(final String siteId, final String listId, final String unescapedPath,
            final FieldValueSet body) {
        final String urlTemplate = "{+baseurl}/sites/{site%2Did}/lists/{list%2Did}/drive/{path}/listItem/fields";

        final Map<String, Object> pathParameters = Map.of( //
                "site%2Did", siteId, //
                "list%2Did", listId, //
                "path", "root:/" + unescapedPath + ":" // root:/doc.txt:
        );

        final String rawUrl = urlGenerator(urlTemplate, pathParameters, Collections.emptyMap());

        return getGraphClient().sites().bySiteId("").lists().byListId("").items().byListItemId("").fields()
                .withUrl(rawUrl).patch(body);
    }