Remove "/" sign in AbstractRestconfStreamRegistry
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / server / api / PatchBody.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.restconf.server.api;
9
10 import static java.util.Objects.requireNonNull;
11
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.text.ParseException;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.eclipse.jdt.annotation.NonNullByDefault;
17 import org.opendaylight.restconf.api.ApiPath;
18 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
19 import org.opendaylight.restconf.common.patch.PatchContext;
20 import org.opendaylight.restconf.server.api.DatabindPath.Data;
21 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
22 import org.opendaylight.yangtools.concepts.Immutable;
23 import org.opendaylight.yangtools.yang.common.ErrorTag;
24 import org.opendaylight.yangtools.yang.common.ErrorType;
25
26 /**
27  * A YANG Patch body.
28  */
29 public abstract sealed class PatchBody extends RequestBody permits JsonPatchBody, XmlPatchBody {
30     /**
31      * Resource context needed to completely resolve a {@link PatchBody}.
32      */
33     @NonNullByDefault
34     public abstract static class ResourceContext implements Immutable {
35         protected final Data path;
36
37         protected ResourceContext(final Data path) {
38             this.path = requireNonNull(path);
39         }
40
41         /**
42          * Return a {@link ResourceContext} for a sub-resource identified by an {@link ApiPath}.
43          *
44          * @param apiPath sub-resource
45          * @return A {@link ResourceContext}
46          * @throws RestconfDocumentedException if the sub-resource cannot be resolved
47          */
48         protected abstract ResourceContext resolveRelative(ApiPath apiPath);
49     }
50
51     PatchBody(final InputStream inputStream) {
52         super(inputStream);
53     }
54
55     public final @NonNull PatchContext toPatchContext(final @NonNull ResourceContext resource) throws IOException {
56         try (var is = consume()) {
57             return toPatchContext(resource, is);
58         }
59     }
60
61     abstract @NonNull PatchContext toPatchContext(@NonNull ResourceContext resource, @NonNull InputStream inputStream)
62         throws IOException;
63
64     static final Data parsePatchTarget(final @NonNull ResourceContext resource, final String target) {
65         // As per: https://www.rfc-editor.org/rfc/rfc8072#page-18:
66         //
67         //        "Identifies the target data node for the edit
68         //        operation.  If the target has the value '/', then
69         //        the target data node is the target resource.
70         //        The target node MUST identify a data resource,
71         //        not the datastore resource.";
72         //
73         final ApiPath targetPath;
74         try {
75             targetPath = ApiPath.parse(target.startsWith("/") ? target.substring(1) : target);
76         } catch (ParseException e) {
77             throw new RestconfDocumentedException("Failed to parse edit target '" + target + "'",
78                 ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE, e);
79         }
80
81         final Data result;
82         try {
83             result = resource.resolveRelative(targetPath).path;
84         } catch (RestconfDocumentedException e) {
85             throw new RestconfDocumentedException("Invalid edit target '" + targetPath + "'",
86                 ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE, e);
87         }
88         if (result.instance().isEmpty()) {
89             throw new RestconfDocumentedException("Target node resource must not be a datastore resource",
90                 ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE);
91         }
92         return result;
93     }
94
95     /**
96      * Not all patch operations support value node. Check if operation requires value or not.
97      *
98      * @param operation Patch edit operation
99      * @return true if operation requires value, false otherwise
100      */
101     static final boolean requiresValue(final Operation operation) {
102         return switch (operation) {
103             case Create, Insert, Merge, Replace -> true;
104             case Delete, Move, Remove -> false;
105         };
106     }
107 }