Do not use TOP schemas in PUT requests
[netconf.git] / restconf / restconf-openapi / src / main / java / org / opendaylight / restconf / openapi / model / builder / OperationBuilder.java
1 /*
2  * Copyright (c) 2020 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.builder;
9
10 import static org.opendaylight.restconf.openapi.impl.DefinitionGenerator.INPUT;
11 import static org.opendaylight.restconf.openapi.impl.DefinitionGenerator.INPUT_SUFFIX;
12 import static org.opendaylight.restconf.openapi.impl.DefinitionGenerator.OUTPUT_SUFFIX;
13
14 import com.fasterxml.jackson.databind.node.ArrayNode;
15 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
16 import com.fasterxml.jackson.databind.node.ObjectNode;
17 import java.util.ArrayList;
18 import java.util.List;
19 import javax.ws.rs.HttpMethod;
20 import javax.ws.rs.core.MediaType;
21 import javax.ws.rs.core.Response;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.restconf.openapi.impl.DefinitionNames;
24 import org.opendaylight.restconf.openapi.model.Operation;
25 import org.opendaylight.restconf.openapi.model.Parameter;
26 import org.opendaylight.restconf.openapi.model.Schema;
27 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.InputSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
31 import org.opendaylight.yangtools.yang.model.api.OutputSchemaNode;
32
33 public final class OperationBuilder {
34     public static final String CONTENT_KEY = "content";
35     public static final String COMPONENTS_PREFIX = "#/components/schemas/";
36     public static final String DESCRIPTION_KEY = "description";
37     public static final String INPUT_KEY = "input";
38     public static final String NAME_KEY = "name";
39     public static final String PROPERTIES_KEY = "properties";
40     public static final String REF_KEY = "$ref";
41     public static final String SCHEMA_KEY = "schema";
42     public static final String SUMMARY_TEMPLATE = "%s - %s - %s - %s";
43     public static final String TOP = "_TOP";
44     public static final String XML_KEY = "xml";
45     private static final List<String> MIME_TYPES = List.of(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON);
46     private static final String OBJECT = "object";
47     private static final String TYPE_KEY = "type";
48
49     private OperationBuilder() {
50         // Hidden on purpose
51     }
52
53     public static Operation buildPost(final String parentName, final String nodeName, final String discriminator,
54             final String moduleName, final @NonNull String deviceName, final String description,
55             final List<Parameter> pathParams) {
56         final var summary = SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName, moduleName, nodeName);
57         final List<String> tags = List.of(deviceName + " " + moduleName);
58         final List<Parameter> parameters = new ArrayList<>(pathParams);
59         final ObjectNode ref = JsonNodeFactory.instance.objectNode();
60         final String cleanDefName = parentName + "_" + nodeName;
61         final String defName = cleanDefName + discriminator;
62         final String xmlDefName = cleanDefName + discriminator;
63         ref.put(REF_KEY, COMPONENTS_PREFIX + defName);
64         final ObjectNode requestBody = createRequestBodyParameter(defName, xmlDefName, nodeName, summary);
65         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
66         responses.set(String.valueOf(Response.Status.CREATED.getStatusCode()),
67                 buildResponse(Response.Status.CREATED.getReasonPhrase()));
68
69         return new Operation.Builder()
70             .tags(tags)
71             .parameters(parameters)
72             .requestBody(requestBody)
73             .responses(responses)
74             .description(description)
75             .summary(summary)
76             .build();
77     }
78
79     public static Operation buildGet(final DataSchemaNode node, final String moduleName,
80             final @NonNull String deviceName, final List<Parameter> pathParams, final String defName,
81             final String defNameTop, final boolean isConfig) {
82         final String description = node.getDescription().orElse("");
83         final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.GET, deviceName, moduleName,
84                 node.getQName().getLocalName());
85         final List<String> tags = List.of(deviceName + " " + moduleName);
86         final List<Parameter> parameters = new ArrayList<>(pathParams);
87         parameters.add(buildQueryParameters(isConfig));
88         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
89         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
90         final ObjectNode xmlSchema = JsonNodeFactory.instance.objectNode();
91         if (node.isConfiguration()) {
92             schema.put(REF_KEY, COMPONENTS_PREFIX + defNameTop);
93             xmlSchema.put(REF_KEY, COMPONENTS_PREFIX + defName);
94         }
95         responses.set(String.valueOf(Response.Status.OK.getStatusCode()),
96                 buildResponse(Response.Status.OK.getReasonPhrase(), schema, xmlSchema));
97
98         return new Operation.Builder()
99             .tags(tags)
100             .parameters(parameters)
101             .responses(responses)
102             .description(description)
103             .summary(summary)
104             .build();
105     }
106
107     private static Parameter buildQueryParameters(final boolean isConfig) {
108         final ArrayNode cases = JsonNodeFactory.instance.arrayNode()
109             .add("config")
110             .add("nonconfig")
111             .add("all");
112
113         return new Parameter.Builder()
114             .in("query")
115             .name("content")
116             .required(!isConfig)
117             .schema(new Schema.Builder().type("string").schemaEnum(cases).build())
118             .build();
119     }
120
121     public static Operation buildPut(final DataSchemaNode node, final String parentName, final String moduleName,
122             final @NonNull String deviceName, final List<Parameter> pathParams, final String fullName) {
123         final String nodeName = node.getQName().getLocalName();
124         final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.PUT, moduleName, deviceName, nodeName);
125         final List<String> tags = List.of(deviceName + " " + moduleName);
126         final List<Parameter> parameters = new ArrayList<>(pathParams);
127         final String defName = parentName + "_" + nodeName;
128         final boolean isList = node instanceof ListSchemaNode;
129         final ObjectNode requestBody = createPutRequestBodyParameter(defName, fullName, isList);
130
131         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
132         responses.set(String.valueOf(Response.Status.CREATED.getStatusCode()),
133             buildResponse(Response.Status.CREATED.getReasonPhrase()));
134         responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()), buildResponse("Updated"));
135
136         return new Operation.Builder()
137             .tags(tags)
138             .parameters(parameters)
139             .requestBody(requestBody)
140             .responses(responses)
141             .description(node.getDescription().orElse(""))
142             .summary(summary)
143             .build();
144     }
145
146     public static Operation buildPatch(final String parentName, final String nodeName, final String moduleName,
147             final @NonNull String deviceName, final String description, final List<Parameter> pathParams) {
148         final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.PATCH, deviceName, moduleName, nodeName);
149         final List<String> tags = List.of(deviceName + " " + moduleName);
150         final List<Parameter> parameters = new ArrayList<>(pathParams);
151         final String defName = parentName + "_" + nodeName + TOP;
152         final String xmlDefName = parentName + "_" + nodeName;
153         final ObjectNode requestBody = createRequestBodyParameter(defName, xmlDefName, nodeName, summary);
154
155         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
156         responses.set(String.valueOf(Response.Status.OK.getStatusCode()),
157                 buildResponse(Response.Status.OK.getReasonPhrase()));
158         responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()), buildResponse("Updated"));
159
160         return new Operation.Builder()
161             .tags(tags)
162             .parameters(parameters)
163             .requestBody(requestBody)
164             .responses(responses)
165             .description(description)
166             .summary(summary)
167             .build();
168     }
169
170     public static Operation buildDelete(final DataSchemaNode node, final String moduleName,
171             final @NonNull String deviceName, final List<Parameter> pathParams) {
172         final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.DELETE, deviceName, moduleName,
173                 node.getQName().getLocalName());
174         final List<String> tags = List.of(deviceName + " " + moduleName);
175         final String description = node.getDescription().orElse("");
176         final List<Parameter> parameters = new ArrayList<>(pathParams);
177
178         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
179         responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()), buildResponse("Deleted"));
180
181         return new Operation.Builder()
182             .tags(tags)
183             .parameters(parameters)
184             .responses(responses)
185             .description(description)
186             .summary(summary)
187             .build();
188     }
189
190     public static Operation buildPostOperation(final OperationDefinition operDef, final String moduleName,
191             final @NonNull String deviceName, final String parentName, final DefinitionNames definitionNames,
192             final List<Parameter> parentPathParameters) {
193         final List<Parameter> parameters = new ArrayList<>(parentPathParameters);
194         final String operationName = operDef.getQName().getLocalName();
195         final String inputName = operationName + INPUT_SUFFIX;
196         final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName, moduleName, operationName);
197
198         final InputSchemaNode input = operDef.getInput();
199         final OutputSchemaNode output = operDef.getOutput();
200         ObjectNode requestBody;
201         if (!input.getChildNodes().isEmpty()) {
202             final String discriminator = definitionNames.getDiscriminator(input);
203             final String clearDefName = parentName + "_" + operationName + INPUT_SUFFIX;
204             final String defName = clearDefName + discriminator;
205             final String defTopName = clearDefName + TOP + discriminator;
206             requestBody = createRequestBodyParameter(defTopName, defName, inputName, summary);
207         } else {
208             final ObjectNode payload = JsonNodeFactory.instance.objectNode();
209             final ObjectNode jsonSchema = JsonNodeFactory.instance.objectNode();
210             final ObjectNode properties = JsonNodeFactory.instance.objectNode();
211             final ObjectNode inputSchema = JsonNodeFactory.instance.objectNode();
212             inputSchema.put(TYPE_KEY, OBJECT);
213             properties.set(INPUT_KEY, inputSchema);
214             jsonSchema.put(TYPE_KEY, OBJECT);
215             jsonSchema.set(PROPERTIES_KEY, properties);
216             final ObjectNode content = JsonNodeFactory.instance.objectNode();
217             final ObjectNode jsonTypeValue = JsonNodeFactory.instance.objectNode();
218             jsonTypeValue.set(SCHEMA_KEY, jsonSchema);
219             content.set(MediaType.APPLICATION_JSON, jsonTypeValue);
220
221             final ObjectNode xmlSchema = JsonNodeFactory.instance.objectNode();
222             xmlSchema.put(TYPE_KEY, OBJECT);
223             final ObjectNode xml = JsonNodeFactory.instance.objectNode();
224             xml.put(NAME_KEY, INPUT);
225             xmlSchema.set(XML_KEY, xml);
226             final ObjectNode xmlTypeValue = JsonNodeFactory.instance.objectNode();
227             xmlTypeValue.set(SCHEMA_KEY, xmlSchema);
228             content.set(MediaType.APPLICATION_XML, xmlTypeValue);
229
230             payload.set(CONTENT_KEY, content);
231             payload.put(DESCRIPTION_KEY, inputName);
232             requestBody = payload;
233         }
234         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
235         final String description = String.format("RPC %s success", operationName);
236
237         if (!output.getChildNodes().isEmpty()) {
238             final ObjectNode schema = JsonNodeFactory.instance.objectNode();
239             final String defName = parentName + "_" + operationName + OUTPUT_SUFFIX + TOP
240                     + definitionNames.getDiscriminator(output);
241             schema.put(REF_KEY, COMPONENTS_PREFIX + defName);
242             responses.set(String.valueOf(Response.Status.OK.getStatusCode()), buildResponse(description, schema));
243         } else {
244             responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()), buildResponse(description));
245         }
246         final String desc = operDef.getDescription().orElse("");
247         final List<String> tags = List.of(deviceName + " " + moduleName);
248         return new Operation.Builder()
249             .tags(tags)
250             .parameters(parameters)
251             .requestBody(requestBody)
252             .responses(responses)
253             .description(desc)
254             .summary(summary)
255             .build();
256     }
257
258     private static ObjectNode createRequestBodyParameter(final String defName, final String xmlDefName,
259             final String name, final String summary) {
260         final ObjectNode payload = JsonNodeFactory.instance.objectNode();
261         final ObjectNode content = JsonNodeFactory.instance.objectNode();
262         if (summary != null && summary.contains(HttpMethod.PATCH)) {
263             content.set("application/yang-data+json", buildMimeTypeValue(defName));
264             content.set("application/yang-data+xml", buildMimeTypeValue(xmlDefName));
265         } else {
266             content.set(MediaType.APPLICATION_JSON, buildMimeTypeValue(defName));
267             content.set(MediaType.APPLICATION_XML, buildMimeTypeValue(xmlDefName));
268         }
269         payload.set(CONTENT_KEY, content);
270         payload.put(DESCRIPTION_KEY, name);
271         return payload;
272     }
273
274     private static ObjectNode createPutRequestBodyParameter(final String defName, final String name,
275             final boolean isList) {
276         final ObjectNode payload = JsonNodeFactory.instance.objectNode();
277         final ObjectNode content = JsonNodeFactory.instance.objectNode();
278         final ObjectNode properties = JsonNodeFactory.instance.objectNode();
279         if (isList) {
280             final ObjectNode list = JsonNodeFactory.instance.objectNode();
281             final ObjectNode listValue = JsonNodeFactory.instance.objectNode();
282             listValue.put(TYPE_KEY, "array");
283             listValue.set("items", buildRefSchema(defName));
284             list.set(name, listValue);
285             properties.set(PROPERTIES_KEY, list);
286         } else {
287             final ObjectNode container = JsonNodeFactory.instance.objectNode();
288             container.set(name, buildRefSchema(defName));
289             properties.set(PROPERTIES_KEY, container);
290         }
291         final ObjectNode jsonSchema = JsonNodeFactory.instance.objectNode();
292         jsonSchema.set(SCHEMA_KEY, properties);
293         content.set(MediaType.APPLICATION_JSON, jsonSchema);
294         content.set(MediaType.APPLICATION_XML, buildMimeTypeValue(defName));
295         payload.set(CONTENT_KEY, content);
296         payload.put(DESCRIPTION_KEY, name);
297         return payload;
298     }
299
300     private static ObjectNode buildRefSchema(final String defName) {
301         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
302         schema.put(REF_KEY, COMPONENTS_PREFIX + defName);
303         return schema;
304     }
305
306     private static ObjectNode buildMimeTypeValue(final String defName) {
307         final ObjectNode mimeTypeValue = JsonNodeFactory.instance.objectNode();
308         mimeTypeValue.set(SCHEMA_KEY, buildRefSchema(defName));
309         return mimeTypeValue;
310     }
311
312     public static ObjectNode buildResponse(final String description, final ObjectNode schema,
313             final ObjectNode xmlSchema) {
314         final ObjectNode response = JsonNodeFactory.instance.objectNode();
315         if (!schema.isEmpty()) {
316             final ObjectNode content = JsonNodeFactory.instance.objectNode();
317             final ObjectNode body = JsonNodeFactory.instance.objectNode();
318             final ObjectNode xmlBody = JsonNodeFactory.instance.objectNode();
319
320             body.set(SCHEMA_KEY, schema);
321             xmlBody.set(SCHEMA_KEY, xmlSchema);
322             content.set(MediaType.APPLICATION_JSON, body);
323             content.set(MediaType.APPLICATION_XML, xmlBody);
324
325             response.set(CONTENT_KEY, content);
326         }
327
328         response.put(DESCRIPTION_KEY, description);
329         return response;
330     }
331
332     private static ObjectNode buildResponse(final String description) {
333         final ObjectNode response = JsonNodeFactory.instance.objectNode();
334         response.put(DESCRIPTION_KEY, description);
335         return response;
336     }
337
338     private static ObjectNode buildResponse(final String description, final ObjectNode schema) {
339         final ObjectNode response = JsonNodeFactory.instance.objectNode();
340         final ObjectNode content = JsonNodeFactory.instance.objectNode();
341         final ObjectNode body = JsonNodeFactory.instance.objectNode();
342         for (final String mimeType : MIME_TYPES) {
343             content.set(mimeType, body);
344         }
345         body.set(SCHEMA_KEY, schema);
346         response.set(CONTENT_KEY, content);
347         response.put(DESCRIPTION_KEY, description);
348         return response;
349     }
350 }