-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Streamlines ISL model using version maker traits #191
Conversation
* adds verison marker traits which will be used to construct schema, types and constraints for a particular ISL version * adds logic constraints implementations * adds type reference, type and import implementations based on verison marker trait * adds example usage of this new change for the ISL model
…aikd-isl-model-revamp
use crate::isl::version_based_isl::{IslTypeRef, IslVersionTrait}; | ||
|
||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct AllOf<V: IslVersionTrait> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗺️ All the constraints are now placed inside constraints
module. Each constraint will now accept a generic V
that represents the ISL version using version marker trait.
phantom: PhantomData<V>, | ||
} | ||
|
||
impl UserReservedFields<IslV2_0> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗺️ This is only defined for ISL 2.0 and hence would result in a compile time error when user tries to create this with ISL 1.0 schema. Please look at the IslSchema#with_user_reserved_fields()
for more details.
} | ||
|
||
/// Creates a nullable [IslTypeRef] using the name of the type referenced inside it for ISL 2.0 | ||
pub fn null_or_named_type_ref<A: Into<String>>(name: A) -> IslTypeRef<IslV2_0> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗺️ This will only be allowed to use with ISL 2.0 schema. Using it with an ISL 1.0 schema would result in compile time error.
Example:
let isl_type = IslType::named(
// represents the `name` of the defined type
"my_type_name".to_owned(),
vec![
// represents the `type: $null_or::int` constraint
IslConstraint::type_constraint(
IslTypeRef::null_or_named_type_ref("int")
),
]
);
// create an ISL schema using above IslType
let mut isl_schema = IslSchema::<IslV1_0>::new("my_schema", vec![], vec![isl_type], vec![], vec![]);
Output:
| let mut isl_schema = IslSchema::<IslV1_0>::new("my_schema", vec![], vec![isl_type], vec![], vec![]);
| ^^^^^^^^ expected `IslType<IslV1_0>`, found `IslType<IslV2_0>`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a few high level comments. I'd like to hear your thoughts on these before I dive into the actual code.
- Eventually, we really need to address the fact that some constraints have their own structs, and others are just an enum variant that's a tuple. If you're rewriting the model, this seems like an ideal time to fix it.
- I'd recommend something like this to eliminate some of the boilerplate for constraints. https://gist.github.com/popematt/11b1e632ed487d11ba50eda1bb43a78a (Note that this will not work exactly because it doesn't have the version it it.) As a bonus, a macro like this will mean that we also get a proper struct for each constraint.
- What other solutions did you consider? (Have you considered creating versioned builders for a versionless model?) What made you choose a versioned model over those other solutions? (I'd like to understand any tradeoffs, etc.)
Overall versioned ISL model gives an ease of use, maintainability and gives compile time checks for ISL version on types, constraints and schema.
Previous ISL model had tuples for enum variants for all the constraints. I have created a struct for each constraint so that we have the same pattern throughout for all constraints be it a constraint thats same for both ISL versions or its different for different ISL versions. It gives us a nice distinction between how 2 different versions would work for a particular constraint. Also eventually if we decide to have single model that will be used for representation as well as validation then it makes sense to create different structs for all the constraints as each one will have its own validation.
Adding a macro to create the structs for constraints does sound like good idea. I could create an issue for it.
I have chosen to go with versioned model using the version maker trait because that gives a better API to construct ISL model constraints/types and schema. This allows us to have compile time errors for when a user tries to mix two different versions of schema. I think this provides an ease of use for customers as well as ensures the different version constraints aren't mixed. |
/// Creates a `type` constraint using the [IslTypeRef] referenced inside it | ||
// type is rust keyword hence this method is named type_constraint unlike other ISL constraint methods | ||
pub fn type_constraint(isl_type_ref: IslTypeRef<V>) -> IslConstraint<V> { | ||
IslConstraint::Type(Type::new(isl_type_ref)) | ||
} | ||
|
||
/// Creates an `all_of` constraint using the [IslTypeRef] referenced inside it | ||
pub fn all_of<A: Into<Vec<IslTypeRef<V>>>>(isl_type_refs: A) -> IslConstraint<V> { | ||
IslConstraint::AllOf(AllOf::new(isl_type_refs.into())) | ||
} | ||
|
||
/// Creates an `any_of` constraint using the [IslTypeRef] referenced inside it | ||
pub fn any_of<A: Into<Vec<IslTypeRef<V>>>>(isl_type_refs: A) -> IslConstraint<V> { | ||
IslConstraint::AnyOf(AnyOf::new(isl_type_refs.into())) | ||
} | ||
|
||
/// Creates a `one_of` constraint using the [IslTypeRef] referenced inside it | ||
pub fn one_of<A: Into<Vec<IslTypeRef<V>>>>(isl_type_refs: A) -> IslConstraint<V> { | ||
IslConstraint::OneOf(OneOf::new(isl_type_refs.into())) | ||
} | ||
|
||
/// Creates a `not` constraint using the [IslTypeRef] referenced inside it | ||
pub fn not(isl_type_ref: IslTypeRef<V>) -> IslConstraint<V> { | ||
IslConstraint::Not(Not::new(isl_type_ref)) | ||
} | ||
|
||
/// Creates an `open_content` using the field name and value referenced inside it | ||
/// Note: This open content has no effect | ||
pub fn open_content(field_name: String, field_value: Element) -> IslConstraint<V> { | ||
IslConstraint::Unknown(OpenContent::new(field_name, field_value)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion—I would leave these out in favor of using the constructor functions on the constraint structs and then having a trivial From
implementation. That makes it more flexible in case we ever need to add a new field to a constraint in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense.
These builder functions give a less verbose API then using the constructor for each constraint but its not a huge difference overall.
IslCosntraint::Type(Type::new(IslTypeRef::named("int"))
// vs.
IslCosntraint::type_constraint(IslTypeRef::named("int"))
pub fn open_content(&self) -> Vec<(&String, &Element)> { | ||
let mut open_contents = vec![]; | ||
for constraint in &self.constraints { | ||
if let IslConstraint::Unknown(open_content) = constraint { | ||
open_contents.push((open_content.field_name(), open_content.field_value())) | ||
} | ||
} | ||
open_contents | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a hint that open content should not be modeled as a constraint. Perhaps something like this instead?
pub struct IslType<V: IslVersionTrait> {
name: Option<String>,
constraints: Vec<IslConstraint<V>>,
open_content: Vec<OpenContent<V>>,
pub(crate) isl_type_struct: Option<Element>,
phantom: PhantomData<V>,
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, I encountered some mental dissonance when I found OpenContent
in the IslConstraint
enum earlier in my review. Separating it would be helpful.
ion-schema/src/isl/version_based_isl/constraints/open_content.rs
Outdated
Show resolved
Hide resolved
|
||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct AllOf<V: IslVersionTrait> { | ||
type_refs: Vec<IslTypeRef<V>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion—this should be a Bag data type, but I'm fine with punting on it for now.
* renames `IslTypeRef` to `IslTypeArgument` * removes version from `OpenContent` * renames all the phantom properties with _version * adds schema header and footer structs * moves `variably_occurring()` to `IslVariablyOccuringTypeArgument`
//! // ], | ||
//! // open_content: "This is an open content!" | ||
//! // } | ||
//! let isl_type: IslType<IslV1_0> = IslType::named( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat!
/// Represents all the inline IslImportTypes in this schema file. | ||
inline_imported_types: Vec<IslImportType>, | ||
/// Represents open content as `Element`s | ||
/// Note: This doesn't preserve the information about where the open content is placed in the schema file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is reasonable, but has interesting effects on how we serialize a schema later. We should talk through the impact it'll have. No TODO
here.
* adds changes for doc comments * adds changes to accept Into<String> and IntoIterator for constructors * Modifies return value of &Vec<_> to be &[_]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am unsure about the maintainability of having a separate file for each individual constraint—I suspect that we may end up repeating a lot of very similar code across all of the files—but it's better than having a single file with thousands of lines of code.
I did leave a comment about adding trivial From
implementations to convert from the inner types of the enum variants to the enum type for IslConstraint
. I think it should be done, but it doesn't need to be done immediately.
Overall though, this is looking good.
use crate::isl::version_based_isl::{IslTypeArgument, IslVersionTrait}; | ||
|
||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct Not<V: IslVersionTrait> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add impl <V: IslVersionTrait> From<Not<V>> for IslConstraint<V>
, and similarly for all other constraints.
#[derive(Debug, Clone, PartialEq)] | ||
pub struct IslType<V: IslVersionTrait> { | ||
name: Option<String>, | ||
constraints: Vec<IslConstraint<V>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have a bag type. There are plenty of 3P bag/multiset types available, but I don't have a particular one in mind. Either way, we need it so that we have proper equality since the order of the constraints is unimportant.
If open content was modeled separately from the constraints, we could use a set instead of a bag since two completely identical constraints are redundant and functionally equivalent to having only one of them.
Issue #187
Description of changes:
This PR works on streamlining the ISL model using version maker traits.
List of changes:
types and constraints for a particular ISL version
marker trait
Note for reviewers:
version_based_isl
which will eventually be placed as the currentisl
module. This is because the change is too big and involves changing mostly entire ISL model. In order to divide the change into pieces createdversion_based_isl
.V
(version marker trait).By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.