Leave Primitive* prelude shapes in 2.0

We previously rewrote models that target smithy.api#Primtive*
shapes to target their prelude counterparts that do not have the
Primitive prefix. For example, smithy.api#PrimitiveInteger would
become smithy.api#Integer. The rationale being that because optionality
is now controlled on members rather than based on the shape targeted by
a member, there is no longer a reason to use Primitive* shapes in 2.0
models.

The problem is that rewriting the shape targets during the loading
process can remove information that tooling might need to determine if
a shape was considered optional in IDL 1.0 (usually in fringe cases).

If a member previously targets PrimitiveInteger and is rewritten to
target Integer, but the member is also marked as required, then the
ModelUpgrader can't add the default trait to the member. If optionality
isn't determined based on the required trait, then a tool has no way
to know that a member that targets Integer previously targeted
PrimitiveInteger, thereby losing information.

Instead of removing Primitive* shapes, we will keep them in 2.0 and
mark them as deprecated. To point this out to end users, I added
validation (that really should have already existed) to detect when
a shape refers to a deprecated shape. However, because we don't want
to break previous test cases and because models should be able to
deprecate shapes without breaking test cases, DeprecatedShape does not
need to be explicitly handled by Smithy errorfile test runners. This
matches the special casing added for DeprecatedTrait and
ModelDeprecation.
This commit is contained in:
Michael Dowling 2022-08-03 16:20:01 -07:00 committed by Michael Dowling
parent e60275c103
commit a6b06611aa
20 changed files with 284 additions and 248 deletions

View File

@ -135,7 +135,7 @@ This proposal:
1. Introduces a `@default` trait
2. Introduces a `@clientOptional` trait
3. Removes the `@box` trait
4. Removes the `Primitive*` shapes from the prelude
4. Deprecates the `Primitive*` shapes from the prelude
5. Make the optionality of a structure completely controlled by members rather
than the shape targeted by a member
@ -195,19 +195,11 @@ optionality controls from shapes to structure members. Smithy IDL 2.0 will:
version bump of the service.
3. Remove the `@box` trait from the Smithy 2.0 prelude and fail to load models
that contain the `@box` trait.
4. Remove the `PrimitiveBoolean`, `PrimitiveShort`, `PrimitiveInteger`,
`PrimitiveLong`, `PrimitiveFloat`, and `PrimitiveDouble` shapes from the
Smithy 2.0 prelude. IDL 2.0 models will fail if they use these shapes.
5. Update the Smithy IDL 2.0 model loader implementation to be able to load
Smithy 1.0 models alongside Smithy 2.0 models.
1. Inject an appropriate `@default` trait on structure members that targeted
shapes with a default zero value and were not marked with the `@box`
trait.
2. Replace the `@box` trait with `@clientOptional` on structure members.
3. Remove the `@box` trait from non-members.
4. Rewrite members that target one of the removed Primitive* shapes to
target the corresponding non-primitive shape in the prelude (for example,
`PrimitiveInteger` is rewritten to target `Integer`).
4. Deprecate the `PrimitiveBoolean`, `PrimitiveShort`, `PrimitiveInteger`,
`PrimitiveLong`, `PrimitiveFloat`, and `PrimitiveDouble` shapes in the
Smithy 2.0 prelude. These shapes are treated exactly the same as their
prelude counterparts not prefixed with "Primitive", so there is no need to
use these shapes.
## `@default` trait

View File

@ -1288,6 +1288,48 @@ referenced from within any namespace using a relative shape ID.
@unitType
structure Unit {}
@deprecated(
message: "Use Boolean instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
boolean PrimitiveBoolean
@deprecated(
message: "Use Byte instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
byte PrimitiveByte
@deprecated(
message: "Use Short instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
short PrimitiveShort
@deprecated(
message: "Use Integer instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
integer PrimitiveInteger
@deprecated(
message: "Use Long instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
long PrimitiveLong
@deprecated(
message: "Use Float instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
float PrimitiveFloat
@deprecated(
message: "Use Double instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
double PrimitiveDouble
.. _unit-type:

View File

@ -46,7 +46,6 @@ import software.amazon.smithy.cli.HelpPrinter;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.model.loader.ModelAssembler;
import software.amazon.smithy.model.loader.ParserUtils;
import software.amazon.smithy.model.loader.Prelude;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.NumberShape;
@ -282,8 +281,6 @@ public final class Upgrade1to2Command extends SimpleCommand {
}
private void handleMemberShape(MemberShape shape) {
replacePrimitiveTarget(shape);
if (hasSyntheticDefault(shape)) {
SourceLocation memberLocation = shape.getSourceLocation();
String padding = "";
@ -309,29 +306,6 @@ public final class Upgrade1to2Command extends SimpleCommand {
}
}
private void replacePrimitiveTarget(MemberShape member) {
Shape target = completeModel.expectShape(member.getTarget());
if (!Prelude.isPreludeShape(target) || !HAD_DEFAULT_VALUE_IN_1_0.contains(target.getType())) {
return;
}
IdlAwareSimpleParser parser = new IdlAwareSimpleParser(writer.flush());
parser.rewind(member.getSourceLocation());
parser.consumeUntilNoLongerMatches(character -> character != ':');
parser.skip();
parser.ws();
// Capture the start of the target identifier.
int start = parser.position();
parser.consumeUntilNoLongerMatches(ParserUtils::isValidIdentifierCharacter);
// Replace the target with the proper target. Note that we don't
// need to do any sort of mapping because smithy already upgraded
// the target, so we can just use the name of the target it added.
writer.replace(start, parser.position(), target.getId().getName());
}
private boolean hasSyntheticDefault(MemberShape shape) {
Optional<SourceLocation> defaultLocation = shape.getTrait(DefaultTrait.class)
.map(Trait::getSourceLocation);

View File

@ -0,0 +1,29 @@
$version: "2.0"
namespace com.example
structure PrimitiveBearer {
@default(0)
int: PrimitiveInteger,
@default(false)
bool: PrimitiveBoolean,
@default(0)
byte: PrimitiveByte,
@default(0)
double: PrimitiveDouble,
@default(0)
float: PrimitiveFloat,
@default(0)
long: PrimitiveLong,
@default(0)
short: PrimitiveShort,
@default(0)
handlesComments: // Nobody actually does this right?
PrimitiveShort,
@required
handlesRequired: PrimitiveLong,
handlesBox: PrimitiveByte,
}

View File

@ -1,29 +0,0 @@
$version: "2.0"
namespace com.example
structure PrimitiveBearer {
@default(0)
int: Integer,
@default(false)
bool: Boolean,
@default(0)
byte: Byte,
@default(0)
double: Double,
@default(0)
float: Float,
@default(0)
long: Long,
@default(0)
short: Short,
@default(0)
handlesComments: // Nobody actually does this right?
Short,
@required
handlesRequired: Long,
handlesBox: Byte,
}

View File

@ -22,7 +22,6 @@ import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.loader.ModelAssembler;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.model.shapes.ToShapeId;
import software.amazon.smithy.model.traits.BoxTrait;
@ -37,22 +36,13 @@ import software.amazon.smithy.utils.SetUtils;
* An index that checks if a member is nullable.
*
* <p>Note: this index assumes Smithy 2.0 nullability semantics.
* There is basic support for detecting 1.0 models by detecting
* when a removed primitive prelude shape is targeted by a member.
* Beyond that, 1.0 models SHOULD be loaded through a {@link ModelAssembler}
* to upgrade them to IDL 2.0.
* 1.0 models SHOULD be loaded through a {@link ModelAssembler}
* to upgrade them in memory to IDL 2.0. Use
* {@link #isMemberNullableInV1(MemberShape)} to check if a shape
* is nullable according to Smithy 1.0 semantics.
*/
public class NullableIndex implements KnowledgeIndex {
private static final Set<ShapeId> V1_REMOVED_PRIMITIVE_SHAPES = SetUtils.of(
ShapeId.from("smithy.api#PrimitiveBoolean"),
ShapeId.from("smithy.api#PrimitiveByte"),
ShapeId.from("smithy.api#PrimitiveShort"),
ShapeId.from("smithy.api#PrimitiveInteger"),
ShapeId.from("smithy.api#PrimitiveLong"),
ShapeId.from("smithy.api#PrimitiveFloat"),
ShapeId.from("smithy.api#PrimitiveDouble"));
private static final Set<ShapeType> V1_INHERENTLY_BOXED = SetUtils.of(
ShapeType.STRING,
ShapeType.BLOB,
@ -148,12 +138,6 @@ public class NullableIndex implements KnowledgeIndex {
* {@link ClientOptionalTrait}, while non-authoritative consumers like clients
* must honor these traits.
*
* <p>This method will also attempt to detect when a member targets a
* primitive prelude shape that was removed in Smithy IDL 2.0 to account
* for models that were created manually without passing through a
* ModelAssembler. If a member targets a removed primitive prelude shape,
* the member is considered non-null.
*
* @param member Member to check.
* @param checkMode The mode used when checking if the member is considered nullable.
* @return Returns true if the member is optional.
@ -171,14 +155,7 @@ public class NullableIndex implements KnowledgeIndex {
}
// Structure members that are @required or @default are not nullable.
if (member.hasTrait(DefaultTrait.class) || member.hasTrait(RequiredTrait.class)) {
return false;
}
// Detect if the member targets a 1.0 primitive prelude shape and the shape wasn't upgraded.
// These removed prelude shapes are impossible to appear in a 2.0 model, so it's safe to
// detect them and honor 1.0 semantics here.
return !V1_REMOVED_PRIMITIVE_SHAPES.contains(member.getTarget());
return !member.hasTrait(DefaultTrait.class) && !member.hasTrait(RequiredTrait.class);
case UNION:
case SET:
// Union and set members are never null.

View File

@ -18,9 +18,7 @@ package software.amazon.smithy.model.loader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
@ -42,13 +40,12 @@ import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidatedResult;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.model.validation.Validator;
import software.amazon.smithy.utils.MapUtils;
import software.amazon.smithy.utils.SetUtils;
/**
* Upgrades Smithy models from IDL v1 to IDL v2, specifically taking into
* account the removal of the Primitive* shapes from the prelude, the
* removal of the @box trait, the change in default value semantics of
* numbers and booleans, and the @default trait.
* account the removal of the box trait, the change in default value
* semantics of numbers and booleans, and the @default trait.
*/
final class ModelUpgrader {
@ -62,18 +59,15 @@ final class ModelUpgrader {
ShapeType.DOUBLE,
ShapeType.BOOLEAN);
/** Provides a mapping of prelude shapes that were removed to the shape to use instead. */
private static final Map<ShapeId, ShapeId> REMOVED_PRIMITIVE_SHAPES = MapUtils.of(
ShapeId.from("smithy.api#PrimitiveBoolean"), ShapeId.from("smithy.api#Boolean"),
ShapeId.from("smithy.api#PrimitiveByte"), ShapeId.from("smithy.api#Byte"),
ShapeId.from("smithy.api#PrimitiveShort"), ShapeId.from("smithy.api#Short"),
ShapeId.from("smithy.api#PrimitiveInteger"), ShapeId.from("smithy.api#Integer"),
ShapeId.from("smithy.api#PrimitiveLong"), ShapeId.from("smithy.api#Long"),
ShapeId.from("smithy.api#PrimitiveFloat"), ShapeId.from("smithy.api#Float"),
ShapeId.from("smithy.api#PrimitiveDouble"), ShapeId.from("smithy.api#Double"));
/** Shapes that were boxed in 1.0, but @box was removed in 2.0. */
private static final Set<ShapeId> PREVIOUSLY_BOXED = new HashSet<>(REMOVED_PRIMITIVE_SHAPES.values());
private static final Set<ShapeId> PREVIOUSLY_BOXED = SetUtils.of(
ShapeId.from("smithy.api#Boolean"),
ShapeId.from("smithy.api#Byte"),
ShapeId.from("smithy.api#Short"),
ShapeId.from("smithy.api#Integer"),
ShapeId.from("smithy.api#Long"),
ShapeId.from("smithy.api#Float"),
ShapeId.from("smithy.api#Double"));
private final Model model;
private final List<ValidationEvent> events;
@ -117,13 +111,6 @@ final class ModelUpgrader {
// This builder will become non-null if/when the member needs to be updated.
MemberShape.Builder builder = null;
// Rewrite the member to target the non-removed prelude shape if it's known to be removed.
if (REMOVED_PRIMITIVE_SHAPES.containsKey(target.getId())) {
emitWhenTargetingRemovedPreludeShape(Severity.WARNING, member);
builder = createOrReuseBuilder(member, builder);
builder.target(REMOVED_PRIMITIVE_SHAPES.get(target.getId()));
}
// Add the @default trait to structure members when needed.
if (shouldV1MemberHaveDefaultTrait(containerType, member, target)) {
events.add(ValidationEvent.builder()
@ -166,7 +153,6 @@ final class ModelUpgrader {
&& containerType == ShapeType.STRUCTURE
&& !member.hasTrait(DefaultTrait.class) // don't add box if it has a default trait.
&& !member.hasTrait(BoxTrait.class) // don't add box again
&& !REMOVED_PRIMITIVE_SHAPES.containsKey(target.getId())
&& (target.hasTrait(BoxTrait.class) || PREVIOUSLY_BOXED.contains(target.getId()));
}
@ -234,10 +220,6 @@ final class ModelUpgrader {
@SuppressWarnings("deprecation")
private void validateV2Member(MemberShape member) {
if (REMOVED_PRIMITIVE_SHAPES.containsKey(member.getTarget())) {
emitWhenTargetingRemovedPreludeShape(Severity.ERROR, member);
}
if (member.hasTrait(BoxTrait.class)) {
events.add(ValidationEvent.builder()
.id(Validator.MODEL_DEPRECATION)
@ -248,16 +230,4 @@ final class ModelUpgrader {
.build());
}
}
private void emitWhenTargetingRemovedPreludeShape(Severity severity, MemberShape member) {
events.add(ValidationEvent.builder()
.id(Validator.MODEL_DEPRECATION)
.severity(severity)
.shape(member)
.sourceLocation(member)
.message("This member targets " + member.getTarget() + " which was removed in Smithy "
+ "IDL " + Model.MODEL_VERSION + ". Target "
+ REMOVED_PRIMITIVE_SHAPES.get(member.getTarget()) + " instead ")
.build());
}
}

View File

@ -144,7 +144,8 @@ public final class SmithyTestCase {
private boolean isModelDeprecationEvent(ValidationEvent event) {
return event.getId().equals(Validator.MODEL_DEPRECATION)
// Trait vendors should be free to deprecate a trait without breaking consumers.
|| (event.getId().equals("DeprecatedTrait"));
|| event.getId().equals("DeprecatedTrait")
|| event.getId().equals("DeprecatedShape");
}
private static String inferErrorFileLocation(String modelLocation) {

View File

@ -21,7 +21,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import software.amazon.smithy.model.Model;
@ -33,11 +33,14 @@ import software.amazon.smithy.model.neighbor.RelationshipType;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.model.traits.DeprecatedTrait;
import software.amazon.smithy.model.traits.MixinTrait;
import software.amazon.smithy.model.traits.TraitDefinition;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.utils.FunctionalUtils;
import software.amazon.smithy.utils.MapUtils;
import software.amazon.smithy.utils.SetUtils;
import software.amazon.smithy.utils.StringUtils;
@ -50,6 +53,18 @@ public final class TargetValidator extends AbstractValidator {
private static final Set<ShapeType> INVALID_MEMBER_TARGETS = SetUtils.of(
ShapeType.SERVICE, ShapeType.RESOURCE, ShapeType.OPERATION, ShapeType.MEMBER);
// Relationship types listed here are checked to see if a shape refers to a deprecated shape.
private static final Map<RelationshipType, String> RELATIONSHIP_TYPE_DEPRECATION_MAPPINGS = MapUtils.of(
RelationshipType.MEMBER_TARGET, "Member targets a deprecated shape",
RelationshipType.RESOURCE, "Binds a deprecated resource",
RelationshipType.OPERATION, "Binds a deprecated operation",
RelationshipType.IDENTIFIER, "Resource identifier targets a deprecated shape",
RelationshipType.PROPERTY, "Resource property targets a deprecated shape",
RelationshipType.INPUT, "Operation input targets a deprecated shape",
RelationshipType.OUTPUT, "Operation output targets a deprecated shape",
RelationshipType.ERROR, "Operation error targets a deprecated shape",
RelationshipType.MIXIN, "Applies a deprecated mixin");
@Override
public List<ValidationEvent> validate(Model model) {
List<ValidationEvent> events = new ArrayList<>();
@ -68,110 +83,144 @@ public final class TargetValidator extends AbstractValidator {
) {
for (Relationship relationship : relationships) {
if (relationship.getNeighborShape().isPresent()) {
validateTarget(model, shape, relationship.getNeighborShape().get(), relationship)
.ifPresent(mutableEvents::add);
validateTarget(model, shape, relationship.getNeighborShape().get(), relationship, mutableEvents);
} else {
mutableEvents.add(unresolvedTarget(model, shape, relationship));
}
}
}
private Optional<ValidationEvent> validateTarget(Model model, Shape shape, Shape target, Relationship rel) {
private void validateTarget(
Model model,
Shape shape,
Shape target,
Relationship rel,
List<ValidationEvent> events
) {
RelationshipType relType = rel.getRelationshipType();
if (relType != RelationshipType.MIXIN && relType.getDirection() == RelationshipDirection.DIRECTED) {
if (target.hasTrait(TraitDefinition.class)) {
return Optional.of(error(shape, format(
events.add(error(shape, format(
"Found a %s reference to trait definition `%s`. Trait definitions cannot be targeted by "
+ "members or referenced by shapes in any other context other than applying them as "
+ "traits.", relType, rel.getNeighborShapeId())));
return;
}
// Ignoring members with the mixin trait, forbid shapes to reference mixins except as mixins.
if (!target.isMemberShape() && target.hasTrait(MixinTrait.class)) {
return Optional.of(error(shape, format(
events.add(error(shape, format(
"Illegal %s reference to mixin `%s`; shapes marked with the mixin trait can only be "
+ "referenced to apply them as a mixin.", relType, rel.getNeighborShapeId())));
return;
}
}
validateDeprecatedTargets(shape, target, relType, events);
switch (relType) {
case PROPERTY:
case MEMBER_TARGET:
// Members and property cannot target other members, service, operation, or resource shapes.
if (INVALID_MEMBER_TARGETS.contains(target.getType())) {
return Optional.of(error(shape, format(
events.add(error(shape, format(
"Members cannot target %s shapes, but found %s", target.getType(), target)));
}
return Optional.empty();
break;
case MAP_KEY:
return target.asMemberShape().flatMap(m -> validateMapKey(shape, m.getTarget(), model));
target.asMemberShape().ifPresent(m -> validateMapKey(shape, m.getTarget(), model, events));
break;
case RESOURCE:
if (target.getType() != ShapeType.RESOURCE) {
return Optional.of(badType(shape, target, relType, ShapeType.RESOURCE));
events.add(badType(shape, target, relType, ShapeType.RESOURCE));
}
return Optional.empty();
break;
case OPERATION:
if (target.getType() != ShapeType.OPERATION) {
return Optional.of(badType(shape, target, relType, ShapeType.OPERATION));
events.add(badType(shape, target, relType, ShapeType.OPERATION));
}
return Optional.empty();
break;
case INPUT:
case OUTPUT:
// Input/output must target structures and cannot have the error trait.
if (target.getType() != ShapeType.STRUCTURE) {
return Optional.of(badType(shape, target, relType, ShapeType.STRUCTURE));
events.add(badType(shape, target, relType, ShapeType.STRUCTURE));
} else if (target.findTrait("error").isPresent()) {
return Optional.of(inputOutputWithErrorTrait(shape, target, rel.getRelationshipType()));
} else {
return Optional.empty();
events.add(inputOutputWithErrorTrait(shape, target, rel.getRelationshipType()));
}
break;
case ERROR:
// Errors must target a structure with the error trait.
if (target.getType() != ShapeType.STRUCTURE) {
return Optional.of(badType(shape, target, relType, ShapeType.STRUCTURE));
events.add(badType(shape, target, relType, ShapeType.STRUCTURE));
} else if (!target.findTrait("error").isPresent()) {
return Optional.of(errorNoTrait(shape, target.getId()));
} else {
return Optional.empty();
events.add(errorNoTrait(shape, target.getId()));
}
break;
case IDENTIFIER:
return validateIdentifier(shape, target);
validateIdentifier(shape, target, events);
break;
case CREATE:
case READ:
case UPDATE:
case DELETE:
case LIST:
if (target.getType() != ShapeType.OPERATION) {
return Optional.of(error(shape, format(
events.add(error(shape, format(
"Resource %s lifecycle operation must target an operation, but found %s",
relType.toString().toLowerCase(Locale.US), target)));
} else {
return Optional.empty();
}
break;
case MIXIN:
if (!target.hasTrait(MixinTrait.class)) {
return Optional.of(error(shape, format(
events.add(error(shape, format(
"Attempted to use %s as a mixin, but it is not marked with the mixin trait",
target.getId())));
}
break;
default:
return Optional.empty();
break;
}
}
private Optional<ValidationEvent> validateMapKey(Shape shape, ShapeId target, Model model) {
return model.getShape(target)
.filter(FunctionalUtils.not(Shape::isStringShape))
.map(resolved -> error(shape, format(
"Map key member targets %s, but is expected to target a string", resolved)));
private void validateDeprecatedTargets(
Shape shape,
Shape target,
RelationshipType relType,
List<ValidationEvent> events
) {
if (!target.hasTrait(DeprecatedTrait.class)) {
return;
}
String relLabel = RELATIONSHIP_TYPE_DEPRECATION_MAPPINGS.get(relType);
if (relLabel == null) {
return;
}
StringBuilder builder = new StringBuilder(relLabel).append(", ").append(target.getId());
DeprecatedTrait deprecatedTrait = target.expectTrait(DeprecatedTrait.class);
deprecatedTrait.getMessage().ifPresent(message -> builder.append(". ").append(message));
deprecatedTrait.getSince().ifPresent(since -> builder.append(" (since ").append(since).append(')'));
events.add(ValidationEvent.builder()
.id("DeprecatedShape")
.severity(Severity.WARNING)
.shape(shape)
.message(builder.toString())
.build());
}
private Optional<ValidationEvent> validateIdentifier(Shape shape, Shape target) {
private void validateMapKey(Shape shape, ShapeId target, Model model, List<ValidationEvent> events) {
model.getShape(target).filter(FunctionalUtils.not(Shape::isStringShape)).ifPresent(resolved -> {
String message = format("Map key member targets %s, but is expected to target a string", resolved);
events.add(error(shape, message));
});
}
private void validateIdentifier(Shape shape, Shape target, List<ValidationEvent> events) {
if (target.getType() != ShapeType.STRING) {
return Optional.of(badType(shape, target, RelationshipType.IDENTIFIER, ShapeType.STRING));
} else {
return Optional.empty();
events.add(badType(shape, target, RelationshipType.IDENTIFIER, ShapeType.STRING));
}
}

View File

@ -2,28 +2,7 @@ $version: "2.0"
namespace smithy.api
@deprecated
boolean PrimitiveBoolean
@deprecated
byte PrimitiveByte
@deprecated
short PrimitiveShort
@deprecated
integer PrimitiveInteger
@deprecated
long PrimitiveLong
@deprecated
float PrimitiveFloat
@deprecated
double PrimitiveDouble
/// Indicates that a shape is boxed.
/// Used in Smithy 1.0 to indicate that a shape is boxed.
///
/// When a boxed shape is the target of a member, the member
/// may or may not contain a value, and the member has no default value.

View File

@ -31,6 +31,48 @@ double Double
@unitType
structure Unit {}
@deprecated(
message: "Use Boolean instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
boolean PrimitiveBoolean
@deprecated(
message: "Use Byte instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
byte PrimitiveByte
@deprecated(
message: "Use Short instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
short PrimitiveShort
@deprecated(
message: "Use Integer instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
integer PrimitiveInteger
@deprecated(
message: "Use Long instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
long PrimitiveLong
@deprecated(
message: "Use Float instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
float PrimitiveFloat
@deprecated(
message: "Use Double instead, and add the @default trait to structure members that targets this shape",
since: "2.0"
)
double PrimitiveDouble
/// Makes a shape a trait.
@trait(
selector: ":is(simpleType, list, map, structure, union)",

View File

@ -231,21 +231,6 @@ public class NullableIndexTest {
);
}
@Test
public void detectsPreviousPrimitivePreludeShapes() {
IntegerShape integer = IntegerShape.builder()
.id("smithy.api#PrimitiveInteger")
.build();
StructureShape struct = StructureShape.builder()
.id("smithy.example#Struct")
.addMember("foo", integer.getId())
.build();
Model model = Model.builder().addShapes(integer, struct).build();
NullableIndex index = NullableIndex.of(model);
assertThat(index.isMemberNullable(struct.getMember("foo").get()), is(false));
}
@Test
public void worksWithV1NullabilityRulesForInteger() {
// In Smithy v1, integer was non-nullable by default.

View File

@ -41,9 +41,9 @@ public class ModelUpgraderTest {
assertThat(ShapeId.from("smithy.example#Shorts$nullable2"), ShapeMatcher.memberIsNullable(result));
assertThat(ShapeId.from("smithy.example#Integers$nullable2"), ShapeMatcher.memberIsNullable(result));
assertThat(ShapeId.from("smithy.example#Bytes$nullable2"), changedMemberTarget(result, "Byte"));
assertThat(ShapeId.from("smithy.example#Shorts$nullable2"), changedMemberTarget(result, "Short"));
assertThat(ShapeId.from("smithy.example#Integers$nullable2"), changedMemberTarget(result, "Integer"));
assertThat(ShapeId.from("smithy.example#Bytes$nullable2"), targetsShape(result, "PrimitiveByte"));
assertThat(ShapeId.from("smithy.example#Shorts$nullable2"), targetsShape(result, "PrimitiveShort"));
assertThat(ShapeId.from("smithy.example#Integers$nullable2"), targetsShape(result, "PrimitiveInteger"));
assertThat(ShapeId.from("smithy.example#Bytes$nonNull"), not(ShapeMatcher.memberIsNullable(result)));
assertThat(ShapeId.from("smithy.example#Shorts$nonNull"), not(ShapeMatcher.memberIsNullable(result)));
@ -57,7 +57,9 @@ public class ModelUpgraderTest {
UpgradeTestCase testCase = UpgradeTestCase.createAndValidate("upgrade/mixed-versions");
ValidatedResult<Model> result = testCase.actualModel;
assertThat(ShapeId.from("smithy.example#Foo$number"), changedMemberTarget(result, "Integer"));
// We don't rewrite or mess with Primitive* shape references. (a previous iteration of IDL 2.0
// attempted to do shape rewrites, but it ended up causing issues with older models).
assertThat(ShapeId.from("smithy.example#Foo$number"), targetsShape(result, "PrimitiveInteger"));
assertThat(ShapeId.from("smithy.example#Foo$number"), addedDefaultTrait(result));
}
@ -66,13 +68,13 @@ public class ModelUpgraderTest {
UpgradeTestCase testCase = UpgradeTestCase.createAndValidate("upgrade/primitives-in-v2");
ValidatedResult<Model> result = testCase.actualModel;
assertThat(ShapeId.from("smithy.example#Bad$boolean"), shapeTargetsInvalidPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$byte"), shapeTargetsInvalidPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$short"), shapeTargetsInvalidPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$integer"), shapeTargetsInvalidPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$long"), shapeTargetsInvalidPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$float"), shapeTargetsInvalidPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$double"), shapeTargetsInvalidPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$boolean"), shapeTargetsDeprecatedPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$byte"), shapeTargetsDeprecatedPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$short"), shapeTargetsDeprecatedPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$integer"), shapeTargetsDeprecatedPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$long"), shapeTargetsDeprecatedPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$float"), shapeTargetsDeprecatedPrimitive(result));
assertThat(ShapeId.from("smithy.example#Bad$double"), shapeTargetsDeprecatedPrimitive(result));
}
@Test
@ -88,9 +90,9 @@ public class ModelUpgraderTest {
UpgradeTestCase testCase = UpgradeTestCase.createAndValidate("upgrade/does-not-introduce-conflict");
ValidatedResult<Model> result = testCase.actualModel;
assertThat(ShapeId.from("smithy.example#Foo$alreadyDefault"), changedMemberTarget(result, "Integer"));
assertThat(ShapeId.from("smithy.example#Foo$alreadyRequired"), changedMemberTarget(result, "Integer"));
assertThat(ShapeId.from("smithy.example#Foo$boxedMember"), changedMemberTarget(result, "Integer"));
assertThat(ShapeId.from("smithy.example#Foo$alreadyDefault"), targetsShape(result, "PrimitiveInteger"));
assertThat(ShapeId.from("smithy.example#Foo$alreadyRequired"), targetsShape(result, "PrimitiveInteger"));
assertThat(ShapeId.from("smithy.example#Foo$boxedMember"), targetsShape(result, "PrimitiveInteger"));
assertThat(ShapeId.from("smithy.example#Foo$boxedMember"), not(addedDefaultTrait(result)));
assertThat(ShapeId.from("smithy.example#Foo$previouslyBoxedTarget"), not(addedDefaultTrait(result)));
assertThat(ShapeId.from("smithy.example#Foo$explicitlyBoxedTarget"), not(addedDefaultTrait(result)));
@ -159,12 +161,12 @@ public class ModelUpgraderTest {
}
}
private static Matcher<ShapeId> changedMemberTarget(ValidatedResult<Model> result, String newShapeName) {
private static Matcher<ShapeId> targetsShape(ValidatedResult<Model> result, String shapeName) {
return ShapeMatcher.builderFor(MemberShape.class, result)
.description("Changed member to target " + newShapeName + " with a warning")
.addAssertion(member -> member.getTarget().equals(ShapeId.fromParts(Prelude.NAMESPACE, newShapeName)),
.description("Targets " + shapeName)
.addAssertion(member -> member.getTarget()
.equals(ShapeId.fromOptionalNamespace(Prelude.NAMESPACE, shapeName)),
member -> "targeted " + member.getTarget())
.addEventAssertion(Validator.MODEL_DEPRECATION, Severity.WARNING, newShapeName + " instead")
.build();
}
@ -177,13 +179,14 @@ public class ModelUpgraderTest {
.build();
}
private static Matcher<ShapeId> shapeTargetsInvalidPrimitive(ValidatedResult<Model> result) {
private static Matcher<ShapeId> shapeTargetsDeprecatedPrimitive(ValidatedResult<Model> result) {
return ShapeMatcher.builderFor(MemberShape.class, result)
.description("shape targets a removed primitive shape")
.description("shape targets a deprecated primitive shape and emits a warning")
.addAssertion(member -> member.getTarget().getName().startsWith("Primitive")
&& member.getTarget().getNamespace().equals(Prelude.NAMESPACE),
member -> "member does not target a primitive prelude shape")
.addEventAssertion(Validator.MODEL_DEPRECATION, Severity.ERROR, "removed in Smithy IDL 2.0")
.addEventAssertion("DeprecatedShape", Severity.WARNING,
"and add the @default trait to structure members that targets this shape")
.build();
}

View File

@ -1,11 +1,7 @@
[WARNING] -: Smithy IDL 1.0 is deprecated. Please upgrade to 2.0. | ModelDeprecation
[WARNING] smithy.example#Integers$noRange: Add the @default trait to this member to make it forward compatible with Smithy IDL 2.0 | ModelDeprecation
[WARNING] smithy.example#Integers$noRange: This member targets smithy.api#PrimitiveInteger which was removed in Smithy IDL 2.0. Target smithy.api#Integer instead | ModelDeprecation
[WARNING] smithy.example#Integers$valid: Add the @default trait to this member to make it forward compatible with Smithy IDL 2.0 | ModelDeprecation
[WARNING] smithy.example#Integers$valid: This member targets smithy.api#PrimitiveInteger which was removed in Smithy IDL 2.0. Target smithy.api#Integer instead | ModelDeprecation
[WARNING] smithy.example#Integers$invalidMin: Add the @default trait to this member to make it forward compatible with Smithy IDL 2.0 | ModelDeprecation
[WARNING] smithy.example#Integers$invalidMin: Cannot add the @default trait to this member due to a minimum range constraint. | ModelDeprecation
[WARNING] smithy.example#Integers$invalidMin: This member targets smithy.api#PrimitiveInteger which was removed in Smithy IDL 2.0. Target smithy.api#Integer instead | ModelDeprecation
[WARNING] smithy.example#Integers$invalidMax: Add the @default trait to this member to make it forward compatible with Smithy IDL 2.0 | ModelDeprecation
[WARNING] smithy.example#Integers$invalidMax: Cannot add the @default trait to this member due to a maximum range constraint. | ModelDeprecation
[WARNING] smithy.example#Integers$invalidMax: This member targets smithy.api#PrimitiveInteger which was removed in Smithy IDL 2.0. Target smithy.api#Integer instead | ModelDeprecation

View File

@ -0,0 +1,4 @@
[WARNING] smithy.example#Test$foo1: Member targets a deprecated shape, smithy.example#Foo1 | DeprecatedShape
[WARNING] smithy.example#Test$foo2: Member targets a deprecated shape, smithy.example#Foo2 (since 2.0) | DeprecatedShape
[WARNING] smithy.example#Test$foo3: Member targets a deprecated shape, smithy.example#Foo3. hello (since 2.0) | DeprecatedShape
[WARNING] smithy.example#Test: Applies a deprecated mixin, smithy.example#DeprecatedMixin | DeprecatedShape

View File

@ -0,0 +1,22 @@
$version: "2.0"
namespace smithy.example
@deprecated
string Foo1
@deprecated(since: "2.0")
string Foo2
@deprecated(message: "hello", since: "2.0")
string Foo3
@mixin
@deprecated
structure DeprecatedMixin {}
structure Test with [DeprecatedMixin] {
foo1: Foo1,
foo2: Foo2,
foo3: Foo3
}

View File

@ -6,31 +6,31 @@ structure Bytes {
nullable: Byte,
@default(0)
nonNull: Byte,
nonNull: PrimitiveByte,
nullable2: Byte,
nullable2: PrimitiveByte,
}
structure Shorts {
nullable: Short,
@default(0)
nonNull: Short,
nonNull: PrimitiveShort,
nullable2: Short,
nullable2: PrimitiveShort,
}
structure Integers {
nullable: Integer,
@default(0)
nonNull: Integer,
nonNull: PrimitiveInteger,
@default(0)
@range(min: 0, max: 1)
ranged: Integer,
ranged: PrimitiveInteger,
nullable2: Integer
nullable2: PrimitiveInteger
}
structure BlobPayload {

View File

@ -4,12 +4,12 @@ namespace smithy.example
structure Foo {
@default(0)
alreadyDefault: Integer,
alreadyDefault: PrimitiveInteger,
@required
alreadyRequired: Integer,
alreadyRequired: PrimitiveInteger,
boxedMember: Integer,
boxedMember: PrimitiveInteger,
previouslyBoxedTarget: Integer,

View File

@ -4,7 +4,7 @@ namespace smithy.example
structure Foo {
@default(0)
number: Integer
number: PrimitiveInteger
}
structure Baz {