private ValidationEvent unresolvedTarget(Model model, Shape shape, Relationship rel) {
        // Detect if there are shape IDs within 2 characters edit distance from the
        // invalid shape ID target. An edit distance > 2 seems to give far more false
        // positives than are useful.
        Collection<String> suggestions = computeTargetSuggestions(model, rel.getNeighborShapeId());
        String suggestionText = !suggestions.isEmpty()
                ? ". Did you mean " + String.join(", ", suggestions) + "?"
                : "";

        if (rel.getRelationshipType() == RelationshipType.MEMBER_TARGET) {
            // Don't show the relationship type for invalid member targets.
            return error(shape,
                    String.format(
                            "member shape targets an unresolved shape `%s`%s",
                            rel.getNeighborShapeId(),
                            suggestionText),
                    UNRESOLVED_SHAPE_PART);
        } else {
            // Use "a" or "an" depending on if the relationship starts with a vowel.
            String indefiniteArticle = isUppercaseVowel(rel.getRelationshipType().toString().charAt(0))
                    ? "an"
                    : "a";
            return error(shape,
                    String.format(
                            "%s shape has %s `%s` relationship to an unresolved shape `%s`%s",
                            shape.getType(),
                            indefiniteArticle,
                            rel.getRelationshipType().toString().toLowerCase(Locale.US),
                            rel.getNeighborShapeId(),
                            suggestionText),
                    UNRESOLVED_SHAPE_PART);
        }
    }
Validates that neighbors target resolvable shapes of the correct type.
smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/TargetValidator.java