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

software.amazon.smithy.model.pattern.UriPattern 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.model.pattern;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import software.amazon.smithy.utils.Pair;

/**
 * Represents a URI pattern.
 *
 * 

A URI pattern is an absolute path segment used to route to operations. * The pattern must start with "/", must not contain empty segments * (i.e., "//"), must not contain a fragment (i.e., "#"), and must not * end with "?". * *

Labels may appear in the pattern in the form of "{label}". Labels must * not be repeated, must make up an entire path segment (e.g., "/{foo}/baz"), * and the label name must match the regex "^[a-zA-Z0-9_]+$". No labels can * appear after the query string. * *

Greedy labels, a specialized type of label, may be specified using * "{label+}". Only a single greedy label may appear in a pattern, and it * must be the last label in a pattern. */ public final class UriPattern extends SmithyPattern { private final Map queryLiterals; private UriPattern(Builder builder, Map queryLiterals) { super(builder); this.queryLiterals = queryLiterals; } /** * Parse a URI pattern string into a UriPattern. * *

The provided value must match the origin-form request-target * grammar production in RFC 9112, section 3.2.1. * * @param uri URI pattern to parse. * @return Returns the parsed URI pattern. * @throws InvalidUriPatternException for invalid URI patterns. * @see RFC 9112 Section 3.2.1 */ public static UriPattern parse(String uri) { if (uri.endsWith("?")) { throw new InvalidUriPatternException("URI patterns must not end with '?'. Found " + uri); } else if (!uri.startsWith("/")) { throw new InvalidUriPatternException("URI pattern must start with '/'. Found " + uri); } else if (uri.contains("#")) { throw new InvalidUriPatternException("URI pattern must not contain a fragment. Found " + uri); } String[] parts = uri.split(Pattern.quote("?"), 2); String[] unparsedSegments = parts[0].split(Pattern.quote("/")); List segments = new ArrayList<>(); // Skip the first "/" segment, and thus assume offset of 1. int offset = 1; for (int i = 1; i < unparsedSegments.length; i++) { String segment = unparsedSegments[i]; segments.add(Segment.parse(segment, offset)); // Add one to account for `/` offset += segment.length() + 1; } Map queryLiterals = new LinkedHashMap<>(); // Parse the query literals outside of the general pattern if (parts.length == 2) { if (parts[1].contains("{") || parts[1].contains("}")) { throw new InvalidUriPatternException("URI labels must not appear in the query string. Found " + uri); } for (String kvp : parts[1].split(Pattern.quote("&"))) { String[] parameterParts = kvp.split("=", 2); String actualKey = parameterParts[0]; if (queryLiterals.containsKey(actualKey)) { throw new InvalidUriPatternException("Literal query parameters must not be repeated: " + uri); } queryLiterals.put(actualKey, parameterParts.length == 2 ? parameterParts[1] : ""); } } return new UriPattern(builder().pattern(uri).segments(segments), queryLiterals); } /** * Get an immutable map of query string literal key-value pairs. * * @return An immutable map of parsed query string literals. */ public Map getQueryLiterals() { return Collections.unmodifiableMap(queryLiterals); } /** * Gets a specific query string literal parameter value. * * @param parameter Case-sensitive name of the parameter to retrieve. * @return Returns the optionally found parameter value. */ public Optional getQueryLiteralValue(String parameter) { return Optional.ofNullable(queryLiterals.get(parameter)); } /** * Determines if the pattern conflicts with another pattern. * * @param otherPattern SmithyPattern to check against. * @return Returns true if there is a conflict. */ public boolean conflictsWith(UriPattern otherPattern) { List segments = getSegments(); List otherSegments = otherPattern.getSegments(); // If one uri has more segments than the other, they don't conflict. if (segments.size() != otherSegments.size()) { return false; } // We check if the patterns are equivalent, if so then there's a conflict. for (int i = 0; i < segments.size(); i++) { Segment segment = segments.get(i); Segment otherSegment = otherSegments.get(i); if (segment.isGreedyLabel() && otherSegment.isGreedyLabel()) { continue; } if (segment.isNonGreedyLabel() && otherSegment.isNonGreedyLabel()) { continue; } if (segment.isLiteral() && otherSegment.isLiteral() && segment.getContent().equals(otherSegment.getContent())) { continue; } return false; } // At this point, the path portions are equivalent. If the query // string literals are the same, then the patterns conflict. return queryLiterals.equals(otherPattern.queryLiterals); } @Deprecated public List> getConflictingLabelSegments(UriPattern otherPattern) { Map conflictingSegments = getConflictingLabelSegmentsMap(otherPattern); return conflictingSegments.entrySet().stream() .map(entry -> Pair.of(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); } @Override public boolean equals(Object other) { if (!(other instanceof UriPattern)) { return false; } UriPattern otherPattern = (UriPattern) other; return super.equals(other) && queryLiterals.equals(otherPattern.queryLiterals); } @Override public int hashCode() { return super.hashCode() + queryLiterals.hashCode(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy