Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (C) 2017 Jens Reimann
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.dentrassi.asyncapi.internal.parser;
import static de.dentrassi.asyncapi.AsyncApi.VERSION;
import static de.dentrassi.asyncapi.internal.parser.Consume.asMap;
import static de.dentrassi.asyncapi.internal.parser.Consume.asOptionalMap;
import static de.dentrassi.asyncapi.internal.parser.Consume.asOptionalSet;
import static de.dentrassi.asyncapi.internal.parser.Consume.asOptionalString;
import static de.dentrassi.asyncapi.internal.parser.Consume.asSet;
import static de.dentrassi.asyncapi.internal.parser.Consume.asString;
import java.io.InputStream;
import java.io.Reader;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.yaml.snakeyaml.Yaml;
import de.dentrassi.asyncapi.ArrayType;
import de.dentrassi.asyncapi.AsyncApi;
import de.dentrassi.asyncapi.CoreType;
import de.dentrassi.asyncapi.EnumType;
import de.dentrassi.asyncapi.Information;
import de.dentrassi.asyncapi.Message;
import de.dentrassi.asyncapi.MessageReference;
import de.dentrassi.asyncapi.ObjectType;
import de.dentrassi.asyncapi.Property;
import de.dentrassi.asyncapi.Topic;
import de.dentrassi.asyncapi.Type;
import de.dentrassi.asyncapi.TypeReference;
/**
* Parser for AsyncAPI definitions encoded as YAML
*/
public class YamlParser {
private final Map document;
private final Map messages = new HashMap<>();
public YamlParser(final InputStream in) throws ParserException {
try {
this.document = asMap(new Yaml().load(in));
} catch (final Exception e) {
throw new ParserException("Failed to parse YAML document", e);
}
}
public YamlParser(final Reader reader) throws ParserException {
try {
this.document = asMap(new Yaml().load(reader));
} catch (final Exception e) {
throw new ParserException("Failed to parse YAML document", e);
}
}
public AsyncApi parse() {
final String version = asString("asyncapi", this.document);
if (!VERSION.equals(version)) {
throw new IllegalStateException(String.format("Only version '%s' is supported, this is version '%s'", VERSION, version));
}
final AsyncApi api = new AsyncApi();
api.setBaseTopic(asOptionalString("baseTopic", this.document).orElse(null));
api.setHost(asString("host", this.document));
api.setSchemes(asSet("schemes", this.document));
api.setInformation(parseInfo(asMap("info", this.document)));
api.setTopics(parseTopics(asMap("topics", this.document)));
final Map components = asMap("components", this.document);
api.setMessages(parseMessages(asOptionalMap("messages", components).orElse(null)));
api.setTypes(parseTypes(asOptionalMap("schemas", components).orElse(null)));
return api;
}
private Set parseTypes(final Map map) {
if (map == null || map.isEmpty()) {
return Collections.emptySet();
}
final Set result = new LinkedHashSet<>();
for (final Map.Entry entry : map.entrySet()) {
final String name = entry.getKey();
result.add(parseExplicitType("types", Collections.emptyList(), name, asMap(entry.getValue())));
}
return result;
}
private Set parseMessages(final Map map) {
if (map == null || map.isEmpty()) {
return Collections.emptySet();
}
final Set result = new LinkedHashSet<>();
for (final Map.Entry entry : map.entrySet()) {
final String name = entry.getKey();
result.add(parseExplicitMessage(name, asMap(entry.getValue())));
}
return result;
}
private static class Reference implements Iterable {
private final List tokens;
public Reference(final List tokens) {
this.tokens = tokens;
if (tokens.isEmpty()) {
throw new IllegalArgumentException("Reference must not be empty");
}
}
@Override
public Iterator iterator() {
return this.tokens.iterator();
}
public static Reference parse(final String ref) {
return new Reference(Arrays.asList(ref.split("/+")));
}
public String last() {
return last(0);
}
public String last(final int reverseIndex) {
return this.tokens.get(this.tokens.size() - (reverseIndex + 1));
}
}
private TypeReference parseType(final String namespace, final List parents, final String name, final Map map) {
final Optional ref = asOptionalString("$ref", map);
if (ref.isPresent()) {
final Reference to = Reference.parse(ref.get());
// FIXME: validate full ref syntax
return new TypeReference(mapPackageName(to.last(1)), to.last());
} else {
return parseExplicitType(namespace, parents, name, map);
}
}
private String mapPackageName(final String type) {
if ("schemas".equals(type)) {
return "types";
}
return type;
}
private Type parseExplicitType(final String namespace, final List parents, final String name, final Map map) {
final String type = asString("type", map);
switch (type) {
case "boolean":
return addCommonTypeInfo(new CoreType(name, Boolean.class), map);
case "integer":
return addCommonTypeInfo(new CoreType(name, Integer.class), map);
case "number":
return addCommonTypeInfo(new CoreType(name, Double.class), map);
case "string": {
if (map.containsKey("enum")) {
return addCommonTypeInfo(parseEnumType(namespace, parents, name, map), map);
}
return addCommonTypeInfo(parseCoreType(name, map), map);
}
case "array":
return addCommonTypeInfo(parseArrayType(namespace, parents, name, map), map);
case "object":
return addCommonTypeInfo(parseObjectType(namespace, parents, name, map), map);
default:
throw new IllegalStateException(String.format("Unsupported type: %s", type));
}
}
private static List push(final List parents, final String name) {
final List result = new ArrayList<>(parents);
result.add(name);
return result;
}
private Type parseArrayType(final String namespace, final List parents, final String name, final Map map) {
final boolean uniqueItems = Consume.asBoolean(map, "uniqueItems");
final TypeReference itemType = parseType(namespace, parents, name + "Item", asMap("items", map));
final ArrayType type = new ArrayType(name, itemType, uniqueItems);
return type;
}
private Type parseEnumType(final String namespace, final List parents, final String name, final Map map) {
final EnumType type = new EnumType(namespace, parents, name);
type.setLiterals(asSet("enum", map));
return type;
}
private CoreType parseCoreType(final String name, final Map map) {
final String format = asOptionalString("format", map).orElse(null);
if (format == null) {
return new CoreType(name, String.class);
}
switch (format) {
case "date-time":
return new CoreType(name, ZonedDateTime.class);
default:
throw new IllegalStateException(String.format("Unknown data format: " + format));
}
}
private Type parseObjectType(final String namespace, final List parents, final String name, final Map map) {
final ObjectType type = new ObjectType(namespace, parents, name);
final Set required = asOptionalSet("required", map).orElse(Collections.emptySet());
final Map prop = asMap("properties", map);
for (final Map.Entry entry : prop.entrySet()) {
final Property p = new Property();
final String propName = entry.getKey();
final Map propValues = asMap(entry.getValue());
p.setName(propName);
p.setDescription(asOptionalString("description", propValues).orElse(null));
p.setRequired(required.contains(propName));
p.setType(parseType(namespace, push(parents, name), entry.getKey(), propValues));
type.getProperties().add(p);
}
return type;
}
private Type addCommonTypeInfo(final Type type, final Map map) {
type.setTitle(asOptionalString("title", map).orElse(null));
type.setDescription(asOptionalString("description", map).orElse(null));
return type;
}
private Set parseTopics(final Map topics) {
final Set result = new HashSet<>();
for (final Map.Entry entry : topics.entrySet()) {
result.add(parseTopic(entry.getKey(), entry.getValue()));
}
return result;
}
private Topic parseTopic(final String key, final Object value) {
final Map map = asMap(value);
final Topic result = new Topic();
result.setName(key);
result.setPublish(asOptionalMap("publish", map).map(v -> parseMessage("Publish. " + key, v)).orElse(null));
result.setSubscribe(asOptionalMap("subscribe", map).map(v -> parseMessage("Subscribe." + key, v)).orElse(null));
return result;
}
private MessageReference parseMessage(final String name, final Map map) {
final Optional ref = asOptionalString("$ref", map);
if (ref.isPresent()) {
final Reference to = Reference.parse(ref.get());
final String refName = to.last();
return new MessageReference(refName);
} else {
return parseExplicitMessage(name, map);
}
}
private Message parseExplicitMessage(final String name, final Map map) {
final Message message = new Message(name);
message.setDescription(asOptionalString("description", map).orElse(null));
message.setSummary(asOptionalString("summary", map).orElse(null));
message.setPayload(parseType("messages", Collections.singletonList(name), "payload", asMap("payload", map)));
this.messages.put(name, message);
return message;
}
private Information parseInfo(final Map map) {
final Information result = new Information();
result.setTitle(asOptionalString("title", map).orElse(null));
result.setVersion(asString("version", map));
return result;
}
}