All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.smithy.build.model.ConfigLoader Maven / Gradle / Ivy

/*
 * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.build.model;

import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import software.amazon.smithy.build.SmithyBuildException;
import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.model.loader.ModelSyntaxException;
import software.amazon.smithy.model.node.ArrayNode;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.NodeMapper;
import software.amazon.smithy.model.node.NodeVisitor;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.node.StringNode;
import software.amazon.smithy.utils.IoUtils;
import software.amazon.smithy.utils.Pair;

/**
 * Loads a {@link SmithyBuildConfig} from disk.
 */
final class ConfigLoader {

    private ConfigLoader() {}

    static SmithyBuildConfig load(Path path) {
        try {
            String content = IoUtils.readUtf8File(path);
            return load(path.getParent(), loadWithJson(path, content).expectObjectNode());
        } catch (ModelSyntaxException e) {
            throw new SmithyBuildException(e);
        }
    }

    private static Node loadWithJson(Path path, String contents) {
        return Node.parseJsonWithComments(contents, path.toString()).accept(new VariableExpander());
    }

    private static SmithyBuildConfig load(Path baseImportPath, ObjectNode node) {
        NodeMapper mapper = new NodeMapper();
        return resolveImports(baseImportPath, mapper.deserialize(node, SmithyBuildConfig.class));
    }

    private static SmithyBuildConfig resolveImports(Path baseImportPath, SmithyBuildConfig config) {
        List imports = config.getImports().stream()
                .map(importPath -> baseImportPath.resolve(importPath).toString())
                .collect(Collectors.toList());

        Map projections = config.getProjections().entrySet().stream()
                .map(entry -> Pair.of(entry.getKey(), resolveProjectionImports(baseImportPath, entry.getValue())))
                .collect(Collectors.toMap(Pair::getKey, Pair::getValue));

        return config.toBuilder()
                .imports(imports)
                .projections(projections)
                .build();
    }

    private static ProjectionConfig resolveProjectionImports(Path baseImportPath, ProjectionConfig config) {
        List imports = config.getImports().stream()
                .map(importPath -> baseImportPath.resolve(importPath).toString())
                .collect(Collectors.toList());
        return config.toBuilder().imports(imports).build();
    }

    /**
     * Expands ${NAME} values inside of strings to a {@code System} property
     * or an environment variable.
     */
    private static final class VariableExpander extends NodeVisitor.Default {

        private static final Pattern INLINE = Pattern.compile("(?:^|[^\\\\])\\$\\{(.+)}");
        private static final Pattern ESCAPED_INLINE = Pattern.compile("\\\\\\$");

        @Override
        protected Node getDefault(Node node) {
            return node;
        }

        @Override
        public Node arrayNode(ArrayNode node) {
            return node.getElements().stream().map(element -> element.accept(this)).collect(ArrayNode.collect());
        }

        @Override
        public Node objectNode(ObjectNode node) {
            return node.getMembers().entrySet().stream()
                    .map(entry -> Pair.of(entry.getKey().accept(this), entry.getValue().accept(this)))
                    .collect(ObjectNode.collect(pair -> pair.getLeft().expectStringNode(), Pair::getRight));
        }

        @Override
        public Node stringNode(StringNode node) {
            Matcher matcher = INLINE.matcher(node.getValue());
            StringBuffer builder = new StringBuffer();

            while (matcher.find()) {
                String variable = matcher.group(1);
                String replacement = expand(node.getSourceLocation(), variable);
                matcher.appendReplacement(builder, replacement);
            }

            matcher.appendTail(builder);

            // Remove escaped variables.
            String result = ESCAPED_INLINE.matcher(builder.toString()).replaceAll("\\$");

            return new StringNode(result, node.getSourceLocation());
        }

        private static String expand(SourceLocation sourceLocation, String variable) {
            String replacement = Optional.ofNullable(System.getProperty(variable))
                    .orElseGet(() -> System.getenv(variable));

            if (replacement == null) {
                throw new SmithyBuildException(String.format(
                        "Unable to expand variable `" + variable + "` to an environment variable or system "
                        + "property: %s", sourceLocation));
            }

            return replacement;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy