OpenAPI: netopeer2 wrong schemas for actions
[netconf.git] / restconf / restconf-openapi / src / main / java / org / opendaylight / restconf / openapi / model / PostEntity.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.openapi.model;
9
10 import static java.util.Objects.requireNonNull;
11 import static javax.ws.rs.core.Response.Status.CREATED;
12 import static javax.ws.rs.core.Response.Status.NO_CONTENT;
13 import static javax.ws.rs.core.Response.Status.OK;
14
15 import com.fasterxml.jackson.core.JsonGenerator;
16 import java.io.IOException;
17 import java.util.List;
18 import javax.ws.rs.HttpMethod;
19 import javax.ws.rs.core.MediaType;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
23 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
24 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
25 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.DocumentedNode;
27 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.Module;
29 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
30 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
31
32 public final class PostEntity extends OperationEntity {
33     private static final String INPUT_SUFFIX = "_input";
34     private static final String INPUT_KEY = "input";
35     private static final String POST_DESCRIPTION = """
36         \n
37         Note:
38         In example payload, you can see only the first data node child of the resource to be created, following the
39         guidelines of RFC 8040, which allows us to create only one resource in POST request.
40         """;
41
42     private final @Nullable DocumentedNode parentNode;
43     private final @NonNull List<SchemaNode> parentNodes;
44
45     public PostEntity(final @NonNull SchemaNode schema, final @NonNull String deviceName,
46             final @NonNull String moduleName, final @NonNull List<ParameterEntity> parameters,
47             final @NonNull String refPath, final @Nullable DocumentedNode parentNode,
48             final @NonNull List<SchemaNode> parentNodes) {
49         super(requireNonNull(schema), deviceName, moduleName, requireNonNull(parameters), requireNonNull(refPath));
50         this.parentNode = parentNode;
51         this.parentNodes = requireNonNull(parentNodes);
52     }
53
54     protected @NonNull String operation() {
55         return "post";
56     }
57
58     @NonNull String summary() {
59         if (parentNode instanceof Module) {
60             return SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName(), moduleName(), moduleName());
61         }
62         if (parentNode != null && !(schema() instanceof OperationDefinition)) {
63             return SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName(), moduleName(),
64                 ((DataSchemaNode) parentNode).getQName().getLocalName());
65         }
66         return SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName(), moduleName(), nodeName());
67     }
68
69     @Override
70     void generateResponses(final @NonNull JsonGenerator generator) throws IOException {
71         generator.writeObjectFieldStart(RESPONSES);
72         if (schema() instanceof OperationDefinition rpc) {
73             final var output = rpc.getOutput();
74             final var operationName = rpc.getQName().getLocalName();
75             if (!output.getChildNodes().isEmpty()) {
76                 final var ref = processOperationsRef(rpc, operationName, "_output");
77                 generator.writeObjectFieldStart(String.valueOf(OK.getStatusCode()));
78                 generator.writeStringField(DESCRIPTION, String.format("RPC %s success", operationName));
79
80                 generator.writeObjectFieldStart(CONTENT);
81                 generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_JSON, ref);
82                 generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_XML, ref);
83                 generator.writeEndObject();
84
85                 generator.writeEndObject();
86
87             } else {
88                 generator.writeObjectFieldStart(String.valueOf(NO_CONTENT.getStatusCode()));
89                 generator.writeStringField(DESCRIPTION, String.format("RPC %s success", operationName));
90                 generator.writeEndObject();
91
92             }
93         } else {
94             generator.writeObjectFieldStart(String.valueOf(CREATED.getStatusCode()));
95             generator.writeStringField(DESCRIPTION, "Created");
96             generator.writeEndObject();
97         }
98         generator.writeEndObject();
99     }
100
101     @Override
102     void generateRequestBody(final @NonNull JsonGenerator generator) throws IOException {
103         generator.writeObjectFieldStart(REQUEST_BODY);
104         if (schema() instanceof OperationDefinition rpc) {
105             final var input = rpc.getInput();
106             final var operationName = rpc.getQName().getLocalName();
107             generator.writeStringField(DESCRIPTION, operationName + INPUT_SUFFIX);
108             generator.writeObjectFieldStart(CONTENT);
109             if (!input.getChildNodes().isEmpty()) {
110                 final var ref = processOperationsRef(rpc, operationName, INPUT_SUFFIX);
111                 generator.writeObjectFieldStart(MediaType.APPLICATION_JSON);
112                 generator.writeObjectFieldStart(SCHEMA);
113                 generator.writeObjectFieldStart("properties");
114                 generator.writeObjectFieldStart(INPUT_KEY);
115                 generator.writeStringField("$ref", ref);
116                 generator.writeStringField(TYPE, OBJECT);
117                 generator.writeEndObject();
118                 generator.writeEndObject();
119                 generator.writeEndObject();
120                 generator.writeEndObject();
121                 generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_XML, ref);
122             } else {
123                 generator.writeObjectFieldStart(MediaType.APPLICATION_JSON);
124                 generator.writeObjectFieldStart(SCHEMA);
125
126                 generator.writeObjectFieldStart(PROPERTIES);
127                 generator.writeObjectFieldStart(INPUT_KEY);
128                 generator.writeStringField(TYPE, OBJECT);
129                 generator.writeEndObject();
130                 generator.writeEndObject();
131
132                 generator.writeStringField(TYPE, OBJECT);
133                 generator.writeEndObject();
134                 generator.writeEndObject();
135
136                 generator.writeObjectFieldStart(MediaType.APPLICATION_XML);
137                 generator.writeObjectFieldStart(SCHEMA);
138
139                 generator.writeObjectFieldStart("xml");
140                 generator.writeStringField(NAME, INPUT_KEY);
141                 generator.writeStringField("namespace", input.getQName().getNamespace().toString());
142                 generator.writeEndObject();
143
144                 generator.writeStringField(TYPE, OBJECT);
145                 generator.writeEndObject();
146                 generator.writeEndObject();
147             }
148             generator.writeEndObject();
149         } else {
150             generator.writeStringField(DESCRIPTION, nodeName());
151             generator.writeObjectFieldStart(CONTENT);
152             final var ref = COMPONENTS_PREFIX + moduleName() + "_" + refPath();
153             var childConfig = true;
154             if (schema() instanceof DataNodeContainer dataSchema) {
155                 final var child = dataSchema.getChildNodes().stream()
156                     .filter(n -> n instanceof ListSchemaNode || n instanceof ContainerSchemaNode)
157                     .findFirst().orElse(null);
158                 if (child != null) {
159                     childConfig = child.isConfiguration();
160                 }
161             }
162             if (parentNode == null && !childConfig) {
163                 generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_JSON, ref);
164             } else {
165                 generator.writeObjectFieldStart(MediaType.APPLICATION_JSON);
166                 generator.writeObjectFieldStart(SCHEMA);
167
168                 generator.writeObjectFieldStart(PROPERTIES);
169                 generator.writeObjectFieldStart(nodeName());
170                 if (schema() instanceof ListSchemaNode) {
171                     generator.writeStringField(TYPE, ARRAY);
172                     generator.writeObjectFieldStart(ITEMS);
173                     generator.writeStringField(REF, ref);
174                     generator.writeStringField(TYPE, OBJECT);
175                     generator.writeEndObject();
176                 } else {
177                     generator.writeStringField(REF, ref);
178                     generator.writeStringField(TYPE, OBJECT);
179                 }
180                 generator.writeEndObject();
181                 generator.writeEndObject();
182                 generator.writeEndObject();
183                 generator.writeEndObject();
184             }
185             generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_XML, ref);
186             generator.writeEndObject();
187         }
188         generator.writeEndObject();
189     }
190
191     @Override
192     public void generateBasics(@NonNull JsonGenerator generator) throws IOException {
193         final var description = description();
194         if (schema() instanceof OperationDefinition) {
195             generator.writeStringField(DESCRIPTION, description);
196         } else {
197             generator.writeStringField(DESCRIPTION, description + POST_DESCRIPTION);
198         }
199         generator.writeStringField(SUMMARY, summary());
200     }
201
202     @Override
203     @NonNull String description() {
204         if (parentNode != null && !(schema() instanceof OperationDefinition)) {
205             return parentNode.getDescription().orElse("");
206         } else {
207             return super.description();
208         }
209     }
210
211     private String processOperationsRef(final OperationDefinition def, final String operationName, final String suf) {
212         final var ref = new StringBuilder(COMPONENTS_PREFIX + moduleName() + "_");
213         if (def instanceof ActionDefinition) {
214             final boolean hasChildNodes = suf.equals(INPUT_SUFFIX) ? !def.getInput().getChildNodes().isEmpty()
215                 : !def.getOutput().getChildNodes().isEmpty();
216             if (hasChildNodes) {
217                 for (final SchemaNode node : parentNodes) {
218                     ref.append(node.getQName().getLocalName()).append("_");
219                 }
220             }
221         }
222         return ref.append(operationName).append(suf).toString();
223     }
224 }