Skip to content

Commit

Permalink
feat(rust): validate nested constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-chew committed Dec 17, 2024
1 parent 5b66889 commit c3e6b46
Show file tree
Hide file tree
Showing 13 changed files with 529 additions and 136 deletions.
38 changes: 34 additions & 4 deletions TestModels/Constraints/Model/Constraints.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ list ListLessThanOrEqualToTen {
member: String
}

@length(min: 1, max: 10)
list ListWithConstraint {
member: MyString
}

@length(min: 1, max: 10)
map MyMap {
key: String,
Expand All @@ -133,6 +138,12 @@ map MapLessThanOrEqualToTen {
value: String,
}

@length(min: 1, max: 10)
map MapWithConstraint {
key: MyString,
value: MyString,
}

// we don't do patterns yet
// @pattern("^[A-Za-z]+$")
// string Alphabetic
Expand Down Expand Up @@ -160,10 +171,21 @@ integer LessThanTen
// member: ComplexListElement
// }

// structure ComplexListElement {
// value: String,
// blob: Blob,
// }
union UnionWithConstraint {
IntegerValue: OneToTen,
StringValue: MyString,
}

structure ComplexStructure {
InnerString: MyString,
@required
InnerBlob: MyBlob,
}

@length(min: 1)
list ComplexStructureList {
member: ComplexStructure,
}

structure GetConstraintsInput {
MyString: MyString,
Expand All @@ -175,9 +197,11 @@ structure GetConstraintsInput {
MyList: MyList,
NonEmptyList: NonEmptyList,
ListLessThanOrEqualToTen: ListLessThanOrEqualToTen,
ListWithConstraint: ListWithConstraint,
MyMap: MyMap,
NonEmptyMap: NonEmptyMap,
MapLessThanOrEqualToTen: MapLessThanOrEqualToTen,
MapWithConstraint: MapWithConstraint,
// Alphabetic: Alphabetic,
OneToTen: OneToTen,
myTenToTen: TenToTen,
Expand All @@ -187,6 +211,8 @@ structure GetConstraintsInput {
// MyComplexUniqueList: MyComplexUniqueList,
MyUtf8Bytes: Utf8Bytes,
MyListOfUtf8Bytes: ListOfUtf8Bytes,
UnionWithConstraint: UnionWithConstraint,
ComplexStructureList: ComplexStructureList,
}

structure GetConstraintsOutput {
Expand All @@ -199,9 +225,11 @@ structure GetConstraintsOutput {
MyList: MyList,
NonEmptyList: NonEmptyList,
ListLessThanOrEqualToTen: ListLessThanOrEqualToTen,
ListWithConstraint: ListWithConstraint,
MyMap: MyMap,
NonEmptyMap: NonEmptyMap,
MapLessThanOrEqualToTen: MapLessThanOrEqualToTen,
MapWithConstraint: MapWithConstraint,
// Alphabetic: Alphabetic,
OneToTen: OneToTen,
thatTenToTen: TenToTen,
Expand All @@ -211,6 +239,8 @@ structure GetConstraintsOutput {
// MyComplexUniqueList: MyComplexUniqueList,
MyUtf8Bytes: Utf8Bytes,
MyListOfUtf8Bytes: ListOfUtf8Bytes,
UnionWithConstraint: UnionWithConstraint,
ComplexStructureList: ComplexStructureList,
}

// See Comment in traits.smithy
Expand Down
1 change: 1 addition & 0 deletions TestModels/Constraints/runtimes/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod operation;
mod standard_library_conversions;
mod standard_library_externs;
pub mod types;
mod validation;
pub mod wrapped;
pub(crate) use crate::implementation_from_dafny::r#_Wrappers_Compile;
pub(crate) use crate::implementation_from_dafny::simple;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ extern crate simple_constraints;
mod simple_constraints_test {
use simple_constraints::*;

use std::collections::HashMap;

fn client() -> Client {
let config = SimpleConstraintsConfig::builder()
.required_string("test string")
Expand Down Expand Up @@ -82,4 +84,87 @@ mod simple_constraints_test {
let message = result.err().expect("error").to_string();
assert!(message.contains("one_to_ten"));
}

#[tokio::test]
async fn test_good_list_with_constraint() {
let vec = vec!["1".to_string(), "123".to_string(), "1234567890".to_string()];
let result = client().get_constraints()
.list_with_constraint(vec)
.send().await;
assert!(result.is_ok());
}

#[tokio::test]
async fn test_bad_list_with_constraint() {
let vec = vec!["".to_string(), "this string is too long".to_string()];
let result = client().get_constraints()
.list_with_constraint(vec)
.send().await;
let message = result.err().expect("error").to_string();
assert!(message.contains("member"));
}

#[tokio::test]
async fn test_good_map_with_constraint() {
let mut map = HashMap::new();
map.insert("foo".to_string(), "bar".to_string());

let result = client().get_constraints()
.map_with_constraint(map)
.send().await;
assert!(result.is_ok());
}

#[tokio::test]
async fn test_bad_map_with_constraint() {
let mut map = HashMap::new();
map.insert("this key is too long".to_string(), "bar".to_string());

let result = client().get_constraints()
.map_with_constraint(map)
.send().await;
let message = result.err().expect("error").to_string();
assert!(message.contains("key"));

let mut map = HashMap::new();
map.insert("foo".to_string(), "this value is too long".to_string());

let result = client().get_constraints()
.map_with_constraint(map)
.send().await;
let message = result.err().expect("error").to_string();
assert!(message.contains("value"));
}

#[tokio::test]
async fn test_good_union_with_constraint() {
let union_val = types::UnionWithConstraint::IntegerValue(1);
let result = client().get_constraints()
.union_with_constraint(union_val)
.send().await;
assert!(result.is_ok());

let union_val = types::UnionWithConstraint::StringValue("foo".to_string());
let result = client().get_constraints()
.union_with_constraint(union_val)
.send().await;
assert!(result.is_ok());
}

#[tokio::test]
async fn test_bad_union_with_constraint() {
let union_val = types::UnionWithConstraint::IntegerValue(100);
let result = client().get_constraints()
.union_with_constraint(union_val)
.send().await;
let message = result.err().expect("error").to_string();
assert!(message.contains("integer_value"));

let union_val = types::UnionWithConstraint::StringValue("this string is too long".to_string());
let result = client().get_constraints()
.union_with_constraint(union_val)
.send().await;
let message = result.err().expect("error").to_string();
assert!(message.contains("string_value"));
}
}
8 changes: 6 additions & 2 deletions TestModels/Constraints/src/SimpleConstraintsImpl.dfy
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,23 @@ module SimpleConstraintsImpl refines AbstractSimpleConstraintsOperations {
MyList := input.MyList,
NonEmptyList := input.NonEmptyList,
ListLessThanOrEqualToTen := input.ListLessThanOrEqualToTen,
ListWithConstraint := input.ListWithConstraint,
MyMap := input.MyMap,
NonEmptyMap := input.NonEmptyMap,
MapLessThanOrEqualToTen := input.MapLessThanOrEqualToTen,
MapWithConstraint := input.MapWithConstraint,
// Alphabetic := input.Alphabetic,
OneToTen := input.OneToTen,
GreaterThanOne := input.GreaterThanOne,
LessThanTen := input.LessThanTen,
// MyUniqueList := input.MyUniqueList,
// MyComplexUniqueList := input.MyComplexUniqueList,
MyUtf8Bytes := input.MyUtf8Bytes,
MyListOfUtf8Bytes := input.MyListOfUtf8Bytes
MyListOfUtf8Bytes := input.MyListOfUtf8Bytes,
UnionWithConstraint := input.UnionWithConstraint,
ComplexStructureList := input.ComplexStructureList
);

return Success(res);
}
}
}
9 changes: 8 additions & 1 deletion TestModels/Constraints/test/Helpers.dfy
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ module Helpers {
MyList := Some(["00", "11"]),
NonEmptyList := Some(["00", "11"]),
ListLessThanOrEqualToTen := Some(["00", "11"]),
ListWithConstraint := Some(["0", "123", "MaxTenChar"]),
MyMap := Some(map["0" := "1", "2" := "3"]),
NonEmptyMap := Some(map["0" := "1", "2" := "3"]),
MapLessThanOrEqualToTen := Some(map["0" := "1", "2" := "3"]),
MapWithConstraint := Some(map["0" := "0123456789", "abcdefghij" := "z"]),
// Alphabetic := Some("alphabetic"),
OneToTen := Some(3),
myTenToTen := Some(3),
Expand All @@ -44,7 +46,12 @@ module Helpers {
// MyUniqueList := Some(["one", "two"]),
// MyComplexUniqueList := Some(myComplexUniqueList),
MyUtf8Bytes := Some(PROVIDER_ID),
MyListOfUtf8Bytes := Some([PROVIDER_ID, PROVIDER_ID])
MyListOfUtf8Bytes := Some([PROVIDER_ID, PROVIDER_ID]),
UnionWithConstraint := Some(IntegerValue(1)),
ComplexStructureList := Some([
ComplexStructure(InnerString := Some("s1"), InnerBlob := [1, 1]),
ComplexStructure(InnerString := Some("s2"), InnerBlob := [2, 3, 4])
])
)
}

Expand Down
66 changes: 66 additions & 0 deletions TestModels/Constraints/test/WrappedSimpleConstraintsTest.dfy
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ module WrappedSimpleConstraintsTest {
TestGetConstraintWithMyList(client);
TestGetConstraintWithNonEmptyList(client);
TestGetConstraintWithListLessThanOrEqualToTen(client);
TestGetConstraintWithListWithConstraint(client);
TestGetConstraintWithMyMap(client);
TestGetConstraintWithNonEmptyMap(client);
TestGetConstraintWithMapLessThanOrEqualToTen(client);
TestGetConstraintWithMapWithConstraint(client);
TestGetConstraintWithGreaterThanOne(client);
TestGetConstraintWithUtf8Bytes(client);
TestGetConstraintWithListOfUtf8Bytes(client);
TestGetConstraintWithUnionWithConstraint(client);
TestGetConstraintWithComplexStructureList(client);

var allowBadUtf8BytesFromDafny := true;
if (allowBadUtf8BytesFromDafny) {
Expand Down Expand Up @@ -373,6 +377,20 @@ module WrappedSimpleConstraintsTest {
expect ret.Failure?;
}

// both list and member have @length(min: 1, max: 10)
method TestGetConstraintWithListWithConstraint(client: ISimpleConstraintsClient)
requires client.ValidState()
modifies client.Modifies
ensures client.ValidState()
{
var input := GetValidInput();
input := input.(ListWithConstraint := Some(["1", "2", "3"]));
var ret := client.GetConstraints(input := input);
expect ret.Success?;

// TODO: Add negative tests once all languages support it
}

// @length(min: 1, max: 10)
method TestGetConstraintWithMyMap(client: ISimpleConstraintsClient)
requires client.ValidState()
Expand Down Expand Up @@ -445,6 +463,20 @@ module WrappedSimpleConstraintsTest {
expect ret.Failure?;
}

// both map and member have @length(min: 1, max: 10)
method TestGetConstraintWithMapWithConstraint(client: ISimpleConstraintsClient)
requires client.ValidState()
modifies client.Modifies
ensures client.ValidState()
{
var input := GetValidInput();
input := input.(MapWithConstraint := Some(map["0" := "1234", "abcd" := "j"]));
var ret := client.GetConstraints(input := input);
expect ret.Success?;

// TODO: Add negative tests once all languages support it
}

// @range(min: 1)
method TestGetConstraintWithGreaterThanOne(client: ISimpleConstraintsClient)
requires client.ValidState()
Expand Down Expand Up @@ -589,4 +621,38 @@ module WrappedSimpleConstraintsTest {
ret := client.GetConstraints(input := input);
expect ret.Failure?;
}

method TestGetConstraintWithUnionWithConstraint(client: ISimpleConstraintsClient)
requires client.ValidState()
modifies client.Modifies
ensures client.ValidState()
{
var input := GetValidInput();
input := input.(UnionWithConstraint := Some(IntegerValue(1)));
var ret := client.GetConstraints(input := input);
expect ret.Success?;

input := GetValidInput();
input := input.(UnionWithConstraint := Some(StringValue("foo")));
ret := client.GetConstraints(input := input);
expect ret.Success?;

// TODO: Add negative tests once all languages support it
}

method TestGetConstraintWithComplexStructureList(client: ISimpleConstraintsClient)
requires client.ValidState()
modifies client.Modifies
ensures client.ValidState()
{
var input := GetValidInput();
input := input.(ComplexStructureList := Some([
ComplexStructure(InnerString := Some("a"), InnerBlob := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
ComplexStructure(InnerString := Some("abcdefghij"), InnerBlob := [0])
]));
var ret := client.GetConstraints(input := input);
expect ret.Success?;

// TODO: Add negative tests once all languages support it
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import software.amazon.smithy.model.traits.ErrorTrait;
import software.amazon.smithy.model.traits.RequiredTrait;
import software.amazon.smithy.model.traits.StringTrait;
import software.amazon.smithy.model.traits.TraitDefinition;
import software.amazon.smithy.model.traits.UnitTypeTrait;

public abstract class AbstractRustShimGenerator {
Expand Down Expand Up @@ -132,8 +133,8 @@ protected boolean shouldGenerateStructForStructure(
StructureShape structureShape
) {
return (
!structureShape.hasTrait(TraitDefinition.class) &&
!structureShape.hasTrait(ErrorTrait.class) &&
!structureShape.hasTrait(ShapeId.from("smithy.api#trait")) &&
!structureShape.hasTrait(ReferenceTrait.class) &&
ModelUtils.isInServiceNamespace(structureShape, service)
);
Expand Down
Loading

0 comments on commit c3e6b46

Please sign in to comment.