54afe2325c4e1610b01be4a911290760210978a8
[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
44     public PostEntity(final @NonNull SchemaNode schema, final @NonNull String deviceName,
45             final @NonNull String moduleName, final @NonNull List<ParameterEntity> parameters,
46             final @NonNull String refPath, final @Nullable DocumentedNode parentNode) {
47         super(requireNonNull(schema), deviceName, moduleName, requireNonNull(parameters), requireNonNull(refPath));
48         this.parentNode = parentNode;
49     }
50
51     protected @NonNull String operation() {
52         return "post";
53     }
54
55     @NonNull String summary() {
56         if (parentNode instanceof Module) {
57             return SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName(), moduleName(), moduleName());
58         }
59         if (parentNode != null && !(schema() instanceof OperationDefinition)) {
60             return SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName(), moduleName(),
61                 ((DataSchemaNode) parentNode).getQName().getLocalName());
62         }
63         return SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName(), moduleName(), nodeName());
64     }
65
66     @Override
67     void generateResponses(final @NonNull JsonGenerator generator) throws IOException {
68         generator.writeObjectFieldStart(RESPONSES);
69         if (schema() instanceof OperationDefinition rpc) {
70             final var output = rpc.getOutput();
71             final var operationName = rpc.getQName().getLocalName();
72             if (!output.getChildNodes().isEmpty()) {
73                 // TODO: add proper discriminator from DefinitionNames when schemas re-implementation is done
74                 final var ref = processOperationsRef(rpc, operationName, "_output");
75                 generator.writeObjectFieldStart(String.valueOf(OK.getStatusCode()));
76                 generator.writeStringField(DESCRIPTION, String.format("RPC %s success", operationName));
77
78                 generator.writeObjectFieldStart(CONTENT);
79                 generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_JSON, ref);
80                 generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_XML, ref);
81                 generator.writeEndObject();
82
83                 generator.writeEndObject();
84
85             } else {
86                 generator.writeObjectFieldStart(String.valueOf(NO_CONTENT.getStatusCode()));
87                 generator.writeStringField(DESCRIPTION, String.format("RPC %s success", operationName));
88                 generator.writeEndObject();
89
90             }
91         } else {
92             generator.writeObjectFieldStart(String.valueOf(CREATED.getStatusCode()));
93             generator.writeStringField(DESCRIPTION, "Created");
94             generator.writeEndObject();
95         }
96         generator.writeEndObject();
97     }
98
99     @Override
100     void generateRequestBody(final @NonNull JsonGenerator generator) throws IOException {
101         generator.writeObjectFieldStart(REQUEST_BODY);
102         if (schema() instanceof OperationDefinition rpc) {
103             final var input = rpc.getInput();
104             final var operationName = rpc.getQName().getLocalName();
105             generator.writeStringField(DESCRIPTION, operationName + INPUT_SUFFIX);
106             generator.writeObjectFieldStart(CONTENT);
107             if (!input.getChildNodes().isEmpty()) {
108                 // TODO: add proper discriminator from DefinitionNames when schemas re-implementation is done
109                 final var ref = processOperationsRef(rpc, operationName, INPUT_SUFFIX);
110                 generator.writeObjectFieldStart(MediaType.APPLICATION_JSON);
111                 generator.writeObjectFieldStart(SCHEMA);
112                 generator.writeObjectFieldStart("properties");
113                 generator.writeObjectFieldStart(INPUT_KEY);
114                 generator.writeStringField("$ref", ref);
115                 generator.writeStringField(TYPE, OBJECT);
116                 generator.writeEndObject();
117                 generator.writeEndObject();
118                 generator.writeEndObject();
119                 generator.writeEndObject();
120                 generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_XML, ref);
121             } else {
122                 generator.writeObjectFieldStart(MediaType.APPLICATION_JSON);
123                 generator.writeObjectFieldStart(SCHEMA);
124
125                 generator.writeObjectFieldStart(PROPERTIES);
126                 generator.writeObjectFieldStart(INPUT_KEY);
127                 generator.writeStringField(TYPE, OBJECT);
128                 generator.writeEndObject();
129                 generator.writeEndObject();
130
131                 generator.writeStringField(TYPE, OBJECT);
132                 generator.writeEndObject();
133                 generator.writeEndObject();
134
135                 generator.writeObjectFieldStart(MediaType.APPLICATION_XML);
136                 generator.writeObjectFieldStart(SCHEMA);
137
138                 generator.writeObjectFieldStart("xml");
139                 generator.writeStringField(NAME, INPUT_KEY);
140                 generator.writeStringField("namespace", input.getQName().getNamespace().toString());
141                 generator.writeEndObject();
142
143                 generator.writeStringField(TYPE, OBJECT);
144                 generator.writeEndObject();
145                 generator.writeEndObject();
146             }
147             generator.writeEndObject();
148         } else {
149             generator.writeStringField(DESCRIPTION, nodeName());
150             generator.writeObjectFieldStart(CONTENT);
151             final var ref = COMPONENTS_PREFIX + moduleName() + "_" + refPath();
152             //TODO: Remove if and fix this weird logic of top level nodes
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         if (def instanceof ActionDefinition && parentNode != null) {
213             final var parentName = ((DataSchemaNode) parentNode).getQName().getLocalName();
214             final boolean hasChildNodes = suf.equals(INPUT_SUFFIX) ? !def.getInput().getChildNodes().isEmpty()
215                     : !def.getOutput().getChildNodes().isEmpty();
216             if (hasChildNodes) {
217                 return COMPONENTS_PREFIX + moduleName() + "_" + parentName + "_" + operationName + suf;
218             }
219         }
220         return COMPONENTS_PREFIX + moduleName() + "_" + operationName + suf;
221     }
222 }