18
18
import java .util .ArrayList ;
19
19
import java .util .List ;
20
20
import java .util .Objects ;
21
- import java .util .StringJoiner ;
22
21
import java .util .stream .Stream ;
23
22
import software .amazon .smithy .diff .ChangedShape ;
24
23
import software .amazon .smithy .diff .Differences ;
25
24
import software .amazon .smithy .model .Model ;
26
25
import software .amazon .smithy .model .knowledge .NullableIndex ;
27
26
import software .amazon .smithy .model .shapes .MemberShape ;
28
27
import software .amazon .smithy .model .shapes .Shape ;
28
+ import software .amazon .smithy .model .shapes .ShapeId ;
29
29
import software .amazon .smithy .model .shapes .StructureShape ;
30
30
import software .amazon .smithy .model .traits .ClientOptionalTrait ;
31
31
import software .amazon .smithy .model .traits .DefaultTrait ;
@@ -45,24 +45,23 @@ public class ChangedNullability extends AbstractDiffEvaluator {
45
45
public List <ValidationEvent > evaluate (Differences differences ) {
46
46
NullableIndex oldIndex = NullableIndex .of (differences .getOldModel ());
47
47
NullableIndex newIndex = NullableIndex .of (differences .getNewModel ());
48
-
49
48
List <ValidationEvent > events = new ArrayList <>();
50
49
51
- Stream < ChangedShape < MemberShape >> changed = Stream .concat (
50
+ Stream .concat (
52
51
// Get members that changed.
53
52
differences .changedShapes (MemberShape .class ),
54
53
// Get members of structures that added/removed the input trait.
55
54
changedInputMembers (differences )
56
- );
57
-
58
- changed .map (change -> {
55
+ ).forEach (change -> {
59
56
// If NullableIndex says the nullability of a member changed, then that's a breaking change.
60
57
MemberShape oldShape = change .getOldShape ();
61
58
MemberShape newShape = change .getNewShape ();
62
59
boolean wasNullable = oldIndex .isMemberNullable (oldShape );
63
60
boolean isNowNullable = newIndex .isMemberNullable (newShape );
64
- return wasNullable == isNowNullable ? null : createError (differences , change , wasNullable );
65
- }).filter (Objects ::nonNull ).forEach (events ::add );
61
+ if (wasNullable != isNowNullable ) {
62
+ createErrors (differences , change , wasNullable , events );
63
+ }
64
+ });
66
65
67
66
return events ;
68
67
}
@@ -79,78 +78,92 @@ private Stream<ChangedShape<MemberShape>> changedInputMembers(Differences differ
79
78
.filter (Objects ::nonNull ));
80
79
}
81
80
82
- private ValidationEvent createError (
81
+ private void createErrors (
83
82
Differences differences ,
84
83
ChangedShape <MemberShape > change ,
85
- boolean wasNullable
84
+ boolean wasNullable ,
85
+ List <ValidationEvent > events
86
86
) {
87
87
MemberShape oldMember = change .getOldShape ();
88
88
MemberShape newMember = change .getNewShape ();
89
89
String message = String .format ("Member `%s` changed from %s to %s: " ,
90
90
oldMember .getMemberName (),
91
91
wasNullable ? "nullable" : "non-nullable" ,
92
92
wasNullable ? "non-nullable" : "nullable" );
93
- StringJoiner joiner = new StringJoiner ("; " , message , "" );
94
93
boolean oldHasInput = hasInputTrait (differences .getOldModel (), oldMember );
95
94
boolean newHasInput = hasInputTrait (differences .getNewModel (), newMember );
95
+ ShapeId shape = change .getShapeId ();
96
96
Shape newTarget = differences .getNewModel ().expectShape (newMember .getTarget ());
97
- Severity severity = null ;
97
+ int currentEventSize = events . size () ;
98
98
99
99
if (oldHasInput && !newHasInput ) {
100
100
// If there was an input trait before, but not now, then the nullability must have
101
101
// changed from nullable to non-nullable.
102
- joiner .add ("The @input trait was removed from " + newMember . getContainer ());
103
- severity = Severity . ERROR ;
102
+ events .add (emit ( Severity . ERROR , "RemovedInputTrait" , shape , message ,
103
+ "The @input trait was removed from " + newMember . getContainer ())) ;
104
104
} else if (!oldHasInput && newHasInput ) {
105
105
// If there was no input trait before, but there is now, then the nullability must have
106
106
// changed from non-nullable to nullable.
107
- joiner .add ("The @input trait was added to " + newMember . getContainer ());
108
- severity = Severity . DANGER ;
107
+ events .add (emit ( Severity . DANGER , "AddedInputTrait" , shape , message ,
108
+ "The @input trait was added to " + newMember . getContainer ())) ;
109
109
} else if (!newHasInput ) {
110
110
// Can't add nullable to a preexisting required member.
111
111
if (change .isTraitAdded (ClientOptionalTrait .ID ) && change .isTraitInBoth (RequiredTrait .ID )) {
112
- joiner .add ("The @nullable trait was added to a @required member." );
113
- severity = Severity . ERROR ;
112
+ events .add (emit ( Severity . ERROR , "AddedNullableTrait" , shape , message ,
113
+ "The @nullable trait was added to a @required member." )) ;
114
114
}
115
115
// Can't add required to a member unless the member is marked as nullable.
116
116
if (change .isTraitAdded (RequiredTrait .ID ) && !newMember .hasTrait (ClientOptionalTrait .ID )) {
117
- joiner .add ("The @required trait was added to a member that is not marked as @nullable." );
118
- severity = Severity . ERROR ;
117
+ events .add (emit ( Severity . ERROR , "AddedRequiredTrait" , shape , message ,
118
+ "The @required trait was added to a member that is not marked as @nullable." )) ;
119
119
}
120
120
// Can't add the default trait to a member unless the member was previously required.
121
121
if (change .isTraitAdded (DefaultTrait .ID ) && !change .isTraitRemoved (RequiredTrait .ID )) {
122
- joiner .add ("The @default trait was added to a member that was not previously @required." );
123
- severity = Severity . ERROR ;
122
+ events .add (emit ( Severity . ERROR , "AddedDefaultTrait" , shape , message ,
123
+ "The @default trait was added to a member that was not previously @required." )) ;
124
124
}
125
125
// Can only remove the required trait if the member was nullable or replaced by the default trait.
126
126
if (change .isTraitRemoved (RequiredTrait .ID )
127
127
&& !newMember .hasTrait (DefaultTrait .ID )
128
128
&& !oldMember .hasTrait (ClientOptionalTrait .ID )) {
129
129
if (newTarget .isStructureShape () || newTarget .isUnionShape ()) {
130
- severity = severity == null ? Severity .WARNING : severity ;
131
- joiner . add ( "The @required trait was removed from a member that targets a " + newTarget . getType ()
132
- + ". This is backward compatible in generators that always treat structures and "
133
- + "unions as optional (e.g., AWS generators)" );
130
+ events . add ( emit ( Severity .WARNING , "RemovedRequiredTrait.StructureOrUnion" , shape , message ,
131
+ "The @required trait was removed from a member that targets a "
132
+ + newTarget . getType () + ". This is backward compatible in generators that "
133
+ + "always treat structures and unions as optional (e.g., AWS generators)" ) );
134
134
} else {
135
- joiner .add ("The @required trait was removed and not replaced with the @default trait and "
136
- + "@addedDefault trait." );
137
- severity = Severity . ERROR ;
135
+ events .add (emit ( Severity . ERROR , "RemovedRequiredTrait" , shape , message ,
136
+ "The @required trait was removed and not replaced with the @default trait and "
137
+ + "@addedDefault trait." )) ;
138
138
}
139
139
}
140
140
}
141
141
142
- // If no explicit severity was detected, then assume an error.
143
- severity = severity == null ? Severity .ERROR : severity ;
144
-
145
- return ValidationEvent .builder ()
146
- .id (getEventId ())
147
- .shapeId (change .getNewShape ())
148
- .severity (severity )
149
- .message (joiner .toString ())
150
- .build ();
142
+ // If not specific event was emitted, emit a generic event.
143
+ if (events .size () == currentEventSize ) {
144
+ events .add (emit (Severity .ERROR , null , shape , null , message ));
145
+ }
151
146
}
152
147
153
148
private boolean hasInputTrait (Model model , MemberShape member ) {
154
149
return model .getShape (member .getContainer ()).filter (shape -> shape .hasTrait (InputTrait .ID )).isPresent ();
155
150
}
151
+
152
+ private ValidationEvent emit (
153
+ Severity severity ,
154
+ String eventIdSuffix ,
155
+ ShapeId shape ,
156
+ String prefixMessage ,
157
+ String message
158
+ ) {
159
+ String actualId = eventIdSuffix == null ? getEventId () : (getEventId () + '.' + eventIdSuffix );
160
+ String actualMessage = prefixMessage == null ? message : (prefixMessage + "; " + message );
161
+ return ValidationEvent .builder ()
162
+ .id (actualId )
163
+ .shapeId (shape )
164
+ .message (actualMessage )
165
+ .severity (severity )
166
+ .message (message )
167
+ .build ();
168
+ }
156
169
}
0 commit comments