Skip to content

Commit

Permalink
Merge pull request #18 from disneystreaming/timestamp-restjson
Browse files Browse the repository at this point in the history
timestamp validator
  • Loading branch information
daddykotex authored Nov 15, 2022
2 parents 857c88d + cad4883 commit c377e2b
Show file tree
Hide file tree
Showing 2 changed files with 288 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* Copyright 2022 Disney Streaming
*
* Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://disneystreaming.github.io/TOST-1.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 alloy.validation;

import alloy.SimpleRestJsonTrait;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.neighbor.Walker;
import software.amazon.smithy.model.neighbor.NeighborProvider;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.shapes.*;
import software.amazon.smithy.model.traits.TimestampFormatTrait;
import software.amazon.smithy.model.validation.ValidationEvent;

import java.util.Optional;
import java.util.Set;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class SimpleRestJsonTimestampValidator extends AbstractValidator {


private static class MemberAndTarget {
private final MemberShape member;
private final Shape target;

public MemberAndTarget(MemberShape member, Shape target) {
this.member = member;
this.target = target;
}

public MemberShape getMember() {
return member;
}

public Shape getTarget() {
return target;
}
}

@Override
public List<ValidationEvent> validate(Model model) {

Walker walker = new Walker(NeighborProvider.of(model));
Set<Shape> entryPoints = model.getShapesWithTrait(SimpleRestJsonTrait.class);
Stream<MemberAndTarget> closure = entryPoints.stream()
.flatMap(restJsonService -> walker.walkShapes(restJsonService).stream().filter(Shape::isMemberShape))
.map(shape -> new MemberAndTarget(shape.asMemberShape().get(), model.expectShape(shape.asMemberShape().get().getTarget())));

return closure
.flatMap(this::validateTimestamp).collect(Collectors.toList());
}

private Stream<ValidationEvent> validateTimestamp(MemberAndTarget shape) {
if (!shape.member.getTrait(TimestampFormatTrait.class).isPresent() && !shape.target.getTrait(TimestampFormatTrait.class).isPresent()) {
return Stream.of(warn(shape.member));
} else {
return Stream.empty();
}
}

private ValidationEvent warn(Shape shape) {
return warning(shape, "A Timestamp shape does not have a timestamp format trait");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/* Copyright 2022 Disney Streaming
*
* Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://disneystreaming.github.io/TOST-1.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 alloy.validation

import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.*
import software.amazon.smithy.model.validation.Severity
import software.amazon.smithy.model.validation.ValidationEvent

import scala.jdk.CollectionConverters.*
import alloy.SimpleRestJsonTrait
import software.amazon.smithy.model.traits.TimestampFormatTrait

final class SimpleRestJsonTimestampFormatValidatorSpec extends munit.FunSuite {
val validator = new SimpleRestJsonTimestampValidator()
val timestamp: TimestampShape = TimestampShape
.builder()
.id("test#time")
.build()

def modelAssembler(shapes: Shape*): Model = Model.assembler().disableValidation().addShapes(shapes: _*).assemble().unwrap()

test(
"warn when EITHER the member targeting a timestamp or the timestamp shape itself does not have a timestamp format trait and is reachable from a service with a rest json trait"
) {

val member0 = MemberShape
.builder()
.id("test#struct$ts")
.target(timestamp.getId)
.build()
val member1 = MemberShape
.builder()
.id("test#struct$ts2")
.target("smithy.api#Timestamp")
.build()
val struct =
StructureShape
.builder()
.id("test#struct")
.addMember(member0)
.addMember(member1)
.build()

val op = OperationShape.builder().id("test#TestOp").input(struct).build()
val service = ServiceShape
.builder()
.id("test#TestService")
.version("1")
.addOperation(op)
.addTrait(new SimpleRestJsonTrait())
.build()

val model =
modelAssembler(timestamp, member0, member1, struct, op, service)

val result = validator.validate(model).asScala.toList

val expected = List(
ValidationEvent
.builder()
.id("SimpleRestJsonTimestamp")
.severity(Severity.WARNING)
.shape(member1)
.message(
"A Timestamp shape does not have a timestamp format trait"
)
.build(),
ValidationEvent
.builder()
.id("SimpleRestJsonTimestamp")
.shape(member0)
.severity(Severity.WARNING)
.message(
"A Timestamp shape does not have a timestamp format trait"
)
.build(),

)
assertEquals(result, expected)
}

test("when a timestamp shape is not accesible from a service annotated with SimpleRestJson a warning is not issued") {

val timestamp = TimestampShape
.builder()
.id("test#time")
.build()
val member0 = MemberShape
.builder()
.id("test#struct$ts")
.target(timestamp.getId)
.build()
val member1 = MemberShape
.builder()
.id("test#struct$ts2")
.target("smithy.api#Timestamp")
.build()
val struct =
StructureShape
.builder()
.id("test#struct")
.addMember(member0)
.addMember(member1)
.build()

val op = OperationShape.builder().id("test#TestOp").input(struct).build()
val service = ServiceShape
.builder()
.id("test#TestService")
.version("1")
.addOperation(op)
.build()

val model =
modelAssembler(timestamp, member1, member0, struct, op, service)

val result = validator.validate(model).asScala.toList

assertEquals(result, List.empty)
}

test("when a timestamp shape is annotated with the timestamp format trait a warning is not issued ") {
val timestamp = TimestampShape
.builder()
.id("test#time")
.addTrait(new TimestampFormatTrait(TimestampFormatTrait.HTTP_DATE))
.build()
val member0 = MemberShape
.builder()
.id("test#struct$ts")
.target(timestamp.getId)
.build()
val struct =
StructureShape
.builder()
.id("test#struct")
.addMember(member0)
.build()

val op = OperationShape.builder().id("test#TestOp").input(struct).build()
val service = ServiceShape
.builder()
.id("test#TestService")
.version("1")
.addOperation(op)
.addTrait(new SimpleRestJsonTrait())
.build()

val model =
modelAssembler(timestamp, member0, struct, op, service)

val result = validator.validate(model).asScala.toList
assertEquals(result, List.empty)
}
test("when a member shape that is targeting a timestamp is annotated with the timestamp format trait, a warning is not issued ") {
val timestamp = TimestampShape
.builder()
.id("test#time")
.build()
val member0 = MemberShape
.builder()
.id("test#struct$ts")
.addTrait(new TimestampFormatTrait(TimestampFormatTrait.HTTP_DATE))
.target(timestamp.getId)
.build()
val member1 = MemberShape
.builder()
.id("test#struct$ts2")
.addTrait(new TimestampFormatTrait(TimestampFormatTrait.HTTP_DATE))
.target("smithy.api#Timestamp")
.build()
val struct =
StructureShape
.builder()
.id("test#struct")
.addMember(member0)
.addMember(member1)
.build()

val op = OperationShape.builder().id("test#TestOp").input(struct).build()
val service = ServiceShape
.builder()
.id("test#TestService")
.version("1")
.addOperation(op)
.addTrait(new SimpleRestJsonTrait())
.build()

val model =
modelAssembler(timestamp, member1, member0, struct, op, service)

val result = validator.validate(model).asScala.toList
assertEquals(result, List.empty)
}
}

0 comments on commit c377e2b

Please sign in to comment.