use std::fmt::Display;
use crate::document::{DocumentViewHash, DocumentViewId};
use crate::operation::{Operation, OperationBuilder};
use crate::schema::error::{SchemaError, SchemaIdError};
use crate::schema::system::{
get_blob, get_blob_piece, get_schema_definition, get_schema_field_definition, SchemaFieldView,
SchemaView,
};
use crate::schema::SchemaName;
use crate::schema::{FieldType, SchemaId, SchemaVersion};
use crate::schema::{SchemaDescription, SchemaFields};
use crate::Human;
pub type FieldName = String;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Schema {
pub(crate) id: SchemaId,
pub(crate) description: SchemaDescription,
pub(crate) fields: SchemaFields,
}
impl Schema {
pub fn new(
id: &SchemaId,
description: &str,
fields: &[(&str, FieldType)],
) -> Result<Self, SchemaError> {
let description = SchemaDescription::new(description)?;
let schema_fields = SchemaFields::new(fields)?;
if let SchemaId::Application(_, _) = id {
let schema = Self {
id: id.to_owned(),
description,
fields: schema_fields,
};
Ok(schema)
} else {
Err(SchemaError::DynamicSystemSchema(id.clone()))
}
}
pub fn from_views(
schema: SchemaView,
fields: Vec<SchemaFieldView>,
) -> Result<Schema, SchemaError> {
for schema_field in schema.fields().iter() {
match fields
.iter()
.find(|schema_field_view| schema_field_view.id() == schema_field)
{
Some(_) => Ok(()),
None => Err(SchemaError::InvalidFields),
}?;
}
if fields.iter().len() > schema.fields().len() {
return Err(SchemaError::InvalidFields);
}
let fields: Vec<(&str, FieldType)> = fields
.iter()
.map(|view| (view.name(), view.field_type().to_owned()))
.collect();
let name = SchemaName::new(schema.name())?;
let description = SchemaDescription::new(schema.description())?;
let schema_fields = SchemaFields::new(&fields)?;
Ok(Schema {
id: SchemaId::new_application(&name, schema.view_id()),
description,
fields: schema_fields,
})
}
pub fn create(name: &str, description: &str, field_view_ids: Vec<DocumentViewId>) -> Operation {
let schema = Self::get_system(SchemaId::SchemaDefinition(1)).unwrap();
OperationBuilder::new(schema.id())
.fields(&[
("name", name.into()),
("description", description.into()),
("fields", field_view_ids.into()),
])
.build()
.unwrap()
}
pub fn create_field(name: &str, field_type: FieldType) -> Operation {
let schema = Self::get_system(SchemaId::SchemaFieldDefinition(1)).unwrap();
OperationBuilder::new(schema.id())
.fields(&[("name", name.into()), ("type", field_type.into())])
.build()
.unwrap()
}
pub fn get_system(schema_id: SchemaId) -> Result<&'static Schema, SchemaIdError> {
match schema_id {
SchemaId::SchemaDefinition(version) => get_schema_definition(version),
SchemaId::SchemaFieldDefinition(version) => get_schema_field_definition(version),
SchemaId::Blob(version) => get_blob(version),
SchemaId::BlobPiece(version) => get_blob_piece(version),
_ => Err(SchemaIdError::UnknownSystemSchema(schema_id.to_string())),
}
}
pub fn id(&self) -> &SchemaId {
&self.id
}
pub fn hash_id(&self) -> String {
match self.id.version() {
SchemaVersion::Application(view_id) => {
format!("{}__{}", self.name(), DocumentViewHash::from(&view_id))
}
SchemaVersion::System(version) => {
format!("{}__{}", self.name(), version)
}
}
}
pub fn version(&self) -> SchemaVersion {
self.id.version()
}
pub fn name(&self) -> SchemaName {
self.id.name()
}
pub fn description(&self) -> &SchemaDescription {
&self.description
}
pub fn fields(&self) -> &SchemaFields {
&self.fields
}
}
impl Display for Schema {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.id)
}
}
impl Human for Schema {
fn display(&self) -> String {
format!("<Schema {}>", self.id.display())
}
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use rstest::rstest;
use crate::document::DocumentViewId;
use crate::document::{DocumentView, DocumentViewFields, DocumentViewValue};
use crate::operation::{OperationId, OperationValue, PinnedRelationList};
use crate::schema::system::{SchemaFieldView, SchemaView};
use crate::schema::{
FieldType, Schema, SchemaDescription, SchemaFields, SchemaId, SchemaName, SchemaVersion,
};
use crate::test_utils::fixtures::{document_view_id, random_operation_id};
use crate::Human;
fn create_schema_view(
fields: &PinnedRelationList,
view_id: &DocumentViewId,
operation_id: &OperationId,
) -> SchemaView {
let mut schema = DocumentViewFields::new();
schema.insert(
"name",
DocumentViewValue::new(
operation_id,
&OperationValue::String("venue_name".to_string()),
),
);
schema.insert(
"description",
DocumentViewValue::new(
operation_id,
&OperationValue::String("Describes a venue".to_string()),
),
);
schema.insert(
"fields",
DocumentViewValue::new(
operation_id,
&OperationValue::PinnedRelationList(fields.clone()),
),
);
let schema_view: SchemaView = DocumentView::new(view_id, &schema).try_into().unwrap();
schema_view
}
fn create_field(
name: &str,
field_type: &str,
view_id: &DocumentViewId,
operation_id: &OperationId,
) -> SchemaFieldView {
let mut capacity_field = DocumentViewFields::new();
capacity_field.insert(
"name",
DocumentViewValue::new(operation_id, &OperationValue::String(name.to_string())),
);
capacity_field.insert(
"type",
DocumentViewValue::new(
operation_id,
&OperationValue::String(field_type.to_string()),
),
);
let capacity_field_view: SchemaFieldView = DocumentView::new(view_id, &capacity_field)
.try_into()
.unwrap();
capacity_field_view
}
#[rstest]
fn string_representation(#[from(document_view_id)] schema_view_id: DocumentViewId) {
let schema_name = SchemaName::new("venue").expect("Valid schema name");
let schema = Schema::new(
&SchemaId::Application(schema_name, schema_view_id),
"Some description",
&[("number", FieldType::Integer)],
)
.unwrap();
assert_eq!(
format!("{}", schema),
"venue_0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543"
);
assert_eq!(
schema.id().to_string(),
"venue_0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543"
);
}
#[rstest]
fn short_representation(#[from(document_view_id)] schema_view_id: DocumentViewId) {
let schema_name = SchemaName::new("venue").expect("Valid schema name");
let schema = Schema::new(
&SchemaId::Application(schema_name, schema_view_id),
"Some description",
&[("number", FieldType::Integer)],
)
.unwrap();
assert_eq!(schema.display(), "<Schema venue 496543>");
let schema_definition = Schema::get_system(SchemaId::SchemaDefinition(1)).unwrap();
assert_eq!(schema_definition.display(), "<Schema schema_definition_v1>");
let schema_field_definition =
Schema::get_system(SchemaId::SchemaFieldDefinition(1)).unwrap();
assert_eq!(
schema_field_definition.display(),
"<Schema schema_field_definition_v1>"
);
}
#[rstest]
#[case("My schema", vec![("message", FieldType::String)])]
#[should_panic]
#[case("My schema", vec![])]
#[should_panic]
#[case("My schema", vec![("message", FieldType::String), ("message", FieldType::String)])]
#[should_panic]
#[case("In common use the term is used to describe the largest species from this
family, the red kangaroo, as well as the antilopine kangaroo, eastern grey
kangaroo, and western grey kangaroo! Kangaroos have large, powerful hind legs,
large feet adapted for leaping, a long muscular tail for balance, and a small
head. Like most marsupials, female kangaroos have a pouch called a marsupium
in which joeys complete postnatal development.",
vec![("message", FieldType::String)]
)]
fn new_schema(
#[from(document_view_id)] schema_view_id: DocumentViewId,
#[case] description: &str,
#[case] fields: Vec<(&str, FieldType)>,
) {
let schema_name = SchemaName::new("venue").expect("Valid schema name");
let result = Schema::new(
&SchemaId::Application(schema_name, schema_view_id),
description,
&fields,
);
assert!(result.is_ok());
}
#[rstest]
fn no_redefinition_of_system_schemas() {
let result = Schema::new(
&SchemaId::SchemaDefinition(1),
"description",
&[("wrong", FieldType::Integer)],
);
assert_eq!(
format!("{}", result.unwrap_err()),
"dynamic redefinition of system schema schema_definition_v1, use `Schema::get_system` instead"
);
}
#[test]
fn test_unsupported_system_schema() {
let result = Schema::get_system(SchemaId::SchemaDefinition(0));
assert_eq!(
format!("{}", result.unwrap_err()),
"unsupported system schema: schema_definition_v0"
);
let result = Schema::get_system(SchemaId::SchemaFieldDefinition(0));
assert_eq!(
format!("{}", result.unwrap_err()),
"unsupported system schema: schema_field_definition_v0"
);
}
#[rstest]
fn test_error_application_schema(document_view_id: DocumentViewId) {
let schema_name = SchemaName::new("events").expect("Valid schema name");
let schema = Schema::get_system(SchemaId::Application(schema_name, document_view_id));
assert!(schema.is_err())
}
#[rstest]
fn construct_schema(
#[from(random_operation_id)] field_operation_id: OperationId,
#[from(random_operation_id)] relation_operation_id_1: OperationId,
#[from(random_operation_id)] relation_operation_id_2: OperationId,
#[from(random_operation_id)] relation_operation_id_3: OperationId,
#[from(document_view_id)] schema_view_id: DocumentViewId,
) {
let fields = PinnedRelationList::new(vec![
DocumentViewId::new(&[relation_operation_id_1.clone()]),
DocumentViewId::new(&[
relation_operation_id_2.clone(),
relation_operation_id_3.clone(),
]),
]);
let schema_view = create_schema_view(&fields, &schema_view_id, &field_operation_id);
let bool_field_view = create_field(
"is_accessible",
"bool",
&DocumentViewId::from(relation_operation_id_1),
&field_operation_id,
);
let capacity_field_view = create_field(
"capacity",
"int",
&DocumentViewId::new(&[relation_operation_id_2, relation_operation_id_3]),
&field_operation_id,
);
let result = Schema::from_views(schema_view, vec![bool_field_view, capacity_field_view]);
assert!(result.is_ok());
let schema = result.unwrap();
let schema_name = SchemaName::new("venue_name").expect("Valid schema name");
let expected_view_id =
"0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543"
.parse::<DocumentViewId>()
.unwrap();
assert_eq!(
schema.id(),
&SchemaId::new_application(&schema_name, &expected_view_id)
);
assert_eq!(schema.name(), schema_name);
assert_eq!(
schema.version(),
SchemaVersion::Application(expected_view_id)
);
assert_eq!(schema.description().to_string(), "Describes a venue");
assert_eq!(schema.fields().len(), 2);
}
#[rstest]
fn hash_id(#[from(document_view_id)] application_schema_view_id: DocumentViewId) {
let schema_name = SchemaName::new("event").expect("Valid schema name");
let description = SchemaDescription::new("test").expect("Valid schema description");
let schema_fields =
SchemaFields::new(&[("is_real", FieldType::Boolean)]).expect("Valid schema fields");
let application_schema = Schema {
id: SchemaId::Application(schema_name, application_schema_view_id),
description: description.clone(),
fields: schema_fields.clone(),
};
let application_schema_hash_id = application_schema.hash_id();
assert_eq!(
"event__0020fc76e3a452648023d5e169369116be1526f6d3fc2b7742ed1af2b55f11bca7fb",
application_schema_hash_id
);
let system_schema = Schema {
id: SchemaId::SchemaDefinition(1),
description,
fields: schema_fields,
};
let system_schema_hash_id = system_schema.hash_id();
assert_eq!("schema_definition__1", system_schema_hash_id);
}
#[rstest]
fn invalid_fields_fail(
#[from(random_operation_id)] field_operation_id: OperationId,
#[from(random_operation_id)] relation_operation_id_1: OperationId,
#[from(random_operation_id)] relation_operation_id_2: OperationId,
#[from(random_operation_id)] invalid_relation_id: OperationId,
#[from(document_view_id)] schema_view_id: DocumentViewId,
) {
let fields = PinnedRelationList::new(vec![
DocumentViewId::from(relation_operation_id_1.clone()),
DocumentViewId::from(relation_operation_id_2.clone()),
]);
let schema_view = create_schema_view(&fields, &schema_view_id, &field_operation_id);
let bool_field_document_view_id = DocumentViewId::from(relation_operation_id_1);
let bool_field_view = create_field(
"is_accessible",
"bool",
&bool_field_document_view_id,
&field_operation_id,
);
let capacity_field_document_view_id = DocumentViewId::from(relation_operation_id_2);
let capacity_field_view = create_field(
"capacity",
"int",
&capacity_field_document_view_id,
&field_operation_id,
);
let invalid_document_view_id = DocumentViewId::from(invalid_relation_id);
let field_with_invalid_document_view_id = create_field(
"capacity",
"int",
&invalid_document_view_id,
&field_operation_id,
);
assert!(Schema::from_views(
schema_view.clone(),
vec![
bool_field_view.clone(),
field_with_invalid_document_view_id.clone()
]
)
.is_err());
assert!(Schema::from_views(schema_view.clone(), vec![bool_field_view.clone()]).is_err());
assert!(Schema::from_views(
schema_view,
vec![
bool_field_view,
capacity_field_view,
field_with_invalid_document_view_id
]
)
.is_err());
}
}