diff --git a/catalyst_voices/apps/voices/lib/common/ext/document_property_ext.dart b/catalyst_voices/apps/voices/lib/common/ext/document_property_ext.dart deleted file mode 100644 index 8034d8f499c..00000000000 --- a/catalyst_voices/apps/voices/lib/common/ext/document_property_ext.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:catalyst_voices/common/ext/string_ext.dart'; -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; - -extension DocumentPropertyExt on DocumentProperty { - String get formattedDescription { - return (schema.description ?? '').starred(isEnabled: schema.isRequired); - } -} diff --git a/catalyst_voices/apps/voices/lib/common/ext/document_property_schema_ext.dart b/catalyst_voices/apps/voices/lib/common/ext/document_property_schema_ext.dart new file mode 100644 index 00000000000..126cdfd7661 --- /dev/null +++ b/catalyst_voices/apps/voices/lib/common/ext/document_property_schema_ext.dart @@ -0,0 +1,10 @@ +import 'package:catalyst_voices/common/ext/string_ext.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; + +extension DocumentPropertySchemaExt on DocumentPropertySchema { + // TODO(dtscalac): convert to markdown + String get formattedDescription { + final string = description?.data; + return (string ?? '').starred(isEnabled: isRequired); + } +} diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/agreement_confirmation_widget.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/agreement_confirmation_widget.dart index 7b22a705167..93ef2e7e506 100644 --- a/catalyst_voices/apps/voices/lib/widgets/document_builder/agreement_confirmation_widget.dart +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/agreement_confirmation_widget.dart @@ -5,21 +5,15 @@ import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:flutter/material.dart'; class AgreementConfirmationWidget extends StatefulWidget { - final bool? value; - final AgreementConfirmationDefinition definition; - final DocumentNodeId nodeId; - final String description; - final String title; + final DocumentValueProperty property; + final DocumentAgreementConfirmationSchema schema; final bool isEditMode; final ValueChanged onChanged; const AgreementConfirmationWidget({ super.key, - required this.value, - required this.definition, - required this.nodeId, - required this.description, - required this.title, + required this.property, + required this.schema, required this.isEditMode, required this.onChanged, }); @@ -34,11 +28,12 @@ class _DocumentCheckboxBuilderWidgetState late bool _initialValue; late bool _currentEditValue; - DocumentNodeId get _nodeId => widget.nodeId; + DocumentNodeId get _nodeId => widget.schema.nodeId; - MarkdownData get _description => MarkdownData(widget.description); + MarkdownData get _description => + widget.schema.description ?? MarkdownData.empty; - bool get _defaultValue => widget.definition.defaultValue; + bool get _defaultValue => widget.schema.defaultValue ?? false; @override void initState() { @@ -55,7 +50,7 @@ class _DocumentCheckboxBuilderWidgetState _currentEditValue = _initialValue; } - if (oldWidget.value != widget.value) { + if (oldWidget.property.value != widget.property.value) { _setInitialValues(); } } @@ -96,7 +91,7 @@ class _DocumentCheckboxBuilderWidgetState }); widget.onChanged( - DocumentChange( + DocumentValueChange( nodeId: _nodeId, value: _currentEditValue, ), @@ -104,7 +99,7 @@ class _DocumentCheckboxBuilderWidgetState } void _setInitialValues() { - _initialValue = widget.value ?? _defaultValue; + _initialValue = widget.property.value ?? _defaultValue; _currentEditValue = _initialValue; } } diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/document_token_value_widget.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/document_token_value_widget.dart index a3ede15e191..1e8c415af84 100644 --- a/catalyst_voices/apps/voices/lib/widgets/document_builder/document_token_value_widget.dart +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/document_token_value_widget.dart @@ -7,7 +7,8 @@ import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart'; import 'package:flutter/material.dart'; class DocumentTokenValueWidget extends StatefulWidget { - final DocumentProperty property; + final DocumentValueProperty property; + final DocumentIntegerSchema schema; final Currency currency; final bool isEditMode; final ValueChanged onChanged; @@ -15,8 +16,9 @@ class DocumentTokenValueWidget extends StatefulWidget { const DocumentTokenValueWidget({ super.key, required this.property, + required this.schema, required this.currency, - this.isEditMode = false, + required this.isEditMode, required this.onChanged, }); @@ -61,8 +63,8 @@ class _DocumentTokenValueWidgetState extends State { @override Widget build(BuildContext context) { - final schema = widget.property.schema; - final label = schema.title ?? ''; + final schema = widget.schema; + final label = schema.title; return TokenField( controller: _controller, @@ -94,8 +96,8 @@ class _DocumentTokenValueWidgetState extends State { } void _notifyChangeListener(int? value) { - final change = DocumentChange( - nodeId: widget.property.schema.nodeId, + final change = DocumentValueChange( + nodeId: widget.schema.nodeId, value: value, ); @@ -103,8 +105,7 @@ class _DocumentTokenValueWidgetState extends State { } VoicesTextFieldValidationResult _validate(int? value, String text) { - final schema = widget.property.schema; - final result = schema.validatePropertyValue(value); + final result = widget.schema.validate(value); if (result.isValid) { return const VoicesTextFieldValidationResult.none(); } else { diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/multiline_text_entry_markdown_widget.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/multiline_text_entry_markdown_widget.dart index e8532173c66..a12a0604858 100644 --- a/catalyst_voices/apps/voices/lib/widgets/document_builder/multiline_text_entry_markdown_widget.dart +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/multiline_text_entry_markdown_widget.dart @@ -1,24 +1,24 @@ import 'dart:async'; import 'package:catalyst_voices/common/codecs/markdown_codec.dart'; -import 'package:catalyst_voices/common/ext/document_property_ext.dart'; +import 'package:catalyst_voices/common/ext/document_property_schema_ext.dart'; import 'package:catalyst_voices/widgets/rich_text/voices_rich_text.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:flutter/material.dart'; import 'package:flutter_quill/flutter_quill.dart' as quill; class MultilineTextEntryMarkdownWidget extends StatefulWidget { - final DocumentProperty property; + final DocumentValueProperty property; + final DocumentMultiLineTextEntryMarkdownSchema schema; final ValueChanged onChanged; final bool isEditMode; - final bool isRequired; const MultilineTextEntryMarkdownWidget({ super.key, required this.property, + required this.schema, required this.onChanged, required this.isEditMode, - required this.isRequired, }); @override @@ -36,15 +36,14 @@ class _MultilineTextEntryMarkdownWidgetState StreamSubscription? _documentChangeSub; quill.Document? _preEditDocument; - String get _description => widget.property.formattedDescription; - int? get _maxLength => widget.property.schema.strLengthRange?.max; - String? get _value => widget.property.value; + String get _description => widget.schema.formattedDescription; + int? get _maxLength => widget.schema.strLengthRange?.max; @override void initState() { super.initState(); - _controller = _buildController(value: _value); + _controller = _buildController(value: widget.property.value); _controller.addListener(_onControllerChanged); _focus = VoicesRichTextFocusNode(); @@ -63,7 +62,7 @@ class _MultilineTextEntryMarkdownWidgetState if (widget.property.value != oldWidget.property.value) { _controller.dispose(); - _controller = _buildController(value: _value); + _controller = _buildController(value: widget.property.value); _controller.addListener(_onControllerChanged); } } @@ -134,8 +133,8 @@ class _MultilineTextEntryMarkdownWidgetState final delta = _controller.document.toDelta(); final markdownData = markdown.decoder.convert(delta); widget.onChanged( - DocumentChange( - nodeId: widget.property.schema.nodeId, + DocumentValueChange( + nodeId: widget.schema.nodeId, value: markdownData.data, ), ); diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/simple_text_entry_widget.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/simple_text_entry_widget.dart index fa2b97773a3..daca8591cdc 100644 --- a/catalyst_voices/apps/voices/lib/widgets/document_builder/simple_text_entry_widget.dart +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/simple_text_entry_widget.dart @@ -1,4 +1,4 @@ -import 'package:catalyst_voices/common/ext/document_property_ext.dart'; +import 'package:catalyst_voices/common/ext/document_property_schema_ext.dart'; import 'package:catalyst_voices/widgets/widgets.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart'; @@ -6,13 +6,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class SimpleTextEntryWidget extends StatefulWidget { - final DocumentProperty property; + final DocumentValueProperty property; + final DocumentStringSchema schema; final bool isEditMode; final ValueChanged onChanged; const SimpleTextEntryWidget({ super.key, required this.property, + required this.schema, required this.isEditMode, required this.onChanged, }); @@ -25,10 +27,9 @@ class _SimpleTextEntryWidgetState extends State { late final TextEditingController _controller; late final FocusNode _focusNode; - String get _description => widget.property.formattedDescription; - int? get _maxLength => widget.property.schema.strLengthRange?.max; - bool get _resizable => - widget.property.schema.definition is MultiLineTextEntryDefinition; + String get _description => widget.schema.formattedDescription; + int? get _maxLength => widget.schema.strLengthRange?.max; + bool get _resizable => widget.schema is DocumentMultiLineTextEntrySchema; @override void initState() { @@ -81,7 +82,7 @@ class _SimpleTextEntryWidgetState extends State { validator: _validate, enabled: widget.isEditMode, // TODO(LynxLynxx): check if this is right after schema is finalized - hintText: widget.property.schema.defaultValue, + hintText: widget.schema.defaultValue, resizable: _resizable, maxLength: _maxLength, ), @@ -108,8 +109,8 @@ class _SimpleTextEntryWidgetState extends State { } void _notifyChangeListener(String? value) { - final change = DocumentChange( - nodeId: widget.property.schema.nodeId, + final change = DocumentValueChange( + nodeId: widget.schema.nodeId, value: value, ); @@ -120,8 +121,8 @@ class _SimpleTextEntryWidgetState extends State { if (!widget.isEditMode) { return const VoicesTextFieldValidationResult.none(); } - final schema = widget.property.schema; - final result = schema.validatePropertyValue(value); + final schema = widget.schema; + final result = schema.validate(value); if (result.isValid) { return const VoicesTextFieldValidationResult.none(); } else { diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/single_dropdown_selection_widget.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/single_dropdown_selection_widget.dart index 6104d8e744f..b9b8de0388e 100644 --- a/catalyst_voices/apps/voices/lib/widgets/document_builder/single_dropdown_selection_widget.dart +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/single_dropdown_selection_widget.dart @@ -5,22 +5,16 @@ import 'package:flutter/material.dart'; class SingleDropdownSelectionWidget extends StatefulWidget { final String value; final List items; - final DropDownSingleSelectDefinition definition; - final DocumentNodeId nodeId; - final String title; + final DocumentDropDownSingleSelectSchema schema; final bool isEditMode; - final bool isRequired; final ValueChanged onChanged; const SingleDropdownSelectionWidget({ super.key, required this.value, required this.items, - required this.definition, - required this.nodeId, - required this.title, + required this.schema, required this.isEditMode, - required this.isRequired, required this.onChanged, }); @@ -76,7 +70,7 @@ class _SingleDropdownSelectionWidgetState crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - widget.title, + widget.schema.title, style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 8), @@ -85,7 +79,11 @@ class _SingleDropdownSelectionWidgetState items: _dropdownMenuEntries, enabled: widget.isEditMode, onSelected: (val) { - widget.onChanged(DocumentChange(nodeId: widget.nodeId, value: val)); + final change = DocumentValueChange( + nodeId: widget.schema.nodeId, + value: val, + ); + widget.onChanged(change); }, initialValue: widget.value, ), diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/single_grouped_tag_selector_widget.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/single_grouped_tag_selector_widget.dart index e7c9acecc64..fb87d008bd7 100644 --- a/catalyst_voices/apps/voices/lib/widgets/document_builder/single_grouped_tag_selector_widget.dart +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/single_grouped_tag_selector_widget.dart @@ -81,7 +81,8 @@ class _SingleGroupedTagSelectorWidgetState setState(() { _selection = value; - final change = DocumentChange(nodeId: widget.id, value: value); + // TODO(dtscalac): this should update children properties, not the parent + final change = DocumentValueChange(nodeId: widget.id, value: value); widget.onChanged(change); }); } diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/single_line_https_url_widget.dart.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/single_line_https_url_widget.dart.dart index 9474d2f54bd..eedf454bb2f 100644 --- a/catalyst_voices/apps/voices/lib/widgets/document_builder/single_line_https_url_widget.dart.dart +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/single_line_https_url_widget.dart.dart @@ -1,4 +1,4 @@ -import 'package:catalyst_voices/common/ext/document_property_ext.dart'; +import 'package:catalyst_voices/common/ext/document_property_schema_ext.dart'; import 'package:catalyst_voices/widgets/text_field/voices_https_text_field.dart'; import 'package:catalyst_voices/widgets/widgets.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; @@ -6,13 +6,15 @@ import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart'; import 'package:flutter/material.dart'; class SingleLineHttpsUrlWidget extends StatefulWidget { - final DocumentProperty property; + final DocumentValueProperty property; + final DocumentStringSchema schema; final bool isEditMode; final ValueChanged onChanged; const SingleLineHttpsUrlWidget({ super.key, required this.property, + required this.schema, required this.isEditMode, required this.onChanged, }); @@ -26,7 +28,7 @@ class _SingleLineHttpsUrlWidgetState extends State { late final TextEditingController _textEditingController; late final FocusNode _focusNode; - String get _description => widget.property.formattedDescription; + String get _description => widget.schema.formattedDescription; @override void initState() { @@ -89,8 +91,8 @@ class _SingleLineHttpsUrlWidgetState extends State { } void _notifyChangeListener(String? value) { - final change = DocumentChange( - nodeId: widget.property.schema.nodeId, + final change = DocumentValueChange( + nodeId: widget.schema.nodeId, value: value, ); @@ -101,8 +103,8 @@ class _SingleLineHttpsUrlWidgetState extends State { if (value == null || value.isEmpty) { return const VoicesTextFieldValidationResult.none(); } - final schema = widget.property.schema; - final result = schema.validatePropertyValue(value); + final schema = widget.schema; + final result = schema.validate(value); if (result.isValid) { return const VoicesTextFieldValidationResult.success(); } else { diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/yes_no_choice_widget.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/yes_no_choice_widget.dart index 4c3fcf12e60..2fd7b225b5a 100644 --- a/catalyst_voices/apps/voices/lib/widgets/document_builder/yes_no_choice_widget.dart +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/yes_no_choice_widget.dart @@ -1,4 +1,4 @@ -import 'package:catalyst_voices/common/ext/document_property_ext.dart'; +import 'package:catalyst_voices/common/ext/document_property_schema_ext.dart'; import 'package:catalyst_voices/widgets/widgets.dart'; import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; @@ -6,17 +6,17 @@ import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart'; import 'package:flutter/material.dart'; class YesNoChoiceWidget extends StatefulWidget { - final DocumentProperty property; + final DocumentValueProperty property; + final DocumentYesNoChoiceSchema schema; final ValueChanged onChanged; final bool isEditMode; - final bool isRequired; const YesNoChoiceWidget({ super.key, required this.property, + required this.schema, required this.onChanged, required this.isEditMode, - required this.isRequired, }); @override @@ -26,7 +26,7 @@ class YesNoChoiceWidget extends StatefulWidget { class _YesNoChoiceWidgetState extends State { late bool? selectedValue; - String get _description => widget.property.formattedDescription; + String get _description => widget.schema.formattedDescription; @override void initState() { @@ -68,8 +68,7 @@ class _YesNoChoiceWidgetState extends State { enabled: widget.isEditMode, onChanged: _handleValueChanged, validator: (value) { - // TODO(dtscalac): add validation - final result = widget.property.schema.validatePropertyValue(value); + final result = widget.schema.validate(value); return LocalizedDocumentValidationResult.from(result) .message(context); @@ -94,8 +93,8 @@ class _YesNoChoiceWidgetState extends State { void _notifyChangeListener(bool? value) { widget.onChanged( - DocumentChange( - nodeId: widget.property.schema.nodeId, + DocumentValueChange( + nodeId: widget.schema.nodeId, value: value, ), ); diff --git a/catalyst_voices/apps/voices/lib/widgets/tiles/document_builder_section_tile.dart b/catalyst_voices/apps/voices/lib/widgets/tiles/document_builder_section_tile.dart index 2f78ac58bc2..05ce495cf12 100644 --- a/catalyst_voices/apps/voices/lib/widgets/tiles/document_builder_section_tile.dart +++ b/catalyst_voices/apps/voices/lib/widgets/tiles/document_builder_section_tile.dart @@ -1,12 +1,20 @@ +import 'package:catalyst_voices/widgets/document_builder/agreement_confirmation_widget.dart'; +import 'package:catalyst_voices/widgets/document_builder/document_token_value_widget.dart'; +import 'package:catalyst_voices/widgets/document_builder/multiline_text_entry_markdown_widget.dart'; +import 'package:catalyst_voices/widgets/document_builder/simple_text_entry_widget.dart'; +import 'package:catalyst_voices/widgets/document_builder/single_dropdown_selection_widget.dart'; +import 'package:catalyst_voices/widgets/document_builder/single_grouped_tag_selector_widget.dart'; +import 'package:catalyst_voices/widgets/document_builder/single_line_https_url_widget.dart.dart'; +import 'package:catalyst_voices/widgets/document_builder/yes_no_choice_widget.dart'; import 'package:catalyst_voices/widgets/widgets.dart'; import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:flutter/material.dart'; -/// Displays a [DocumentSection] as list tile in edit / view mode. +/// Displays a [DocumentSectionSchema] as list tile in edit / view mode. class DocumentBuilderSectionTile extends StatefulWidget { - /// A section of the document that groups [DocumentProperty]. - final DocumentSection section; + /// A section of the document that groups [DocumentValueProperty]. + final DocumentObjectProperty section; /// A callback that should be called with a list of [DocumentChange] /// when the user wants to save the changes. @@ -31,8 +39,8 @@ class DocumentBuilderSectionTile extends StatefulWidget { class _DocumentBuilderSectionTileState extends State { - late DocumentSection _editedSection; - late DocumentSectionBuilder _builder; + late DocumentObjectProperty _editedSection; + late DocumentObjectPropertyBuilder _builder; final _pendingChanges = []; @@ -59,7 +67,7 @@ class _DocumentBuilderSectionTileState @override Widget build(BuildContext context) { - final title = _editedSection.schema.title ?? ''; + final title = _editedSection.schema.title; return SelectableTile( child: Padding( @@ -98,7 +106,6 @@ class _DocumentBuilderSectionTileState void _saveChanges() { widget.onChanged(List.of(_pendingChanges)); - // ignore: unnecessary_lambdas setState(() { _pendingChanges.clear(); _isEditMode = false; @@ -126,12 +133,12 @@ class _DocumentBuilderSectionTileState } class _Header extends StatelessWidget { - final String? title; + final String title; final bool isEditMode; final VoidCallback? onToggleEditMode; const _Header({ - this.title, + required this.title, this.isEditMode = false, this.onToggleEditMode, }); @@ -142,7 +149,7 @@ class _Header extends StatelessWidget { children: [ Expanded( child: Text( - title ?? '', + title, style: Theme.of(context).textTheme.titleMedium, ), ), @@ -196,105 +203,224 @@ class _PropertyBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - final definition = property.schema.definition; - - switch (definition) { - case SegmentDefinition(): - case SectionDefinition(): - case TagGroupDefinition(): - case TagSelectionDefinition(): - throw UnsupportedError( - '${property.schema.definition} unsupported ' - 'by $DocumentBuilderSectionTile', + final property = this.property; + switch (property) { + case DocumentListProperty(): + return _PropertyListBuilder( + list: property, + isEditMode: isEditMode, + onChanged: onChanged, ); - - case MultiSelectDefinition(): - case SingleLineTextEntryListDefinition(): - case MultiLineTextEntryListMarkdownDefinition(): - case SingleLineHttpsURLEntryListDefinition(): - case NestedQuestionsListDefinition(): - case NestedQuestionsDefinition(): - case DurationInMonthsDefinition(): - case SPDXLicenceOrUrlDefinition(): - case LanguageCodeDefinition(): - return Text('${definition.runtimeType} not implemented'); - case SingleLineHttpsURLEntryDefinition(): - /* final castProperty = definition.castProperty(property); - return SingleLineHttpsUrlWidget( - property: castProperty, + case DocumentObjectProperty(): + return _PropertyObjectBuilder( + property: property, isEditMode: isEditMode, onChanged: onChanged, - );*/ - case SingleGroupedTagSelectorDefinition(): - /* final castProperty = definition.castProperty(property); + ); + case DocumentValueProperty(): + return _PropertyValueBuilder( + property: property, + isEditMode: isEditMode, + onChanged: onChanged, + ); + } + } +} + +class _PropertyListBuilder extends StatelessWidget { + final DocumentListProperty list; + final bool isEditMode; + final ValueChanged onChanged; + + const _PropertyListBuilder({ + required this.list, + required this.isEditMode, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + // TODO(dtscalac): build a property list, similar to a section, + // below is just dummy implementation + + // TODO(dtscalac): there should be a plus button or something similar + // to add more items into the list + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final property in list.properties) + _PropertyBuilder( + key: key, + property: property, + isEditMode: isEditMode, + onChanged: onChanged, + ), + ], + ); + } +} + +class _PropertyObjectBuilder extends StatelessWidget { + final DocumentObjectProperty property; + final bool isEditMode; + final ValueChanged onChanged; + + const _PropertyObjectBuilder({ + required this.property, + required this.isEditMode, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + final schema = property.schema; + switch (schema) { + case DocumentSingleGroupedTagSelectorSchema(): return SingleGroupedTagSelectorWidget( - id: castProperty.schema.nodeId, - selection: castProperty.value ?? const GroupedTagsSelection(), - groupedTags: definition.groupedTags(castProperty.schema), + id: property.schema.nodeId, + selection: schema.groupedTagsSelection(property) ?? + const GroupedTagsSelection(), + groupedTags: schema.groupedTags(), isEditMode: isEditMode, onChanged: onChanged, - isRequired: castProperty.schema.isRequired, - );*/ - case DropDownSingleSelectDefinition(): - /* final castProperty = definition.castProperty(property); + isRequired: schema.isRequired, + ); + + case DocumentNestedQuestionsSchema(): + case DocumentGenericObjectSchema(): + // TODO(dtscalac): build a property object, similar to a section, + // below is just dummy implementation + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final property in property.properties) + _PropertyBuilder( + key: key, + property: property, + isEditMode: isEditMode, + onChanged: onChanged, + ), + ], + ); + + case DocumentSegmentSchema(): + case DocumentSectionSchema(): + throw UnsupportedError( + '${schema.type} not supported on this level.', + ); + } + } +} + +class _PropertyValueBuilder extends StatelessWidget { + final DocumentValueProperty property; + final bool isEditMode; + final ValueChanged onChanged; + + const _PropertyValueBuilder({ + required this.property, + required this.isEditMode, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + final schema = property.schema; + switch (schema) { + case DocumentDropDownSingleSelectSchema(): + final castProperty = schema.castProperty(property); return SingleDropdownSelectionWidget( value: castProperty.value ?? castProperty.schema.defaultValue ?? '', items: castProperty.schema.enumValues ?? [], - definition: definition, - nodeId: castProperty.schema.nodeId, - title: castProperty.schema.title ?? '', + schema: schema, isEditMode: isEditMode, - isRequired: castProperty.schema.isRequired, onChanged: onChanged, - );*/ - case AgreementConfirmationDefinition(): - /*final castProperty = definition.castProperty(property); + ); + case DocumentAgreementConfirmationSchema(): + final castProperty = schema.castProperty(property); return AgreementConfirmationWidget( - value: castProperty.value, - definition: definition, - nodeId: castProperty.schema.nodeId, - description: castProperty.schema.description ?? '', - title: castProperty.schema.title ?? '', + property: castProperty, + schema: schema, isEditMode: isEditMode, onChanged: onChanged, - );*/ - case TokenValueCardanoADADefinition(): - /*final castProperty = definition.castProperty(property); + ); + case DocumentTokenValueCardanoAdaSchema(): return DocumentTokenValueWidget( - property: castProperty as DocumentProperty, + property: schema.castProperty(property), + schema: schema, currency: const Currency.ada(), isEditMode: isEditMode, onChanged: onChanged, - );*/ - case SingleLineTextEntryDefinition(): - case MultiLineTextEntryDefinition(): - /*final castProperty = definition.castProperty(property); - return SimpleTextEntryWidget( - property: castProperty as DocumentProperty, + ); + case DocumentYesNoChoiceSchema(): + return YesNoChoiceWidget( + property: schema.castProperty(property), + schema: schema, + onChanged: onChanged, + isEditMode: isEditMode, + ); + case DocumentSingleLineHttpsUrlEntrySchema(): + return SingleLineHttpsUrlWidget( + property: schema.castProperty(property), + schema: schema, isEditMode: isEditMode, onChanged: onChanged, - );*/ - case YesNoChoiceDefinition(): - /*final castProperty = definition.castProperty(property); - return YesNoChoiceWidget( - property: castProperty, + ); + case DocumentSingleLineTextEntrySchema(): + return SimpleTextEntryWidget( + property: schema.castProperty(property), + schema: schema, + isEditMode: isEditMode, onChanged: onChanged, + ); + case DocumentMultiLineTextEntrySchema(): + return SimpleTextEntryWidget( + property: schema.castProperty(property), + schema: schema, isEditMode: isEditMode, - isRequired: castProperty.schema.isRequired, - );*/ + onChanged: onChanged, + ); - case MultiLineTextEntryMarkdownDefinition(): - /*final castProperty = definition.castProperty(property); - final castProperty = definition.castProperty(property); + case DocumentMultiLineTextEntryMarkdownSchema(): return MultilineTextEntryMarkdownWidget( - property: castProperty, - onChanged: onChanged, + property: schema.castProperty(property), + schema: schema, isEditMode: isEditMode, - isRequired: castProperty.schema.isRequired, - );*/ + onChanged: onChanged, + ); - // TODO(dtscalac): uncomment tiles when casting works. - return Text('${definition.runtimeType} casting problem'); + case DocumentTagGroupSchema(): + case DocumentTagSelectionSchema(): + case DocumentSpdxLicenseOrUrlSchema(): + case DocumentLanguageCodeSchema(): + case DocumentGenericStringSchema(): + case DocumentDurationInMonthsSchema(): + case DocumentGenericIntegerSchema(): + case DocumentGenericNumberSchema(): + case DocumentGenericBooleanSchema(): + return _UnimplementedWidget(schema: schema); } } } + +// TODO(dtscalac): remove this widget when all document properties +// are implemented +class _UnimplementedWidget extends StatelessWidget { + final DocumentPropertySchema schema; + + const _UnimplementedWidget({ + required this.schema, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + child: Text( + 'Unimplemented ${schema.runtimeType}', + style: const TextStyle(color: Colors.red), + ), + ); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_blocs/test/proposals/proposals_cubit_test.dart b/catalyst_voices/packages/internal/catalyst_voices_blocs/test/proposals/proposals_cubit_test.dart index b22233d96ff..ae031e972d7 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_blocs/test/proposals/proposals_cubit_test.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_blocs/test/proposals/proposals_cubit_test.dart @@ -13,8 +13,8 @@ void main() { const proposalTemplate = DocumentSchema( jsonSchema: '', title: '', - description: '', - segments: [], + description: MarkdownData.empty, + properties: [], order: [], propertiesSchema: '', ); @@ -27,7 +27,7 @@ void main() { document: const Document( schemaUrl: '', schema: proposalTemplate, - segments: [], + properties: [], ), ); diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart index 6c321ff3167..a84c6820f98 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart @@ -5,18 +5,22 @@ export 'auth/password_strength.dart'; export 'campaign/campaign.dart'; export 'campaign/campaign_base.dart'; export 'crypto/lock_factor.dart'; -export 'document/defined_property/grouped_tags.dart'; +export 'document/builder/document_builder.dart'; +export 'document/builder/document_change.dart'; export 'document/document.dart'; -export 'document/document_builder.dart'; -export 'document/document_change.dart'; -export 'document/document_definitions.dart'; export 'document/document_node_id.dart'; -export 'document/document_schema.dart'; -export 'document/document_validator.dart'; +export 'document/enums/document_content_media_type.dart'; +export 'document/enums/document_property_format.dart'; +export 'document/enums/document_property_type.dart'; +export 'document/schema/document_schema.dart'; +export 'document/schema/property/document_property_schema.dart'; export 'document/signed_document_data.dart'; export 'document/signed_document_ref.dart'; export 'document/specialized/proposal_document.dart'; export 'document/specialized/proposal_template.dart'; +export 'document/validation/document_validation_result.dart'; +export 'document/validation/document_validator.dart'; +export 'document/values/grouped_tags.dart'; export 'errors/errors.dart'; export 'file/voices_file.dart'; export 'markdown_data.dart'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/builder/document_builder.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/builder/document_builder.dart new file mode 100644 index 00000000000..1bcdf733d2e --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/builder/document_builder.dart @@ -0,0 +1,340 @@ +import 'package:catalyst_voices_models/src/document/builder/document_change.dart'; +import 'package:catalyst_voices_models/src/document/document.dart'; +import 'package:catalyst_voices_models/src/document/document_node_id.dart'; +import 'package:catalyst_voices_models/src/document/schema/document_schema.dart'; +import 'package:catalyst_voices_models/src/document/schema/property/document_property_schema.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:collection/collection.dart'; + +/// A mutable document builder that understands the [DocumentSchema]. +/// +/// All edits are done on this instance of the builder, +/// copying is not supported for performance reasons. +/// +/// Once edits are done convert the builder to a [Document] with [build] method. +final class DocumentBuilder { + String _schemaUrl; + DocumentSchema _schema; + List _properties; + + /// The default constructor for the [DocumentBuilder]. + DocumentBuilder({ + required String schemaUrl, + required DocumentSchema schema, + required List properties, + }) : _schemaUrl = schemaUrl, + _schema = schema, + _properties = properties; + + /// Creates an empty [DocumentBuilder] from a [schema]. + factory DocumentBuilder.fromSchema({ + required String schemaUrl, + required DocumentSchema schema, + }) { + return DocumentBuilder( + schemaUrl: schemaUrl, + schema: schema, + properties: + schema.properties.map(DocumentPropertyBuilder.fromSchema).toList(), + ); + } + + /// Creates a [DocumentBuilder] from existing [document]. + factory DocumentBuilder.fromDocument(Document document) { + return DocumentBuilder( + schemaUrl: document.schemaUrl, + schema: document.schema, + properties: document.properties + .map(DocumentPropertyBuilder.fromProperty) + .toList(), + ); + } + + /// Applies [changes] in FIFO manner on this builder. + void addChanges(List changes) { + for (final change in changes) { + addChange(change); + } + } + + /// Applies a [change] on this instance of the builder + /// without creating a copy. + void addChange(DocumentChange change) { + _properties.findTargetFor(change).addChange(change); + } + + /// Builds an immutable [Document]. + Document build() { + final mappedProperties = _properties.map((e) => e.build()).toList() + ..sortByOrder(_schema.order); + + return Document( + schemaUrl: _schemaUrl, + schema: _schema, + properties: List.unmodifiable(mappedProperties), + ); + } +} + +/// A builder for a single [DocumentProperty]. +sealed class DocumentPropertyBuilder implements DocumentNode { + /// The default constructor for the [DocumentPropertyBuilder]. + const DocumentPropertyBuilder(); + + /// Creates a [DocumentPropertyBuilder] from a [schema]. + factory DocumentPropertyBuilder.fromSchema( + DocumentPropertySchema schema, + ) { + switch (schema) { + case DocumentListSchema(): + return DocumentListPropertyBuilder.fromSchema(schema); + case DocumentObjectSchema(): + return DocumentObjectPropertyBuilder.fromSchema(schema); + case DocumentValueSchema(): + return DocumentValuePropertyBuilder.fromSchema(schema); + } + } + + /// Creates a [DocumentPropertyBuilder] from a [property]. + factory DocumentPropertyBuilder.fromProperty(DocumentProperty property) { + switch (property) { + case DocumentListProperty(): + return DocumentListPropertyBuilder.fromProperty(property); + case DocumentObjectProperty(): + return DocumentObjectPropertyBuilder.fromProperty(property); + case DocumentValueProperty(): + return DocumentValuePropertyBuilder.fromProperty(property); + } + } + + /// Applies a change on this builder or any child builder. + void addChange(DocumentChange change); + + /// Builds an immutable [DocumentProperty]. + DocumentProperty build(); +} + +/// A [DocumentProperty] builder suited to work with [DocumentListProperty]. +final class DocumentListPropertyBuilder extends DocumentPropertyBuilder { + /// The schema of the document property. + DocumentListSchema _schema; + + /// The list of children properties. + List _properties; + + /// The default constructor for the [DocumentListPropertyBuilder]. + DocumentListPropertyBuilder({ + required DocumentListSchema schema, + required List properties, + }) : _schema = schema, + _properties = properties; + + /// Creates a [DocumentListPropertyBuilder] from a [schema]. + factory DocumentListPropertyBuilder.fromSchema( + DocumentListSchema schema, + ) { + return DocumentListPropertyBuilder( + schema: schema, + properties: [], + ); + } + + /// Creates a [DocumentListPropertyBuilder] from existing [property]. + factory DocumentListPropertyBuilder.fromProperty( + DocumentListProperty property, + ) { + return DocumentListPropertyBuilder( + schema: property.schema, + properties: property.properties + .map(DocumentPropertyBuilder.fromProperty) + .toList(), + ); + } + + @override + DocumentNodeId get nodeId => _schema.nodeId; + + @override + void addChange(DocumentChange change) { + switch (change) { + case DocumentValueChange(): + _handleValueChange(change); + case DocumentAddListItemChange(): + _handleAddListItemChange(change); + case DocumentRemoveListItemChange(): + _handleRemoveListItemChange(change); + } + } + + /// Builds an immutable [DocumentListProperty]. + @override + DocumentListProperty build() { + final mappedProperties = _properties.map((e) => e.build()); + + return _schema.buildProperty( + properties: List.unmodifiable(mappedProperties), + ); + } + + void _handleValueChange(DocumentValueChange change) { + _properties.findTargetFor(change).addChange(change); + } + + void _handleAddListItemChange(DocumentAddListItemChange change) { + if (change.nodeId == nodeId) { + // targets this property + final property = _schema.itemsSchema.createChildPropertyAt(); + _properties.add(DocumentPropertyBuilder.fromProperty(property)); + } else { + // targets child property + _properties.findTargetFor(change).addChange(change); + } + } + + void _handleRemoveListItemChange(DocumentRemoveListItemChange change) { + if (change.nodeId == nodeId) { + // targets this property + _properties.removeWhere((e) => e.nodeId == change.nodeId); + } else { + // targets child property + _properties.findTargetFor(change).addChange(change); + } + } +} + +/// A [DocumentProperty] builder suited to work with [DocumentObjectProperty]. +final class DocumentObjectPropertyBuilder extends DocumentPropertyBuilder { + /// The schema of the document property. + DocumentObjectSchema _schema; + + /// The list of children properties. + List _properties; + + /// The default constructor for the [DocumentObjectPropertyBuilder]. + DocumentObjectPropertyBuilder({ + required DocumentObjectSchema schema, + required List properties, + }) : _schema = schema, + _properties = properties; + + /// Creates a [DocumentObjectPropertyBuilder] from a [schema]. + factory DocumentObjectPropertyBuilder.fromSchema( + DocumentObjectSchema schema, + ) { + final properties = schema.properties; + return DocumentObjectPropertyBuilder( + schema: schema, + properties: properties.map(DocumentPropertyBuilder.fromSchema).toList(), + ); + } + + /// Creates a [DocumentObjectPropertyBuilder] from existing [property]. + factory DocumentObjectPropertyBuilder.fromProperty( + DocumentObjectProperty property, + ) { + return DocumentObjectPropertyBuilder( + schema: property.schema, + properties: property.properties + .map(DocumentPropertyBuilder.fromProperty) + .toList(), + ); + } + + @override + DocumentNodeId get nodeId => _schema.nodeId; + + @override + void addChange(DocumentChange change) { + _properties.findTargetFor(change).addChange(change); + } + + /// Builds an immutable [DocumentObjectProperty]. + @override + DocumentObjectProperty build() { + final mappedProperties = _properties.map((e) => e.build()).toList() + ..sortByOrder(_schema.order); + + return _schema.buildProperty( + properties: List.unmodifiable(mappedProperties), + ); + } +} + +/// A [DocumentProperty] builder suited to work with [DocumentValueProperty]. +final class DocumentValuePropertyBuilder + extends DocumentPropertyBuilder { + /// The schema of the document property. + DocumentValueSchema _schema; + + /// The current value this property holds. + T? _value; + + /// The default constructor for the [DocumentValuePropertyBuilder]. + DocumentValuePropertyBuilder({ + required DocumentValueSchema schema, + required T? value, + }) : _schema = schema, + _value = value; + + /// Creates a [DocumentValuePropertyBuilder] from a [schema]. + factory DocumentValuePropertyBuilder.fromSchema( + DocumentValueSchema schema, + ) { + return DocumentValuePropertyBuilder( + schema: schema, + value: schema.defaultValue, + ); + } + + /// Creates a [DocumentValuePropertyBuilder] from existing [property]. + factory DocumentValuePropertyBuilder.fromProperty( + DocumentValueProperty property, + ) { + return DocumentValuePropertyBuilder( + schema: property.schema, + value: property.value, + ); + } + + @override + DocumentNodeId get nodeId => _schema.nodeId; + + @override + void addChange(DocumentChange change) { + if (change is! DocumentValueChange) { + throw ArgumentError( + '$DocumentValuePropertyBuilder only supports $DocumentValueChange', + ); + } + + if (_schema.nodeId != change.nodeId) { + throw ArgumentError( + 'Cannot apply change to ${_schema.nodeId}, ' + 'the target node is ${change.nodeId}', + ); + } + + _value = _schema.castValue(change.value); + } + + /// Builds an immutable [DocumentValueProperty]. + @override + DocumentValueProperty build() { + return _schema.buildProperty(value: _value); + } +} + +extension _DocumentNodeIterableExt on Iterable { + T findTargetFor(DocumentChange change) { + final targetProperty = firstWhereOrNull(change.targetsDocumentNode); + + if (targetProperty == null) { + throw ArgumentError( + "Couldn't find a suitable node to apply " + 'a change to ${change.nodeId} in this node.', + ); + } + + return targetProperty; + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/builder/document_change.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/builder/document_change.dart new file mode 100644 index 00000000000..f5ad2bbf6e1 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/builder/document_change.dart @@ -0,0 +1,73 @@ +import 'package:catalyst_voices_models/src/document/document.dart'; +import 'package:catalyst_voices_models/src/document/document_node_id.dart'; +import 'package:equatable/equatable.dart'; + +/// Describes an intent to change a document. +/// +/// This allows to enqueue changes coming +/// from multiple sources and apply them together. +sealed class DocumentChange extends Equatable { + const DocumentChange(); + + /// The [DocumentNodeId] of the node that will be changed. + DocumentNodeId get nodeId; + + /// Returns `true` is this [DocumentChange] is intended for the [node], + /// `false` otherwise. + bool targetsDocumentNode(DocumentNode node) { + final targetedNodeId = nodeId; + return targetedNodeId == node.nodeId || + targetedNodeId.isChildOf(node.nodeId); + } +} + +/// Describes an intent to change a property value in the document. +final class DocumentValueChange extends DocumentChange { + /// The id of the document node to be updated. + @override + final DocumentNodeId nodeId; + + /// The new value to be assigned to the [nodeId] in the [Document]. + final T? value; + + /// The default constructor for the [DocumentValueChange]. + const DocumentValueChange({ + required this.nodeId, + required this.value, + }); + + @override + List get props => [nodeId, value]; +} + +/// Describes an intent to add a new (empty) item in a [DocumentListProperty]. +final class DocumentAddListItemChange extends DocumentChange { + /// The [DocumentNodeId] of the [DocumentListProperty] + /// where the new item will be added. + @override + final DocumentNodeId nodeId; + + /// The default constructor for the [DocumentAddListItemChange]. + const DocumentAddListItemChange({ + required this.nodeId, + }); + + @override + List get props => [nodeId]; +} + +/// Describes an intent to remove an item from the [DocumentListProperty]. +final class DocumentRemoveListItemChange extends DocumentChange { + /// The [DocumentNodeId] of the child in [DocumentListProperty] + /// which is going to be removed. + @override + final DocumentNodeId nodeId; + + /// The default constructor for the [DocumentRemoveListItemChange]. + const DocumentRemoveListItemChange({ + required this.nodeId, + }); + + @override + List get props => [nodeId]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/agreement_confirmation_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/agreement_confirmation_definition.dart deleted file mode 100644 index 640adf5188b..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/agreement_confirmation_definition.dart +++ /dev/null @@ -1,33 +0,0 @@ -part of '../document_definitions.dart'; - -final class AgreementConfirmationDefinition - extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - final bool defaultValue; - final bool constValue; - - const AgreementConfirmationDefinition({ - required super.type, - required super.note, - required this.format, - required this.defaultValue, - required this.constValue, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - bool? value, - ) { - return DocumentValidator.validateBool(schema, value); - } - - @override - List get props => [ - format, - defaultValue, - constValue, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/drop_down_single_select_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/drop_down_single_select_definition.dart deleted file mode 100644 index d7ba742e93f..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/drop_down_single_select_definition.dart +++ /dev/null @@ -1,33 +0,0 @@ -part of '../document_definitions.dart'; - -final class DropDownSingleSelectDefinition - extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - final DocumentDefinitionsContentMediaType contentMediaType; - final String pattern; - - const DropDownSingleSelectDefinition({ - required super.type, - required super.note, - required this.format, - required this.contentMediaType, - required this.pattern, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - String? value, - ) { - return DocumentValidator.validateString(schema, value); - } - - @override - List get props => [ - format, - contentMediaType, - pattern, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/duration_in_months_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/duration_in_months_definition.dart deleted file mode 100644 index 7abb611fe17..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/duration_in_months_definition.dart +++ /dev/null @@ -1,26 +0,0 @@ -part of '../document_definitions.dart'; - -final class DurationInMonthsDefinition extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - - const DurationInMonthsDefinition({ - required super.type, - required super.note, - required this.format, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - int? value, - ) { - return DocumentValidator.validateNum(schema, value); - } - - @override - List get props => [ - type, - note, - format, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/language_code_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/language_code_definition.dart deleted file mode 100644 index 22b58e50d20..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/language_code_definition.dart +++ /dev/null @@ -1,29 +0,0 @@ -part of '../document_definitions.dart'; - -final class LanguageCodeDefinition extends BaseDocumentDefinition { - final String defaultValue; - final List enumValues; - - const LanguageCodeDefinition({ - required super.type, - required super.note, - required this.defaultValue, - required this.enumValues, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - String? value, - ) { - return DocumentValidator.validateString(schema, value); - } - - @override - List get props => [ - defaultValue, - enumValues, - note, - type, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_line_text_entry_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_line_text_entry_definition.dart deleted file mode 100644 index 9922b5d6345..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_line_text_entry_definition.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of '../document_definitions.dart'; - -final class MultiLineTextEntryDefinition - extends BaseDocumentDefinition { - final DocumentDefinitionsContentMediaType contentMediaType; - final String pattern; - - const MultiLineTextEntryDefinition({ - required super.type, - required super.note, - required this.contentMediaType, - required this.pattern, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - String? value, - ) { - return DocumentValidator.validateString(schema, value); - } - - @override - List get props => [ - contentMediaType, - pattern, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_line_text_entry_list_markdown_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_line_text_entry_list_markdown_definition.dart deleted file mode 100644 index 12d9c9fc8eb..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_line_text_entry_list_markdown_definition.dart +++ /dev/null @@ -1,36 +0,0 @@ -part of '../document_definitions.dart'; - -final class MultiLineTextEntryListMarkdownDefinition - extends BaseDocumentDefinition> { - final DocumentDefinitionsFormat format; - final bool uniqueItems; - final List defaultValue; - final Map items; - - const MultiLineTextEntryListMarkdownDefinition({ - required super.type, - required super.note, - required this.format, - required this.uniqueItems, - required this.defaultValue, - required this.items, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty> schema, - List? value, - ) { - return DocumentValidator.validateList(schema, value); - } - - @override - List get props => [ - format, - uniqueItems, - type, - note, - defaultValue, - items, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_line_text_entry_markdown_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_line_text_entry_markdown_definition.dart deleted file mode 100644 index d6818014b1d..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_line_text_entry_markdown_definition.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of '../document_definitions.dart'; - -final class MultiLineTextEntryMarkdownDefinition - extends BaseDocumentDefinition { - final DocumentDefinitionsContentMediaType contentMediaType; - final String pattern; - - const MultiLineTextEntryMarkdownDefinition({ - required super.type, - required super.note, - required this.contentMediaType, - required this.pattern, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - String? value, - ) { - return DocumentValidator.validateString(schema, value); - } - - @override - List get props => [ - contentMediaType, - pattern, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_select_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_select_definition.dart deleted file mode 100644 index f23ef81eff4..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/multi_select_definition.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of '../document_definitions.dart'; - -final class MultiSelectDefinition - extends BaseDocumentDefinition> { - final DocumentDefinitionsFormat format; - final bool uniqueItems; - - const MultiSelectDefinition({ - required super.type, - required super.note, - required this.format, - required this.uniqueItems, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty> schema, - List? value, - ) { - return DocumentValidator.validateList(schema, value); - } - - @override - List get props => [ - format, - uniqueItems, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/nested_questions_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/nested_questions_definition.dart deleted file mode 100644 index 0774a69e229..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/nested_questions_definition.dart +++ /dev/null @@ -1,31 +0,0 @@ -part of '../document_definitions.dart'; - -// TODO(LynxLynxx): Verify BaseDocumentDefinition type -final class NestedQuestionsDefinition - extends BaseDocumentDefinition> { - final DocumentDefinitionsFormat format; - final bool additionalProperties; - - const NestedQuestionsDefinition({ - required super.type, - required super.note, - required this.format, - required this.additionalProperties, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty> schema, - List? value, - ) { - return DocumentValidator.validateList(schema, value); - } - - @override - List get props => [ - format, - additionalProperties, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/nested_questions_list_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/nested_questions_list_definition.dart deleted file mode 100644 index eeb66e8eb0e..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/nested_questions_list_definition.dart +++ /dev/null @@ -1,34 +0,0 @@ -part of '../document_definitions.dart'; - -// TODO(dtscalac): parse the Map into a question properties -final class NestedQuestionsListDefinition - extends BaseDocumentDefinition>> { - final DocumentDefinitionsFormat format; - final bool uniqueItems; - final List defaultValue; - - const NestedQuestionsListDefinition({ - required super.type, - required super.note, - required this.format, - required this.uniqueItems, - required this.defaultValue, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty>> schema, - List>? value, - ) { - return DocumentValidator.validateList(schema, value); - } - - @override - List get props => [ - format, - uniqueItems, - type, - note, - defaultValue, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/section_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/section_definition.dart deleted file mode 100644 index bd6a3421c30..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/section_definition.dart +++ /dev/null @@ -1,36 +0,0 @@ -part of '../document_definitions.dart'; - -final class SectionDefinition extends BaseDocumentDefinition { - final bool additionalProperties; - - const SectionDefinition({ - required super.type, - required super.note, - required this.additionalProperties, - }); - - @override - Object? castValue(Object? value) { - throw UnsupportedError('Section cannot have a value'); - } - - @override - DocumentProperty castProperty(DocumentProperty property) { - throw UnsupportedError('Section cannot have a property'); - } - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - Object? value, - ) { - throw UnsupportedError('Section cannot have a property'); - } - - @override - List get props => [ - additionalProperties, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/segment_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/segment_definition.dart deleted file mode 100644 index 67970d0c2e4..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/segment_definition.dart +++ /dev/null @@ -1,36 +0,0 @@ -part of '../document_definitions.dart'; - -final class SegmentDefinition extends BaseDocumentDefinition { - final bool additionalProperties; - - const SegmentDefinition({ - required super.type, - required super.note, - required this.additionalProperties, - }); - - @override - Object? castValue(Object? value) { - throw UnsupportedError('Segment cannot have a value'); - } - - @override - DocumentProperty castProperty(DocumentProperty property) { - throw UnsupportedError('Segment cannot have a property'); - } - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - Object? value, - ) { - throw UnsupportedError('Segment cannot have a property'); - } - - @override - List get props => [ - type, - note, - additionalProperties, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_grouped_tag_selector_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_grouped_tag_selector_definition.dart deleted file mode 100644 index 196cd2fc574..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_grouped_tag_selector_definition.dart +++ /dev/null @@ -1,71 +0,0 @@ -part of '../document_definitions.dart'; - -final class SingleGroupedTagSelectorDefinition - extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - final bool additionalProperties; - - const SingleGroupedTagSelectorDefinition({ - required super.type, - required super.note, - required this.format, - required this.additionalProperties, - }); - - @visibleForTesting - const SingleGroupedTagSelectorDefinition.dummy() - : this( - type: DocumentDefinitionsObjectType.object, - note: '', - format: DocumentDefinitionsFormat.singleGroupedTagSelector, - additionalProperties: true, - ); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - GroupedTagsSelection? value, - ) { - final result = DocumentValidator.validateBasic(schema, value); - if (result.isInvalid) { - return result; - } - - if (value == null) { - // whether the property is required or not is validated by the - // validateBasic since it passed the validation the property - // is not required - return const SuccessfulDocumentValidation(); - } - - // TODO(dtscalac): validate whether group & tag are oneOf - // specified by the schema - if (value.isValid) { - return const SuccessfulDocumentValidation(); - } else { - return MissingRequiredDocumentValue( - invalidNodeId: schema.nodeId, - ); - } - } - - List groupedTags( - DocumentSchemaProperty schema, - ) { - assert( - schema.definition is SingleGroupedTagSelectorDefinition, - 'Grouped tags are available only for SingleGroupedTagSelector', - ); - - final oneOf = schema.oneOf ?? const []; - return GroupedTags.fromLogicalGroups(oneOf); - } - - @override - List get props => [ - format, - additionalProperties, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_https_url_entry_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_https_url_entry_definition.dart deleted file mode 100644 index a66608299ae..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_https_url_entry_definition.dart +++ /dev/null @@ -1,35 +0,0 @@ -part of '../document_definitions.dart'; - -final class SingleLineHttpsURLEntryDefinition - extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - final String pattern; - - const SingleLineHttpsURLEntryDefinition({ - required super.type, - required super.note, - required this.format, - required this.pattern, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - String? value, - ) { - final stringValidationResult = - DocumentValidator.validateString(schema, value); - if (stringValidationResult.isInvalid) { - return stringValidationResult; - } - return DocumentValidator.validatePattern(pattern, value); - } - - @override - List get props => [ - format, - pattern, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_https_url_entry_list_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_https_url_entry_list_definition.dart deleted file mode 100644 index fd7fe3bc672..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_https_url_entry_list_definition.dart +++ /dev/null @@ -1,36 +0,0 @@ -part of '../document_definitions.dart'; - -final class SingleLineHttpsURLEntryListDefinition - extends BaseDocumentDefinition> { - final DocumentDefinitionsFormat format; - final bool uniqueItems; - final List defaultValue; - final Map items; - - const SingleLineHttpsURLEntryListDefinition({ - required super.type, - required super.note, - required this.format, - required this.uniqueItems, - required this.defaultValue, - required this.items, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty> schema, - List? value, - ) { - return DocumentValidator.validateList(schema, value); - } - - @override - List get props => [ - format, - uniqueItems, - type, - note, - defaultValue, - items, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_text_entry_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_text_entry_definition.dart deleted file mode 100644 index 0627c1b532f..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_text_entry_definition.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of '../document_definitions.dart'; - -final class SingleLineTextEntryDefinition - extends BaseDocumentDefinition { - final DocumentDefinitionsContentMediaType contentMediaType; - final String pattern; - - const SingleLineTextEntryDefinition({ - required super.type, - required super.note, - required this.contentMediaType, - required this.pattern, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - String? value, - ) { - return DocumentValidator.validateString(schema, value); - } - - @override - List get props => [ - contentMediaType, - pattern, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_text_entry_list_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_text_entry_list_definition.dart deleted file mode 100644 index 06d3e12efac..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/single_line_text_entry_list_definition.dart +++ /dev/null @@ -1,36 +0,0 @@ -part of '../document_definitions.dart'; - -final class SingleLineTextEntryListDefinition - extends BaseDocumentDefinition> { - final DocumentDefinitionsFormat format; - final bool uniqueItems; - final List defaultValues; - final Map items; - - const SingleLineTextEntryListDefinition({ - required super.type, - required super.note, - required this.format, - required this.uniqueItems, - required this.defaultValues, - required this.items, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty> schema, - List? value, - ) { - return DocumentValidator.validateList(schema, value); - } - - @override - List get props => [ - format, - uniqueItems, - type, - note, - defaultValues, - items, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/spdx_license_or_url_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/spdx_license_or_url_definition.dart deleted file mode 100644 index 5651e8aa972..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/spdx_license_or_url_definition.dart +++ /dev/null @@ -1,31 +0,0 @@ -part of '../document_definitions.dart'; - -final class SPDXLicenceOrUrlDefinition extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - final String pattern; - final DocumentDefinitionsContentMediaType contentMediaType; - - const SPDXLicenceOrUrlDefinition({ - required super.type, - required super.note, - required this.format, - required this.pattern, - required this.contentMediaType, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - String? value, - ) { - return DocumentValidator.validateString(schema, value); - } - - @override - List get props => [ - format, - pattern, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/tag_group_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/tag_group_definition.dart deleted file mode 100644 index 1568a26e4bc..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/tag_group_definition.dart +++ /dev/null @@ -1,38 +0,0 @@ -part of '../document_definitions.dart'; - -final class TagGroupDefinition extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - final String pattern; - - const TagGroupDefinition({ - required super.type, - required super.note, - required this.format, - required this.pattern, - }); - - @visibleForTesting - const TagGroupDefinition.dummy() - : this( - type: DocumentDefinitionsObjectType.string, - note: '', - format: DocumentDefinitionsFormat.tagGroup, - pattern: '', - ); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - String? value, - ) { - return DocumentValidator.validateString(schema, value); - } - - @override - List get props => [ - format, - pattern, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/tag_selection_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/tag_selection_definition.dart deleted file mode 100644 index ab78162e3da..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/tag_selection_definition.dart +++ /dev/null @@ -1,38 +0,0 @@ -part of '../document_definitions.dart'; - -final class TagSelectionDefinition extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - final String pattern; - - const TagSelectionDefinition({ - required super.type, - required super.note, - required this.format, - required this.pattern, - }); - - @visibleForTesting - const TagSelectionDefinition.dummy() - : this( - type: DocumentDefinitionsObjectType.string, - note: '', - format: DocumentDefinitionsFormat.tagSelection, - pattern: '', - ); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - String? value, - ) { - return DocumentValidator.validateString(schema, value); - } - - @override - List get props => [ - format, - pattern, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/token_value_cardano_ada_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/token_value_cardano_ada_definition.dart deleted file mode 100644 index bbef0b4ad49..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/token_value_cardano_ada_definition.dart +++ /dev/null @@ -1,26 +0,0 @@ -part of '../document_definitions.dart'; - -final class TokenValueCardanoADADefinition extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - - const TokenValueCardanoADADefinition({ - required super.type, - required super.note, - required this.format, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - int? value, - ) { - return DocumentValidator.validateNum(schema, value); - } - - @override - List get props => [ - format, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/yes_no_choice_definition.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/yes_no_choice_definition.dart deleted file mode 100644 index 6cefc153d3a..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/definitions/yes_no_choice_definition.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of '../document_definitions.dart'; - -final class YesNoChoiceDefinition extends BaseDocumentDefinition { - final DocumentDefinitionsFormat format; - final bool defaultValue; - - const YesNoChoiceDefinition({ - required super.type, - required super.note, - required this.format, - required this.defaultValue, - }); - - @override - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - bool? value, - ) { - // TODO(dtscalac): validate yes no choice - return DocumentValidator.validateBool(schema, value); - } - - @override - List get props => [ - format, - defaultValue, - type, - note, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document.dart index e371e4cbe68..670f4bee2c8 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document.dart @@ -1,10 +1,11 @@ -import 'package:catalyst_voices_models/src/document/document_builder.dart'; -import 'package:catalyst_voices_models/src/document/document_schema.dart'; -import 'package:catalyst_voices_models/src/document/document_validator.dart'; +import 'package:catalyst_voices_models/src/document/builder/document_builder.dart'; +import 'package:catalyst_voices_models/src/document/document_node_id.dart'; +import 'package:catalyst_voices_models/src/document/schema/document_schema.dart'; +import 'package:catalyst_voices_models/src/document/schema/property/document_property_schema.dart'; +import 'package:catalyst_voices_models/src/document/validation/document_validation_result.dart'; +import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; -// TODO(dtscalac): tests - /// A class that represents the content described by a [DocumentSchema]. /// /// The document is immutable, in order to edit it make use @@ -17,105 +18,174 @@ final class Document extends Equatable { final DocumentSchema schema; /// The top-level groupings for sections. - final List segments; + final List properties; /// The default constructor for the [Document]. const Document({ required this.schemaUrl, required this.schema, - required this.segments, + required this.properties, }); + /// Returns the list of segments from filtered [properties]. + List get segments => properties + .whereType() + .where((e) => e.schema is DocumentSegmentSchema) + .toList(); + /// Creates a new [DocumentBuilder] from this document. DocumentBuilder toBuilder() { return DocumentBuilder.fromDocument(this); } @override - List get props => [ - schemaUrl, - schema, - segments, - ]; + List get props => [schemaUrl, schema, properties]; } -/// A segment that groups multiple [DocumentSection]'s. -final class DocumentSegment extends Equatable { - /// The schema of the document segment. - final DocumentSchemaSegment schema; +/// A property of the [Document]. +/// +/// See: +/// - [DocumentListProperty] +/// - [DocumentObjectProperty] +/// - [DocumentValueProperty]. +sealed class DocumentProperty extends Equatable implements DocumentNode { + /// The default constructor for the [DocumentProperty]. + const DocumentProperty(); - /// The list of sections that group the [DocumentProperty]. - final List sections; + @override + DocumentNodeId get nodeId => schema.nodeId; - /// The default constructor for the [DocumentSegment]. - const DocumentSegment({ + /// The schema of the property. + DocumentPropertySchema get schema; + + /// Returns true if the property (including children properties) are valid, + /// false otherwise. + bool get isValid; + + /// Returns a builder that can update the property state. + DocumentPropertyBuilder toBuilder(); +} + +/// A list of properties, each property in [properties] +/// will have exactly the same type. +/// +/// More properties of the same type might be added to the list. +final class DocumentListProperty extends DocumentProperty { + /// The schema of the document property. + @override + final DocumentListSchema schema; + + /// The children properties. + final List properties; + + /// The validation result against the [schema]. + final DocumentValidationResult validationResult; + + const DocumentListProperty({ required this.schema, - required this.sections, + required this.properties, + required this.validationResult, }); - /// Creates a new [DocumentSegmentBuilder] from this segment. - DocumentSegmentBuilder toBuilder() { - return DocumentSegmentBuilder.fromSegment(this); + @override + bool get isValid { + if (validationResult.isInvalid) { + return false; + } + + return properties.every((e) => e.isValid); } @override - List get props => [schema, sections]; + DocumentListPropertyBuilder toBuilder() { + return DocumentListPropertyBuilder.fromProperty(this); + } + + @override + List get props => [schema, properties]; } -/// A section that groups multiple [DocumentProperty]'s. -final class DocumentSection extends Equatable { - /// The schema of the document section. - final DocumentSchemaSection schema; +/// A list of properties, each property can be a different type. +/// +/// More properties cannot be added to the list. +final class DocumentObjectProperty extends DocumentProperty { + /// The schema of the document property. + @override + final DocumentObjectSchema schema; - /// The list of properties within this section. + /// The children properties. final List properties; - /// The default constructor for the [DocumentSection]. - const DocumentSection({ + /// The validation result against the [schema]. + final DocumentValidationResult validationResult; + + const DocumentObjectProperty({ required this.schema, required this.properties, + required this.validationResult, }); - /// Returns `false` if any of the section [properties] is invalid, - /// `true` otherwise. + @override bool get isValid { + if (validationResult.isInvalid) { + return false; + } + for (final property in properties) { - final result = property.validationResult; - if (result.isInvalid) return false; + if (!property.isValid) return false; } return true; } - /// Creates a new [DocumentSectionBuilder] from this section. - DocumentSectionBuilder toBuilder() { - return DocumentSectionBuilder.fromSection(this); + DocumentProperty? + getPropertyWithSchemaType() { + return properties.firstWhereOrNull((e) => e.schema is T); } + /// Returns the list of sections from filtered [properties]. + List get sections => properties + .whereType() + .where((e) => e.schema is DocumentSectionSchema) + .toList(); + @override - List get props => [schema, properties]; + DocumentObjectPropertyBuilder toBuilder() { + return DocumentObjectPropertyBuilder.fromProperty(this); + } + + @override + List get props => [schema, properties, validationResult]; } -final class DocumentProperty extends Equatable { +/// A property with a value with no additional children. +final class DocumentValueProperty extends DocumentProperty { /// The schema of the document property. - final DocumentSchemaProperty schema; + @override + final DocumentValueSchema schema; /// The current value this property holds. final T? value; - /// The validation result for the [value] against the [schema]. + /// The validation result against the [schema]. final DocumentValidationResult validationResult; - /// The default constructor for the [DocumentProperty]. - const DocumentProperty({ + /// The default constructor for the [DocumentValueProperty]. + const DocumentValueProperty({ required this.schema, required this.value, required this.validationResult, }); - /// Creates a new [DocumentPropertyBuilder] from this property. - DocumentPropertyBuilder toBuilder() { - return DocumentPropertyBuilder.fromProperty(this); + @override + bool get isValid { + return validationResult.isValid; + } + + /// Creates a new [DocumentValuePropertyBuilder] from this property. + @override + DocumentValuePropertyBuilder toBuilder() { + return DocumentValuePropertyBuilder.fromProperty(this); } @override diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_builder.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_builder.dart deleted file mode 100644 index 24b5bd86ffe..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_builder.dart +++ /dev/null @@ -1,261 +0,0 @@ -import 'package:catalyst_voices_models/src/document/document.dart'; -import 'package:catalyst_voices_models/src/document/document_change.dart'; -import 'package:catalyst_voices_models/src/document/document_node_id.dart'; -import 'package:catalyst_voices_models/src/document/document_schema.dart'; -import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; - -/// A mutable document builder that understands the [DocumentSchema]. -/// -/// All edits are done on this instance of the builder, -/// copying is not supported for performance reasons. -/// -/// Once edits are done convert the builder to a [Document] with [build] method. -final class DocumentBuilder implements DocumentNode { - String _schemaUrl; - DocumentSchema _schema; - List _segments; - - /// The default constructor for the [DocumentBuilder]. - DocumentBuilder({ - required String schemaUrl, - required DocumentSchema schema, - required List segments, - }) : _schemaUrl = schemaUrl, - _schema = schema, - _segments = segments; - - /// Creates an empty [DocumentBuilder] from a [schema]. - factory DocumentBuilder.fromSchema({ - required String schemaUrl, - required DocumentSchema schema, - }) { - return DocumentBuilder( - schemaUrl: schemaUrl, - schema: schema, - segments: schema.segments.map(DocumentSegmentBuilder.fromSchema).toList(), - ); - } - - /// Creates a [DocumentBuilder] from existing [document]. - factory DocumentBuilder.fromDocument(Document document) { - return DocumentBuilder( - schemaUrl: document.schemaUrl, - schema: document.schema, - segments: - document.segments.map(DocumentSegmentBuilder.fromSegment).toList(), - ); - } - - @override - DocumentNodeId get nodeId => DocumentNodeId.root; - - /// Applies [changes] in FIFO manner on this builder. - void addChanges(List changes) { - for (final change in changes) { - addChange(change); - } - } - - /// Applies a [change] on this instance of the builder - /// without creating a copy. - void addChange(DocumentChange change) { - final segmentIndex = - _segments.indexWhere((e) => change.nodeId.isChildOf(e._schema.nodeId)); - - if (segmentIndex < 0) { - throw ArgumentError( - 'Cannot edit property ${change.nodeId}, ' - 'it does not exist in this document', - ); - } - - _segments[segmentIndex].addChange(change); - } - - /// Builds an immutable [Document]. - Document build() { - _segments.sortByOrder(_schema.order); - - return Document( - schemaUrl: _schemaUrl, - schema: _schema, - segments: List.unmodifiable(_segments.map((e) => e.build())), - ); - } -} - -final class DocumentSegmentBuilder implements DocumentNode { - /// The schema of the document segment. - DocumentSchemaSegment _schema; - - /// The list of sections that group the [DocumentPropertyBuilder]. - List _sections; - - /// The default constructor for the [DocumentSegmentBuilder]. - DocumentSegmentBuilder({ - required DocumentSchemaSegment schema, - required List sections, - }) : _schema = schema, - _sections = sections; - - /// Creates a [DocumentSegmentBuilder] from a [schema]. - factory DocumentSegmentBuilder.fromSchema(DocumentSchemaSegment schema) { - return DocumentSegmentBuilder( - schema: schema, - sections: schema.sections.map(DocumentSectionBuilder.fromSchema).toList(), - ); - } - - /// Creates a [DocumentSegmentBuilder] from existing [segment]. - factory DocumentSegmentBuilder.fromSegment(DocumentSegment segment) { - return DocumentSegmentBuilder( - schema: segment.schema, - sections: - segment.sections.map(DocumentSectionBuilder.fromSection).toList(), - ); - } - - @override - DocumentNodeId get nodeId => _schema.nodeId; - - /// Applies a [change] on this instance. - void addChange(DocumentChange change) { - final sectionIndex = - _sections.indexWhere((e) => change.nodeId.isChildOf(e._schema.nodeId)); - - if (sectionIndex < 0) { - throw ArgumentError( - 'Cannot edit property ${change.nodeId}, ' - 'it does not exist in this segment', - ); - } - - _sections[sectionIndex].addChange(change); - } - - /// Builds an immutable [DocumentSegment]. - DocumentSegment build() { - _sections.sortByOrder(_schema.order); - - return DocumentSegment( - schema: _schema, - sections: List.unmodifiable(_sections.map((e) => e.build())), - ); - } -} - -final class DocumentSectionBuilder implements DocumentNode { - /// The schema of the document section. - DocumentSchemaSection _schema; - - /// The list of properties within this section. - List _properties; - - /// The default constructor for the [DocumentSectionBuilder]. - DocumentSectionBuilder({ - required DocumentSchemaSection schema, - required List properties, - }) : _schema = schema, - _properties = properties; - - /// Creates a [DocumentSectionBuilder] from a [schema]. - factory DocumentSectionBuilder.fromSchema(DocumentSchemaSection schema) { - return DocumentSectionBuilder( - schema: schema, - properties: - schema.properties.map(DocumentPropertyBuilder.fromSchema).toList(), - ); - } - - /// Creates a [DocumentSectionBuilder] from existing [section]. - factory DocumentSectionBuilder.fromSection(DocumentSection section) { - return DocumentSectionBuilder( - schema: section.schema, - properties: - section.properties.map(DocumentPropertyBuilder.fromProperty).toList(), - ); - } - - @override - DocumentNodeId get nodeId => _schema.nodeId; - - /// Applies a [change] on this instance. - void addChange(DocumentChange change) { - final propertyIndex = - _properties.indexWhere((e) => e._schema.nodeId == change.nodeId); - - if (propertyIndex < 0) { - throw ArgumentError( - 'Cannot edit property ${change.nodeId}, ' - 'it does not exist in this section', - ); - } - - _properties[propertyIndex].addChange(change); - } - - /// Builds an immutable [DocumentSection]. - DocumentSection build() { - _properties.sortByOrder(_schema.order); - - return DocumentSection( - schema: _schema, - properties: List.unmodifiable(_properties.map((e) => e.build())), - ); - } -} - -final class DocumentPropertyBuilder implements DocumentNode { - /// The schema of the document property. - DocumentSchemaProperty _schema; - - /// The current value this property holds. - T? _value; - - /// The default constructor for the [DocumentPropertyBuilder]. - DocumentPropertyBuilder({ - required DocumentSchemaProperty schema, - required T? value, - }) : _schema = schema, - _value = value; - - /// Creates a [DocumentPropertyBuilder] from a [schema]. - factory DocumentPropertyBuilder.fromSchema(DocumentSchemaProperty schema) { - return DocumentPropertyBuilder( - schema: schema, - value: schema.defaultValue, - ); - } - - /// Creates a [DocumentPropertyBuilder] from existing [property]. - factory DocumentPropertyBuilder.fromProperty(DocumentProperty property) { - return DocumentPropertyBuilder( - schema: property.schema, - value: property.value, - ); - } - - @override - DocumentNodeId get nodeId => _schema.nodeId; - - /// Applies a [change] on this property. - void addChange(DocumentChange change) { - if (_schema.nodeId != change.nodeId) { - throw ArgumentError( - 'Cannot apply change to ${_schema.nodeId}, ' - 'the target node is ${change.nodeId}', - ); - } - - _value = _schema.definition.castValue(change.value); - } - - /// Builds an immutable [DocumentProperty]. - DocumentProperty build() { - return DocumentProperty( - schema: _schema, - value: _value, - validationResult: _schema.validatePropertyValue(_value), - ); - } -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_change.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_change.dart deleted file mode 100644 index 33caa47a46d..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_change.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:catalyst_voices_models/src/document/document.dart'; -import 'package:catalyst_voices_models/src/document/document_node_id.dart'; -import 'package:equatable/equatable.dart'; - -/// Describes an intent to change a property in the document. -/// -/// This allows to enqueue changes coming -/// from multiple sources and apply them together. -final class DocumentChange extends Equatable { - /// The id of the document node to be updated. - final DocumentNodeId nodeId; - - /// The new value to be assigned to the [nodeId] in the [Document]. - final Object? value; - - /// The default constructor for the [DocumentChange]. - const DocumentChange({ - required this.nodeId, - required this.value, - }); - - @override - List get props => [nodeId, value]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_definitions.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_definitions.dart deleted file mode 100644 index 9ad5cde8a0f..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_definitions.dart +++ /dev/null @@ -1,217 +0,0 @@ -library catalysts_voices_models; - -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; - -part 'definitions/agreement_confirmation_definition.dart'; -part 'definitions/drop_down_single_select_definition.dart'; -part 'definitions/duration_in_months_definition.dart'; -part 'definitions/language_code_definition.dart'; -part 'definitions/multi_line_text_entry_definition.dart'; -part 'definitions/multi_line_text_entry_list_markdown_definition.dart'; -part 'definitions/multi_line_text_entry_markdown_definition.dart'; -part 'definitions/multi_select_definition.dart'; -part 'definitions/nested_questions_definition.dart'; -part 'definitions/nested_questions_list_definition.dart'; -part 'definitions/section_definition.dart'; -part 'definitions/segment_definition.dart'; -part 'definitions/single_grouped_tag_selector_definition.dart'; -part 'definitions/single_line_https_url_entry_definition.dart'; -part 'definitions/single_line_https_url_entry_list_definition.dart'; -part 'definitions/single_line_text_entry_definition.dart'; -part 'definitions/single_line_text_entry_list_definition.dart'; -part 'definitions/spdx_license_or_url_definition.dart'; -part 'definitions/tag_group_definition.dart'; -part 'definitions/tag_selection_definition.dart'; -part 'definitions/token_value_cardano_ada_definition.dart'; -part 'definitions/yes_no_choice_definition.dart'; - -enum DocumentDefinitionsObjectType { - string, - object, - integer, - boolean, - array, - unknown; - - static DocumentDefinitionsObjectType fromString(String value) { - return DocumentDefinitionsObjectType.values.asNameMap()[value] ?? - DocumentDefinitionsObjectType.unknown; - } -} - -enum DocumentDefinitionsContentMediaType { - textPlain('text/plain'), - markdown('text/markdown'), - unknown('unknown'); - - final String schemaValue; - - const DocumentDefinitionsContentMediaType(this.schemaValue); - - static DocumentDefinitionsContentMediaType fromString(String value) { - return DocumentDefinitionsContentMediaType.values - .firstWhereOrNull((e) => e.schemaValue.toLowerCase() == value) ?? - DocumentDefinitionsContentMediaType.unknown; - } -} - -enum DocumentDefinitionsFormat { - path('path'), - uri('uri'), - dropDownSingleSelect('dropDownSingleSelect'), - multiSelect('multiSelect'), - singleLineTextEntryList('singleLineTextEntryList'), - singleLineTextEntryListMarkdown('singleLineTextEntryListMarkdown'), - singleLineHttpsURLEntryList('singleLineHttpsURLEntryList'), - nestedQuestionsList('nestedQuestionsList'), - nestedQuestions('nestedQuestions'), - singleGroupedTagSelector('singleGroupedTagSelector'), - tagGroup('tagGroup'), - tagSelection('tagSelection'), - tokenCardanoADA('token:cardano:ada'), - durationInMonths('datetime:duration:months'), - yesNoChoice('yesNoChoice'), - agreementConfirmation('agreementConfirmation'), - spdxLicenseOrURL('spdxLicenseOrURL'), - unknown('unknown'); - - final String value; - - const DocumentDefinitionsFormat(this.value); - - static DocumentDefinitionsFormat fromString(String value) { - return DocumentDefinitionsFormat.values - .firstWhereOrNull((e) => e.value.toLowerCase() == value) ?? - DocumentDefinitionsFormat.unknown; - } -} - -sealed class BaseDocumentDefinition extends Equatable { - final DocumentDefinitionsObjectType type; - final String note; - - const BaseDocumentDefinition({ - required this.type, - required this.note, - }); - - @visibleForTesting - static final Map refPathToDefinitionType = { - 'segment': SegmentDefinition, - 'section': SectionDefinition, - 'singleLineTextEntry': SingleLineTextEntryDefinition, - 'singleLineHttpsURLEntry': SingleLineHttpsURLEntryDefinition, - 'multiLineTextEntry': MultiLineTextEntryDefinition, - 'multiLineTextEntryMarkdown': MultiLineTextEntryMarkdownDefinition, - 'dropDownSingleSelect': DropDownSingleSelectDefinition, - 'multiSelect': MultiSelectDefinition, - 'singleLineTextEntryList': SingleLineTextEntryListDefinition, - 'multiLineTextEntryListMarkdown': MultiLineTextEntryListMarkdownDefinition, - 'singleLineHttpsURLEntryList': SingleLineHttpsURLEntryListDefinition, - 'nestedQuestionsList': NestedQuestionsListDefinition, - 'nestedQuestions': NestedQuestionsDefinition, - 'singleGroupedTagSelector': SingleGroupedTagSelectorDefinition, - 'tagGroup': TagGroupDefinition, - 'tagSelection': TagSelectionDefinition, - 'tokenValueCardanoADA': TokenValueCardanoADADefinition, - 'durationInMonths': DurationInMonthsDefinition, - 'yesNoChoice': YesNoChoiceDefinition, - 'agreementConfirmation': AgreementConfirmationDefinition, - 'spdxLicenseOrURL': SPDXLicenceOrUrlDefinition, - 'languageCode': LanguageCodeDefinition, - }; - - static Type typeFromRefPath(String refPath) { - final ref = refPath.split('/').last; - return refPathToDefinitionType[ref] ?? - (throw ArgumentError('Unknown refPath: $refPath')); - } - - static bool isKnownType(String refPath) { - final ref = refPath.split('/').last; - return refPathToDefinitionType[ref] != null; - } - - /// Creates an instance of [DocumentSchemaProperty] - /// of the same type [T] as this definition has. - /// - /// This is needed when processing schemas - /// in bulk and when the type T is not known. - DocumentSchemaProperty createSchema({ - required DocumentNodeId nodeId, - required String id, - required String? title, - required String? description, - required T? defaultValue, - required String? guidance, - required List? enumValues, - required Range? numRange, - required Range? strLengthRange, - required Range? itemsRange, - required List? oneOf, - required bool isRequired, - }) { - return DocumentSchemaProperty( - definition: this, - nodeId: nodeId, - id: id, - title: title, - description: description, - defaultValue: defaultValue, - guidance: guidance, - enumValues: enumValues, - numRange: numRange, - strLengthRange: strLengthRange, - itemsRange: itemsRange, - oneOf: oneOf, - isRequired: isRequired, - ); - } - - /// Casts a dynamic value from external JSON to type [T]. - /// - /// Since JSON data types are dynamic, this method uses known - /// definition types to cast values to [T] for easier usage in UI widgets. - /// - /// Returns the value as type [T] if successful, or `null` otherwise. - T? castValue(Object? value) { - return value as T?; - } - - /// Casts a [DocumentProperty] to [DocumentProperty]. - /// - /// This method sets a specific type [T] for a [DocumentProperty], - /// which holds a user-provided answer in the frontend. - /// - /// [property] is the [DocumentProperty] to be cast. - /// - /// Returns a [DocumentProperty] with its value cast to type [T]. - DocumentProperty castProperty(DocumentProperty property) { - if (property.schema.definition != this) { - throw ArgumentError( - 'The ${property.schema.nodeId} cannot be cast ' - 'by $this document definition', - ); - } - return property as DocumentProperty; - } - - /// Validates the property [value] against document rules. - DocumentValidationResult validatePropertyValue( - DocumentSchemaProperty schema, - T? value, - ); -} - -extension BaseDocumentDefinitionListExt on List { - BaseDocumentDefinition getDefinition(String refPath) { - final definitionType = BaseDocumentDefinition.typeFromRefPath(refPath); - final classType = definitionType; - - return firstWhere((e) => e.runtimeType == classType); - } -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_node_id.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_node_id.dart index 6ca893626f6..76449186478 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_node_id.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_node_id.dart @@ -29,6 +29,11 @@ final class DocumentNodeId extends NodeId { paths: paths, ); + /// The most nested path id. + /// + /// Effectively the string after the last dot. + String get lastPath => paths.isNotEmpty ? paths.last : ''; + /// Returns a parent node. /// /// For [root] node it returns [root] node as it doesn't have any parent. diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_schema.dart deleted file mode 100644 index 01703d3c61b..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_schema.dart +++ /dev/null @@ -1,230 +0,0 @@ -import 'package:catalyst_voices_models/src/document/document_definitions.dart'; -import 'package:catalyst_voices_models/src/document/document_node_id.dart'; -import 'package:catalyst_voices_models/src/document/document_validator.dart'; -import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; -import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; - -/// A document schema that describes the structure of a document. -/// -/// The document consists of top level [segments]. -/// [segments] contain [DocumentSchemaSegment.sections] -/// and sections contain [DocumentSchemaProperty]'s. -final class DocumentSchema extends Equatable implements DocumentNode { - final String jsonSchema; - final String title; - final String description; - final List segments; - final List order; - final String propertiesSchema; - - const DocumentSchema({ - required this.jsonSchema, - required this.title, - required this.description, - required this.segments, - required this.order, - required this.propertiesSchema, - }); - - @override - DocumentNodeId get nodeId => DocumentNodeId.root; - - @override - List get props => [ - jsonSchema, - title, - description, - segments, - order, - propertiesSchema, - ]; -} - -/// A top-level grouping object of the document. -final class DocumentSchemaSegment extends Equatable implements DocumentNode { - final BaseDocumentDefinition definition; - @override - final DocumentNodeId nodeId; - final String id; - final String title; - final String? description; - final List sections; - final List order; - - const DocumentSchemaSegment({ - required this.definition, - required this.nodeId, - required this.id, - required this.title, - required this.description, - required this.sections, - required this.order, - }); - - @override - List get props => [ - definition, - nodeId, - id, - title, - description, - sections, - order, - ]; -} - -/// A grouping object in a document on a section level. -final class DocumentSchemaSection extends Equatable implements DocumentNode { - final BaseDocumentDefinition definition; - @override - final DocumentNodeId nodeId; - final String id; - final String? title; - final String? description; - final List properties; - final bool isRequired; - final List order; - - const DocumentSchemaSection({ - required this.definition, - required this.nodeId, - required this.id, - required this.title, - required this.description, - required this.properties, - required this.isRequired, - required this.order, - }); - - @override - List get props => [ - definition, - nodeId, - id, - title, - description, - properties, - isRequired, - order, - ]; -} - -/// A single property (field) in a document. -final class DocumentSchemaProperty extends Equatable - implements DocumentNode { - final BaseDocumentDefinition definition; - @override - final DocumentNodeId nodeId; - final String id; - final String? title; - final String? description; - final T? defaultValue; - final String? guidance; - final List? enumValues; - - /// Minimum-maximum (both inclusive) numerical range. - final Range? numRange; - - /// Minimum-maximum (both inclusive) length of a string. - final Range? strLengthRange; - - /// Minimum-maximum (both inclusive) count of items. - final Range? itemsRange; - - /// Allowed combination of values this property can take. - final List? oneOf; - - /// Whether the property is required. - final bool isRequired; - - const DocumentSchemaProperty({ - required this.definition, - required this.nodeId, - required this.id, - required this.title, - required this.description, - required this.defaultValue, - required this.guidance, - required this.enumValues, - required this.numRange, - required this.strLengthRange, - required this.itemsRange, - required this.oneOf, - required this.isRequired, - }); - - @visibleForTesting - const DocumentSchemaProperty.optional({ - required this.definition, - required this.nodeId, - required this.id, - this.title, - this.description, - this.defaultValue, - this.guidance, - this.enumValues, - this.numRange, - this.strLengthRange, - this.itemsRange, - this.oneOf, - this.isRequired = false, - }); - - /// Validates the property [value] against document rules. - DocumentValidationResult validatePropertyValue(T? value) { - return definition.validatePropertyValue(this, value); - } - - @override - List get props => [ - definition, - nodeId, - id, - title, - description, - defaultValue, - guidance, - enumValues, - numRange, - strLengthRange, - itemsRange, - oneOf, - isRequired, - ]; -} - -final class DocumentSchemaLogicalGroup extends Equatable { - final List conditions; - - const DocumentSchemaLogicalGroup({ - required this.conditions, - }); - - @override - List get props => [ - conditions, - ]; -} - -final class DocumentSchemaLogicalCondition extends Equatable { - final BaseDocumentDefinition definition; - final String id; - final Object? value; - final List? enumValues; - - const DocumentSchemaLogicalCondition({ - required this.definition, - required this.id, - required this.value, - required this.enumValues, - }); - - @override - List get props => [ - definition, - id, - value, - enumValues, - ]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_validator.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_validator.dart deleted file mode 100644 index 2e71bba9d48..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/document_validator.dart +++ /dev/null @@ -1,213 +0,0 @@ -import 'package:catalyst_voices_models/src/document/document.dart'; -import 'package:catalyst_voices_models/src/document/document_node_id.dart'; -import 'package:catalyst_voices_models/src/document/document_schema.dart'; -import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; -import 'package:equatable/equatable.dart'; - -/// Validates [DocumentProperty]. -final class DocumentValidator { - /// Validates the property [value] against all common rules that - /// apply to all properties. - /// - /// There are no specific checks for different types like [String] or [int]. - static DocumentValidationResult validateBasic( - DocumentSchemaProperty schema, - Object? value, - ) { - if (schema.isRequired && value == null) { - return MissingRequiredDocumentValue(invalidNodeId: schema.nodeId); - } - - return const SuccessfulDocumentValidation(); - } - - static DocumentValidationResult validateString( - DocumentSchemaProperty schema, - String? value, - ) { - final result = validateBasic(schema, value); - if (result.isInvalid) { - return result; - } - - final strRange = schema.strLengthRange; - if (strRange != null && value != null) { - if (!strRange.contains(value.length)) { - return DocumentStringOutOfRange( - invalidNodeId: schema.nodeId, - expectedRange: strRange, - actualLength: value.length, - ); - } - } - - return const SuccessfulDocumentValidation(); - } - - static DocumentValidationResult validateNum( - DocumentSchemaProperty schema, - num? value, - ) { - final result = validateBasic(schema, value); - if (result.isInvalid) { - return result; - } - - final numRange = schema.numRange; - if (numRange != null && value != null) { - if (!numRange.contains(value)) { - return DocumentNumOutOfRange( - invalidNodeId: schema.nodeId, - expectedRange: numRange, - actualValue: value, - ); - } - } - - return const SuccessfulDocumentValidation(); - } - - static DocumentValidationResult validateList( - DocumentSchemaProperty> schema, - List? value, - ) { - final result = validateBasic(schema, value); - if (result.isInvalid) { - return result; - } - - final itemsRange = schema.itemsRange; - if (itemsRange != null && value != null) { - if (!itemsRange.contains(value.length)) { - return DocumentItemsOutOfRange( - invalidNodeId: schema.nodeId, - expectedRange: itemsRange, - actualItems: value.length, - ); - } - } - - return const SuccessfulDocumentValidation(); - } - - static DocumentValidationResult validateBool( - DocumentSchemaProperty schema, - // ignore: avoid_positional_boolean_parameters - bool? value, - ) { - final result = validateBasic(schema, value); - - if (result.isInvalid) { - return result; - } - - if (value == null) { - return MissingRequiredDocumentValue(invalidNodeId: schema.nodeId); - } - return const SuccessfulDocumentValidation(); - } - - static DocumentValidationResult validatePattern( - String pattern, - String? value, - ) { - final regex = RegExp(pattern); - if (value != null) { - if (!regex.hasMatch(value)) { - return DocumentPatternMismatch(pattern: pattern, value: value); - } - } - return const SuccessfulDocumentValidation(); - } -} - -sealed class DocumentValidationResult extends Equatable { - const DocumentValidationResult(); - - /// Returns `true` if the validation was successful, `false` otherwise. - bool get isValid => this is SuccessfulDocumentValidation; - - /// Returns `true` if the validation was unsuccessful, `false` otherwise. - bool get isInvalid => !isValid; -} - -/// The validation passed successfully. -final class SuccessfulDocumentValidation extends DocumentValidationResult { - const SuccessfulDocumentValidation(); - - @override - List get props => []; -} - -/// A required document property is missing. -final class MissingRequiredDocumentValue extends DocumentValidationResult { - final DocumentNodeId invalidNodeId; - - const MissingRequiredDocumentValue({required this.invalidNodeId}); - - @override - List get props => [invalidNodeId]; -} - -/// The numerical [actualValue] is not within [expectedRange]. -final class DocumentNumOutOfRange extends DocumentValidationResult { - final DocumentNodeId invalidNodeId; - final Range expectedRange; - final num actualValue; - - const DocumentNumOutOfRange({ - required this.invalidNodeId, - required this.expectedRange, - required this.actualValue, - }); - - @override - List get props => [invalidNodeId, expectedRange, actualValue]; -} - -/// The [String]'s [actualLength] is not within [expectedRange]. -final class DocumentStringOutOfRange - extends DocumentValidationResult { - final DocumentNodeId invalidNodeId; - final Range expectedRange; - final int actualLength; - - const DocumentStringOutOfRange({ - required this.invalidNodeId, - required this.expectedRange, - required this.actualLength, - }); - - @override - List get props => [invalidNodeId, expectedRange, actualLength]; -} - -/// The [List]'s length is not within [expectedRange]. -final class DocumentItemsOutOfRange - extends DocumentValidationResult { - final DocumentNodeId invalidNodeId; - final Range expectedRange; - final int actualItems; - - const DocumentItemsOutOfRange({ - required this.invalidNodeId, - required this.expectedRange, - required this.actualItems, - }); - - @override - List get props => [invalidNodeId, expectedRange, actualItems]; -} - -final class DocumentPatternMismatch extends DocumentValidationResult { - final String pattern; - final String? value; - - const DocumentPatternMismatch({ - required this.pattern, - required this.value, - }); - - @override - List get props => [pattern, value]; -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/enums/document_content_media_type.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/enums/document_content_media_type.dart new file mode 100644 index 00000000000..741602d8f61 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/enums/document_content_media_type.dart @@ -0,0 +1,20 @@ +/// The content type of document's string property. +enum DocumentContentMediaType { + textPlain('text/plain'), + markdown('text/markdown'), + unknown('unknown'); + + final String value; + + const DocumentContentMediaType(this.value); + + factory DocumentContentMediaType.fromString(String string) { + for (final type in values) { + if (type.value.toLowerCase() == string.toLowerCase()) { + return type; + } + } + + return DocumentContentMediaType.unknown; + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/enums/document_property_format.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/enums/document_property_format.dart new file mode 100644 index 00000000000..41228270ee6 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/enums/document_property_format.dart @@ -0,0 +1,34 @@ +/// The format expected by the property value. +enum DocumentPropertyFormat { + path('path'), + uri('uri'), + dropDownSingleSelect('dropDownSingleSelect'), + multiSelect('multiSelect'), + singleLineTextEntryList('singleLineTextEntryList'), + singleLineTextEntryListMarkdown('singleLineTextEntryListMarkdown'), + singleLineHttpsUrlEntryList('singleLineHttpsURLEntryList'), + nestedQuestionsList('nestedQuestionsList'), + nestedQuestions('nestedQuestions'), + singleGroupedTagSelector('singleGroupedTagSelector'), + tagGroup('tagGroup'), + tagSelection('tagSelection'), + tokenCardanoAda('token:cardano:ada'), + durationInMonths('datetime:duration:months'), + yesNoChoice('yesNoChoice'), + agreementConfirmation('agreementConfirmation'), + spdxLicenseOrUrl('spdxLicenseOrURL'), + unknown('unknown'); + + final String value; + + const DocumentPropertyFormat(this.value); + + factory DocumentPropertyFormat.fromString(String string) { + for (final format in values) { + if (format.value.toLowerCase() == string.toLowerCase()) { + return format; + } + } + return DocumentPropertyFormat.unknown; + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/enums/document_property_type.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/enums/document_property_type.dart new file mode 100644 index 00000000000..5271b1cb64f --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/enums/document_property_type.dart @@ -0,0 +1,22 @@ +/// The type of the document property. +enum DocumentPropertyType { + /// A list of properties, new items might be added. + list, + + /// A set of properties, new items cannot be added. + /// + /// Equivalent to an object with fields. + object, + + /// A [String] property type without children. + string, + + /// A [int] property type without children. + integer, + + /// A [double] property type without children. + number, + + /// A [boolean] property type without children. + boolean; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/document_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/document_schema.dart new file mode 100644 index 00000000000..604580b5df0 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/document_schema.dart @@ -0,0 +1,74 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_models/src/catalyst_voices_models.dart'; +import 'package:equatable/equatable.dart'; + +/// A document schema that describes the structure of a document. +/// +/// The document consists of top level [properties]. +/// [properties] contain [DocumentSegmentSchema.sections] +/// and sections contain [DocumentPropertySchema]'s. +final class DocumentSchema extends Equatable implements DocumentNode { + final String jsonSchema; + final String propertiesSchema; + final String title; + final MarkdownData description; + final List properties; + final List order; + + const DocumentSchema({ + required this.jsonSchema, + required this.propertiesSchema, + required this.title, + required this.description, + required this.properties, + required this.order, + }); + + @override + DocumentNodeId get nodeId => DocumentNodeId.root; + + List get segments => + properties.whereType().toList(); + + @override + List get props => [ + jsonSchema, + propertiesSchema, + title, + description, + properties, + order, + ]; +} + +final class DocumentSchemaLogicalGroup extends Equatable { + final List conditions; + + const DocumentSchemaLogicalGroup({ + required this.conditions, + }); + + @override + List get props => [ + conditions, + ]; +} + +final class DocumentSchemaLogicalCondition extends Equatable { + final DocumentPropertySchema schema; + final Object? constValue; + final List? enumValues; + + const DocumentSchemaLogicalCondition({ + required this.schema, + required this.constValue, + required this.enumValues, + }); + + @override + List get props => [ + schema, + constValue, + enumValues, + ]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_boolean_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_boolean_schema.dart new file mode 100644 index 00000000000..b0f9ad41659 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_boolean_schema.dart @@ -0,0 +1,98 @@ +part of 'document_property_schema.dart'; + +sealed class DocumentBooleanSchema extends DocumentValueSchema { + const DocumentBooleanSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.enumValues, + required super.defaultValue, + }) : super( + type: DocumentPropertyType.boolean, + ); + + @override + DocumentValidationResult validate(bool? value) { + return DocumentValidationResult.merge([ + DocumentValidator.validateIfRequired(this, value), + DocumentValidator.validateBool(this, value), + ]); + } +} + +final class DocumentYesNoChoiceSchema extends DocumentBooleanSchema { + const DocumentYesNoChoiceSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + }); + + @override + DocumentYesNoChoiceSchema withNodeId(DocumentNodeId nodeId) { + return DocumentYesNoChoiceSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + ); + } +} + +final class DocumentAgreementConfirmationSchema extends DocumentBooleanSchema { + const DocumentAgreementConfirmationSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + }); + + @override + DocumentAgreementConfirmationSchema withNodeId(DocumentNodeId nodeId) { + return DocumentAgreementConfirmationSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + ); + } +} + +final class DocumentGenericBooleanSchema extends DocumentBooleanSchema { + const DocumentGenericBooleanSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + }); + + @override + DocumentGenericBooleanSchema withNodeId(DocumentNodeId nodeId) { + return DocumentGenericBooleanSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + ); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_integer_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_integer_schema.dart new file mode 100644 index 00000000000..e83d43c6c17 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_integer_schema.dart @@ -0,0 +1,111 @@ +part of 'document_property_schema.dart'; + +sealed class DocumentIntegerSchema extends DocumentValueSchema { + final Range? numRange; + + const DocumentIntegerSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required this.numRange, + }) : super( + type: DocumentPropertyType.integer, + ); + + @override + DocumentValidationResult validate(int? value) { + return DocumentValidationResult.merge([ + DocumentValidator.validateIfRequired(this, value), + DocumentValidator.validateIntegerRange(this, value), + ]); + } + + @override + @mustCallSuper + List get props => super.props + [numRange]; +} + +final class DocumentTokenValueCardanoAdaSchema extends DocumentIntegerSchema { + const DocumentTokenValueCardanoAdaSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.numRange, + }); + + @override + DocumentTokenValueCardanoAdaSchema withNodeId(DocumentNodeId nodeId) { + return DocumentTokenValueCardanoAdaSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + numRange: numRange, + ); + } +} + +final class DocumentDurationInMonthsSchema extends DocumentIntegerSchema { + const DocumentDurationInMonthsSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.numRange, + }); + + @override + DocumentDurationInMonthsSchema withNodeId(DocumentNodeId nodeId) { + return DocumentDurationInMonthsSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + numRange: numRange, + ); + } +} + +final class DocumentGenericIntegerSchema extends DocumentIntegerSchema { + const DocumentGenericIntegerSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.numRange, + }); + + @override + DocumentGenericIntegerSchema withNodeId(DocumentNodeId nodeId) { + return DocumentGenericIntegerSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + numRange: numRange, + ); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_list_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_list_schema.dart new file mode 100644 index 00000000000..9187284da9f --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_list_schema.dart @@ -0,0 +1,210 @@ +part of 'document_property_schema.dart'; + +sealed class DocumentListSchema extends DocumentPropertySchema { + final DocumentPropertySchema itemsSchema; + final Range? itemsRange; + + const DocumentListSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required this.itemsSchema, + required this.itemsRange, + }) : super( + type: DocumentPropertyType.list, + ); + + /// A method that builds typed properties. + /// + /// Helps to create properties which generic type + /// is synced with the schema's generic type. + DocumentListProperty buildProperty({ + required List properties, + }) { + return DocumentListProperty( + schema: this, + properties: properties, + validationResult: validate(properties), + ); + } + + @override + DocumentListProperty createChildPropertyAt([DocumentNodeId? parentNodeId]) { + parentNodeId ??= nodeId; + + final childId = const Uuid().v4(); + final childNodeId = parentNodeId.child(childId); + + final updatedSchema = withNodeId(childNodeId) as DocumentListSchema; + const updatedProperties = []; + + return updatedSchema.buildProperty( + properties: List.unmodifiable(updatedProperties), + ); + } + + /// Validates the property against document rules. + DocumentValidationResult validate(List properties) { + return DocumentValidator.validateListItems(this, properties); + } + + @override + @mustCallSuper + List get props => super.props + [itemsSchema, itemsRange]; +} + +final class DocumentMultiSelectSchema extends DocumentListSchema { + const DocumentMultiSelectSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.itemsSchema, + required super.itemsRange, + }); + + @override + DocumentMultiSelectSchema withNodeId(DocumentNodeId nodeId) { + return DocumentMultiSelectSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + itemsSchema: itemsSchema.withNodeId(nodeId), + itemsRange: itemsRange, + ); + } +} + +final class DocumentSingleLineTextEntryListSchema extends DocumentListSchema { + const DocumentSingleLineTextEntryListSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.itemsSchema, + required super.itemsRange, + }); + + @override + DocumentSingleLineTextEntryListSchema withNodeId(DocumentNodeId nodeId) { + return DocumentSingleLineTextEntryListSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + itemsSchema: itemsSchema.withNodeId(nodeId), + itemsRange: itemsRange, + ); + } +} + +final class DocumentMultiLineTextEntryListMarkdownSchema + extends DocumentListSchema { + const DocumentMultiLineTextEntryListMarkdownSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.itemsSchema, + required super.itemsRange, + }); + + @override + DocumentMultiLineTextEntryListMarkdownSchema withNodeId( + DocumentNodeId nodeId, + ) { + return DocumentMultiLineTextEntryListMarkdownSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + itemsSchema: itemsSchema.withNodeId(nodeId), + itemsRange: itemsRange, + ); + } +} + +final class DocumentSingleLineHttpsUrlEntryListSchema + extends DocumentListSchema { + const DocumentSingleLineHttpsUrlEntryListSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.itemsSchema, + required super.itemsRange, + }); + + @override + DocumentSingleLineHttpsUrlEntryListSchema withNodeId(DocumentNodeId nodeId) { + return DocumentSingleLineHttpsUrlEntryListSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + itemsSchema: itemsSchema.withNodeId(nodeId), + itemsRange: itemsRange, + ); + } +} + +final class DocumentNestedQuestionsListSchema extends DocumentListSchema { + const DocumentNestedQuestionsListSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.itemsSchema, + required super.itemsRange, + }); + + @override + DocumentNestedQuestionsListSchema withNodeId(DocumentNodeId nodeId) { + return DocumentNestedQuestionsListSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + itemsSchema: itemsSchema.withNodeId(nodeId), + itemsRange: itemsRange, + ); + } +} + +final class DocumentGenericListSchema extends DocumentListSchema { + const DocumentGenericListSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.itemsSchema, + required super.itemsRange, + }); + + @override + DocumentGenericListSchema withNodeId(DocumentNodeId nodeId) { + return DocumentGenericListSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + itemsSchema: itemsSchema.withNodeId(nodeId), + itemsRange: itemsRange, + ); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_number_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_number_schema.dart new file mode 100644 index 00000000000..a919d8af8a1 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_number_schema.dart @@ -0,0 +1,57 @@ +part of 'document_property_schema.dart'; + +sealed class DocumentNumberSchema extends DocumentValueSchema { + final Range? numRange; + + const DocumentNumberSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required this.numRange, + }) : super( + type: DocumentPropertyType.number, + ); + + @override + DocumentValidationResult validate(double? value) { + return DocumentValidationResult.merge([ + DocumentValidator.validateIfRequired(this, value), + DocumentValidator.validateNumberRange(this, value), + ]); + } + + @override + @mustCallSuper + List get props => super.props + [numRange]; +} + +final class DocumentGenericNumberSchema extends DocumentNumberSchema { + const DocumentGenericNumberSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.numRange, + }); + + @override + DocumentGenericNumberSchema withNodeId(DocumentNodeId nodeId) { + return DocumentGenericNumberSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + numRange: numRange, + ); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_object_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_object_schema.dart new file mode 100644 index 00000000000..16db30b5316 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_object_schema.dart @@ -0,0 +1,239 @@ +part of 'document_property_schema.dart'; + +sealed class DocumentObjectSchema extends DocumentPropertySchema { + final List properties; + final List? oneOf; + final List order; + + const DocumentObjectSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required this.properties, + required this.oneOf, + required this.order, + }) : super( + type: DocumentPropertyType.object, + ); + + /// A method that builds typed properties. + /// + /// Helps to create properties which generic type + /// is synced with the schema's generic type. + DocumentObjectProperty buildProperty({ + required List properties, + }) { + return DocumentObjectProperty( + schema: this, + properties: properties, + validationResult: validate(properties), + ); + } + + @override + DocumentObjectProperty createChildPropertyAt([DocumentNodeId? parentNodeId]) { + parentNodeId ??= nodeId; + + final childId = const Uuid().v4(); + final childNodeId = parentNodeId.child(childId); + + final updatedSchema = withNodeId(childNodeId) as DocumentObjectSchema; + final updatedProperties = + properties.map((e) => e.createChildPropertyAt(parentNodeId)).toList(); + + return updatedSchema.buildProperty( + properties: List.unmodifiable(updatedProperties), + ); + } + + /// Validates the property against document rules. + DocumentValidationResult validate(List properties) { + // TODO(dtscalac): object type validation + return const SuccessfulDocumentValidation(); + } + + @override + @mustCallSuper + List get props => super.props + [properties, oneOf, order]; +} + +/// A top-level grouping object of the document. +final class DocumentSegmentSchema extends DocumentObjectSchema { + const DocumentSegmentSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.properties, + required super.oneOf, + required super.order, + }); + + List get sections => + properties.whereType().toList(); + + @override + DocumentSegmentSchema withNodeId(DocumentNodeId nodeId) { + return DocumentSegmentSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + properties: + properties.map((e) => e.withNodeId(nodeId.child(e.id))).toList(), + oneOf: oneOf, + order: order, + ); + } +} + +/// A grouping object in a document on a section level. +final class DocumentSectionSchema extends DocumentObjectSchema { + const DocumentSectionSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.properties, + required super.oneOf, + required super.order, + }); + + @override + DocumentSectionSchema withNodeId(DocumentNodeId nodeId) { + return DocumentSectionSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + properties: + properties.map((e) => e.withNodeId(nodeId.child(e.id))).toList(), + oneOf: oneOf, + order: order, + ); + } +} + +final class DocumentNestedQuestionsSchema extends DocumentObjectSchema { + const DocumentNestedQuestionsSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.properties, + required super.oneOf, + required super.order, + }); + + @override + DocumentNestedQuestionsSchema withNodeId(DocumentNodeId nodeId) { + return DocumentNestedQuestionsSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + properties: + properties.map((e) => e.withNodeId(nodeId.child(e.id))).toList(), + oneOf: oneOf, + order: order, + ); + } +} + +final class DocumentSingleGroupedTagSelectorSchema + extends DocumentObjectSchema { + const DocumentSingleGroupedTagSelectorSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.properties, + required super.oneOf, + required super.order, + }); + + @override + DocumentSingleGroupedTagSelectorSchema withNodeId(DocumentNodeId nodeId) { + return DocumentSingleGroupedTagSelectorSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + properties: + properties.map((e) => e.withNodeId(nodeId.child(e.id))).toList(), + oneOf: oneOf, + order: order, + ); + } + + GroupedTagsSelection? groupedTagsSelection(DocumentObjectProperty property) { + assert( + property.schema == this, + 'Value of the property can only be accessed by ' + 'the schema to which the property belongs', + ); + + final groupProperty = + property.getPropertyWithSchemaType() + as DocumentValueProperty?; + + final tagProperty = + property.getPropertyWithSchemaType() + as DocumentValueProperty?; + + final group = groupProperty?.value; + final tag = tagProperty?.value; + + if (group == null && tag == null) { + return null; + } + + return GroupedTagsSelection( + group: group, + tag: tag, + ); + } + + List groupedTags() { + final oneOf = this.oneOf ?? const []; + return GroupedTags.fromLogicalGroups(oneOf); + } +} + +final class DocumentGenericObjectSchema extends DocumentObjectSchema { + const DocumentGenericObjectSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.properties, + required super.oneOf, + required super.order, + }); + + @override + DocumentGenericObjectSchema withNodeId(DocumentNodeId nodeId) { + return DocumentGenericObjectSchema( + nodeId: nodeId, + format: format, + title: title, + description: description, + isRequired: isRequired, + properties: + properties.map((e) => e.withNodeId(nodeId.child(e.id))).toList(), + oneOf: oneOf, + order: order, + ); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_property_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_property_schema.dart new file mode 100644 index 00000000000..928ad65878b --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_property_schema.dart @@ -0,0 +1,147 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:uuid/uuid.dart'; + +part 'document_boolean_schema.dart'; +part 'document_integer_schema.dart'; +part 'document_list_schema.dart'; +part 'document_number_schema.dart'; +part 'document_object_schema.dart'; +part 'document_string_schema.dart'; + +/// A schema of [DocumentProperty]. +/// Defines the type, formatting, appearance and validation rules. +/// +/// There are two major types of properties, grouping properties: +/// - [DocumentObjectSchema] +/// - [DocumentListSchema] +/// +/// and properties with values: +/// +/// - [DocumentValueSchema] +/// - [DocumentBooleanSchema] +/// - [DocumentIntegerSchema] +/// - [DocumentNumberSchema] +/// - [DocumentStringSchema] +/// +/// Grouping properties have children, value properties have values. +sealed class DocumentPropertySchema extends Equatable implements DocumentNode { + @override + final DocumentNodeId nodeId; + final DocumentPropertyType type; + final DocumentPropertyFormat? format; + final String title; + final MarkdownData? description; + + /// True if the property must exist and be non-nullable, + /// false if the property may not exist or be nullable. + final bool isRequired; + + const DocumentPropertySchema({ + required this.nodeId, + required this.type, + required this.format, + required this.title, + required this.description, + required this.isRequired, + }); + + /// The most nested object ID in the schema. + String get id => nodeId.lastPath; + + /// Creates a new property from this schema with a default value. + /// + /// Specify the [parentNodeId] if the created property should + /// be moved to another node. By default it is created under + /// the same node that this schema points to. + /// + /// This is useful to create new items for the [DocumentListProperty]. + DocumentProperty createChildPropertyAt([DocumentNodeId? parentNodeId]); + + /// Moves the schema and it's children to the [nodeId]. + DocumentPropertySchema withNodeId(DocumentNodeId nodeId); + + @override + @mustCallSuper + List get props => [ + nodeId, + type, + format, + title, + description, + isRequired, + ]; +} + +/// A schema property that can have a value. +sealed class DocumentValueSchema + extends DocumentPropertySchema { + final T? defaultValue; + final List? enumValues; + + const DocumentValueSchema({ + required super.nodeId, + required super.type, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required this.defaultValue, + required this.enumValues, + }); + + /// A method that builds typed properties. + /// + /// Helps to create properties which generic type [T] + /// is synced with the schema's generic type. + DocumentValueProperty buildProperty({required T? value}) { + return DocumentValueProperty( + schema: this, + value: value, + validationResult: validate(value), + ); + } + + @override + DocumentValueProperty createChildPropertyAt([ + DocumentNodeId? parentNodeId, + ]) { + parentNodeId ??= nodeId; + + final childId = const Uuid().v4(); + final value = defaultValue; + + final updatedSchema = + withNodeId(parentNodeId.child(childId)) as DocumentValueSchema; + + return updatedSchema.buildProperty(value: value); + } + + /// Casts the property linked to this schema so that + /// the property generic value type is synced with schema type. + DocumentValueProperty castProperty( + DocumentValueProperty property, + ) { + assert( + property.schema == this, + 'A property can only be cast by the schema it belongs to', + ); + + return property as DocumentValueProperty; + } + + /// Casts the property value linked to this schema so that + /// the property value type is synced with schema type. + T? castValue(Object? value) { + return value as T?; + } + + /// Validates the property [value] against document rules. + DocumentValidationResult validate(T? value); + + @override + @mustCallSuper + List get props => super.props + [defaultValue, enumValues]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_string_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_string_schema.dart new file mode 100644 index 00000000000..5ad782dfa62 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/schema/property/document_string_schema.dart @@ -0,0 +1,347 @@ +part of 'document_property_schema.dart'; + +sealed class DocumentStringSchema extends DocumentValueSchema { + final DocumentContentMediaType? contentMediaType; + final Range? strLengthRange; + final RegExp? pattern; + + const DocumentStringSchema({ + required super.nodeId, + required super.format, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required this.contentMediaType, + required this.strLengthRange, + required this.pattern, + }) : super( + type: DocumentPropertyType.string, + ); + + @override + DocumentValidationResult validate(String? value) { + return DocumentValidationResult.merge([ + DocumentValidator.validateIfRequired(this, value), + DocumentValidator.validateStringLength(this, value), + DocumentValidator.validateStringPattern(this, value), + ]); + } + + @override + @mustCallSuper + List get props => + super.props + [contentMediaType, strLengthRange, pattern]; +} + +final class DocumentSingleLineTextEntrySchema extends DocumentStringSchema { + const DocumentSingleLineTextEntrySchema({ + required super.nodeId, + required super.format, + required super.contentMediaType, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentSingleLineTextEntrySchema withNodeId(DocumentNodeId nodeId) { + return DocumentSingleLineTextEntrySchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} + +final class DocumentSingleLineHttpsUrlEntrySchema extends DocumentStringSchema { + const DocumentSingleLineHttpsUrlEntrySchema({ + required super.nodeId, + required super.title, + required super.format, + required super.contentMediaType, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentSingleLineHttpsUrlEntrySchema withNodeId(DocumentNodeId nodeId) { + return DocumentSingleLineHttpsUrlEntrySchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} + +final class DocumentMultiLineTextEntrySchema extends DocumentStringSchema { + const DocumentMultiLineTextEntrySchema({ + required super.nodeId, + required super.format, + required super.contentMediaType, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentMultiLineTextEntrySchema withNodeId(DocumentNodeId nodeId) { + return DocumentMultiLineTextEntrySchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} + +final class DocumentMultiLineTextEntryMarkdownSchema + extends DocumentStringSchema { + const DocumentMultiLineTextEntryMarkdownSchema({ + required super.nodeId, + required super.format, + required super.contentMediaType, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentMultiLineTextEntryMarkdownSchema withNodeId(DocumentNodeId nodeId) { + return DocumentMultiLineTextEntryMarkdownSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} + +final class DocumentDropDownSingleSelectSchema extends DocumentStringSchema { + const DocumentDropDownSingleSelectSchema({ + required super.nodeId, + required super.format, + required super.contentMediaType, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentDropDownSingleSelectSchema withNodeId(DocumentNodeId nodeId) { + return DocumentDropDownSingleSelectSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} + +final class DocumentTagGroupSchema extends DocumentStringSchema { + const DocumentTagGroupSchema({ + required super.nodeId, + required super.format, + required super.contentMediaType, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentTagGroupSchema withNodeId(DocumentNodeId nodeId) { + return DocumentTagGroupSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} + +final class DocumentTagSelectionSchema extends DocumentStringSchema { + const DocumentTagSelectionSchema({ + required super.nodeId, + required super.format, + required super.contentMediaType, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentTagSelectionSchema withNodeId(DocumentNodeId nodeId) { + return DocumentTagSelectionSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} + +final class DocumentSpdxLicenseOrUrlSchema extends DocumentStringSchema { + const DocumentSpdxLicenseOrUrlSchema({ + required super.nodeId, + required super.format, + required super.contentMediaType, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentSpdxLicenseOrUrlSchema withNodeId(DocumentNodeId nodeId) { + return DocumentSpdxLicenseOrUrlSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} + +final class DocumentLanguageCodeSchema extends DocumentStringSchema { + const DocumentLanguageCodeSchema({ + required super.nodeId, + required super.format, + required super.contentMediaType, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentLanguageCodeSchema withNodeId(DocumentNodeId nodeId) { + return DocumentLanguageCodeSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} + +final class DocumentGenericStringSchema extends DocumentStringSchema { + const DocumentGenericStringSchema({ + required super.nodeId, + required super.format, + required super.contentMediaType, + required super.title, + required super.description, + required super.isRequired, + required super.defaultValue, + required super.enumValues, + required super.strLengthRange, + required super.pattern, + }); + + @override + DocumentGenericStringSchema withNodeId(DocumentNodeId nodeId) { + return DocumentGenericStringSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: description, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: pattern, + ); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/validation/document_validation_result.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/validation/document_validation_result.dart new file mode 100644 index 00000000000..539937aa1a0 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/validation/document_validation_result.dart @@ -0,0 +1,108 @@ +import 'package:catalyst_voices_models/src/document/document_node_id.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:equatable/equatable.dart'; + +sealed class DocumentValidationResult extends Equatable { + const DocumentValidationResult(); + + /// Returns `true` if the validation was successful, `false` otherwise. + bool get isValid => this is SuccessfulDocumentValidation; + + /// Returns `true` if the validation was unsuccessful, `false` otherwise. + bool get isInvalid => !isValid; + + /// Merges the list of validation results, + /// returns the first failure or the [SuccessfulDocumentValidation]. + static DocumentValidationResult merge( + List results, + ) { + for (final result in results) { + if (result.isInvalid) { + return result; + } + } + + return const SuccessfulDocumentValidation(); + } +} + +/// The validation passed successfully. +final class SuccessfulDocumentValidation extends DocumentValidationResult { + const SuccessfulDocumentValidation(); + + @override + List get props => []; +} + +/// A required document property is missing. +final class MissingRequiredDocumentValue extends DocumentValidationResult { + final DocumentNodeId invalidNodeId; + + const MissingRequiredDocumentValue({required this.invalidNodeId}); + + @override + List get props => [invalidNodeId]; +} + +/// The numerical [actualValue] is not within [expectedRange]. +final class DocumentNumOutOfRange extends DocumentValidationResult { + final DocumentNodeId invalidNodeId; + final Range expectedRange; + final num actualValue; + + const DocumentNumOutOfRange({ + required this.invalidNodeId, + required this.expectedRange, + required this.actualValue, + }); + + @override + List get props => [invalidNodeId, expectedRange, actualValue]; +} + +/// The [String]'s [actualLength] is not within [expectedRange]. +final class DocumentStringOutOfRange + extends DocumentValidationResult { + final DocumentNodeId invalidNodeId; + final Range expectedRange; + final int actualLength; + + const DocumentStringOutOfRange({ + required this.invalidNodeId, + required this.expectedRange, + required this.actualLength, + }); + + @override + List get props => [invalidNodeId, expectedRange, actualLength]; +} + +/// The [List]'s length is not within [expectedRange]. +final class DocumentItemsOutOfRange + extends DocumentValidationResult { + final DocumentNodeId invalidNodeId; + final Range expectedRange; + final int actualItems; + + const DocumentItemsOutOfRange({ + required this.invalidNodeId, + required this.expectedRange, + required this.actualItems, + }); + + @override + List get props => [invalidNodeId, expectedRange, actualItems]; +} + +final class DocumentPatternMismatch extends DocumentValidationResult { + final RegExp pattern; + final String? value; + + const DocumentPatternMismatch({ + required this.pattern, + required this.value, + }); + + @override + List get props => [pattern, value]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/validation/document_validator.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/validation/document_validator.dart new file mode 100644 index 00000000000..e5aea01e5e4 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/validation/document_validator.dart @@ -0,0 +1,116 @@ +import 'package:catalyst_voices_models/src/document/document.dart'; +import 'package:catalyst_voices_models/src/document/schema/property/document_property_schema.dart'; +import 'package:catalyst_voices_models/src/document/validation/document_validation_result.dart'; + +/// Validates [DocumentProperty]. +final class DocumentValidator { + static DocumentValidationResult validateIfRequired( + DocumentPropertySchema schema, + Object? value, + ) { + if (schema.isRequired && value == null) { + return MissingRequiredDocumentValue(invalidNodeId: schema.nodeId); + } + + return const SuccessfulDocumentValidation(); + } + + static DocumentValidationResult validateStringLength( + DocumentStringSchema schema, + String? value, + ) { + final strRange = schema.strLengthRange; + if (strRange != null && value != null) { + if (!strRange.contains(value.length)) { + return DocumentStringOutOfRange( + invalidNodeId: schema.nodeId, + expectedRange: strRange, + actualLength: value.length, + ); + } + } + + return const SuccessfulDocumentValidation(); + } + + static DocumentValidationResult validateStringPattern( + DocumentStringSchema schema, + String? value, + ) { + final pattern = schema.pattern; + if (pattern != null && value != null) { + if (!pattern.hasMatch(value)) { + return DocumentPatternMismatch( + pattern: pattern, + value: value, + ); + } + } + return const SuccessfulDocumentValidation(); + } + + static DocumentValidationResult validateIntegerRange( + DocumentIntegerSchema schema, + int? value, + ) { + final numRange = schema.numRange; + if (numRange != null && value != null) { + if (!numRange.contains(value)) { + return DocumentNumOutOfRange( + invalidNodeId: schema.nodeId, + expectedRange: numRange, + actualValue: value, + ); + } + } + + return const SuccessfulDocumentValidation(); + } + + static DocumentValidationResult validateNumberRange( + DocumentNumberSchema schema, + double? value, + ) { + final numRange = schema.numRange; + if (numRange != null && value != null) { + if (!numRange.contains(value)) { + return DocumentNumOutOfRange( + invalidNodeId: schema.nodeId, + expectedRange: numRange, + actualValue: value, + ); + } + } + + return const SuccessfulDocumentValidation(); + } + + static DocumentValidationResult validateListItems( + DocumentListSchema schema, + List? value, + ) { + final itemsRange = schema.itemsRange; + if (itemsRange != null && value != null) { + if (!itemsRange.contains(value.length)) { + return DocumentItemsOutOfRange( + invalidNodeId: schema.nodeId, + expectedRange: itemsRange, + actualItems: value.length, + ); + } + } + + return const SuccessfulDocumentValidation(); + } + + static DocumentValidationResult validateBool( + DocumentBooleanSchema schema, + // ignore: avoid_positional_boolean_parameters + bool? value, + ) { + if (value == null) { + return MissingRequiredDocumentValue(invalidNodeId: schema.nodeId); + } + return const SuccessfulDocumentValidation(); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/defined_property/grouped_tags.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/values/grouped_tags.dart similarity index 79% rename from catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/defined_property/grouped_tags.dart rename to catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/values/grouped_tags.dart index dd306615d1a..a44e8dd39b9 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/defined_property/grouped_tags.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/values/grouped_tags.dart @@ -1,5 +1,6 @@ import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; final class GroupedTagsSelection extends Equatable { @@ -52,9 +53,6 @@ final class GroupedTags extends Equatable { required this.tags, }); - // Note. this method may be converted to factory function for - // SingleGroupedTagSelector which extends DocumentProperty. - // SingleGroupedTagSelector could easily implement validation. static List fromLogicalGroups( List groups, { Logger? logger, @@ -66,20 +64,22 @@ final class GroupedTags extends Equatable { return false; } - final group = conditions[0]; - final selection = conditions[1]; + final group = conditions + .firstWhereOrNull((e) => e.schema is DocumentTagGroupSchema); + final selection = conditions + .firstWhereOrNull((e) => e.schema is DocumentTagSelectionSchema); - if (group.definition is! TagGroupDefinition) { + if (group == null) { logger?.warning('Group[$group] definition is not group'); return false; } - if (group.value is! String) { + if (group.constValue is! String) { logger?.warning('Group[$group] does not have String value'); return false; } - if (selection.definition is! TagSelectionDefinition) { + if (selection == null) { logger?.warning('Group[$selection] definition is not selection'); return false; } @@ -92,10 +92,13 @@ final class GroupedTags extends Equatable { return true; }).map( (e) { - final group = e.conditions[0].value! as String; - final values = e.conditions[1].enumValues!; + final group = e.conditions[0].constValue! as String; + final values = e.conditions[1].enumValues!.cast(); - return GroupedTags(group: group, tags: values); + return GroupedTags( + group: group, + tags: values, + ); }, ).toList(); } diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/markdown_data.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/markdown_data.dart index 6c5bebcfef8..02a43354e21 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/markdown_data.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/markdown_data.dart @@ -1 +1,4 @@ -extension type const MarkdownData(String data) implements Object {} +extension type const MarkdownData(String data) implements Object { + /// An empty instance of [MarkdownData]. + static const MarkdownData empty = MarkdownData(''); +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/pubspec.yaml b/catalyst_voices/packages/internal/catalyst_voices_models/pubspec.yaml index eb6deb589af..53473de876d 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/pubspec.yaml +++ b/catalyst_voices/packages/internal/catalyst_voices_models/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: json_annotation: ^4.9.0 meta: ^1.10.0 password_strength: ^0.2.0 + uuid: ^4.5.1 dev_dependencies: build_runner: ^2.4.12 diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/test/document/builder/document_change_test.dart b/catalyst_voices/packages/internal/catalyst_voices_models/test/document/builder/document_change_test.dart new file mode 100644 index 00000000000..e678c1aa553 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/test/document/builder/document_change_test.dart @@ -0,0 +1,44 @@ +import 'package:catalyst_voices_models/src/document/builder/document_change.dart'; +import 'package:catalyst_voices_models/src/document/document_node_id.dart'; +import 'package:test/test.dart'; + +void main() { + group(DocumentChange, () { + final nodeId = DocumentNodeId.root.child('node1'); + final node = _DocumentNode(nodeId); + + test('targetsDocumentNode should return true for direct match', () { + final change = DocumentValueChange( + nodeId: DocumentNodeId.root.child('node1'), + value: 'TestValue', + ); + + expect(change.targetsDocumentNode(node), isTrue); + }); + + test('targetsDocumentNode should return true for child match', () { + final change = DocumentValueChange( + nodeId: DocumentNodeId.root.child('node1').child('child1'), + value: 'TestValue', + ); + + expect(change.targetsDocumentNode(node), isTrue); + }); + + test('targetsDocumentNode should return false for unrelated node', () { + final change = DocumentValueChange( + nodeId: DocumentNodeId.root.child('node2'), + value: 'TestValue', + ); + + expect(change.targetsDocumentNode(node), isFalse); + }); + }); +} + +class _DocumentNode implements DocumentNode { + @override + final DocumentNodeId nodeId; + + const _DocumentNode(this.nodeId); +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/test/document/defined_property/grouped_tags.dart b/catalyst_voices/packages/internal/catalyst_voices_models/test/document/defined_property/grouped_tags.dart deleted file mode 100644 index df00d2d576a..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/test/document/defined_property/grouped_tags.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:test/test.dart'; - -void main() { - group(GroupedTags, () { - const group = DocumentSchemaLogicalCondition( - definition: TagGroupDefinition.dummy(), - id: 'group', - value: 'Governance', - enumValues: null, - ); - const groupSelector = DocumentSchemaLogicalCondition( - definition: TagSelectionDefinition.dummy(), - id: 'tag', - value: null, - enumValues: [ - 'Governance', - 'DAO', - ], - ); - - test('correct oneOf produces valid selector', () { - // Given - const oneOf = [ - DocumentSchemaLogicalGroup( - conditions: [ - group, - groupSelector, - ], - ), - ]; - - const expected = [ - GroupedTags(group: 'Governance', tags: ['Governance', 'DAO']), - ]; - - // When - final selector = GroupedTags.fromLogicalGroups(oneOf); - - // Then - expect(selector, expected); - }); - - test('when group is missing selector is not created', () { - // Given - const oneOf = [ - DocumentSchemaLogicalGroup( - conditions: [ - groupSelector, - ], - ), - ]; - - const expected = []; - - // When - final selector = GroupedTags.fromLogicalGroups(oneOf); - - // Then - expect(selector, expected); - }); - - test('when group selector is missing selector is not created', () { - // Given - const oneOf = [ - DocumentSchemaLogicalGroup( - conditions: [ - group, - ], - ), - ]; - - const expected = []; - - // When - final selector = GroupedTags.fromLogicalGroups(oneOf); - - // Then - expect(selector, expected); - }); - - test('when group value is missing selector is not created', () { - // Given - const oneOf = [ - DocumentSchemaLogicalGroup( - conditions: [ - DocumentSchemaLogicalCondition( - definition: TagGroupDefinition.dummy(), - id: 'group', - value: null, - enumValues: null, - ), - groupSelector, - ], - ), - ]; - - const expected = []; - - // When - final selector = GroupedTags.fromLogicalGroups(oneOf); - - // Then - expect(selector, expected); - }); - - test('when group value is invalid selector is not created', () { - // Given - const oneOf = [ - DocumentSchemaLogicalGroup( - conditions: [ - DocumentSchemaLogicalCondition( - definition: TagGroupDefinition.dummy(), - id: 'group', - value: 1, - enumValues: null, - ), - groupSelector, - ], - ), - ]; - - const expected = []; - - // When - final selector = GroupedTags.fromLogicalGroups(oneOf); - - // Then - expect(selector, expected); - }); - - test('when group selector enum is missing selector is not created', () { - // Given - const oneOf = [ - DocumentSchemaLogicalGroup( - conditions: [ - group, - DocumentSchemaLogicalCondition( - definition: TagSelectionDefinition.dummy(), - id: 'tag', - value: null, - enumValues: null, - ), - ], - ), - ]; - - const expected = []; - - // When - final selector = GroupedTags.fromLogicalGroups(oneOf); - - // Then - expect(selector, expected); - }); - }); -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/test/document/document_node_id_test.dart b/catalyst_voices/packages/internal/catalyst_voices_models/test/document/document_node_id_test.dart index 841b28578f2..062203dc878 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/test/document/document_node_id_test.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/test/document/document_node_id_test.dart @@ -1,103 +1,76 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_models/src/document/document_node_id.dart'; import 'package:test/test.dart'; void main() { group(DocumentNodeId, () { - const paths = ['setup', 'title', 'title']; - - test('root node id has empty paths', () { - // Given - const id = DocumentNodeId.root; - - // When - final paths = id.paths; - - // Then - expect(paths, isEmpty); + test('root node has no paths and empty value', () { + expect(DocumentNodeId.root.paths, isEmpty); + expect(DocumentNodeId.root.value, ''); }); - test('paths getter returns expected values', () { - // Given - - // When - final nodeId = paths.fold( - DocumentNodeId.root, - (previousValue, element) => previousValue.child(element), - ); - - // Then - expect(nodeId.paths, paths); + test('child node adds path correctly', () { + final childNode = DocumentNodeId.root.child('section1'); + expect(childNode.paths, ['section1']); + expect(childNode.value, 'section1'); }); - test('two nodes from same paths equals', () { - // Given - - // When - final nodeIdOne = paths.fold( - DocumentNodeId.root, - (previousValue, element) => previousValue.child(element), - ); - final nodeIdTwo = paths.fold( - DocumentNodeId.root, - (previousValue, element) => previousValue.child(element), - ); - - // Then - expect(nodeIdOne, nodeIdTwo); + test('nested child nodes add paths correctly', () { + final childNode = + DocumentNodeId.root.child('section1').child('paragraph1'); + expect(childNode.paths, ['section1', 'paragraph1']); + expect(childNode.value, 'section1.paragraph1'); }); - test('parent works as expected', () { - // Given - final parentPaths = paths.sublist(0, paths.length - 1); - - // When - final nodeId = paths.fold( - DocumentNodeId.root, - (previousValue, element) => previousValue.child(element), - ); - final parentNodeId = parentPaths.fold( - DocumentNodeId.root, - (previousValue, element) => previousValue.child(element), - ); + test('lastPath returns the last path segment', () { + final node = DocumentNodeId.root.child('section1').child('paragraph1'); + expect(node.lastPath, 'paragraph1'); + }); - // Then - expect(nodeId.parent(), parentNodeId); + test('parent returns correct parent node', () { + final node = DocumentNodeId.root.child('section1').child('paragraph1'); + final parentNode = node.parent(); + expect(parentNode.paths, ['section1']); + expect(parentNode.value, 'section1'); }); - test('isChildOf returns true for correct parent', () { - // Given - final parentPaths = paths.sublist(0, paths.length - 1); + test('parent of root returns root itself', () { + final parentOfRoot = DocumentNodeId.root.parent(); + expect(parentOfRoot, DocumentNodeId.root); + }); - // When - final nodeId = paths.fold( - DocumentNodeId.root, - (previousValue, element) => previousValue.child(element), - ); - final parentNodeId = parentPaths.fold( - DocumentNodeId.root, - (previousValue, element) => previousValue.child(element), - ); + test('child node with empty string adds a path', () { + final childNode = DocumentNodeId.root.child(''); + expect(childNode.paths, ['']); + expect(childNode.value, ''); + }); - // Then - expect(nodeId.isChildOf(parentNodeId), isTrue); + test('parent of a single child node returns root', () { + final childNode = DocumentNodeId.root.child('section1'); + final parentNode = childNode.parent(); + expect(parentNode, DocumentNodeId.root); }); - test('isChildOf returns false for different parent', () { - // Given - final parentPaths = [...paths]..[0] = 'summary'; + test('multiple parent calls reduce paths step by step', () { + final node = DocumentNodeId.root + .child('section1') + .child('paragraph1') + .child('sentence1'); + final parentNode1 = node.parent(); + final parentNode2 = parentNode1.parent(); + expect(parentNode1.value, 'section1.paragraph1'); + expect(parentNode2.value, 'section1'); + }); - // When - final nodeId = paths.fold( - DocumentNodeId.root, - (previousValue, element) => previousValue.child(element), - ); - final parentNodeId = parentPaths.fold( - DocumentNodeId.root, - (previousValue, element) => previousValue.child(element), - ); + test('isChildOf correctly identifies child nodes', () { + final parentNode = DocumentNodeId.root.child('section1'); + final childNode = parentNode.child('paragraph1'); + expect(childNode.isChildOf(parentNode), isTrue); + expect(parentNode.isChildOf(childNode), isFalse); + }); - // Then - expect(nodeId.isChildOf(parentNodeId), isFalse); + test('toString outputs the value', () { + final node = DocumentNodeId.root.child('section1').child('paragraph1'); + expect(node.toString(), 'section1.paragraph1'); }); }); } diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/test/document/validation/document_validation_test.dart b/catalyst_voices/packages/internal/catalyst_voices_models/test/document/validation/document_validation_test.dart deleted file mode 100644 index b18920102c5..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_models/test/document/validation/document_validation_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:test/test.dart'; - -void main() { - group('$DocumentValidator', () { - group('$SingleLineHttpsURLEntryDefinition validation', () { - late DocumentProperty property; - - setUp(() { - property = const DocumentProperty( - schema: DocumentSchemaProperty( - definition: SingleLineHttpsURLEntryDefinition( - type: DocumentDefinitionsObjectType.string, - note: '', - format: DocumentDefinitionsFormat.uri, - pattern: '^https://', - ), - nodeId: DocumentNodeId.root, - id: '', - title: '', - description: '', - defaultValue: null, - guidance: '', - enumValues: null, - numRange: null, - strLengthRange: null, - itemsRange: null, - oneOf: null, - isRequired: true, - ), - value: null, - validationResult: SuccessfulDocumentValidation(), - ); - }); - - test('value cannot be null when required', () { - final result = property.schema.validatePropertyValue(null); - - expect(result, isA()); - }); - - test('value is valid when matches pattern', () { - final result = - property.schema.validatePropertyValue('https://www.catalyst.org/'); - - expect(result, isA()); - }); - }); - }); -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/build.yaml b/catalyst_voices/packages/internal/catalyst_voices_repositories/build.yaml index ceb38a39b0a..42a96ff5318 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/build.yaml +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/build.yaml @@ -15,7 +15,7 @@ targets: separate_models: true overriden_models: - file_name: "vitss-openapi" - import_url: "../../src/api_models/overriden_models.dart" + import_url: "../../src/api_models/overridden_models.dart" overriden_models: - SimpleProposal$ProposalCategory - SimpleProposal$Proposer diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/api_models/overriden_models.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/api_models/overridden_models.dart similarity index 100% rename from catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/api_models/overriden_models.dart rename to catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/api_models/overridden_models.dart diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/defined_property_dto/grouped_tags_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/defined_property_dto/grouped_tags_dto.dart deleted file mode 100644 index 2b67972eb1c..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/defined_property_dto/grouped_tags_dto.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'grouped_tags_dto.g.dart'; - -@JsonSerializable() -final class GroupedTagsSelectionDto { - final String? group; - final String? tag; - - const GroupedTagsSelectionDto({ - this.group, - this.tag, - }); - - factory GroupedTagsSelectionDto.fromModel(GroupedTagsSelection tags) { - return GroupedTagsSelectionDto( - group: tags.group, - tag: tags.tag, - ); - } - - factory GroupedTagsSelectionDto.fromJson(Map json) => - _$GroupedTagsSelectionDtoFromJson(json); - - Map toJson() => _$GroupedTagsSelectionDtoToJson(this); - - GroupedTagsSelection toModel() { - return GroupedTagsSelection( - group: group, - tag: tag, - ); - } -} - -class GroupedTagsSelectionConverter - implements JsonConverter?> { - const GroupedTagsSelectionConverter(); - - @override - GroupedTagsSelection? fromJson(Map? json) { - if (json == null) { - return null; - } - - return GroupedTagsSelectionDto.fromJson(json).toModel(); - } - - @override - Map? toJson(GroupedTagsSelection? object) { - if (object == null) { - return null; - } - - return GroupedTagsSelectionDto.fromModel(object).toJson(); - } -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/document_data_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/document_data_dto.dart index ddeb5abf03a..4c5847ce626 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/document_data_dto.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/document_data_dto.dart @@ -10,11 +10,11 @@ final class DocumentDataDto { factory DocumentDataDto.fromDocument({ required String schemaUrl, - required Iterable> segments, + required Iterable> properties, }) { return DocumentDataDto.fromJson({ r'$schema': schemaUrl, - for (final segment in segments) ...segment, + for (final property in properties) ...property, }); } @@ -29,8 +29,14 @@ final class DocumentDataDto { for (final path in nodeId.paths) { if (object is Map) { object = object[path]; + } else if (object is List) { + final index = int.tryParse(path); + if (index == null) { + // index must be a number + return null; + } + object = object[index]; } else { - // invalid path return null; } } diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/document_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/document_dto.dart index b51a9e55db2..8f9c08f9b76 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/document_dto.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/document_dto.dart @@ -1,6 +1,5 @@ import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_repositories/src/dto/document/document_data_dto.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_converter_ext.dart'; /// A data transfer object for the [Document]. /// @@ -9,12 +8,12 @@ import 'package:catalyst_voices_repositories/src/dto/document/schema/document_de final class DocumentDto { final String schemaUrl; final DocumentSchema schema; - final List segments; + final List properties; const DocumentDto({ required this.schemaUrl, required this.schema, - required this.segments, + required this.properties, }); factory DocumentDto.fromJsonSchema( @@ -24,9 +23,9 @@ final class DocumentDto { return DocumentDto( schemaUrl: data.schemaUrl, schema: schema, - segments: schema.segments + properties: schema.properties .map( - (segment) => DocumentSegmentDto.fromJsonSchema( + (segment) => DocumentPropertyDto.fromJsonSchema( segment, data: data, ), @@ -39,17 +38,17 @@ final class DocumentDto { return DocumentDto( schemaUrl: model.schemaUrl, schema: model.schema, - segments: model.segments.map(DocumentSegmentDto.fromModel).toList(), + properties: model.properties.map(DocumentPropertyDto.fromModel).toList(), ); } Document toModel() { - // building a document via builder to sort segments/sections/properties + // building a document via builder to sort properties return DocumentBuilder( schemaUrl: schemaUrl, schema: schema, - segments: segments - .map((e) => DocumentSegmentBuilder.fromSegment(e.toModel())) + properties: properties + .map((e) => DocumentPropertyBuilder.fromProperty(e.toModel())) .toList(), ).build(); } @@ -57,146 +56,193 @@ final class DocumentDto { DocumentDataDto toJson() { return DocumentDataDto.fromDocument( schemaUrl: schemaUrl, - segments: segments.map((e) => e.toJson()), + properties: properties.map((e) => e.toJson()), ); } } -final class DocumentSegmentDto { - final DocumentSchemaSegment schema; - final List sections; +sealed class DocumentPropertyDto { + const DocumentPropertyDto(); + + factory DocumentPropertyDto.fromJsonSchema( + DocumentPropertySchema schema, { + required DocumentDataDto data, + }) { + switch (schema) { + case DocumentObjectSchema(): + return DocumentPropertyObjectDto.fromJsonSchema( + schema, + data: data, + ); + case DocumentListSchema(): + return DocumentPropertyListDto.fromJsonSchema( + schema, + data: data, + ); + case DocumentValueSchema(): + return DocumentPropertyValueDto.fromJsonSchema( + schema, + data: data, + ); + } + } + + factory DocumentPropertyDto.fromModel(DocumentProperty property) { + switch (property) { + case DocumentListProperty(): + return DocumentPropertyListDto.fromModel(property); + case DocumentObjectProperty(): + return DocumentPropertyObjectDto.fromModel(property); + case DocumentValueProperty(): + return DocumentPropertyValueDto.fromModel(property); + } + } + + DocumentPropertySchema get schema; + + DocumentProperty toModel(); + + Map toJson(); +} + +final class DocumentPropertyListDto extends DocumentPropertyDto { + @override + final DocumentListSchema schema; + final List properties; - const DocumentSegmentDto({ + const DocumentPropertyListDto({ required this.schema, - required this.sections, + required this.properties, }); - factory DocumentSegmentDto.fromJsonSchema( - DocumentSchemaSegment schema, { + factory DocumentPropertyListDto.fromJsonSchema( + DocumentListSchema schema, { required DocumentDataDto data, }) { - return DocumentSegmentDto( + final values = data.getProperty(schema.nodeId) as List? ?? []; + final itemsSchema = schema.itemsSchema; + + return DocumentPropertyListDto( schema: schema, - sections: schema.sections - .map( - (section) => DocumentSectionDto.fromJsonSchema( - section, - data: data, - ), - ) - .toList(), + properties: [ + for (int i = 0; i < values.length; i++) + DocumentPropertyDto.fromJsonSchema( + itemsSchema.withNodeId(schema.nodeId.child('$i')), + data: data, + ), + ], ); } - factory DocumentSegmentDto.fromModel(DocumentSegment model) { - return DocumentSegmentDto( + factory DocumentPropertyListDto.fromModel(DocumentListProperty model) { + return DocumentPropertyListDto( schema: model.schema, - sections: model.sections.map(DocumentSectionDto.fromModel).toList(), + properties: model.properties.map(DocumentPropertyDto.fromModel).toList(), ); } - DocumentSegment toModel() { - return DocumentSegment( - schema: schema, - sections: sections.map((e) => e.toModel()).toList(), + @override + DocumentListProperty toModel() { + final mappedProperties = properties.map((e) => e.toModel()).toList(); + + return schema.buildProperty( + properties: List.unmodifiable(mappedProperties), ); } - Map toJson() { - return { - schema.id: { - for (final section in sections) ...section.toJson(), - }, - }; - } + @override + Map toJson() => { + schema.id: [ + for (final property in properties) ...property.toJson().values, + ], + }; } -final class DocumentSectionDto { - final DocumentSchemaSection schema; +final class DocumentPropertyObjectDto extends DocumentPropertyDto { + @override + final DocumentObjectSchema schema; final List properties; - const DocumentSectionDto({ + const DocumentPropertyObjectDto({ required this.schema, required this.properties, }); - factory DocumentSectionDto.fromJsonSchema( - DocumentSchemaSection schema, { + factory DocumentPropertyObjectDto.fromJsonSchema( + DocumentObjectSchema schema, { required DocumentDataDto data, }) { - return DocumentSectionDto( + return DocumentPropertyObjectDto( schema: schema, - properties: schema.properties - .map( - (property) => DocumentPropertyDto.fromJsonSchema( - property, - data: data, - ), - ) - .toList(), + properties: schema.properties.map((childSchema) { + return DocumentPropertyDto.fromJsonSchema( + childSchema, + data: data, + ); + }).toList(), ); } - factory DocumentSectionDto.fromModel(DocumentSection model) { - return DocumentSectionDto( + factory DocumentPropertyObjectDto.fromModel(DocumentObjectProperty model) { + return DocumentPropertyObjectDto( schema: model.schema, properties: model.properties.map(DocumentPropertyDto.fromModel).toList(), ); } - DocumentSection toModel() { - return DocumentSection( - schema: schema, - properties: properties.map((e) => e.toModel()).toList(), + @override + DocumentObjectProperty toModel() { + final mappedProperties = properties.map((e) => e.toModel()).toList(); + + return schema.buildProperty( + properties: List.unmodifiable(mappedProperties), ); } - Map toJson() { - return { - schema.id: { - for (final property in properties) ...property.toJson(), - }, - }; - } + @override + Map toJson() => { + schema.id: { + for (final property in properties) ...property.toJson(), + }, + }; } -final class DocumentPropertyDto { - final DocumentSchemaProperty schema; +final class DocumentPropertyValueDto + extends DocumentPropertyDto { + @override + final DocumentValueSchema schema; final T? value; - const DocumentPropertyDto({ + const DocumentPropertyValueDto({ required this.schema, required this.value, }); - factory DocumentPropertyDto.fromJsonSchema( - DocumentSchemaProperty schema, { + factory DocumentPropertyValueDto.fromJsonSchema( + DocumentValueSchema schema, { required DocumentDataDto data, }) { final property = data.getProperty(schema.nodeId); - final value = schema.definition.converter.fromJson(property); - return DocumentPropertyDto( + return DocumentPropertyValueDto( schema: schema, - value: value, + value: property as T?, ); } - factory DocumentPropertyDto.fromModel(DocumentProperty model) { - return DocumentPropertyDto( + factory DocumentPropertyValueDto.fromModel(DocumentValueProperty model) { + return DocumentPropertyValueDto( schema: model.schema, value: model.value, ); } - DocumentProperty toModel() { - return DocumentProperty( - schema: schema, - value: value, - validationResult: schema.validatePropertyValue(value), - ); + @override + DocumentValueProperty toModel() { + return schema.buildProperty(value: value); } + @override Map toJson() => { - schema.id: schema.definition.converter.toJson(value), + schema.id: value, }; } diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_definitions_converter_ext.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_definitions_converter_ext.dart deleted file mode 100644 index a876049889f..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_definitions_converter_ext.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/defined_property_dto/grouped_tags_dto.dart'; -import 'package:catalyst_voices_repositories/src/utils/json_converters.dart'; -import 'package:json_annotation/json_annotation.dart'; - -extension DocumentDefinitionConverterExt - on BaseDocumentDefinition { - JsonConverter get converter { - switch (this) { - case SingleLineTextEntryDefinition(): - case SingleLineHttpsURLEntryDefinition(): - case MultiLineTextEntryDefinition(): - case MultiLineTextEntryMarkdownDefinition(): - case DropDownSingleSelectDefinition(): - case MultiSelectDefinition(): - case NestedQuestionsDefinition(): - case TagGroupDefinition(): - case TagSelectionDefinition(): - case TokenValueCardanoADADefinition(): - case DurationInMonthsDefinition(): - case YesNoChoiceDefinition(): - case AgreementConfirmationDefinition(): - case SPDXLicenceOrUrlDefinition(): - case LanguageCodeDefinition(): - case SegmentDefinition(): - case SectionDefinition(): - return NoopConverter(); - case SingleGroupedTagSelectorDefinition(): - return const GroupedTagsSelectionConverter() - as JsonConverter; - case SingleLineTextEntryListDefinition(): - case MultiLineTextEntryListMarkdownDefinition(): - case SingleLineHttpsURLEntryListDefinition(): - return const ListStringConverter() as JsonConverter; - case NestedQuestionsListDefinition(): - return const ListOfJsonConverter() as JsonConverter; - } - } -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_definitions_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_definitions_dto.dart index e2cd76aa2bf..f353e7ac446 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_definitions_dto.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_definitions_dto.dart @@ -1,771 +1,29 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:catalyst_voices_repositories/src/utils/json_converters.dart'; -import 'package:json_annotation/json_annotation.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; +import 'package:collection/collection.dart'; -part 'document_definitions_dto.g.dart'; - -@JsonSerializable() final class DocumentDefinitionsDto { - final SegmentDto segment; - final SectionDto section; - final SingleLineTextEntryDto singleLineTextEntry; - final SingleLineHttpsURLEntryDto singleLineHttpsURLEntry; - final MultiLineTextEntryDto multiLineTextEntry; - final MultiLineTextEntryMarkdownDto multiLineTextEntryMarkdown; - final DropDownSingleSelectDto dropDownSingleSelect; - final MultiSelectDto multiSelect; - final SingleLineTextEntryListDto singleLineTextEntryList; - final MultiLineTextEntryListMarkdownDto multiLineTextEntryListMarkdown; - final SingleLineHttpsURLEntryListDto singleLineHttpsURLEntryList; - final NestedQuestionsListDto nestedQuestionsList; - final NestedQuestionsDto nestedQuestions; - final SingleGroupedTagSelectorDto singleGroupedTagSelector; - final TagGroupDto tagGroup; - final TagSelectionDto tagSelection; - @JsonKey(name: 'tokenValueCardanoADA') - final TokenValueCardanoAdaDto tokenValueCardanoAda; - final DurationInMonthsDto durationInMonths; - final YesNoChoiceDto yesNoChoice; - final AgreementConfirmationDto agreementConfirmation; - @JsonKey(name: 'spdxLicenseOrURL') - final SPDXLicenceOrUrlDto spdxLicenceOrUrl; - final LanguageCodeDto languageCode; - - const DocumentDefinitionsDto({ - required this.segment, - required this.section, - required this.singleLineTextEntry, - required this.singleLineHttpsURLEntry, - required this.multiLineTextEntry, - required this.multiLineTextEntryMarkdown, - required this.dropDownSingleSelect, - required this.multiSelect, - required this.singleLineTextEntryList, - required this.multiLineTextEntryListMarkdown, - required this.singleLineHttpsURLEntryList, - required this.nestedQuestionsList, - required this.nestedQuestions, - required this.singleGroupedTagSelector, - required this.tagGroup, - required this.tagSelection, - required this.tokenValueCardanoAda, - required this.durationInMonths, - required this.yesNoChoice, - required this.agreementConfirmation, - required this.spdxLicenceOrUrl, - required this.languageCode, - }); - - factory DocumentDefinitionsDto.fromJson(Map json) => - _$DocumentDefinitionsDtoFromJson(json); - - Map toJson() => _$DocumentDefinitionsDtoToJson(this); - - List get models => [ - segment.toModel(), - section.toModel(), - singleLineTextEntry.toModel(), - multiLineTextEntry.toModel(), - multiLineTextEntryMarkdown.toModel(), - dropDownSingleSelect.toModel(), - multiSelect.toModel(), - singleLineTextEntryList.toModel(), - multiLineTextEntryListMarkdown.toModel(), - singleLineHttpsURLEntry.toModel(), - singleLineHttpsURLEntryList.toModel(), - nestedQuestionsList.toModel(), - nestedQuestions.toModel(), - singleGroupedTagSelector.toModel(), - tagGroup.toModel(), - tagSelection.toModel(), - tokenValueCardanoAda.toModel(), - durationInMonths.toModel(), - yesNoChoice.toModel(), - agreementConfirmation.toModel(), - spdxLicenceOrUrl.toModel(), - languageCode.toModel(), - ]; -} - -@JsonSerializable() -final class SegmentDto { - final String type; - final bool additionalProperties; - @JsonKey(name: 'x-note') - final String note; - - const SegmentDto({ - required this.type, - required this.additionalProperties, - required this.note, - }); - - factory SegmentDto.fromJson(Map json) => - _$SegmentDtoFromJson(json); - - Map toJson() => _$SegmentDtoToJson(this); - - SegmentDefinition toModel() => SegmentDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - additionalProperties: additionalProperties, - ); -} - -@JsonSerializable() -final class SectionDto { - final String type; - final bool additionalProperties; - @JsonKey(name: 'x-note') - final String note; - - const SectionDto({ - required this.type, - required this.additionalProperties, - required this.note, - }); - - factory SectionDto.fromJson(Map json) => - _$SectionDtoFromJson(json); - - Map toJson() => _$SectionDtoToJson(this); - - SectionDefinition toModel() => SectionDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - additionalProperties: additionalProperties, - ); -} - -@JsonSerializable() -class SingleLineTextEntryDto { - final String type; - final String contentMediaType; - final String pattern; - @JsonKey(name: 'x-note') - final String note; - - const SingleLineTextEntryDto({ - required this.type, - required this.contentMediaType, - required this.pattern, - required this.note, - }); - - factory SingleLineTextEntryDto.fromJson(Map json) => - _$SingleLineTextEntryDtoFromJson(json); - - Map toJson() => _$SingleLineTextEntryDtoToJson(this); - - SingleLineTextEntryDefinition toModel() => SingleLineTextEntryDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - contentMediaType: - DocumentDefinitionsContentMediaType.fromString(contentMediaType), - pattern: pattern, - ); -} - -@JsonSerializable() -final class SingleLineHttpsURLEntryDto { - final String type; - final String format; - final String pattern; - @JsonKey(name: 'x-note') - final String note; - - const SingleLineHttpsURLEntryDto({ - required this.type, - required this.format, - required this.pattern, - required this.note, - }); - - factory SingleLineHttpsURLEntryDto.fromJson(Map json) => - _$SingleLineHttpsURLEntryDtoFromJson(json); - - Map toJson() => _$SingleLineHttpsURLEntryDtoToJson(this); - - SingleLineHttpsURLEntryDefinition toModel() { - return SingleLineHttpsURLEntryDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - pattern: pattern, - ); - } -} - -@JsonSerializable() -final class MultiLineTextEntryDto { - final String type; - final String contentMediaType; - final String pattern; - @JsonKey(name: 'x-note') - final String note; - - const MultiLineTextEntryDto({ - required this.type, - required this.contentMediaType, - required this.pattern, - required this.note, - }); - - factory MultiLineTextEntryDto.fromJson(Map json) => - _$MultiLineTextEntryDtoFromJson(json); - - Map toJson() => _$MultiLineTextEntryDtoToJson(this); - - MultiLineTextEntryDefinition toModel() { - return MultiLineTextEntryDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - contentMediaType: - DocumentDefinitionsContentMediaType.fromString(contentMediaType), - pattern: pattern, - ); - } -} - -@JsonSerializable() -final class MultiLineTextEntryMarkdownDto { - final String type; - final String contentMediaType; - final String pattern; - @JsonKey(name: 'x-note') - final String note; - - const MultiLineTextEntryMarkdownDto({ - required this.type, - required this.contentMediaType, - required this.pattern, - required this.note, - }); - - factory MultiLineTextEntryMarkdownDto.fromJson(Map json) => - _$MultiLineTextEntryMarkdownDtoFromJson(json); - - Map toJson() => _$MultiLineTextEntryMarkdownDtoToJson(this); - - MultiLineTextEntryMarkdownDefinition toModel() { - return MultiLineTextEntryMarkdownDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - contentMediaType: - DocumentDefinitionsContentMediaType.fromString(contentMediaType), - pattern: pattern, - ); - } -} - -@JsonSerializable() -final class DropDownSingleSelectDto { - final String type; - final String contentMediaType; - final String pattern; - final String format; - @JsonKey(name: 'x-note') - final String note; - - const DropDownSingleSelectDto({ - required this.type, - required this.contentMediaType, - required this.pattern, - required this.format, - required this.note, - }); - - factory DropDownSingleSelectDto.fromJson(Map json) => - _$DropDownSingleSelectDtoFromJson(json); - - Map toJson() => _$DropDownSingleSelectDtoToJson(this); - - DropDownSingleSelectDefinition toModel() { - return DropDownSingleSelectDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - contentMediaType: - DocumentDefinitionsContentMediaType.fromString(contentMediaType), - pattern: pattern, - format: DocumentDefinitionsFormat.fromString(format), - ); - } -} - -@JsonSerializable() -final class MultiSelectDto { - final String type; - final bool uniqueItems; - final String format; - @JsonKey(name: 'x-note') - final String note; - - const MultiSelectDto({ - required this.type, - required this.uniqueItems, - required this.format, - required this.note, - }); - - factory MultiSelectDto.fromJson(Map json) => - _$MultiSelectDtoFromJson(json); - - Map toJson() => _$MultiSelectDtoToJson(this); - - MultiSelectDefinition toModel() { - return MultiSelectDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - uniqueItems: uniqueItems, - ); - } -} - -@JsonSerializable() -final class SingleLineTextEntryListDto { - final String type; - final String format; - final bool uniqueItems; - @JsonKey(name: 'default') - @ListStringConverter() - final List? defaultValue; - final Map items; - @JsonKey(name: 'x-note') - final String note; - - const SingleLineTextEntryListDto({ - required this.type, - required this.format, - required this.uniqueItems, - required this.defaultValue, - required this.items, - required this.note, - }); - - factory SingleLineTextEntryListDto.fromJson(Map json) => - _$SingleLineTextEntryListDtoFromJson(json); - - Map toJson() => _$SingleLineTextEntryListDtoToJson(this); - - SingleLineTextEntryListDefinition toModel() => - SingleLineTextEntryListDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - uniqueItems: uniqueItems, - defaultValues: defaultValue ?? [], - items: items, - ); -} - -@JsonSerializable() -final class MultiLineTextEntryListMarkdownDto { - final String type; - final String format; - final bool uniqueItems; - @JsonKey(name: 'default') - @ListStringConverter() - final List? defaultValue; - final Map items; - @JsonKey(name: 'x-note') - final String note; - - const MultiLineTextEntryListMarkdownDto({ - required this.type, - required this.format, - required this.uniqueItems, - required this.defaultValue, - required this.items, - required this.note, - }); - - factory MultiLineTextEntryListMarkdownDto.fromJson( - Map json, - ) => - _$MultiLineTextEntryListMarkdownDtoFromJson(json); - - Map toJson() => - _$MultiLineTextEntryListMarkdownDtoToJson(this); + final Map _definitions; - MultiLineTextEntryListMarkdownDefinition toModel() => - MultiLineTextEntryListMarkdownDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - uniqueItems: uniqueItems, - defaultValue: defaultValue ?? [], - items: items, - ); -} - -@JsonSerializable() -final class SingleLineHttpsURLEntryListDto { - final String type; - final String format; - final bool uniqueItems; - @JsonKey(name: 'default') - @ListStringConverter() - final List? defaultValue; - final Map items; - @JsonKey(name: 'x-note') - final String note; - - const SingleLineHttpsURLEntryListDto({ - required this.type, - required this.format, - required this.uniqueItems, - required this.defaultValue, - required this.items, - required this.note, - }); - - factory SingleLineHttpsURLEntryListDto.fromJson(Map json) => - _$SingleLineHttpsURLEntryListDtoFromJson(json); - - Map toJson() => _$SingleLineHttpsURLEntryListDtoToJson(this); - - SingleLineHttpsURLEntryListDefinition toModel() => - SingleLineHttpsURLEntryListDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - uniqueItems: uniqueItems, - defaultValue: defaultValue ?? [], - items: items, - ); -} - -@JsonSerializable() -final class NestedQuestionsListDto { - final String type; - final String format; - final bool uniqueItems; - @JsonKey(name: 'default') - @ListStringConverter() - final List? defaultValue; - @JsonKey(name: 'x-note') - final String note; - - const NestedQuestionsListDto({ - required this.type, - required this.format, - required this.uniqueItems, - required this.defaultValue, - required this.note, - }); - - factory NestedQuestionsListDto.fromJson(Map json) => - _$NestedQuestionsListDtoFromJson(json); + const DocumentDefinitionsDto(this._definitions); - Map toJson() => _$NestedQuestionsListDtoToJson(this); - - NestedQuestionsListDefinition toModel() => NestedQuestionsListDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - uniqueItems: uniqueItems, - defaultValue: defaultValue ?? [], - ); -} - -@JsonSerializable() -final class NestedQuestionsDto { - final String type; - final String format; - final bool additionalProperties; - @JsonKey(name: 'x-note') - final String note; - - const NestedQuestionsDto({ - required this.type, - required this.format, - required this.additionalProperties, - required this.note, - }); - - factory NestedQuestionsDto.fromJson(Map json) => - _$NestedQuestionsDtoFromJson(json); - - Map toJson() => _$NestedQuestionsDtoToJson(this); - - NestedQuestionsDefinition toModel() { - return NestedQuestionsDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - additionalProperties: additionalProperties, + factory DocumentDefinitionsDto.fromJson(Map json) { + final map = json.map( + (key, value) { + return MapEntry( + key, + DocumentPropertySchemaDto.fromJson(value as Map), + ); + }, ); - } -} -@JsonSerializable() -final class SingleGroupedTagSelectorDto { - final String type; - final String format; - final bool additionalProperties; - @JsonKey(name: 'x-note') - final String note; - - const SingleGroupedTagSelectorDto({ - required this.type, - required this.format, - required this.additionalProperties, - required this.note, - }); - - factory SingleGroupedTagSelectorDto.fromJson(Map json) => - _$SingleGroupedTagSelectorDtoFromJson(json); - - Map toJson() => _$SingleGroupedTagSelectorDtoToJson(this); - - SingleGroupedTagSelectorDefinition toModel() { - return SingleGroupedTagSelectorDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - additionalProperties: additionalProperties, - ); - } -} - -@JsonSerializable() -final class TagGroupDto { - final String type; - final String format; - final String pattern; - @JsonKey(name: 'x-note') - final String note; - - const TagGroupDto({ - required this.type, - required this.format, - required this.pattern, - required this.note, - }); - - factory TagGroupDto.fromJson(Map json) => - _$TagGroupDtoFromJson(json); - - Map toJson() => _$TagGroupDtoToJson(this); - - TagGroupDefinition toModel() { - return TagGroupDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - pattern: pattern, - ); + return DocumentDefinitionsDto(map); } -} - -@JsonSerializable() -final class TagSelectionDto { - final String type; - final String format; - final String pattern; - @JsonKey(name: 'x-note') - final String note; - - const TagSelectionDto({ - required this.type, - required this.format, - required this.pattern, - required this.note, - }); - - factory TagSelectionDto.fromJson(Map json) => - _$TagSelectionDtoFromJson(json); - - Map toJson() => _$TagSelectionDtoToJson(this); - TagSelectionDefinition toModel() { - return TagSelectionDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - pattern: pattern, - ); + Map toJson() { + return _definitions.map((key, value) => MapEntry(key, value.toJson())); } -} - -@JsonSerializable() -final class TokenValueCardanoAdaDto { - final String type; - final String format; - @JsonKey(name: 'x-note') - final String note; - - const TokenValueCardanoAdaDto({ - required this.type, - required this.format, - required this.note, - }); - - factory TokenValueCardanoAdaDto.fromJson(Map json) => - _$TokenValueCardanoAdaDtoFromJson(json); - - Map toJson() => _$TokenValueCardanoAdaDtoToJson(this); - - TokenValueCardanoADADefinition toModel() => TokenValueCardanoADADefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - ); -} - -@JsonSerializable() -final class DurationInMonthsDto { - final String type; - final String format; - @JsonKey(name: 'x-note') - final String note; - - const DurationInMonthsDto({ - required this.type, - required this.format, - required this.note, - }); - factory DurationInMonthsDto.fromJson(Map json) => - _$DurationInMonthsDtoFromJson(json); - - Map toJson() => _$DurationInMonthsDtoToJson(this); - - DurationInMonthsDefinition toModel() { - return DurationInMonthsDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - ); + DocumentPropertySchemaDto? getDefinition(String? def) { + return _definitions.entries.firstWhereOrNull((e) => e.key == def)?.value; } } - -@JsonSerializable() -final class YesNoChoiceDto { - final String type; - final String format; - @JsonKey(name: 'default') - final bool? defaultValue; - @JsonKey(name: 'x-note') - final String note; - - const YesNoChoiceDto({ - required this.type, - required this.format, - required this.defaultValue, - required this.note, - }); - - factory YesNoChoiceDto.fromJson(Map json) => - _$YesNoChoiceDtoFromJson(json); - - Map toJson() => _$YesNoChoiceDtoToJson(this); - - YesNoChoiceDefinition toModel() => YesNoChoiceDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - defaultValue: defaultValue ?? false, - ); -} - -@JsonSerializable() -final class AgreementConfirmationDto { - final String type; - final String format; - @JsonKey(name: 'default') - final bool? defaultValue; - @JsonKey(name: 'const') - final bool? constValue; - @JsonKey(name: 'x-note') - final String note; - - const AgreementConfirmationDto({ - required this.type, - required this.format, - required this.defaultValue, - required this.constValue, - required this.note, - }); - - factory AgreementConfirmationDto.fromJson(Map json) => - _$AgreementConfirmationDtoFromJson(json); - - Map toJson() => _$AgreementConfirmationDtoToJson(this); - - AgreementConfirmationDefinition toModel() => AgreementConfirmationDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - defaultValue: defaultValue ?? false, - constValue: constValue ?? true, - ); -} - -@JsonSerializable() -final class SPDXLicenceOrUrlDto { - final String type; - final String contentMediaType; - final String pattern; - final String format; - @JsonKey(name: 'x-note') - final String note; - - const SPDXLicenceOrUrlDto({ - required this.type, - required this.contentMediaType, - required this.pattern, - required this.format, - required this.note, - }); - - factory SPDXLicenceOrUrlDto.fromJson(Map json) => - _$SPDXLicenceOrUrlDtoFromJson(json); - - Map toJson() => _$SPDXLicenceOrUrlDtoToJson(this); - - SPDXLicenceOrUrlDefinition toModel() => SPDXLicenceOrUrlDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - format: DocumentDefinitionsFormat.fromString(format), - pattern: pattern, - contentMediaType: - DocumentDefinitionsContentMediaType.fromString(contentMediaType), - ); -} - -@JsonSerializable() -final class LanguageCodeDto { - final String type; - final String? title; - final String? description; - @JsonKey(name: 'enum') - @ListStringConverter() - final List? enumValues; - @JsonKey(name: 'default') - final String? defaultValue; - @JsonKey(name: 'x-note') - final String note; - - const LanguageCodeDto({ - required this.type, - required this.title, - required this.description, - required this.enumValues, - required this.defaultValue, - required this.note, - }); - - factory LanguageCodeDto.fromJson(Map json) => - _$LanguageCodeDtoFromJson(json); - - Map toJson() => _$LanguageCodeDtoToJson(this); - - LanguageCodeDefinition toModel() => LanguageCodeDefinition( - type: DocumentDefinitionsObjectType.fromString(type), - note: note, - defaultValue: defaultValue ?? 'en', - enumValues: enumValues ?? [], - ); -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_property_schema_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_property_schema_dto.dart new file mode 100644 index 00000000000..5540c1b9e33 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_property_schema_dto.dart @@ -0,0 +1,299 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/enum/document_property_type_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/mapper/document_boolean_schema_mapper.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/mapper/document_integer_schema_mapper.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/mapper/document_list_schema_mapper.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/mapper/document_number_schema_mapper.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/mapper/document_object_schema_mapper.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/mapper/document_string_schema_mapper.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'document_property_schema_dto.g.dart'; + +@JsonSerializable(includeIfNull: false) +final class DocumentPropertySchemaDto { + @JsonKey(name: r'$ref') + final String? ref; + @JsonKey(name: 'type') + @DocumentPropertyTypeDtoConverter() + final List? types; + final String? format; + final String? contentMediaType; + final String? title; + final String? description; + @JsonKey(name: 'default') + final Object? defaultValue; + @JsonKey(name: 'x-guidance') + final String? guidance; + @JsonKey(name: 'const') + final Object? constValue; + @JsonKey(name: 'enum') + final List? enumValues; + final Map? properties; + final DocumentPropertySchemaDto? items; + final int? minimum; + final int? maximum; + final int? minLength; + final int? maxLength; + final int? minItems; + final int? maxItems; + final String? pattern; + + /// Logical boolean algebra conditions. + final List? oneOf; + + /// The list of required [properties]. + final List? required; + + /// The order of children [properties]. + @JsonKey(name: 'x-order') + final List? order; + + const DocumentPropertySchemaDto({ + required this.ref, + required this.types, + required this.format, + required this.contentMediaType, + required this.title, + required this.description, + required this.defaultValue, + required this.guidance, + required this.constValue, + required this.enumValues, + required this.properties, + required this.items, + required this.minimum, + required this.maximum, + required this.minLength, + required this.maxLength, + required this.maxItems, + required this.minItems, + required this.oneOf, + required this.required, + required this.order, + required this.pattern, + }); + + const DocumentPropertySchemaDto.optional({ + this.ref, + this.types, + this.format, + this.contentMediaType, + this.title, + this.description, + this.defaultValue, + this.guidance, + this.constValue, + this.enumValues, + this.properties, + this.items, + this.minimum, + this.maximum, + this.minLength, + this.maxLength, + this.maxItems, + this.minItems, + this.oneOf, + this.required, + this.order, + this.pattern, + }); + + factory DocumentPropertySchemaDto.fromJson(Map json) => + _$DocumentPropertySchemaDtoFromJson(json); + + Map toJson() => _$DocumentPropertySchemaDtoToJson(this); + + DocumentPropertySchema toModel({ + required DocumentDefinitionsDto definitions, + required DocumentNodeId nodeId, + required bool isRequired, + }) { + final definitionSchema = definitions.getDefinition(definition()); + final schema = + definitionSchema != null ? mergeWith(definitionSchema) : this; + final types = schema.types ?? []; + final isRequiredAndNonNullable = + isRequired && !types.contains(DocumentPropertyTypeDto.nullable); + + final type = types.firstWhere( + (e) => e != DocumentPropertyTypeDto.nullable, + orElse: () => + throw ArgumentError('Property type cannot be empty or nullable'), + ); + + switch (type) { + case DocumentPropertyTypeDto.array: + return DocumentListSchemaMapper.toModel( + definitions: definitions, + schema: schema, + nodeId: nodeId, + isRequired: isRequiredAndNonNullable, + ); + case DocumentPropertyTypeDto.object: + return DocumentObjectSchemaMapper.toModel( + definitions: definitions, + schema: schema, + nodeId: nodeId, + isRequired: isRequiredAndNonNullable, + ); + case DocumentPropertyTypeDto.string: + return DocumentStringSchemaMapper.toModel( + definitions: definitions, + schema: schema, + nodeId: nodeId, + isRequired: isRequiredAndNonNullable, + ); + case DocumentPropertyTypeDto.integer: + return DocumentIntegerSchemaMapper.toModel( + definitions: definitions, + schema: schema, + nodeId: nodeId, + isRequired: isRequiredAndNonNullable, + ); + case DocumentPropertyTypeDto.number: + return DocumentNumberSchemaMapper.toModel( + definitions: definitions, + schema: schema, + nodeId: nodeId, + isRequired: isRequiredAndNonNullable, + ); + case DocumentPropertyTypeDto.boolean: + return DocumentBooleanSchemaMapper.toModel( + definitions: definitions, + schema: schema, + nodeId: nodeId, + isRequired: isRequiredAndNonNullable, + ); + case DocumentPropertyTypeDto.nullable: + throw ArgumentError('The primary property type cannot be "null".'); + } + } + + DocumentSchemaLogicalGroup toLogicalGroup({ + required DocumentDefinitionsDto definitions, + required DocumentNodeId nodeId, + required bool isRequired, + }) { + final properties = this.properties?.values ?? []; + + return DocumentSchemaLogicalGroup( + conditions: [ + for (final property in properties) + property.toLogicalCondition( + definitions: definitions, + nodeId: nodeId, + isRequired: isRequired, + ), + ], + ); + } + + DocumentSchemaLogicalCondition toLogicalCondition({ + required DocumentDefinitionsDto definitions, + required DocumentNodeId nodeId, + required bool isRequired, + }) { + final definition = definitions.getDefinition(ref); + final schema = definition != null ? mergeWith(definition) : this; + + return DocumentSchemaLogicalCondition( + schema: schema.toModel( + definitions: definitions, + nodeId: nodeId, + isRequired: isRequired, + ), + constValue: constValue, + enumValues: enumValues, + ); + } + + /// Returns a new copy of the [DocumentPropertySchemaDto], + /// fields from this and [other] instance are merged into a single instance. + /// + /// Fields from this instance have more priority than from the + /// [other] instance (in case they appear in both instances). + DocumentPropertySchemaDto mergeWith(DocumentPropertySchemaDto other) { + final mergedItems = _mergeItems(items, other.items); + final mergedOneOf = oneOf ?? other.oneOf; + + var mergedProperties = _mergeProperties(properties, other.properties); + if (mergedOneOf != null) { + for (final item in mergedOneOf) { + mergedProperties = _mergeProperties(mergedProperties, item.properties); + } + } + + return DocumentPropertySchemaDto( + ref: ref ?? other.ref, + types: types ?? other.types, + format: format ?? other.format, + contentMediaType: contentMediaType ?? other.contentMediaType, + title: title ?? other.title, + description: description ?? other.description, + defaultValue: defaultValue ?? other.defaultValue, + guidance: guidance ?? other.guidance, + constValue: constValue ?? other.constValue, + enumValues: enumValues ?? other.enumValues, + properties: mergedProperties, + items: mergedItems, + minimum: minimum ?? other.minimum, + maximum: maximum ?? other.maximum, + minLength: minLength ?? other.minLength, + maxLength: maxLength ?? other.maxLength, + minItems: minItems ?? other.minItems, + maxItems: maxItems ?? other.maxItems, + oneOf: oneOf ?? other.oneOf, + required: required ?? other.required, + order: order ?? other.order, + pattern: pattern ?? other.pattern, + ); + } + + DocumentPropertySchemaDto? _mergeItems( + DocumentPropertySchemaDto? first, + DocumentPropertySchemaDto? second, + ) { + if (first == null || second == null) { + return first ?? second; + } else { + return first.mergeWith(second); + } + } + + Map? _mergeProperties( + Map? first, + Map? second, + ) { + if (first == null || second == null) { + return first ?? second; + } + + final map = Map.of(first); + for (final entry in second.entries) { + final firstEntry = map[entry.key]; + if (firstEntry != null) { + map[entry.key] = firstEntry.mergeWith(entry.value); + } else { + map[entry.key] = entry.value; + } + } + + return map; + } + + String? definition() { + final ref = this.ref; + if (ref == null) { + return null; + } + + final index = ref.lastIndexOf('/'); + if (index < 0) { + return null; + } + + return ref.substring(index + 1); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_dto.dart index c1cf33d1a6d..a7ac6c60a03 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_dto.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_dto.dart @@ -1,12 +1,11 @@ import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_dto.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_segment_dto.dart'; -import 'package:catalyst_voices_repositories/src/utils/document_schema_dto_converter.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; import 'package:json_annotation/json_annotation.dart'; part 'document_schema_dto.g.dart'; -@JsonSerializable() +@JsonSerializable(includeIfNull: false) final class DocumentSchemaDto { @JsonKey(name: r'$schema') final String jsonSchema; @@ -17,9 +16,8 @@ final class DocumentSchemaDto { final DocumentDefinitionsDto definitions; final String type; final bool additionalProperties; - @JsonKey(name: 'properties') - @DocumentSchemaSegmentsDtoConverter() - final List segments; + final Map properties; + final List? required; @JsonKey(name: 'x-order') final List? order; @JsonKey(includeToJson: false) @@ -33,7 +31,8 @@ final class DocumentSchemaDto { required this.definitions, this.type = 'object', this.additionalProperties = false, - required this.segments, + required this.properties, + required this.required, required this.order, required this.propertiesSchema, }); @@ -50,14 +49,15 @@ final class DocumentSchemaDto { DocumentSchema toModel() { const nodeId = DocumentNodeId.root; + final required = this.required ?? const []; final order = this.order ?? const []; - final mappedSegments = segments - .where((e) => e.ref.contains('segment')) + final mappedProperties = properties.entries .map( - (e) => e.toModel( - definitions.models, - parentNodeId: DocumentNodeId.root, + (property) => property.value.toModel( + definitions: definitions, + nodeId: DocumentNodeId.root.child(property.key), + isRequired: required.contains(property.key), ), ) .toList(); @@ -65,8 +65,8 @@ final class DocumentSchemaDto { return DocumentSchema( jsonSchema: jsonSchema, title: title, - description: description, - segments: mappedSegments, + description: MarkdownData(description), + properties: mappedProperties, order: order.map(nodeId.child).toList(), propertiesSchema: propertiesSchema, ); diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_logical_property_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_logical_property_dto.dart deleted file mode 100644 index 06eb027f72e..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_logical_property_dto.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:catalyst_voices_repositories/src/utils/document_schema_dto_converter.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'document_schema_logical_property_dto.g.dart'; - -@JsonSerializable(includeIfNull: false) -final class DocumentSchemaLogicalGroupDto { - @DocumentSchemaLogicalPropertiesDtoConverter() - @JsonKey(name: 'properties') - final List? conditions; - - DocumentSchemaLogicalGroupDto({ - this.conditions, - }); - - factory DocumentSchemaLogicalGroupDto.fromJson( - Map json, - ) { - return _$DocumentSchemaLogicalGroupDtoFromJson(json); - } - - Map toJson() { - return _$DocumentSchemaLogicalGroupDtoToJson(this); - } - - DocumentSchemaLogicalGroup toModel( - List definitions, - ) { - final conditions = this.conditions ?? const []; - - return DocumentSchemaLogicalGroup( - conditions: conditions.map((e) => e.toModel(definitions)).toList(), - ); - } -} - -@JsonSerializable(includeIfNull: false) -final class DocumentSchemaLogicalConditionDto { - @JsonKey(name: r'$ref') - final String ref; - @JsonKey(includeToJson: false) - final String id; - @JsonKey(name: 'const') - final Object? value; - @JsonKey(name: 'enum') - final List? enumValues; - - const DocumentSchemaLogicalConditionDto({ - this.ref = '', - required this.id, - this.value, - this.enumValues, - }); - - factory DocumentSchemaLogicalConditionDto.fromJson( - Map json, - ) { - return _$DocumentSchemaLogicalConditionDtoFromJson(json); - } - - Map toJson() { - return _$DocumentSchemaLogicalConditionDtoToJson(this); - } - - DocumentSchemaLogicalCondition toModel( - List definitions, - ) { - return DocumentSchemaLogicalCondition( - definition: definitions.getDefinition(ref), - id: id, - value: value, - enumValues: enumValues, - ); - } -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_property_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_property_dto.dart deleted file mode 100644 index ff34a84e3a3..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_property_dto.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_converter_ext.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_logical_property_dto.dart'; -import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'document_schema_property_dto.g.dart'; - -@JsonSerializable(includeIfNull: false) -final class DocumentSchemaPropertyDto { - @JsonKey(name: r'$ref') - final String ref; - @JsonKey(includeToJson: false) - final String id; - final String? title; - final String? description; - @JsonKey(name: 'default') - final Object? defaultValue; - @JsonKey(name: 'x-guidance') - final String? guidance; - @JsonKey(name: 'enum') - final List? enumValues; - final int? minimum; - final int? maximum; - final int? minLength; - final int? maxLength; - final int? minItems; - final int? maxItems; - - // TODO(LynxLynxx): return to this - final Map? items; - - /// Logical boolean algebra conditions. - final List? oneOf; - - const DocumentSchemaPropertyDto({ - this.ref = '', - required this.id, - this.title, - this.description, - this.defaultValue, - this.guidance, - this.enumValues, - this.minimum, - this.maximum, - this.minLength, - this.maxLength, - this.maxItems, - this.minItems, - this.items, - this.oneOf, - }); - - factory DocumentSchemaPropertyDto.fromJson(Map json) => - _$DocumentSchemaPropertyDtoFromJson(json); - - Map toJson() => _$DocumentSchemaPropertyDtoToJson(this); - - DocumentSchemaProperty toModel( - List definitions, { - required DocumentNodeId parentNodeId, - required bool isRequired, - }) { - final definition = definitions.getDefinition(ref); - return definition.createSchema( - nodeId: parentNodeId.child(id), - id: id, - title: title, - description: description, - defaultValue: definition.converter.fromJson(defaultValue), - guidance: guidance, - enumValues: enumValues, - numRange: Range.optionalIntRangeOf(min: minimum, max: maximum), - strLengthRange: Range.optionalIntRangeOf(min: minLength, max: maxLength), - itemsRange: Range.optionalIntRangeOf(min: minItems, max: maxItems), - oneOf: oneOf?.map((e) => e.toModel(definitions)).toList(), - isRequired: isRequired, - ); - } -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_section_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_section_dto.dart deleted file mode 100644 index d63487761e0..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_section_dto.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_property_dto.dart'; -import 'package:catalyst_voices_repositories/src/utils/document_schema_dto_converter.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'document_schema_section_dto.g.dart'; - -@JsonSerializable() -final class DocumentSchemaSectionDto { - @JsonKey(includeToJson: false) - final String id; - @JsonKey(name: r'$ref') - final String ref; - final String? title; - final String? description; - @DocumentSchemaPropertiesDtoConverter() - final List properties; - final List? required; - @JsonKey(name: 'x-order') - final List? order; - - const DocumentSchemaSectionDto({ - required this.id, - required this.ref, - this.title, - this.description, - required this.properties, - this.required, - this.order, - }); - - factory DocumentSchemaSectionDto.fromJson(Map json) => - _$DocumentSchemaSectionDtoFromJson(json); - - Map toJson() => _$DocumentSchemaSectionDtoToJson(this); - - DocumentSchemaSection toModel( - List definitions, { - required DocumentNodeId parentNodeId, - required bool isRequired, - }) { - final nodeId = parentNodeId.child(id); - final order = this.order ?? const []; - final required = this.required ?? const []; - - final mappedProperties = properties - .where((property) => BaseDocumentDefinition.isKnownType(property.ref)) - .map( - (e) => e.toModel( - definitions, - parentNodeId: nodeId, - isRequired: required.contains(e.id), - ), - ) - .toList(); - - return DocumentSchemaSection( - definition: definitions.getDefinition(ref), - nodeId: nodeId, - id: id, - title: title, - description: description, - properties: mappedProperties, - isRequired: isRequired, - order: order.map(nodeId.child).toList(), - ); - } -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_segment_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_segment_dto.dart deleted file mode 100644 index 9366b70631f..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/document_schema_segment_dto.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_section_dto.dart'; -import 'package:catalyst_voices_repositories/src/utils/document_schema_dto_converter.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'document_schema_segment_dto.g.dart'; - -@JsonSerializable() -final class DocumentSchemaSegmentDto { - @JsonKey(includeToJson: false) - final String id; - @JsonKey(name: r'$ref') - final String ref; - final String title; - final String? description; - @JsonKey(name: 'properties') - @DocumentSchemaSectionsDtoConverter() - final List sections; - final List? required; - @JsonKey(name: 'x-order') - final List? order; - - const DocumentSchemaSegmentDto({ - required this.id, - required this.ref, - required this.title, - this.description, - required this.sections, - this.required, - this.order, - }); - - factory DocumentSchemaSegmentDto.fromJson(Map json) => - _$DocumentSchemaSegmentDtoFromJson(json); - - Map toJson() => _$DocumentSchemaSegmentDtoToJson(this); - - DocumentSchemaSegment toModel( - List definitions, { - required DocumentNodeId parentNodeId, - }) { - final nodeId = parentNodeId.child(id); - final order = this.order ?? const []; - final required = this.required ?? const []; - - final mappedSections = sections - .where((section) => section.ref.contains('section')) - .map( - (e) => e.toModel( - definitions, - parentNodeId: nodeId, - isRequired: required.contains(e.id), - ), - ) - .toList(); - - return DocumentSchemaSegment( - definition: definitions.getDefinition(ref), - nodeId: nodeId, - id: id, - title: title, - description: description, - sections: mappedSections, - order: order.map(nodeId.child).toList(), - ); - } -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/enum/document_property_type_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/enum/document_property_type_dto.dart new file mode 100644 index 00000000000..ea5263110bb --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/enum/document_property_type_dto.dart @@ -0,0 +1,54 @@ +import 'package:json_annotation/json_annotation.dart'; + +enum DocumentPropertyTypeDto { + array('array'), + object('object'), + string('string'), + integer('integer'), + number('number'), + boolean('boolean'), + nullable('null'); + + final String value; + + const DocumentPropertyTypeDto(this.value); + + factory DocumentPropertyTypeDto.fromString(String string) { + for (final type in values) { + if (type.value.toLowerCase() == string.toLowerCase()) { + return type; + } + } + throw ArgumentError('Unsupported $string document property type'); + } +} + +final class DocumentPropertyTypeDtoConverter + extends JsonConverter?, Object?> { + const DocumentPropertyTypeDtoConverter(); + + @override + List? fromJson(Object? json) { + if (json == null) { + return null; + } else if (json is String) { + return [DocumentPropertyTypeDto.fromString(json)]; + } else if (json is List) { + final strings = json.cast(); + return strings.map(DocumentPropertyTypeDto.fromString).toList(); + } else { + throw ArgumentError('Cannot parse $DocumentPropertyTypeDto from $json'); + } + } + + @override + Object? toJson(List? object) { + if (object == null) { + return null; + } else if (object.length == 1) { + return object.first.value; + } else { + return object.map((e) => e.value).toList(); + } + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_boolean_schema_mapper.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_boolean_schema_mapper.dart new file mode 100644 index 00000000000..2037220ebe4 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_boolean_schema_mapper.dart @@ -0,0 +1,74 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; + +enum _DocumentBooleanDefinition { + yesNoChoice('yesNoChoice'), + agreementConfirmation('agreementConfirmation'), + unknown('unknown'); + + final String def; + + const _DocumentBooleanDefinition(this.def); + + factory _DocumentBooleanDefinition.fromDef(String? def) { + for (final value in values) { + if (value.def.toLowerCase() == def?.toLowerCase()) { + return value; + } + } + + return _DocumentBooleanDefinition.unknown; + } +} + +final class DocumentBooleanSchemaMapper { + static DocumentBooleanSchema toModel({ + required DocumentDefinitionsDto definitions, + required DocumentPropertySchemaDto schema, + required DocumentNodeId nodeId, + required bool isRequired, + }) { + final format = DocumentPropertyFormat.fromString(schema.format ?? ''); + final title = schema.title ?? ''; + final description = schema.description; + final descriptionMarkdown = + description != null ? MarkdownData(description) : null; + final defaultValue = schema.defaultValue as bool?; + final enumValues = schema.enumValues?.cast(); + final definition = _DocumentBooleanDefinition.fromDef(schema.definition()); + + switch (definition) { + case _DocumentBooleanDefinition.yesNoChoice: + return DocumentYesNoChoiceSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + ); + case _DocumentBooleanDefinition.agreementConfirmation: + return DocumentAgreementConfirmationSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + ); + case _DocumentBooleanDefinition.unknown: + return DocumentGenericBooleanSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + ); + } + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_integer_schema_mapper.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_integer_schema_mapper.dart new file mode 100644 index 00000000000..5f33d04c05c --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_integer_schema_mapper.dart @@ -0,0 +1,80 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; + +enum _DocumentIntegerDefinition { + tokenValueCardanoAda('tokenValueCardanoADA'), + durationInMonths('durationInMonths'), + unknown('unknown'); + + final String def; + + const _DocumentIntegerDefinition(this.def); + + factory _DocumentIntegerDefinition.fromDef(String? def) { + for (final value in values) { + if (value.def.toLowerCase() == def?.toLowerCase()) { + return value; + } + } + + return _DocumentIntegerDefinition.unknown; + } +} + +final class DocumentIntegerSchemaMapper { + static DocumentIntegerSchema toModel({ + required DocumentDefinitionsDto definitions, + required DocumentPropertySchemaDto schema, + required DocumentNodeId nodeId, + required bool isRequired, + }) { + final format = DocumentPropertyFormat.fromString(schema.format ?? ''); + final title = schema.title ?? ''; + final description = schema.description; + final descriptionMarkdown = + description != null ? MarkdownData(description) : null; + final defaultValue = schema.defaultValue as int?; + final enumValues = schema.enumValues?.cast(); + final numRange = + Range.optionalRangeOf(min: schema.minimum, max: schema.maximum); + final definition = _DocumentIntegerDefinition.fromDef(schema.definition()); + + switch (definition) { + case _DocumentIntegerDefinition.tokenValueCardanoAda: + return DocumentTokenValueCardanoAdaSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + numRange: numRange, + ); + case _DocumentIntegerDefinition.durationInMonths: + return DocumentDurationInMonthsSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + numRange: numRange, + ); + case _DocumentIntegerDefinition.unknown: + return DocumentGenericIntegerSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + numRange: numRange, + ); + } + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_list_schema_mapper.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_list_schema_mapper.dart new file mode 100644 index 00000000000..4760fdc4970 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_list_schema_mapper.dart @@ -0,0 +1,115 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; + +enum _DocumentArrayDefinition { + multiSelect('multiSelect'), + singleLineTextEntryList('singleLineTextEntryList'), + multiLineTextEntryListMarkdown('multiLineTextEntryListMarkdown'), + singleLineHttpsURLEntryList('singleLineHttpsURLEntryList'), + nestedQuestionsList('nestedQuestionsList'), + unknown('unknown'); + + final String def; + + const _DocumentArrayDefinition(this.def); + + factory _DocumentArrayDefinition.fromDef(String? def) { + for (final value in values) { + if (value.def.toLowerCase() == def?.toLowerCase()) { + return value; + } + } + + return _DocumentArrayDefinition.unknown; + } +} + +final class DocumentListSchemaMapper { + static DocumentListSchema toModel({ + required DocumentDefinitionsDto definitions, + required DocumentPropertySchemaDto schema, + required DocumentNodeId nodeId, + required bool isRequired, + }) { + final format = DocumentPropertyFormat.fromString(schema.format ?? ''); + final title = schema.title ?? ''; + final description = schema.description; + final descriptionMarkdown = + description != null ? MarkdownData(description) : null; + final itemsSchema = schema.items!.toModel( + definitions: definitions, + nodeId: nodeId, + isRequired: isRequired, + ); + final itemsRange = Range.optionalRangeOf( + min: schema.minItems, + max: schema.maxItems, + ); + final definition = _DocumentArrayDefinition.fromDef(schema.definition()); + + switch (definition) { + case _DocumentArrayDefinition.multiSelect: + return DocumentMultiSelectSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + itemsSchema: itemsSchema, + itemsRange: itemsRange, + ); + case _DocumentArrayDefinition.singleLineTextEntryList: + return DocumentSingleLineTextEntryListSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + itemsSchema: itemsSchema, + itemsRange: itemsRange, + ); + case _DocumentArrayDefinition.multiLineTextEntryListMarkdown: + return DocumentMultiLineTextEntryListMarkdownSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + itemsSchema: itemsSchema, + itemsRange: itemsRange, + ); + case _DocumentArrayDefinition.singleLineHttpsURLEntryList: + return DocumentSingleLineHttpsUrlEntryListSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + itemsSchema: itemsSchema, + itemsRange: itemsRange, + ); + case _DocumentArrayDefinition.nestedQuestionsList: + return DocumentNestedQuestionsListSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + itemsSchema: itemsSchema, + itemsRange: itemsRange, + ); + case _DocumentArrayDefinition.unknown: + return DocumentGenericListSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + itemsSchema: itemsSchema, + itemsRange: itemsRange, + ); + } + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_number_schema_mapper.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_number_schema_mapper.dart new file mode 100644 index 00000000000..41ff7473d45 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_number_schema_mapper.dart @@ -0,0 +1,58 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; + +enum _DocumentNumberDefinition { + unknown('unknown'); + + final String def; + + const _DocumentNumberDefinition(this.def); + + factory _DocumentNumberDefinition.fromDef(String? def) { + for (final value in values) { + if (value.def.toLowerCase() == def?.toLowerCase()) { + return value; + } + } + + return _DocumentNumberDefinition.unknown; + } +} + +final class DocumentNumberSchemaMapper { + static DocumentNumberSchema toModel({ + required DocumentDefinitionsDto definitions, + required DocumentPropertySchemaDto schema, + required DocumentNodeId nodeId, + required bool isRequired, + }) { + final format = DocumentPropertyFormat.fromString(schema.format ?? ''); + final title = schema.title ?? ''; + final description = schema.description; + final descriptionMarkdown = + description != null ? MarkdownData(description) : null; + final defaultValue = schema.defaultValue as double?; + final enumValues = schema.enumValues?.cast(); + final numRange = Range.optionalRangeOf( + min: schema.minimum?.toDouble(), + max: schema.maximum?.toDouble(), + ); + final definition = _DocumentNumberDefinition.fromDef(schema.definition()); + + switch (definition) { + case _DocumentNumberDefinition.unknown: + return DocumentGenericNumberSchema( + nodeId: nodeId, + title: title, + format: format, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + numRange: numRange, + ); + } + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_object_schema_mapper.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_object_schema_mapper.dart new file mode 100644 index 00000000000..b0896cccc7b --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_object_schema_mapper.dart @@ -0,0 +1,125 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; + +enum _DocumentObjectDefinition { + segment('segment'), + section('section'), + nestedQuestions('nestedQuestions'), + singleGroupedTagSelector('singleGroupedTagSelector'), + unknown('unknown'); + + final String def; + + const _DocumentObjectDefinition(this.def); + + factory _DocumentObjectDefinition.fromDef(String? def) { + for (final value in values) { + if (value.def.toLowerCase() == def?.toLowerCase()) { + return value; + } + } + + return _DocumentObjectDefinition.unknown; + } +} + +final class DocumentObjectSchemaMapper { + static DocumentObjectSchema toModel({ + required DocumentDefinitionsDto definitions, + required DocumentPropertySchemaDto schema, + required DocumentNodeId nodeId, + required bool isRequired, + }) { + final format = DocumentPropertyFormat.fromString(schema.format ?? ''); + final title = schema.title ?? ''; + final description = schema.description; + final descriptionMarkdown = + description != null ? MarkdownData(description) : null; + final properties = schema.properties ?? const {}; + final required = schema.required ?? const []; + final oneOf = schema.oneOf; + final order = schema.order ?? const []; + + final mappedProperties = properties.entries + .map( + (prop) => prop.value.toModel( + definitions: definitions, + nodeId: nodeId.child(prop.key), + isRequired: required.contains(prop.key), + ), + ) + .toList(); + + final mappedOneOf = oneOf + ?.map( + (e) => e.toLogicalGroup( + definitions: definitions, + nodeId: nodeId, + isRequired: isRequired, + ), + ) + .toList(); + final mappedOrder = order.map(nodeId.child).toList(); + + final definition = _DocumentObjectDefinition.fromDef(schema.definition()); + switch (definition) { + case _DocumentObjectDefinition.segment: + return DocumentSegmentSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + properties: mappedProperties, + oneOf: mappedOneOf, + order: mappedOrder, + ); + + case _DocumentObjectDefinition.section: + return DocumentSectionSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + properties: mappedProperties, + oneOf: mappedOneOf, + order: mappedOrder, + ); + case _DocumentObjectDefinition.nestedQuestions: + return DocumentNestedQuestionsSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + properties: mappedProperties, + oneOf: mappedOneOf, + order: mappedOrder, + ); + case _DocumentObjectDefinition.singleGroupedTagSelector: + return DocumentSingleGroupedTagSelectorSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + properties: mappedProperties, + oneOf: mappedOneOf, + order: mappedOrder, + ); + case _DocumentObjectDefinition.unknown: + return DocumentGenericObjectSchema( + nodeId: nodeId, + format: format, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + properties: mappedProperties, + oneOf: mappedOneOf, + order: mappedOrder, + ); + } + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_string_schema_mapper.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_string_schema_mapper.dart new file mode 100644 index 00000000000..2b4e98d6e35 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document/schema/mapper/document_string_schema_mapper.dart @@ -0,0 +1,188 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; + +enum _DocumentStringDefinition { + singleLineTextEntry('singleLineTextEntry'), + singleLineHttpsUrlEntry('singleLineHttpsURLEntry'), + multiLineTextEntry('multiLineTextEntry'), + multiLineTextEntryMarkdown('multiLineTextEntryMarkdown'), + dropDownSingleSelect('dropDownSingleSelect'), + tagGroup('tagGroup'), + tagSelection('tagSelection'), + spdxLicenseOrUrl('spdxLicenseOrURL'), + languageCode('languageCode'), + unknown('unknown'); + + final String def; + + const _DocumentStringDefinition(this.def); + + factory _DocumentStringDefinition.fromDef(String? def) { + for (final value in values) { + if (value.def.toLowerCase() == def?.toLowerCase()) { + return value; + } + } + + return _DocumentStringDefinition.unknown; + } +} + +final class DocumentStringSchemaMapper { + static DocumentStringSchema toModel({ + required DocumentDefinitionsDto definitions, + required DocumentPropertySchemaDto schema, + required DocumentNodeId nodeId, + required bool isRequired, + }) { + final format = DocumentPropertyFormat.fromString(schema.format ?? ''); + final contentMediaType = + DocumentContentMediaType.fromString(schema.contentMediaType ?? ''); + final title = schema.title ?? ''; + final description = schema.description; + final descriptionMarkdown = + description != null ? MarkdownData(description) : null; + final defaultValue = schema.defaultValue as String?; + final enumValues = schema.enumValues?.cast(); + final strLengthRange = + Range.optionalRangeOf(min: schema.minLength, max: schema.maxLength); + final pattern = schema.pattern; + final patternRegExp = pattern != null ? RegExp(pattern) : null; + final definition = _DocumentStringDefinition.fromDef(schema.definition()); + + switch (definition) { + case _DocumentStringDefinition.singleLineTextEntry: + return DocumentSingleLineTextEntrySchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + case _DocumentStringDefinition.singleLineHttpsUrlEntry: + return DocumentSingleLineHttpsUrlEntrySchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + case _DocumentStringDefinition.multiLineTextEntry: + return DocumentMultiLineTextEntrySchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + case _DocumentStringDefinition.multiLineTextEntryMarkdown: + return DocumentMultiLineTextEntryMarkdownSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + case _DocumentStringDefinition.dropDownSingleSelect: + return DocumentDropDownSingleSelectSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + case _DocumentStringDefinition.tagGroup: + return DocumentTagGroupSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + case _DocumentStringDefinition.tagSelection: + return DocumentTagSelectionSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + case _DocumentStringDefinition.spdxLicenseOrUrl: + return DocumentSpdxLicenseOrUrlSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + case _DocumentStringDefinition.languageCode: + return DocumentLanguageCodeSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + case _DocumentStringDefinition.unknown: + return DocumentGenericStringSchema( + nodeId: nodeId, + format: format, + contentMediaType: contentMediaType, + title: title, + description: descriptionMarkdown, + isRequired: isRequired, + defaultValue: defaultValue, + enumValues: enumValues, + strLengthRange: strLengthRange, + pattern: patternRegExp, + ); + } + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/utils/document_schema_dto_converter.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/utils/document_schema_dto_converter.dart deleted file mode 100644 index 631de313204..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/utils/document_schema_dto_converter.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_logical_property_dto.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_property_dto.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_section_dto.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_segment_dto.dart'; -import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; -import 'package:json_annotation/json_annotation.dart'; - -final class DocumentSchemaSegmentsDtoConverter - implements - JsonConverter, Map?> { - const DocumentSchemaSegmentsDtoConverter(); - - @override - List fromJson(Map? json) { - if (json == null) { - return []; - } - - final properties = json.convertMapToListWithIds(); - return properties.map(DocumentSchemaSegmentDto.fromJson).toList(); - } - - @override - Map? toJson(List segments) { - return { - for (final segment in segments) segment.id: segment.toJson(), - }; - } -} - -final class DocumentSchemaSectionsDtoConverter - implements - JsonConverter, Map?> { - const DocumentSchemaSectionsDtoConverter(); - - @override - List fromJson(Map? json) { - if (json == null) { - return []; - } - - final properties = json.convertMapToListWithIds(); - return properties.map(DocumentSchemaSectionDto.fromJson).toList(); - } - - @override - Map? toJson(List sections) { - return { - for (final section in sections) section.id: section.toJson(), - }; - } -} - -final class DocumentSchemaPropertiesDtoConverter - implements - JsonConverter, Map?> { - const DocumentSchemaPropertiesDtoConverter(); - - @override - List fromJson(Map? json) { - if (json == null) { - return []; - } - - final properties = json.convertMapToListWithIds(); - return properties.map(DocumentSchemaPropertyDto.fromJson).toList(); - } - - @override - Map? toJson(List properties) { - return { - for (final property in properties) property.id: property.toJson(), - }; - } -} - -final class DocumentSchemaLogicalPropertiesDtoConverter - implements - JsonConverter, - Map> { - const DocumentSchemaLogicalPropertiesDtoConverter(); - - @override - List fromJson(Map json) { - final properties = json.convertMapToListWithIds(); - return properties.map(DocumentSchemaLogicalConditionDto.fromJson).toList(); - } - - @override - Map toJson( - List properties, - ) { - return { - for (final property in properties) property.id: property.toJson(), - }; - } -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/utils/json_converters.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/utils/json_converters.dart index 5ad6f72bfe5..7d70e21da1b 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/utils/json_converters.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/utils/json_converters.dart @@ -32,36 +32,6 @@ final class ShelleyAddressConverter String toJson(ShelleyAddress object) => object.toBech32(); } -final class ListStringConverter - implements JsonConverter?, List?> { - const ListStringConverter(); - - @override - List? fromJson(List? json) { - if (json == null) return null; - - return json.cast(); - } - - @override - List? toJson(List? object) => object; -} - -final class ListOfJsonConverter - implements JsonConverter>?, List?> { - const ListOfJsonConverter(); - - @override - List>? fromJson(List? json) { - if (json == null) return null; - - return json.cast(); - } - - @override - List? toJson(List>? object) => object; -} - /// A converter that only casts json to a target type. /// /// Can be used for simple types like [String], [int], etc, diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/pubspec.yaml b/catalyst_voices/packages/internal/catalyst_voices_repositories/pubspec.yaml index 6e4efc5599f..da11218c63f 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/pubspec.yaml +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: catalyst_voices_shared: path: ../catalyst_voices_shared chopper: ^8.0.3 + collection: ^1.18.0 equatable: ^2.0.7 flutter: sdk: flutter diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_data_dto_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_data_dto_test.dart new file mode 100644 index 00000000000..899e13e1835 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_data_dto_test.dart @@ -0,0 +1,66 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/document_data_dto.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group(DocumentDataDto, () { + test('getProperty returns correct value for valid path', () { + final json = { + 'title': 'Test Document', + 'content': { + 'text': 'This is a test.', + }, + 'tags': ['test', 'flutter'], + }; + + final dto = DocumentDataDto.fromJson(json); + final nodeId = DocumentNodeId.root.child('content').child('text'); + + expect(dto.getProperty(nodeId), 'This is a test.'); + }); + + test('getProperty returns correct value for lists', () { + final json = { + 'tags': ['test', 'flutter'], + }; + + final dto = DocumentDataDto.fromJson(json); + final nodeId = DocumentNodeId.root.child('tags').child('1'); + + expect(dto.getProperty(nodeId), 'flutter'); + }); + + test('getProperty returns null for invalid path', () { + final json = { + 'title': 'Test Document', + }; + + final dto = DocumentDataDto.fromJson(json); + final nodeId = DocumentNodeId.root.child('content').child('text'); + + expect(dto.getProperty(nodeId), isNull); + }); + + test('getProperty returns null for invalid list index', () { + final json = { + 'tags': ['test', 'flutter'], + }; + + final dto = DocumentDataDto.fromJson(json); + final nodeId = DocumentNodeId.root.child('tags').child('invalid_index'); + + expect(dto.getProperty(nodeId), isNull); + }); + + test('getProperty returns null for accessing non-collection', () { + final json = { + 'title': 'Test Document', + }; + + final dto = DocumentDataDto.fromJson(json); + final nodeId = DocumentNodeId.root.child('title').child('extra'); + + expect(dto.getProperty(nodeId), isNull); + }); + }); +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_dto_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_dto_test.dart index cc17f62c9b5..2c805f6c9d9 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_dto_test.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_dto_test.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_repositories/src/dto/document/document_data_dto.dart'; import 'package:catalyst_voices_repositories/src/dto/document/document_dto.dart'; import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_dto.dart'; @@ -24,100 +23,40 @@ void main() { 'should result in the same document', () { final schema = DocumentSchemaDto.fromJson(schemaJson).toModel(); - final data = DocumentDataDto.fromJson(documentJson); // original final originalJsonString = json.encode(documentJson); // serialized and deserialized - final documentDto = DocumentDto.fromJsonSchema(data, schema); + final documentData = DocumentDataDto.fromJson(documentJson); + final documentDto = DocumentDto.fromJsonSchema(documentData, schema); final documentDtoJson = documentDto.toJson(); - final serializedJsonString = json.encode(documentDtoJson); + final serializedJsonString = json.encode(documentDtoJson.json); // verify they are the same expect(serializedJsonString, equals(originalJsonString)); }, - // TODO(dtscalac): fix parsing the document and enable this test - // the reason it fails is that we are ignoring some properties - // and not outputting them back but they are present in the original doc - skip: true, ); test( 'Roundtrip from json to model and reverse ' 'should result in the same document', () { final schema = DocumentSchemaDto.fromJson(schemaJson).toModel(); - final data = DocumentDataDto.fromJson(documentJson); // original - final originalDocDto = DocumentDto.fromJsonSchema(data, schema); + final originalDocData = DocumentDataDto.fromJson(documentJson); + final originalDocDto = + DocumentDto.fromJsonSchema(originalDocData, schema); final originalDoc = originalDocDto.toModel(); // serialized and deserialized - final serializedDocJson = DocumentDto.fromModel(originalDoc).toJson(); + final serializeDocData = DocumentDto.fromModel(originalDoc).toJson(); final deserializedDocDto = - DocumentDto.fromJsonSchema(serializedDocJson, schema); + DocumentDto.fromJsonSchema(serializeDocData, schema); final deserializedDoc = deserializedDocDto.toModel(); // verify they are the same expect(deserializedDoc, equals(originalDoc)); }); - - test('Converts segments list into object for JSON', () { - final schemaDto = DocumentSchemaDto.fromJson(schemaJson); - final schema = schemaDto.toModel(); - - final document = DocumentBuilder.fromSchema( - schemaUrl: '', - schema: schema, - ).build(); - - final documentDto = DocumentDto.fromModel(document); - final documentData = documentDto.toJson(); - - for (final segment in documentDto.segments) { - final actual = documentData.json[segment.schema.id]; - expect(actual, isA>()); - } - }); - - test('Converts object from JSON into List of segments', () { - final schemaDto = DocumentSchemaDto.fromJson(schemaJson); - final schema = schemaDto.toModel(); - - final document = DocumentBuilder.fromSchema( - schemaUrl: '', - schema: schema, - ).build(); - - final documentDto = DocumentDto.fromModel(document); - - final documentJson = documentDto.toJson(); - final documentDtoFromJson = - DocumentDto.fromJsonSchema(documentJson, schema); - - expect( - documentDtoFromJson.segments.length, - documentDto.segments.length, - ); - }); - - test('After serialization $DocumentPropertyDto has correct type', () { - final schemaDto = DocumentSchemaDto.fromJson(schemaJson); - final schema = schemaDto.toModel(); - final data = DocumentDataDto.fromJson(documentJson); - - final documentDto = DocumentDto.fromJsonSchema(data, schema); - - final agreementSegment = documentDto.segments - .indexWhere((e) => e.schema.nodeId.paths.last == 'agreements'); - expect(agreementSegment, isNot(equals(-1))); - final agreementSections = documentDto.segments[agreementSegment].sections; - expect( - agreementSections.first.properties.first.value, - isA(), - ); - expect(agreementSections.first.properties.first.value, true); - }); }); } diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_definitions_dto_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_definitions_dto_test.dart index e9ce604cefc..3cd94c3edcf 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_definitions_dto_test.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_definitions_dto_test.dart @@ -1,51 +1,53 @@ -import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_definitions_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); + group(DocumentDefinitionsDto, () { + test('fromJson should create a valid instance from JSON', () { + final json = { + 'def1': {'title': 'Title 1'}, + 'def2': {'title': 'Title 2'}, + }; - group('$DocumentSchemaDto definitions', () { - late Map schemaJson; + final dto = DocumentDefinitionsDto.fromJson(json); - setUpAll(() async { - schemaJson = await VoicesDocumentsTemplates.proposalF14Schema; + expect(dto.getDefinition('def1')?.title, 'Title 1'); + expect(dto.getDefinition('def2')?.title, 'Title 2'); }); - test( - // ignore: lines_longer_than_80_chars - 'Check if all definition are in definition list inside DefinitionDto model', - () async { - final schemaDto = DocumentSchemaDto.fromJson(schemaJson); - final definitions = schemaDto.definitions.models; - - for (final value - in BaseDocumentDefinition.refPathToDefinitionType.values) { - final occurrences = definitions - .where((element) => element.runtimeType == value) - .length; - expect( - occurrences, - equals(1), - reason: 'Value $value appears $occurrences times in the list', - ); - } - }, - ); - - test('Check if document definition media type is parse correctly', () { - final schemaDto = DocumentSchemaDto.fromJson(schemaJson); - final definitions = schemaDto.definitions.models; - - final singleLineTextEntry = - definitions.getDefinition('#/definitions/singleLineTextEntry') - as SingleLineTextEntryDefinition; - - expect( - singleLineTextEntry.contentMediaType, - DocumentDefinitionsContentMediaType.textPlain, - ); + test('toJson should convert instance to JSON correctly', () { + final definitions = { + 'def1': const DocumentPropertySchemaDto.optional(title: 'Title 1'), + 'def2': const DocumentPropertySchemaDto.optional(title: 'Title 2'), + }; + + final dto = DocumentDefinitionsDto(definitions); + final json = dto.toJson(); + + expect((json['def1'] as Map)['title'], 'Title 1'); + expect((json['def2'] as Map)['title'], 'Title 2'); + }); + + test('getDefinition should return the correct definition', () { + final definitions = { + 'def1': const DocumentPropertySchemaDto.optional(title: 'Title 1'), + }; + + final dto = DocumentDefinitionsDto(definitions); + + expect(dto.getDefinition('def1')?.title, 'Title 1'); + expect(dto.getDefinition('def2'), isNull); + }); + + test('getDefinition should return null for non-existent definition', () { + final definitions = { + 'def1': const DocumentPropertySchemaDto.optional(title: 'Title 1'), + }; + + final dto = DocumentDefinitionsDto(definitions); + + expect(dto.getDefinition('non_existent'), isNull); }); }); } diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_property_schema_dto_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_property_schema_dto_test.dart new file mode 100644 index 00000000000..005e80f35f6 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_property_schema_dto_test.dart @@ -0,0 +1,87 @@ +import 'package:catalyst_voices_repositories/src/dto/document/schema/document_property_schema_dto.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group(DocumentPropertySchemaDto, () { + group('mergeWith', () { + test('should merge two schemas with non-overlapping properties', () { + const schema1 = DocumentPropertySchemaDto.optional( + title: 'Title 1', + description: 'Description 1', + ); + + const schema2 = DocumentPropertySchemaDto.optional( + format: 'Format 2', + contentMediaType: 'Media 2', + ); + + final merged = schema1.mergeWith(schema2); + + expect(merged.title, 'Title 1'); + expect(merged.description, 'Description 1'); + expect(merged.format, 'Format 2'); + expect(merged.contentMediaType, 'Media 2'); + }); + + test('should prefer non-null properties from the original schema', () { + const schema1 = DocumentPropertySchemaDto.optional( + title: 'Title 1', + format: 'Format 1', + ); + + const schema2 = DocumentPropertySchemaDto.optional( + title: 'Title 2', + format: 'Format 2', + ); + + final merged = schema1.mergeWith(schema2); + + expect(merged.title, 'Title 1'); + expect(merged.format, 'Format 1'); + }); + + test('should merge nested properties correctly', () { + const schema1 = DocumentPropertySchemaDto.optional( + properties: { + 'prop1': DocumentPropertySchemaDto.optional(title: 'Prop1 Title'), + }, + ); + + const schema2 = DocumentPropertySchemaDto.optional( + properties: { + 'prop2': DocumentPropertySchemaDto.optional(title: 'Prop2 Title'), + }, + ); + + final merged = schema1.mergeWith(schema2); + + expect(merged.properties!.length, 2); + expect(merged.properties!['prop1']!.title, 'Prop1 Title'); + expect(merged.properties!['prop2']!.title, 'Prop2 Title'); + }); + + test('should merge nested schemas recursively', () { + const schema1 = DocumentPropertySchemaDto.optional( + items: DocumentPropertySchemaDto.optional(title: 'Item Title 1'), + ); + + const schema2 = DocumentPropertySchemaDto.optional( + items: DocumentPropertySchemaDto.optional(title: 'Item Title 2'), + ); + + final merged = schema1.mergeWith(schema2); + + expect(merged.items!.title, 'Item Title 1'); + }); + }); + + group('definition', () { + test('returns the correct ref', () { + const dto = DocumentPropertySchemaDto.optional( + ref: '#/definitions/singleLineTextEntry', + ); + expect(dto.definition(), equals('singleLineTextEntry')); + }); + }); + }); +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_schema_dto_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_schema_dto_test.dart index 3acb7c2e2dc..7fbc58bf6eb 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_schema_dto_test.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_schema_dto_test.dart @@ -1,5 +1,4 @@ import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; -import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_dto.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -13,44 +12,17 @@ void main() { schemaJson = await VoicesDocumentsTemplates.proposalF14Schema; }); - test('X-order of segments is kept in model class', () async { - final schemaDto = DocumentSchemaDto.fromJson(schemaJson); + test('document schema can be decoded and encoded', () { + final originalSchema = DocumentSchemaDto.fromJson(schemaJson); + final originalModel = originalSchema.toModel(); + expect(originalModel.properties, isNotEmpty); - final schema = schemaDto.toModel(); + final encodedSchema = originalSchema.toJson(); + expect(encodedSchema, isNotEmpty); - if (schemaDto.order?.length != schema.segments.length) { - return; - } - for (var i = 0; i < schema.segments.length; i++) { - expect(schema.segments[i].id, schemaDto.order?[i]); - } - }); - - test('X-order of section is kept in model class', () { - final schemaDto = DocumentSchemaDto.fromJson(schemaJson); - final schema = schemaDto.toModel(); - - for (var i = 0; i < schema.segments.length; i++) { - if (schemaDto.segments[i].order?.length != - schema.segments[i].sections.length) { - continue; - } - for (var j = 0; j < schema.segments[i].sections.length; j++) { - expect( - schema.segments[i].sections[j].id, - schemaDto.segments[i].order?[j], - ); - } - } - }); - - test('Check if every segment has a SegmentDefinition as ref', () { - final schemaDto = DocumentSchemaDto.fromJson(schemaJson); - final schema = schemaDto.toModel(); - - for (final segment in schema.segments) { - expect(segment.definition, isA()); - } + final recodedSchema = DocumentSchemaDto.fromJson(encodedSchema); + final recodedModel = recodedSchema.toModel(); + expect(recodedModel, equals(originalModel)); }); }); } diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_schema_property_dto_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_schema_property_dto_test.dart deleted file mode 100644 index 898da822afa..00000000000 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/schema/document_schema_property_dto_test.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; -import 'package:catalyst_voices_repositories/src/dto/document/schema/document_schema_property_dto.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group(DocumentSchemaPropertyDto, () { - late Map schemaJson; - - setUpAll(() async { - schemaJson = await VoicesDocumentsTemplates.proposalF14Schema; - }); - - test('includeIfNull does not add keys for values that are null', () { - // Given - const dto = DocumentSchemaPropertyDto( - ref: '#/definitions/section', - id: 'solution', - ); - const expectedJson = { - r'$ref': '#/definitions/section', - }; - - // When - final json = dto.toJson(); - - // Then - expect(json, expectedJson); - }); - - group('grouped_tag', () { - test('oneOf is parsed correctly', () { - // Given - // ignore: avoid_dynamic_calls - final json = schemaJson['properties']['horizons']['properties']['theme'] - ['properties']['grouped_tag'] as Map - ..['id'] = 'grouped_tag'; - - // When - final dto = DocumentSchemaPropertyDto.fromJson(json); - - // Then - expect(dto.ref, '#/definitions/singleGroupedTagSelector'); - expect( - dto.oneOf, - allOf(isNotNull, hasLength(13)), - ); - - for (final group in dto.oneOf!) { - expect(group.conditions, hasLength(2)); - expect(group.conditions![0].id, 'group'); - expect(group.conditions![1].id, 'tag'); - } - }); - }); - }); -} diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart index 2cd79a21509..98ff676c353 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart @@ -24,9 +24,9 @@ class Range extends Equatable { required this.max, }); - /// Creates an [int] [Range] which assumes if + /// Creates a [Range] which assumes if /// [min] or [max] are null then they are unconstrained. - static Range? optionalIntRangeOf({int? min, int? max}) { + static Range? optionalRangeOf({T? min, T? max}) { if (min == null && max == null) { return null; } diff --git a/catalyst_voices/packages/internal/catalyst_voices_view_models/lib/src/document/validation/localized_document_validation_result.dart b/catalyst_voices/packages/internal/catalyst_voices_view_models/lib/src/document/validation/localized_document_validation_result.dart index 430d66961de..c6435c151dc 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_view_models/lib/src/document/validation/localized_document_validation_result.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_view_models/lib/src/document/validation/localized_document_validation_result.dart @@ -65,14 +65,14 @@ final class LocalizedMissingRequiredDocumentValue final class LocalizedDocumentNumOutOfRange extends LocalizedDocumentValidationResult { - final Range range; + final Range range; const LocalizedDocumentNumOutOfRange({required this.range}); @override String? message(BuildContext context) { - final min = range.min; - final max = range.max; + final min = range.min?.toInt(); + final max = range.max?.toInt(); if (min != null && max != null) { return context.l10n.errorValidationNumFieldOutOfRange(min, max); diff --git a/catalyst_voices/packages/internal/catalyst_voices_view_models/lib/src/proposal_builder/proposal_builder_segments.dart b/catalyst_voices/packages/internal/catalyst_voices_view_models/lib/src/proposal_builder/proposal_builder_segments.dart index 8ea3baf4d0c..e93e5212354 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_view_models/lib/src/proposal_builder/proposal_builder_segments.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_view_models/lib/src/proposal_builder/proposal_builder_segments.dart @@ -3,7 +3,7 @@ import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart'; import 'package:flutter/widgets.dart'; final class ProposalBuilderSegment extends BaseSegment { - final DocumentSegment documentSegment; + final DocumentObjectProperty documentSegment; const ProposalBuilderSegment({ required super.id, @@ -18,7 +18,7 @@ final class ProposalBuilderSegment extends BaseSegment { } final class ProposalBuilderSection extends BaseSection { - final DocumentSection documentSection; + final DocumentObjectProperty documentSection; const ProposalBuilderSection({ required super.id, @@ -29,6 +29,6 @@ final class ProposalBuilderSection extends BaseSection { @override String resolveTitle(BuildContext context) { - return documentSection.schema.title ?? ''; + return documentSection.schema.title; } }