Bump upstreams
[netconf.git] / restconf / restconf-openapi / src / main / java / org / opendaylight / restconf / openapi / model / PropertyEntity.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
12 import com.fasterxml.jackson.core.JsonGenerator;
13 import com.google.common.collect.Range;
14 import com.google.common.collect.RangeSet;
15 import dk.brics.automaton.RegExp;
16 import java.io.IOException;
17 import java.math.BigDecimal;
18 import java.math.BigInteger;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.regex.Pattern;
25 import java.util.stream.Collectors;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.opendaylight.restconf.openapi.impl.DefinitionNames;
28 import org.opendaylight.yangtools.yang.common.AbstractQName;
29 import org.opendaylight.yangtools.yang.common.Decimal64;
30 import org.opendaylight.yangtools.yang.common.XMLNamespace;
31 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
36 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
38 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint;
39 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraintAware;
40 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
45 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.meta.ModelStatement;
49 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
52 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
53 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
54 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
55 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
56 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
57 import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
58 import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
59 import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
60 import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
63 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
64 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
67 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
68 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
69 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
70 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
71 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 public class PropertyEntity {
76     private static final Logger LOG = LoggerFactory.getLogger(PropertyEntity.class);
77     private static final String COMPONENTS_PREFIX = "#/components/schemas/";
78     private static final String ARRAY_TYPE = "array";
79     private static final String DESCRIPTION = "description";
80     private static final String DEFAULT = "default";
81     private static final String ENUM = "enum";
82     private static final String EXAMPLE = "example";
83     private static final String FORMAT = "format";
84     private static final String ITEMS = "items";
85     private static final String STRING_TYPE = "string";
86     private static final String OBJECT_TYPE = "object";
87     private static final String NUMBER_TYPE = "number";
88     private static final String TYPE = "type";
89
90     private static final Pattern AUTOMATON_SPECIAL_CHARACTERS = Pattern.compile("[@&\"<>#~]");
91     // Adaptation from YANG regex to Automaton regex
92     // See https://github.com/mifmif/Generex/blob/master/src/main/java/com/mifmif/common/regex/Generex.java
93     private static final Map<String, String> PREDEFINED_CHARACTER_CLASSES = Map.of("\\\\d", "[0-9]",
94         "\\\\D", "[^0-9]", "\\\\s", "[ \t\n\f\r]", "\\\\S", "[^ \t\n\f\r]",
95         "\\\\w", "[a-zA-Z_0-9]", "\\\\W", "[^a-zA-Z_0-9]");
96
97     private final @NonNull DataSchemaNode node;
98     private final @NonNull JsonGenerator generator;
99     private final @NonNull List<String> required;
100     private final @NonNull String parentName;
101     private final @NonNull DefinitionNames definitionNames;
102
103     public PropertyEntity(final @NonNull DataSchemaNode node, final @NonNull JsonGenerator generator,
104             final @NonNull SchemaInferenceStack stack, final @NonNull List<String> required,
105             final @NonNull String parentName, final boolean isParentConfig,
106             final @NonNull DefinitionNames definitionNames) throws IOException {
107         this.node = requireNonNull(node);
108         this.generator = requireNonNull(generator);
109         this.required = requireNonNull(required);
110         this.parentName = requireNonNull(parentName);
111         this.definitionNames = requireNonNull(definitionNames);
112         generate(stack, isParentConfig);
113
114     }
115
116     private void generate(final SchemaInferenceStack stack, final boolean isParentConfig) throws IOException {
117         if (node instanceof ChoiceSchemaNode choice) {
118             stack.enterSchemaTree(node.getQName());
119             final var isConfig = isParentConfig && node.isConfiguration();
120             processChoiceNodeRecursively(isConfig, stack, choice);
121             stack.exit();
122         } else {
123             generator.writeObjectFieldStart(node.getQName().getLocalName());
124             processChildNode(node, stack, isParentConfig);
125             generator.writeEndObject();
126         }
127     }
128
129     private void processChoiceNodeRecursively(final boolean isConfig, final SchemaInferenceStack stack,
130             final ChoiceSchemaNode choice) throws IOException {
131         if (!choice.getCases().isEmpty()) {
132             final var caseSchemaNode = choice.getDefaultCase().orElse(
133                 choice.getCases().stream().findFirst().orElseThrow());
134             stack.enterSchemaTree(caseSchemaNode.getQName());
135             for (final var childNode : caseSchemaNode.getChildNodes()) {
136                 if (childNode instanceof ChoiceSchemaNode childChoice) {
137                     final var isChildConfig = isConfig && childNode.isConfiguration();
138                     stack.enterSchemaTree(childNode.getQName());
139                     processChoiceNodeRecursively(isChildConfig, stack, childChoice);
140                     stack.exit();
141                 } else if (!isConfig || childNode.isConfiguration()) {
142                     generator.writeObjectFieldStart(childNode.getQName().getLocalName());
143                     processChildNode(childNode, stack, isConfig);
144                     generator.writeEndObject();
145                 }
146             }
147             stack.exit();
148         }
149     }
150
151     private void processChildNode(final DataSchemaNode schemaNode, final SchemaInferenceStack stack,
152             final boolean isParentConfig) throws IOException {
153         final var parentNamespace = stack.toSchemaNodeIdentifier().lastNodeIdentifier().getNamespace();
154         stack.enterSchemaTree(schemaNode.getQName());
155         final var name = schemaNode.getQName().getLocalName();
156         final var shouldBeAddedAsChild = !isParentConfig || schemaNode.isConfiguration();
157         if (schemaNode instanceof ListSchemaNode || schemaNode instanceof ContainerSchemaNode) {
158             processDataNodeContainer((DataNodeContainer) schemaNode);
159             if (shouldBeAddedAsChild && isSchemaNodeMandatory(schemaNode)) {
160                 required.add(name);
161             }
162         } else if (shouldBeAddedAsChild) {
163             if (schemaNode instanceof LeafSchemaNode leaf) {
164                 processLeafNode(leaf, name, stack, parentNamespace);
165             } else if (schemaNode instanceof AnyxmlSchemaNode || schemaNode instanceof AnydataSchemaNode) {
166                 processUnknownDataSchemaNode(schemaNode, name, parentNamespace);
167             } else if (schemaNode instanceof LeafListSchemaNode leafList) {
168                 if (isSchemaNodeMandatory(schemaNode)) {
169                     required.add(name);
170                 }
171                 processLeafListNode(leafList, stack);
172             } else {
173                 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + schemaNode.getClass());
174             }
175         }
176         stack.exit();
177     }
178
179     private void processDataNodeContainer(final DataNodeContainer dataNode) throws IOException {
180         final var schemaNode = (SchemaNode) dataNode;
181         final var localName = schemaNode.getQName().getLocalName();
182         final var nodeName = parentName + "_" + localName;
183
184
185         final String discriminator;
186         if (!definitionNames.isListedNode(schemaNode)) {
187             final var parentNameConfigLocalName = parentName + "_" + localName;
188             final var names = List.of(parentNameConfigLocalName);
189             discriminator = definitionNames.pickDiscriminator(schemaNode, names);
190         } else {
191             discriminator = definitionNames.getDiscriminator(schemaNode);
192         }
193
194         processRef(nodeName, schemaNode, discriminator);
195     }
196
197     private void processRef(final String name, final SchemaNode schemaNode, String discriminator) throws IOException {
198         final var ref = COMPONENTS_PREFIX + name + discriminator;
199         if (schemaNode instanceof ListSchemaNode listNode) {
200             generator.writeStringField(TYPE, ARRAY_TYPE);
201             generator.writeObjectFieldStart(ITEMS);
202             generator.writeStringField("$ref", ref);
203             generator.writeEndObject();
204             generator.writeStringField(DESCRIPTION, schemaNode.getDescription().orElse(""));
205
206             if (listNode.getElementCountConstraint().isPresent()) {
207                 final var minElements = listNode.getElementCountConstraint().orElseThrow().getMinElements();
208                 final var maxElements = listNode.getElementCountConstraint().orElseThrow().getMaxElements();
209                 if (minElements != null) {
210                     createExamples(listNode, minElements);
211                     generator.writeNumberField("minItems", minElements);
212                 }
213                 if (maxElements != null) {
214                     generator.writeNumberField("maxItems", maxElements);
215                 }
216             }
217         } else {
218              /*
219                 Description can't be added, because nothing allowed alongside $ref.
220                 allOf is not an option, because ServiceNow can't parse it.
221               */
222             generator.writeStringField("$ref", ref);
223         }
224     }
225
226     private void createExamples(final ListSchemaNode schemaNode,
227         @NonNull final Integer minElements) throws IOException {
228         final var firstExampleMap = prepareFirstListExample(schemaNode);
229         final var examples = new ArrayList<Map<String, Object>>();
230         examples.add(firstExampleMap);
231
232         final var unqiueContraintsNameSet = schemaNode.getUniqueConstraints().stream()
233             .map(ModelStatement::argument)
234             .flatMap(uniqueSt -> uniqueSt.stream()
235                 .map(schemaNI -> schemaNI.lastNodeIdentifier().getLocalName()))
236             .collect(Collectors.toSet());
237         final var keysNameSet = schemaNode.getKeyDefinition().stream()
238             .map(AbstractQName::getLocalName)
239             .collect(Collectors.toSet());
240         for (int i = 1; i < minElements; i++) {
241             final var exampleMap = new HashMap<String, Object>();
242             for (final var example : firstExampleMap.entrySet()) {
243                 final Object exampleValue;
244                 if (keysNameSet.contains(example.getKey()) || unqiueContraintsNameSet.contains(example.getKey())) {
245                     exampleValue = editExample(example.getValue(), i);
246                 } else {
247                     exampleValue = example.getValue();
248                 }
249                 exampleMap.put(example.getKey(), exampleValue);
250             }
251             examples.add(exampleMap);
252         }
253         generator.writeArrayFieldStart(EXAMPLE);
254         for (final var example : examples) {
255             generator.writeStartObject();
256             for (final var elem : example.entrySet()) {
257                 writeValue(elem.getKey(), elem.getValue());
258             }
259             generator.writeEndObject();
260         }
261         generator.writeEndArray();
262     }
263
264     private HashMap<String, Object> prepareFirstListExample(final ListSchemaNode schemaNode) {
265         final var childNodes = schemaNode.getChildNodes();
266         final var firstExampleMap = new HashMap<String, Object>();
267         // Cycle for each child node
268         for (final var childNode : childNodes) {
269             if (childNode instanceof TypedDataSchemaNode leafSchemaNode) {
270                 final var def = new TypeDef();
271                 processTypeDef(leafSchemaNode.getType(), leafSchemaNode, null, def);
272                 if (def.hasExample()) {
273                     firstExampleMap.put(leafSchemaNode.getQName().getLocalName(), def.getExample());
274                 }
275             }
276         }
277         return firstExampleMap;
278     }
279
280     private Object editExample(final Object exampleValue, final int edit) {
281         if (exampleValue instanceof String string) {
282             return string + "_" + edit;
283         } else if (exampleValue instanceof Integer number) {
284             return number + edit;
285         } else if (exampleValue instanceof Long number) {
286             return number + edit;
287         } else if (exampleValue instanceof Decimal64 number) {
288             return Decimal64.valueOf(BigDecimal.valueOf(number.intValue() + edit));
289         }
290         return exampleValue;
291     }
292
293     private void processUnknownDataSchemaNode(final DataSchemaNode leafNode, final String name,
294             final XMLNamespace parentNamespace) throws IOException {
295         assert (leafNode instanceof AnydataSchemaNode || leafNode instanceof AnyxmlSchemaNode);
296
297         final var leafDescription = leafNode.getDescription().orElse("");
298         generator.writeStringField(DESCRIPTION, leafDescription);
299
300         final var localName = leafNode.getQName().getLocalName();
301         generator.writeStringField(EXAMPLE, String.format("<%s> ... </%s>", localName, localName));
302         generator.writeStringField(TYPE, STRING_TYPE);
303         if (!leafNode.getQName().getNamespace().equals(parentNamespace)) {
304             // If the parent is not from the same model, define the child XML namespace.
305             buildXmlParameter(leafNode);
306         }
307         processMandatory((MandatoryAware) leafNode, name, required);
308     }
309
310     private void processLeafListNode(final LeafListSchemaNode listNode, final SchemaInferenceStack stack)
311             throws IOException {
312         generator.writeStringField(TYPE, ARRAY_TYPE);
313
314         Integer minElements = null;
315         final var optConstraint = listNode.getElementCountConstraint();
316         if (optConstraint.isPresent()) {
317             minElements = optConstraint.orElseThrow().getMinElements();
318             if (minElements != null) {
319                 generator.writeNumberField("minItems", minElements);
320             }
321             final var maxElements = optConstraint.orElseThrow().getMaxElements();
322             if (maxElements != null) {
323                 generator.writeNumberField("maxItems", maxElements);
324             }
325         }
326         final var def = new TypeDef();
327         processTypeDef(listNode.getType(), listNode, stack, def);
328
329         generator.writeObjectFieldStart(ITEMS);
330         processTypeDef(listNode.getType(), listNode, stack);
331         generator.writeEndObject();
332         generator.writeStringField(DESCRIPTION, listNode.getDescription().orElse(""));
333
334         if (def.hasExample() && minElements != null) {
335             final var listOfExamples = new ArrayList<>();
336             for (int i = 0; i < minElements; i++) {
337                 listOfExamples.add(def.getExample());
338             }
339             generator.writeArrayFieldStart(EXAMPLE);
340             for (final var example : listOfExamples) {
341                 generator.writeString(example.toString());
342             }
343             generator.writeEndArray();
344         }
345     }
346
347     private void processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
348             final SchemaInferenceStack stack, final XMLNamespace parentNamespace) throws IOException {
349         final var leafDescription = leafNode.getDescription().orElse("");
350         generator.writeStringField(DESCRIPTION, leafDescription);
351         processTypeDef(leafNode.getType(), leafNode, stack);
352         if (!leafNode.getQName().getNamespace().equals(parentNamespace)) {
353             // If the parent is not from the same model, define the child XML namespace.
354             buildXmlParameter(leafNode);
355         }
356         processMandatory(leafNode, jsonLeafName, required);
357     }
358
359     private static void processMandatory(final MandatoryAware node, final String nodeName,
360             final List<String> required) {
361         if (node.isMandatory()) {
362             required.add(nodeName);
363         }
364     }
365
366     private void buildXmlParameter(final SchemaNode schemaNode) throws IOException {
367         generator.writeObjectFieldStart("xml");
368         generator.writeStringField("name", schemaNode.getQName().getLocalName());
369         generator.writeStringField("namespace", schemaNode.getQName().getNamespace().toString());
370         generator.writeEndObject();
371     }
372
373     protected static boolean isSchemaNodeMandatory(final DataSchemaNode schemaNode) {
374         if (schemaNode instanceof ContainerSchemaNode containerNode) {
375             if (containerNode.isPresenceContainer()) {
376                 return false;
377             }
378             for (final var childNode : containerNode.getChildNodes()) {
379                 if (childNode instanceof MandatoryAware mandatoryAware && mandatoryAware.isMandatory()) {
380                     return true;
381                 }
382             }
383         }
384         //  A list or leaf-list node with a "min-elements" statement with a value greater than zero.
385         return schemaNode instanceof ElementCountConstraintAware constraintAware
386             && constraintAware.getElementCountConstraint()
387             .map(ElementCountConstraint::getMinElements)
388             .orElse(0) > 0;
389     }
390
391     private void processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode schemaNode,
392         final SchemaInferenceStack stack) throws IOException {
393         final var def = new TypeDef();
394
395         final var jsonType = processTypeDef(leafTypeDef, schemaNode, stack, def);
396
397         generator.writeStringField(TYPE, jsonType);
398         generateTypeDef(def);
399     }
400
401     private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode schemaNode,
402             final SchemaInferenceStack stack, final TypeDef def) {
403         final String jsonType;
404         if (leafTypeDef instanceof BinaryTypeDefinition binaryType) {
405             jsonType = processBinaryType(binaryType, def);
406         } else if (leafTypeDef instanceof BitsTypeDefinition bitsType) {
407             jsonType = processBitsType(bitsType, def);
408         } else if (leafTypeDef instanceof EnumTypeDefinition enumType) {
409             jsonType = processEnumType(enumType, def);
410         } else if (leafTypeDef instanceof IdentityrefTypeDefinition identityrefType) {
411             jsonType = processIdentityRefType(identityrefType, stack.modelContext(), def);
412         } else if (leafTypeDef instanceof StringTypeDefinition stringType) {
413             jsonType = processStringType(stringType, schemaNode.getQName().getLocalName(), def);
414         } else if (leafTypeDef instanceof UnionTypeDefinition unionType) {
415             jsonType = processTypeDef(unionType.getTypes().iterator().next(), schemaNode, stack, def);
416         } else if (leafTypeDef instanceof EmptyTypeDefinition) {
417             jsonType = OBJECT_TYPE;
418         } else if (leafTypeDef instanceof LeafrefTypeDefinition leafrefType) {
419             jsonType = processTypeDef(stack.resolveLeafref(leafrefType), schemaNode, stack, def);
420         } else if (leafTypeDef instanceof BooleanTypeDefinition) {
421             jsonType = "boolean";
422             if (leafTypeDef.getDefaultValue().isPresent()) {
423                 def.setDefaultValue(Boolean.parseBoolean((String) leafTypeDef.getDefaultValue().orElseThrow()));
424             }
425             def.setExample(true);
426         } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition<?, ?> rangeRestrictedType) {
427             jsonType = processNumberType(rangeRestrictedType, def);
428         } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition instanceIdentifierType) {
429             jsonType = processInstanceIdentifierType(instanceIdentifierType, schemaNode,
430                 stack.modelContext(), def);
431         } else {
432             jsonType = STRING_TYPE;
433         }
434
435         if (leafTypeDef.getDefaultValue().isPresent()) {
436             final var defaultValue = leafTypeDef.getDefaultValue().orElseThrow();
437             if (defaultValue instanceof String stringDefaultValue) {
438                 if (leafTypeDef instanceof BooleanTypeDefinition) {
439                     def.setDefaultValue(Boolean.valueOf(stringDefaultValue));
440                 } else if (leafTypeDef instanceof DecimalTypeDefinition
441                     || leafTypeDef instanceof Uint64TypeDefinition) {
442                     def.setDefaultValue(new BigDecimal(stringDefaultValue));
443                 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition<?, ?> rangeRestrictedType) {
444                     //uint8,16,32 int8,16,32,64
445                     if (isHexadecimalOrOctal(rangeRestrictedType)) {
446                         def.setDefaultValue(stringDefaultValue);
447                     } else {
448                         def.setDefaultValue(Long.valueOf(stringDefaultValue));
449                     }
450                 } else {
451                     def.setDefaultValue(stringDefaultValue);
452                 }
453             }
454         }
455         return jsonType;
456     }
457
458     private void writeValue(final String field, final Object value) throws IOException {
459         if (value instanceof Number) {
460             if (value instanceof Integer intValue) {
461                 generator.writeNumberField(field, intValue);
462             } else if (value instanceof Long longValue) {
463                 generator.writeNumberField(field, longValue);
464             } else if (value instanceof BigDecimal decimalValue) {
465                 generator.writeNumberField(field, decimalValue);
466             } else if (value instanceof BigInteger bigIntValue) {
467                 generator.writeNumberField(field, bigIntValue);
468             }
469         } else if (value instanceof Boolean bool) {
470             generator.writeBooleanField(field, bool);
471         } else {
472             generator.writeStringField(field, (String) value);
473         }
474     }
475
476     private void generateTypeDef(final TypeDef def) throws IOException {
477         if (def.hasEnums()) {
478             generator.writeArrayFieldStart(ENUM);
479             for (final var enumElem : def.getEnums()) {
480                 generator.writeString(enumElem);
481             }
482             generator.writeEndArray();
483         }
484         if (def.hasFormat()) {
485             generator.writeStringField(FORMAT, def.getFormat());
486         }
487         if (def.hasMinItems()) {
488             generator.writeNumberField("minItems", def.getMinItems());
489         }
490         if (def.hasDefaultValue()) {
491             writeValue(DEFAULT, def.getDefaultValue());
492         }
493         if (def.hasExample()) {
494             writeValue(EXAMPLE, def.getExample());
495         }
496         if (def.hasUniqueItems()) {
497             generator.writeBooleanField("uniqueItems", def.getUniqueItems());
498         }
499         if (def.hasMinLength()) {
500             generator.writeNumberField("minLength", def.getMinLength());
501         }
502         if (def.hasMaxLength()) {
503             generator.writeNumberField("maxLength", def.getMaxLength());
504         }
505     }
506
507     private String processBinaryType(final BinaryTypeDefinition definition, final TypeDef def) {
508         if (definition.getDefaultValue().isPresent()) {
509             def.setDefaultValue(definition.getDefaultValue().toString());
510         }
511         def.setFormat("byte");
512         return STRING_TYPE;
513     }
514
515     private String processBitsType(final BitsTypeDefinition bitsType, final TypeDef def) {
516         def.setMinItems(0);
517         def.setUniqueItems(true);
518         final var bits = bitsType.getBits();
519         final var enumNames = bits.stream()
520             .map(BitsTypeDefinition.Bit::getName)
521             .toList();
522
523         def.setEnums(enumNames);
524
525         def.setDefaultValue(bitsType.getDefaultValue().isPresent()
526             ? bitsType.getDefaultValue().orElseThrow().toString() :
527             (enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1)));
528         return STRING_TYPE;
529     }
530
531     private String processEnumType(final EnumTypeDefinition enumLeafType, final TypeDef def) {
532         final var enumPairs = enumLeafType.getValues();
533         final var enumNames = enumPairs.stream()
534             .map(EnumTypeDefinition.EnumPair::getName)
535             .toList();
536
537         def.setEnums(enumNames);
538
539         if (enumLeafType.getDefaultValue().isPresent()) {
540             final var defaultValue = enumLeafType.getDefaultValue().orElseThrow().toString();
541             def.setDefaultValue(defaultValue);
542         }
543         def.setExample(enumLeafType.getValues().iterator().next().getName());
544         return STRING_TYPE;
545     }
546
547     private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef,
548             final EffectiveModelContext schemaContext, final TypeDef def) {
549         final var schemaNode = leafTypeDef.getIdentities().iterator().next();
550         def.setExample(schemaNode.getQName().getLocalName());
551
552         final var derivedIds = schemaContext.getDerivedIdentities(schemaNode);
553         final var enumPayload = new ArrayList<String>();
554         enumPayload.add(schemaNode.getQName().getLocalName());
555         populateEnumWithDerived(derivedIds, enumPayload, schemaContext);
556         final var schemaEnum = new ArrayList<>(enumPayload);
557
558         def.setEnums(schemaEnum);
559
560         return STRING_TYPE;
561     }
562
563     private void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
564             final List<String> enumPayload, final EffectiveModelContext context) {
565         for (final var derivedId : derivedIds) {
566             enumPayload.add(derivedId.getQName().getLocalName());
567             populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
568         }
569     }
570
571     private String processStringType(final StringTypeDefinition stringType, final String nodeName, final TypeDef def) {
572         var type = stringType;
573         while (type.getLengthConstraint().isEmpty() && type.getBaseType() != null) {
574             type = type.getBaseType();
575         }
576
577         if (type.getLengthConstraint().isPresent()) {
578             final var range = type.getLengthConstraint().orElseThrow().getAllowedRanges().span();
579             def.setMinLength(range.lowerEndpoint());
580             def.setMaxLength(range.upperEndpoint());
581         }
582
583         if (type.getPatternConstraints().iterator().hasNext()) {
584             final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
585             var regex = pattern.getRegularExpressionString();
586             // Escape special characters to prevent issues inside Automaton.
587             regex = AUTOMATON_SPECIAL_CHARACTERS.matcher(regex).replaceAll("\\\\$0");
588             for (final var charClass : PREDEFINED_CHARACTER_CLASSES.entrySet()) {
589                 regex = regex.replaceAll(charClass.getKey(), charClass.getValue());
590             }
591             var defaultValue = "";
592             try {
593                 final var regExp = new RegExp(regex);
594                 defaultValue = regExp.toAutomaton().getShortestExample(true);
595             } catch (IllegalArgumentException ex) {
596                 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
597             }
598             def.setExample(defaultValue);
599         } else {
600             def.setExample("Some " + nodeName);
601         }
602
603         if (stringType.getDefaultValue().isPresent()) {
604             def.setDefaultValue(stringType.getDefaultValue().orElseThrow().toString());
605         }
606         return STRING_TYPE;
607     }
608
609     private String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,final TypeDef def) {
610         final var maybeLower = leafTypeDef.getRangeConstraint()
611             .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
612
613         if (isHexadecimalOrOctal(leafTypeDef)) {
614             return STRING_TYPE;
615         }
616
617         if (leafTypeDef instanceof DecimalTypeDefinition) {
618             if (leafTypeDef.getDefaultValue().isPresent()) {
619                 def.setDefaultValue(Decimal64.valueOf(
620                     leafTypeDef.getDefaultValue().orElseThrow().toString()).decimalValue());
621             }
622             if (maybeLower.isPresent()) {
623                 def.setExample(((Decimal64) maybeLower.orElseThrow()).decimalValue());
624             }
625             return NUMBER_TYPE;
626         }
627         if (leafTypeDef instanceof Uint8TypeDefinition
628             || leafTypeDef instanceof Uint16TypeDefinition
629             || leafTypeDef instanceof Int8TypeDefinition
630             || leafTypeDef instanceof Int16TypeDefinition
631             || leafTypeDef instanceof Int32TypeDefinition) {
632
633             def.setFormat("int32");
634             if (leafTypeDef.getDefaultValue().isPresent()) {
635                 def.setDefaultValue(Integer.parseInt(leafTypeDef.getDefaultValue().orElseThrow().toString()));
636             }
637             if (maybeLower.isPresent()) {
638                 def.setExample(Integer.parseInt(maybeLower.orElseThrow().toString()));
639             }
640         } else if (leafTypeDef instanceof Uint32TypeDefinition
641             || leafTypeDef instanceof Int64TypeDefinition) {
642             def.setFormat("int64");
643             if (leafTypeDef.getDefaultValue().isPresent()) {
644                 def.setDefaultValue(Long.parseLong(leafTypeDef.getDefaultValue().orElseThrow().toString()));
645             }
646             if (maybeLower.isPresent()) {
647                 def.setExample(Long.parseLong(maybeLower.orElseThrow().toString()));
648             }
649         } else {
650             //uint64
651             if (leafTypeDef.getDefaultValue().isPresent()) {
652                 def.setDefaultValue(new BigInteger(leafTypeDef.getDefaultValue().orElseThrow().toString()));
653             }
654             def.setExample(0);
655         }
656         return "integer";
657     }
658
659     private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
660         final var optDefaultValue = typeDef.getDefaultValue();
661         if (optDefaultValue.isPresent()) {
662             final var defaultValue = (String) optDefaultValue.orElseThrow();
663             return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
664         }
665         return false;
666     }
667
668     private String processInstanceIdentifierType(final InstanceIdentifierTypeDefinition iidType,
669             final DataSchemaNode schemaNode, final EffectiveModelContext schemaContext,final TypeDef def) {
670         // create example instance-identifier to the first container of node's module if exists or leave it empty
671         final var module = schemaContext.findModule(schemaNode.getQName().getModule());
672         if (module.isPresent()) {
673             final var container = module.orElseThrow().getChildNodes().stream()
674                 .filter(n -> n instanceof ContainerSchemaNode)
675                 .findFirst();
676             if (container.isPresent()) {
677                 def.setExample(String.format("/%s:%s", module.orElseThrow().getPrefix(),
678                     container.orElseThrow().getQName().getLocalName()));
679             }
680         }
681         // set default value
682         if (iidType.getDefaultValue().isPresent()) {
683             def.setDefaultValue(iidType.getDefaultValue().orElseThrow().toString());
684         }
685         return STRING_TYPE;
686     }
687 }