diff --git a/.github/workflows/dart.yaml b/.github/workflows/dart.yaml new file mode 100644 index 0000000..69b9b65 --- /dev/null +++ b/.github/workflows/dart.yaml @@ -0,0 +1,26 @@ +name: Dart CI + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1 + with: + sdk: 'stable' + + - name: Get dependencies + run: dart pub get + + - name: Run codegen (build_runner) + run: dart run build_runner build --delete-conflicting-outputs + + - name: Run tests + run: dart test -r expanded diff --git a/.gitignore b/.gitignore index 3150b40..9241052 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ doc/api/ .flutter-plugins .flutter-plugins-dependencies + +# Mac stuff +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..eacd5ae --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# JsonFeed Parser for Dart + +A Dart library for parsing [JSON Feed](https://www.jsonfeed.org/version/1.1/) format feeds. This package provides strongly-typed models and utilities for working with JSON Feed data, including validation and error handling. + +## Features + +- Fully-typed models for JSON Feed, Item, Author, Attachment, and Hub (using [freezed](https://pub.dev/packages/freezed) and [json_serializable](https://pub.dev/packages/json_serializable)) +- Simple API for parsing JSON Feed strings +- Custom exception handling for invalid feeds +- Null safety and modern Dart support + +## Getting Started + +Add this package to your `pubspec.yaml`: + +```yaml +dependencies: + jsonfeed: + git: + url: https://github.com/imprologic/json_feed.git +``` + +Import and use in your Dart code: + +```dart +import 'package:jsonfeed/json_feed.dart'; + +void main() { + final jsonString = '{ ... }'; // your JSON Feed string + try { + final feed = parseJsonFeed(jsonString); + print(feed.title); + } catch (e) { + print('Failed to parse feed: $e'); + } +} +``` + +## Code Generation + +This package uses code generation for immutable models. Run the following command to generate the necessary files: + +```bash +dart run build_runner watch -d +``` + +## Running Tests + +To run the unit tests: + +```bash +dart test +``` + +## Project Structure + +- `lib/json_feed.dart`: Main library entry point and parser +- `lib/src/models.dart`: Data models for JSON Feed and related types +- `test/json_feed_test.dart`: Unit tests + +## License + +See [LICENSE](LICENSE). + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..238d889 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ + +analyzer: + errors: + invalid_annotation_target: ignore \ No newline at end of file diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 0000000..d6ef700 --- /dev/null +++ b/example/main.dart @@ -0,0 +1,9 @@ +import 'package:json_feed/json_feed.dart'; +import 'dart:io'; + +void main() { + final sample = File('example/sample_feed.json').readAsStringSync(); + final feed = parseJsonFeed(sample); + print('Feed title: ${feed.title}'); + print('Item count: ${feed.items.length}'); +} diff --git a/lib/json_feed.dart b/lib/json_feed.dart new file mode 100644 index 0000000..aa0fba3 --- /dev/null +++ b/lib/json_feed.dart @@ -0,0 +1,34 @@ +library json_feed; + +export 'src/models.dart'; + +import 'dart:convert'; + +import 'src/models.dart'; + +class JsonFeedParseException implements Exception { + final String message; + JsonFeedParseException(this.message); + @override + String toString() => 'JsonFeedParseException: $message'; +} + +JsonFeed parseJsonFeed(String jsonString) { + final decoded = json.decode(jsonString); + if (decoded is! Map) + throw JsonFeedParseException('Top-level JSON must be an object.'); + + // validate minimal requirements per spec + final version = decoded['version']; + if (version is! String || version.isEmpty) + throw JsonFeedParseException('Missing required "version" string.'); + final items = decoded['items']; + if (items is! List) + throw JsonFeedParseException('Missing required "items" array.'); + // authors fallback and extension extraction can be added here + + // Use generated fromJson + final feed = JsonFeed.fromJson(decoded); + + return feed; +} diff --git a/lib/src/models.dart b/lib/src/models.dart new file mode 100644 index 0000000..5d0eef0 --- /dev/null +++ b/lib/src/models.dart @@ -0,0 +1,78 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'models.freezed.dart'; +part 'models.g.dart'; + +@freezed +abstract class Author with _$Author { + const factory Author({String? name, String? url, String? avatar}) = _Author; + + factory Author.fromJson(Map json) => _$AuthorFromJson(json); +} + +@freezed +abstract class Attachment with _$Attachment { + const factory Attachment({ + required String url, + @JsonKey(name: 'mime_type') required String mimeType, + String? title, + @JsonKey(name: 'size_in_bytes') int? sizeInBytes, + @JsonKey(name: 'duration_in_seconds') double? durationInSeconds, + }) = _Attachment; + + factory Attachment.fromJson(Map json) => + _$AttachmentFromJson(json); +} + +@freezed +abstract class Hub with _$Hub { + const factory Hub({required String type, required String url}) = _Hub; + + factory Hub.fromJson(Map json) => _$HubFromJson(json); +} + +@freezed +abstract class Item with _$Item { + const factory Item({ + required String id, + String? url, + @JsonKey(name: 'external_url') String? externalUrl, + String? title, + @JsonKey(name: 'content_html') String? contentHtml, + @JsonKey(name: 'content_text') String? contentText, + String? summary, + String? image, + @JsonKey(name: 'banner_image') String? bannerImage, + @JsonKey(name: 'date_published') String? datePublished, + @JsonKey(name: 'date_modified') String? dateModified, + List? authors, + List? tags, + String? language, + List? attachments, + }) = _Item; + + factory Item.fromJson(Map json) => _$ItemFromJson(json); +} + +@freezed +abstract class JsonFeed with _$JsonFeed { + const factory JsonFeed({ + required String version, + required String title, + @JsonKey(name: 'home_page_url') String? homePageUrl, + @JsonKey(name: 'feed_url') String? feedUrl, + String? description, + @JsonKey(name: 'user_comment') String? userComment, + @JsonKey(name: 'next_url') String? nextUrl, + String? icon, + String? favicon, + List? authors, + String? language, + bool? expired, + List? hubs, + @Default([]) List items, + }) = _JsonFeed; + + factory JsonFeed.fromJson(Map json) => + _$JsonFeedFromJson(json); +} diff --git a/lib/src/models.freezed.dart b/lib/src/models.freezed.dart new file mode 100644 index 0000000..8902ffe --- /dev/null +++ b/lib/src/models.freezed.dart @@ -0,0 +1,1477 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$Author { + + String? get name; String? get url; String? get avatar; +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AuthorCopyWith get copyWith => _$AuthorCopyWithImpl(this as Author, _$identity); + + /// Serializes this Author to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Author&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url)&&(identical(other.avatar, avatar) || other.avatar == avatar)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,name,url,avatar); + +@override +String toString() { + return 'Author(name: $name, url: $url, avatar: $avatar)'; +} + + +} + +/// @nodoc +abstract mixin class $AuthorCopyWith<$Res> { + factory $AuthorCopyWith(Author value, $Res Function(Author) _then) = _$AuthorCopyWithImpl; +@useResult +$Res call({ + String? name, String? url, String? avatar +}); + + + + +} +/// @nodoc +class _$AuthorCopyWithImpl<$Res> + implements $AuthorCopyWith<$Res> { + _$AuthorCopyWithImpl(this._self, this._then); + + final Author _self; + final $Res Function(Author) _then; + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? name = freezed,Object? url = freezed,Object? avatar = freezed,}) { + return _then(_self.copyWith( +name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String?,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?,avatar: freezed == avatar ? _self.avatar : avatar // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Author]. +extension AuthorPatterns on Author { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Author value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Author value) $default,){ +final _that = this; +switch (_that) { +case _Author(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Author value)? $default,){ +final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? name, String? url, String? avatar)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that.name,_that.url,_that.avatar);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? name, String? url, String? avatar) $default,) {final _that = this; +switch (_that) { +case _Author(): +return $default(_that.name,_that.url,_that.avatar);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? name, String? url, String? avatar)? $default,) {final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that.name,_that.url,_that.avatar);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Author implements Author { + const _Author({this.name, this.url, this.avatar}); + factory _Author.fromJson(Map json) => _$AuthorFromJson(json); + +@override final String? name; +@override final String? url; +@override final String? avatar; + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AuthorCopyWith<_Author> get copyWith => __$AuthorCopyWithImpl<_Author>(this, _$identity); + +@override +Map toJson() { + return _$AuthorToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Author&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url)&&(identical(other.avatar, avatar) || other.avatar == avatar)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,name,url,avatar); + +@override +String toString() { + return 'Author(name: $name, url: $url, avatar: $avatar)'; +} + + +} + +/// @nodoc +abstract mixin class _$AuthorCopyWith<$Res> implements $AuthorCopyWith<$Res> { + factory _$AuthorCopyWith(_Author value, $Res Function(_Author) _then) = __$AuthorCopyWithImpl; +@override @useResult +$Res call({ + String? name, String? url, String? avatar +}); + + + + +} +/// @nodoc +class __$AuthorCopyWithImpl<$Res> + implements _$AuthorCopyWith<$Res> { + __$AuthorCopyWithImpl(this._self, this._then); + + final _Author _self; + final $Res Function(_Author) _then; + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? name = freezed,Object? url = freezed,Object? avatar = freezed,}) { + return _then(_Author( +name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String?,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?,avatar: freezed == avatar ? _self.avatar : avatar // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + + +/// @nodoc +mixin _$Attachment { + + String get url;@JsonKey(name: 'mime_type') String get mimeType; String? get title;@JsonKey(name: 'size_in_bytes') int? get sizeInBytes;@JsonKey(name: 'duration_in_seconds') double? get durationInSeconds; +/// Create a copy of Attachment +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AttachmentCopyWith get copyWith => _$AttachmentCopyWithImpl(this as Attachment, _$identity); + + /// Serializes this Attachment to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Attachment&&(identical(other.url, url) || other.url == url)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.title, title) || other.title == title)&&(identical(other.sizeInBytes, sizeInBytes) || other.sizeInBytes == sizeInBytes)&&(identical(other.durationInSeconds, durationInSeconds) || other.durationInSeconds == durationInSeconds)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,url,mimeType,title,sizeInBytes,durationInSeconds); + +@override +String toString() { + return 'Attachment(url: $url, mimeType: $mimeType, title: $title, sizeInBytes: $sizeInBytes, durationInSeconds: $durationInSeconds)'; +} + + +} + +/// @nodoc +abstract mixin class $AttachmentCopyWith<$Res> { + factory $AttachmentCopyWith(Attachment value, $Res Function(Attachment) _then) = _$AttachmentCopyWithImpl; +@useResult +$Res call({ + String url,@JsonKey(name: 'mime_type') String mimeType, String? title,@JsonKey(name: 'size_in_bytes') int? sizeInBytes,@JsonKey(name: 'duration_in_seconds') double? durationInSeconds +}); + + + + +} +/// @nodoc +class _$AttachmentCopyWithImpl<$Res> + implements $AttachmentCopyWith<$Res> { + _$AttachmentCopyWithImpl(this._self, this._then); + + final Attachment _self; + final $Res Function(Attachment) _then; + +/// Create a copy of Attachment +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? url = null,Object? mimeType = null,Object? title = freezed,Object? sizeInBytes = freezed,Object? durationInSeconds = freezed,}) { + return _then(_self.copyWith( +url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,mimeType: null == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable +as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,sizeInBytes: freezed == sizeInBytes ? _self.sizeInBytes : sizeInBytes // ignore: cast_nullable_to_non_nullable +as int?,durationInSeconds: freezed == durationInSeconds ? _self.durationInSeconds : durationInSeconds // ignore: cast_nullable_to_non_nullable +as double?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Attachment]. +extension AttachmentPatterns on Attachment { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Attachment value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Attachment() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Attachment value) $default,){ +final _that = this; +switch (_that) { +case _Attachment(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Attachment value)? $default,){ +final _that = this; +switch (_that) { +case _Attachment() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String url, @JsonKey(name: 'mime_type') String mimeType, String? title, @JsonKey(name: 'size_in_bytes') int? sizeInBytes, @JsonKey(name: 'duration_in_seconds') double? durationInSeconds)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Attachment() when $default != null: +return $default(_that.url,_that.mimeType,_that.title,_that.sizeInBytes,_that.durationInSeconds);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String url, @JsonKey(name: 'mime_type') String mimeType, String? title, @JsonKey(name: 'size_in_bytes') int? sizeInBytes, @JsonKey(name: 'duration_in_seconds') double? durationInSeconds) $default,) {final _that = this; +switch (_that) { +case _Attachment(): +return $default(_that.url,_that.mimeType,_that.title,_that.sizeInBytes,_that.durationInSeconds);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String url, @JsonKey(name: 'mime_type') String mimeType, String? title, @JsonKey(name: 'size_in_bytes') int? sizeInBytes, @JsonKey(name: 'duration_in_seconds') double? durationInSeconds)? $default,) {final _that = this; +switch (_that) { +case _Attachment() when $default != null: +return $default(_that.url,_that.mimeType,_that.title,_that.sizeInBytes,_that.durationInSeconds);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Attachment implements Attachment { + const _Attachment({required this.url, @JsonKey(name: 'mime_type') required this.mimeType, this.title, @JsonKey(name: 'size_in_bytes') this.sizeInBytes, @JsonKey(name: 'duration_in_seconds') this.durationInSeconds}); + factory _Attachment.fromJson(Map json) => _$AttachmentFromJson(json); + +@override final String url; +@override@JsonKey(name: 'mime_type') final String mimeType; +@override final String? title; +@override@JsonKey(name: 'size_in_bytes') final int? sizeInBytes; +@override@JsonKey(name: 'duration_in_seconds') final double? durationInSeconds; + +/// Create a copy of Attachment +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AttachmentCopyWith<_Attachment> get copyWith => __$AttachmentCopyWithImpl<_Attachment>(this, _$identity); + +@override +Map toJson() { + return _$AttachmentToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Attachment&&(identical(other.url, url) || other.url == url)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.title, title) || other.title == title)&&(identical(other.sizeInBytes, sizeInBytes) || other.sizeInBytes == sizeInBytes)&&(identical(other.durationInSeconds, durationInSeconds) || other.durationInSeconds == durationInSeconds)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,url,mimeType,title,sizeInBytes,durationInSeconds); + +@override +String toString() { + return 'Attachment(url: $url, mimeType: $mimeType, title: $title, sizeInBytes: $sizeInBytes, durationInSeconds: $durationInSeconds)'; +} + + +} + +/// @nodoc +abstract mixin class _$AttachmentCopyWith<$Res> implements $AttachmentCopyWith<$Res> { + factory _$AttachmentCopyWith(_Attachment value, $Res Function(_Attachment) _then) = __$AttachmentCopyWithImpl; +@override @useResult +$Res call({ + String url,@JsonKey(name: 'mime_type') String mimeType, String? title,@JsonKey(name: 'size_in_bytes') int? sizeInBytes,@JsonKey(name: 'duration_in_seconds') double? durationInSeconds +}); + + + + +} +/// @nodoc +class __$AttachmentCopyWithImpl<$Res> + implements _$AttachmentCopyWith<$Res> { + __$AttachmentCopyWithImpl(this._self, this._then); + + final _Attachment _self; + final $Res Function(_Attachment) _then; + +/// Create a copy of Attachment +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? url = null,Object? mimeType = null,Object? title = freezed,Object? sizeInBytes = freezed,Object? durationInSeconds = freezed,}) { + return _then(_Attachment( +url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,mimeType: null == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable +as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,sizeInBytes: freezed == sizeInBytes ? _self.sizeInBytes : sizeInBytes // ignore: cast_nullable_to_non_nullable +as int?,durationInSeconds: freezed == durationInSeconds ? _self.durationInSeconds : durationInSeconds // ignore: cast_nullable_to_non_nullable +as double?, + )); +} + + +} + + +/// @nodoc +mixin _$Hub { + + String get type; String get url; +/// Create a copy of Hub +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$HubCopyWith get copyWith => _$HubCopyWithImpl(this as Hub, _$identity); + + /// Serializes this Hub to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Hub&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,type,url); + +@override +String toString() { + return 'Hub(type: $type, url: $url)'; +} + + +} + +/// @nodoc +abstract mixin class $HubCopyWith<$Res> { + factory $HubCopyWith(Hub value, $Res Function(Hub) _then) = _$HubCopyWithImpl; +@useResult +$Res call({ + String type, String url +}); + + + + +} +/// @nodoc +class _$HubCopyWithImpl<$Res> + implements $HubCopyWith<$Res> { + _$HubCopyWithImpl(this._self, this._then); + + final Hub _self; + final $Res Function(Hub) _then; + +/// Create a copy of Hub +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,}) { + return _then(_self.copyWith( +type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Hub]. +extension HubPatterns on Hub { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Hub value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Hub() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Hub value) $default,){ +final _that = this; +switch (_that) { +case _Hub(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Hub value)? $default,){ +final _that = this; +switch (_that) { +case _Hub() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String type, String url)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Hub() when $default != null: +return $default(_that.type,_that.url);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String type, String url) $default,) {final _that = this; +switch (_that) { +case _Hub(): +return $default(_that.type,_that.url);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String type, String url)? $default,) {final _that = this; +switch (_that) { +case _Hub() when $default != null: +return $default(_that.type,_that.url);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Hub implements Hub { + const _Hub({required this.type, required this.url}); + factory _Hub.fromJson(Map json) => _$HubFromJson(json); + +@override final String type; +@override final String url; + +/// Create a copy of Hub +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$HubCopyWith<_Hub> get copyWith => __$HubCopyWithImpl<_Hub>(this, _$identity); + +@override +Map toJson() { + return _$HubToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Hub&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,type,url); + +@override +String toString() { + return 'Hub(type: $type, url: $url)'; +} + + +} + +/// @nodoc +abstract mixin class _$HubCopyWith<$Res> implements $HubCopyWith<$Res> { + factory _$HubCopyWith(_Hub value, $Res Function(_Hub) _then) = __$HubCopyWithImpl; +@override @useResult +$Res call({ + String type, String url +}); + + + + +} +/// @nodoc +class __$HubCopyWithImpl<$Res> + implements _$HubCopyWith<$Res> { + __$HubCopyWithImpl(this._self, this._then); + + final _Hub _self; + final $Res Function(_Hub) _then; + +/// Create a copy of Hub +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,}) { + return _then(_Hub( +type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$Item { + + String get id; String? get url;@JsonKey(name: 'external_url') String? get externalUrl; String? get title;@JsonKey(name: 'content_html') String? get contentHtml;@JsonKey(name: 'content_text') String? get contentText; String? get summary; String? get image;@JsonKey(name: 'banner_image') String? get bannerImage;@JsonKey(name: 'date_published') String? get datePublished;@JsonKey(name: 'date_modified') String? get dateModified; List? get authors; List? get tags; String? get language; List? get attachments; +/// Create a copy of Item +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ItemCopyWith get copyWith => _$ItemCopyWithImpl(this as Item, _$identity); + + /// Serializes this Item to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Item&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.externalUrl, externalUrl) || other.externalUrl == externalUrl)&&(identical(other.title, title) || other.title == title)&&(identical(other.contentHtml, contentHtml) || other.contentHtml == contentHtml)&&(identical(other.contentText, contentText) || other.contentText == contentText)&&(identical(other.summary, summary) || other.summary == summary)&&(identical(other.image, image) || other.image == image)&&(identical(other.bannerImage, bannerImage) || other.bannerImage == bannerImage)&&(identical(other.datePublished, datePublished) || other.datePublished == datePublished)&&(identical(other.dateModified, dateModified) || other.dateModified == dateModified)&&const DeepCollectionEquality().equals(other.authors, authors)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.language, language) || other.language == language)&&const DeepCollectionEquality().equals(other.attachments, attachments)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,url,externalUrl,title,contentHtml,contentText,summary,image,bannerImage,datePublished,dateModified,const DeepCollectionEquality().hash(authors),const DeepCollectionEquality().hash(tags),language,const DeepCollectionEquality().hash(attachments)); + +@override +String toString() { + return 'Item(id: $id, url: $url, externalUrl: $externalUrl, title: $title, contentHtml: $contentHtml, contentText: $contentText, summary: $summary, image: $image, bannerImage: $bannerImage, datePublished: $datePublished, dateModified: $dateModified, authors: $authors, tags: $tags, language: $language, attachments: $attachments)'; +} + + +} + +/// @nodoc +abstract mixin class $ItemCopyWith<$Res> { + factory $ItemCopyWith(Item value, $Res Function(Item) _then) = _$ItemCopyWithImpl; +@useResult +$Res call({ + String id, String? url,@JsonKey(name: 'external_url') String? externalUrl, String? title,@JsonKey(name: 'content_html') String? contentHtml,@JsonKey(name: 'content_text') String? contentText, String? summary, String? image,@JsonKey(name: 'banner_image') String? bannerImage,@JsonKey(name: 'date_published') String? datePublished,@JsonKey(name: 'date_modified') String? dateModified, List? authors, List? tags, String? language, List? attachments +}); + + + + +} +/// @nodoc +class _$ItemCopyWithImpl<$Res> + implements $ItemCopyWith<$Res> { + _$ItemCopyWithImpl(this._self, this._then); + + final Item _self; + final $Res Function(Item) _then; + +/// Create a copy of Item +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? url = freezed,Object? externalUrl = freezed,Object? title = freezed,Object? contentHtml = freezed,Object? contentText = freezed,Object? summary = freezed,Object? image = freezed,Object? bannerImage = freezed,Object? datePublished = freezed,Object? dateModified = freezed,Object? authors = freezed,Object? tags = freezed,Object? language = freezed,Object? attachments = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?,externalUrl: freezed == externalUrl ? _self.externalUrl : externalUrl // ignore: cast_nullable_to_non_nullable +as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,contentHtml: freezed == contentHtml ? _self.contentHtml : contentHtml // ignore: cast_nullable_to_non_nullable +as String?,contentText: freezed == contentText ? _self.contentText : contentText // ignore: cast_nullable_to_non_nullable +as String?,summary: freezed == summary ? _self.summary : summary // ignore: cast_nullable_to_non_nullable +as String?,image: freezed == image ? _self.image : image // ignore: cast_nullable_to_non_nullable +as String?,bannerImage: freezed == bannerImage ? _self.bannerImage : bannerImage // ignore: cast_nullable_to_non_nullable +as String?,datePublished: freezed == datePublished ? _self.datePublished : datePublished // ignore: cast_nullable_to_non_nullable +as String?,dateModified: freezed == dateModified ? _self.dateModified : dateModified // ignore: cast_nullable_to_non_nullable +as String?,authors: freezed == authors ? _self.authors : authors // ignore: cast_nullable_to_non_nullable +as List?,tags: freezed == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable +as List?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable +as String?,attachments: freezed == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Item]. +extension ItemPatterns on Item { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Item value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Item() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Item value) $default,){ +final _that = this; +switch (_that) { +case _Item(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Item value)? $default,){ +final _that = this; +switch (_that) { +case _Item() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String? url, @JsonKey(name: 'external_url') String? externalUrl, String? title, @JsonKey(name: 'content_html') String? contentHtml, @JsonKey(name: 'content_text') String? contentText, String? summary, String? image, @JsonKey(name: 'banner_image') String? bannerImage, @JsonKey(name: 'date_published') String? datePublished, @JsonKey(name: 'date_modified') String? dateModified, List? authors, List? tags, String? language, List? attachments)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Item() when $default != null: +return $default(_that.id,_that.url,_that.externalUrl,_that.title,_that.contentHtml,_that.contentText,_that.summary,_that.image,_that.bannerImage,_that.datePublished,_that.dateModified,_that.authors,_that.tags,_that.language,_that.attachments);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String? url, @JsonKey(name: 'external_url') String? externalUrl, String? title, @JsonKey(name: 'content_html') String? contentHtml, @JsonKey(name: 'content_text') String? contentText, String? summary, String? image, @JsonKey(name: 'banner_image') String? bannerImage, @JsonKey(name: 'date_published') String? datePublished, @JsonKey(name: 'date_modified') String? dateModified, List? authors, List? tags, String? language, List? attachments) $default,) {final _that = this; +switch (_that) { +case _Item(): +return $default(_that.id,_that.url,_that.externalUrl,_that.title,_that.contentHtml,_that.contentText,_that.summary,_that.image,_that.bannerImage,_that.datePublished,_that.dateModified,_that.authors,_that.tags,_that.language,_that.attachments);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String? url, @JsonKey(name: 'external_url') String? externalUrl, String? title, @JsonKey(name: 'content_html') String? contentHtml, @JsonKey(name: 'content_text') String? contentText, String? summary, String? image, @JsonKey(name: 'banner_image') String? bannerImage, @JsonKey(name: 'date_published') String? datePublished, @JsonKey(name: 'date_modified') String? dateModified, List? authors, List? tags, String? language, List? attachments)? $default,) {final _that = this; +switch (_that) { +case _Item() when $default != null: +return $default(_that.id,_that.url,_that.externalUrl,_that.title,_that.contentHtml,_that.contentText,_that.summary,_that.image,_that.bannerImage,_that.datePublished,_that.dateModified,_that.authors,_that.tags,_that.language,_that.attachments);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Item implements Item { + const _Item({required this.id, this.url, @JsonKey(name: 'external_url') this.externalUrl, this.title, @JsonKey(name: 'content_html') this.contentHtml, @JsonKey(name: 'content_text') this.contentText, this.summary, this.image, @JsonKey(name: 'banner_image') this.bannerImage, @JsonKey(name: 'date_published') this.datePublished, @JsonKey(name: 'date_modified') this.dateModified, final List? authors, final List? tags, this.language, final List? attachments}): _authors = authors,_tags = tags,_attachments = attachments; + factory _Item.fromJson(Map json) => _$ItemFromJson(json); + +@override final String id; +@override final String? url; +@override@JsonKey(name: 'external_url') final String? externalUrl; +@override final String? title; +@override@JsonKey(name: 'content_html') final String? contentHtml; +@override@JsonKey(name: 'content_text') final String? contentText; +@override final String? summary; +@override final String? image; +@override@JsonKey(name: 'banner_image') final String? bannerImage; +@override@JsonKey(name: 'date_published') final String? datePublished; +@override@JsonKey(name: 'date_modified') final String? dateModified; + final List? _authors; +@override List? get authors { + final value = _authors; + if (value == null) return null; + if (_authors is EqualUnmodifiableListView) return _authors; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + + final List? _tags; +@override List? get tags { + final value = _tags; + if (value == null) return null; + if (_tags is EqualUnmodifiableListView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + +@override final String? language; + final List? _attachments; +@override List? get attachments { + final value = _attachments; + if (value == null) return null; + if (_attachments is EqualUnmodifiableListView) return _attachments; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + + +/// Create a copy of Item +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ItemCopyWith<_Item> get copyWith => __$ItemCopyWithImpl<_Item>(this, _$identity); + +@override +Map toJson() { + return _$ItemToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Item&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.externalUrl, externalUrl) || other.externalUrl == externalUrl)&&(identical(other.title, title) || other.title == title)&&(identical(other.contentHtml, contentHtml) || other.contentHtml == contentHtml)&&(identical(other.contentText, contentText) || other.contentText == contentText)&&(identical(other.summary, summary) || other.summary == summary)&&(identical(other.image, image) || other.image == image)&&(identical(other.bannerImage, bannerImage) || other.bannerImage == bannerImage)&&(identical(other.datePublished, datePublished) || other.datePublished == datePublished)&&(identical(other.dateModified, dateModified) || other.dateModified == dateModified)&&const DeepCollectionEquality().equals(other._authors, _authors)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.language, language) || other.language == language)&&const DeepCollectionEquality().equals(other._attachments, _attachments)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,url,externalUrl,title,contentHtml,contentText,summary,image,bannerImage,datePublished,dateModified,const DeepCollectionEquality().hash(_authors),const DeepCollectionEquality().hash(_tags),language,const DeepCollectionEquality().hash(_attachments)); + +@override +String toString() { + return 'Item(id: $id, url: $url, externalUrl: $externalUrl, title: $title, contentHtml: $contentHtml, contentText: $contentText, summary: $summary, image: $image, bannerImage: $bannerImage, datePublished: $datePublished, dateModified: $dateModified, authors: $authors, tags: $tags, language: $language, attachments: $attachments)'; +} + + +} + +/// @nodoc +abstract mixin class _$ItemCopyWith<$Res> implements $ItemCopyWith<$Res> { + factory _$ItemCopyWith(_Item value, $Res Function(_Item) _then) = __$ItemCopyWithImpl; +@override @useResult +$Res call({ + String id, String? url,@JsonKey(name: 'external_url') String? externalUrl, String? title,@JsonKey(name: 'content_html') String? contentHtml,@JsonKey(name: 'content_text') String? contentText, String? summary, String? image,@JsonKey(name: 'banner_image') String? bannerImage,@JsonKey(name: 'date_published') String? datePublished,@JsonKey(name: 'date_modified') String? dateModified, List? authors, List? tags, String? language, List? attachments +}); + + + + +} +/// @nodoc +class __$ItemCopyWithImpl<$Res> + implements _$ItemCopyWith<$Res> { + __$ItemCopyWithImpl(this._self, this._then); + + final _Item _self; + final $Res Function(_Item) _then; + +/// Create a copy of Item +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? url = freezed,Object? externalUrl = freezed,Object? title = freezed,Object? contentHtml = freezed,Object? contentText = freezed,Object? summary = freezed,Object? image = freezed,Object? bannerImage = freezed,Object? datePublished = freezed,Object? dateModified = freezed,Object? authors = freezed,Object? tags = freezed,Object? language = freezed,Object? attachments = freezed,}) { + return _then(_Item( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?,externalUrl: freezed == externalUrl ? _self.externalUrl : externalUrl // ignore: cast_nullable_to_non_nullable +as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,contentHtml: freezed == contentHtml ? _self.contentHtml : contentHtml // ignore: cast_nullable_to_non_nullable +as String?,contentText: freezed == contentText ? _self.contentText : contentText // ignore: cast_nullable_to_non_nullable +as String?,summary: freezed == summary ? _self.summary : summary // ignore: cast_nullable_to_non_nullable +as String?,image: freezed == image ? _self.image : image // ignore: cast_nullable_to_non_nullable +as String?,bannerImage: freezed == bannerImage ? _self.bannerImage : bannerImage // ignore: cast_nullable_to_non_nullable +as String?,datePublished: freezed == datePublished ? _self.datePublished : datePublished // ignore: cast_nullable_to_non_nullable +as String?,dateModified: freezed == dateModified ? _self.dateModified : dateModified // ignore: cast_nullable_to_non_nullable +as String?,authors: freezed == authors ? _self._authors : authors // ignore: cast_nullable_to_non_nullable +as List?,tags: freezed == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable +as List?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable +as String?,attachments: freezed == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + + +} + + +/// @nodoc +mixin _$JsonFeed { + + String get version; String get title;@JsonKey(name: 'home_page_url') String? get homePageUrl;@JsonKey(name: 'feed_url') String? get feedUrl; String? get description;@JsonKey(name: 'user_comment') String? get userComment;@JsonKey(name: 'next_url') String? get nextUrl; String? get icon; String? get favicon; List? get authors; String? get language; bool? get expired; List? get hubs; List get items; +/// Create a copy of JsonFeed +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$JsonFeedCopyWith get copyWith => _$JsonFeedCopyWithImpl(this as JsonFeed, _$identity); + + /// Serializes this JsonFeed to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is JsonFeed&&(identical(other.version, version) || other.version == version)&&(identical(other.title, title) || other.title == title)&&(identical(other.homePageUrl, homePageUrl) || other.homePageUrl == homePageUrl)&&(identical(other.feedUrl, feedUrl) || other.feedUrl == feedUrl)&&(identical(other.description, description) || other.description == description)&&(identical(other.userComment, userComment) || other.userComment == userComment)&&(identical(other.nextUrl, nextUrl) || other.nextUrl == nextUrl)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.favicon, favicon) || other.favicon == favicon)&&const DeepCollectionEquality().equals(other.authors, authors)&&(identical(other.language, language) || other.language == language)&&(identical(other.expired, expired) || other.expired == expired)&&const DeepCollectionEquality().equals(other.hubs, hubs)&&const DeepCollectionEquality().equals(other.items, items)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,version,title,homePageUrl,feedUrl,description,userComment,nextUrl,icon,favicon,const DeepCollectionEquality().hash(authors),language,expired,const DeepCollectionEquality().hash(hubs),const DeepCollectionEquality().hash(items)); + +@override +String toString() { + return 'JsonFeed(version: $version, title: $title, homePageUrl: $homePageUrl, feedUrl: $feedUrl, description: $description, userComment: $userComment, nextUrl: $nextUrl, icon: $icon, favicon: $favicon, authors: $authors, language: $language, expired: $expired, hubs: $hubs, items: $items)'; +} + + +} + +/// @nodoc +abstract mixin class $JsonFeedCopyWith<$Res> { + factory $JsonFeedCopyWith(JsonFeed value, $Res Function(JsonFeed) _then) = _$JsonFeedCopyWithImpl; +@useResult +$Res call({ + String version, String title,@JsonKey(name: 'home_page_url') String? homePageUrl,@JsonKey(name: 'feed_url') String? feedUrl, String? description,@JsonKey(name: 'user_comment') String? userComment,@JsonKey(name: 'next_url') String? nextUrl, String? icon, String? favicon, List? authors, String? language, bool? expired, List? hubs, List items +}); + + + + +} +/// @nodoc +class _$JsonFeedCopyWithImpl<$Res> + implements $JsonFeedCopyWith<$Res> { + _$JsonFeedCopyWithImpl(this._self, this._then); + + final JsonFeed _self; + final $Res Function(JsonFeed) _then; + +/// Create a copy of JsonFeed +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? version = null,Object? title = null,Object? homePageUrl = freezed,Object? feedUrl = freezed,Object? description = freezed,Object? userComment = freezed,Object? nextUrl = freezed,Object? icon = freezed,Object? favicon = freezed,Object? authors = freezed,Object? language = freezed,Object? expired = freezed,Object? hubs = freezed,Object? items = null,}) { + return _then(_self.copyWith( +version: null == version ? _self.version : version // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,homePageUrl: freezed == homePageUrl ? _self.homePageUrl : homePageUrl // ignore: cast_nullable_to_non_nullable +as String?,feedUrl: freezed == feedUrl ? _self.feedUrl : feedUrl // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,userComment: freezed == userComment ? _self.userComment : userComment // ignore: cast_nullable_to_non_nullable +as String?,nextUrl: freezed == nextUrl ? _self.nextUrl : nextUrl // ignore: cast_nullable_to_non_nullable +as String?,icon: freezed == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable +as String?,favicon: freezed == favicon ? _self.favicon : favicon // ignore: cast_nullable_to_non_nullable +as String?,authors: freezed == authors ? _self.authors : authors // ignore: cast_nullable_to_non_nullable +as List?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable +as String?,expired: freezed == expired ? _self.expired : expired // ignore: cast_nullable_to_non_nullable +as bool?,hubs: freezed == hubs ? _self.hubs : hubs // ignore: cast_nullable_to_non_nullable +as List?,items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [JsonFeed]. +extension JsonFeedPatterns on JsonFeed { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _JsonFeed value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _JsonFeed() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _JsonFeed value) $default,){ +final _that = this; +switch (_that) { +case _JsonFeed(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _JsonFeed value)? $default,){ +final _that = this; +switch (_that) { +case _JsonFeed() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String version, String title, @JsonKey(name: 'home_page_url') String? homePageUrl, @JsonKey(name: 'feed_url') String? feedUrl, String? description, @JsonKey(name: 'user_comment') String? userComment, @JsonKey(name: 'next_url') String? nextUrl, String? icon, String? favicon, List? authors, String? language, bool? expired, List? hubs, List items)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _JsonFeed() when $default != null: +return $default(_that.version,_that.title,_that.homePageUrl,_that.feedUrl,_that.description,_that.userComment,_that.nextUrl,_that.icon,_that.favicon,_that.authors,_that.language,_that.expired,_that.hubs,_that.items);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String version, String title, @JsonKey(name: 'home_page_url') String? homePageUrl, @JsonKey(name: 'feed_url') String? feedUrl, String? description, @JsonKey(name: 'user_comment') String? userComment, @JsonKey(name: 'next_url') String? nextUrl, String? icon, String? favicon, List? authors, String? language, bool? expired, List? hubs, List items) $default,) {final _that = this; +switch (_that) { +case _JsonFeed(): +return $default(_that.version,_that.title,_that.homePageUrl,_that.feedUrl,_that.description,_that.userComment,_that.nextUrl,_that.icon,_that.favicon,_that.authors,_that.language,_that.expired,_that.hubs,_that.items);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String version, String title, @JsonKey(name: 'home_page_url') String? homePageUrl, @JsonKey(name: 'feed_url') String? feedUrl, String? description, @JsonKey(name: 'user_comment') String? userComment, @JsonKey(name: 'next_url') String? nextUrl, String? icon, String? favicon, List? authors, String? language, bool? expired, List? hubs, List items)? $default,) {final _that = this; +switch (_that) { +case _JsonFeed() when $default != null: +return $default(_that.version,_that.title,_that.homePageUrl,_that.feedUrl,_that.description,_that.userComment,_that.nextUrl,_that.icon,_that.favicon,_that.authors,_that.language,_that.expired,_that.hubs,_that.items);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _JsonFeed implements JsonFeed { + const _JsonFeed({required this.version, required this.title, @JsonKey(name: 'home_page_url') this.homePageUrl, @JsonKey(name: 'feed_url') this.feedUrl, this.description, @JsonKey(name: 'user_comment') this.userComment, @JsonKey(name: 'next_url') this.nextUrl, this.icon, this.favicon, final List? authors, this.language, this.expired, final List? hubs, final List items = const []}): _authors = authors,_hubs = hubs,_items = items; + factory _JsonFeed.fromJson(Map json) => _$JsonFeedFromJson(json); + +@override final String version; +@override final String title; +@override@JsonKey(name: 'home_page_url') final String? homePageUrl; +@override@JsonKey(name: 'feed_url') final String? feedUrl; +@override final String? description; +@override@JsonKey(name: 'user_comment') final String? userComment; +@override@JsonKey(name: 'next_url') final String? nextUrl; +@override final String? icon; +@override final String? favicon; + final List? _authors; +@override List? get authors { + final value = _authors; + if (value == null) return null; + if (_authors is EqualUnmodifiableListView) return _authors; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + +@override final String? language; +@override final bool? expired; + final List? _hubs; +@override List? get hubs { + final value = _hubs; + if (value == null) return null; + if (_hubs is EqualUnmodifiableListView) return _hubs; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + + final List _items; +@override@JsonKey() List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); +} + + +/// Create a copy of JsonFeed +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$JsonFeedCopyWith<_JsonFeed> get copyWith => __$JsonFeedCopyWithImpl<_JsonFeed>(this, _$identity); + +@override +Map toJson() { + return _$JsonFeedToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _JsonFeed&&(identical(other.version, version) || other.version == version)&&(identical(other.title, title) || other.title == title)&&(identical(other.homePageUrl, homePageUrl) || other.homePageUrl == homePageUrl)&&(identical(other.feedUrl, feedUrl) || other.feedUrl == feedUrl)&&(identical(other.description, description) || other.description == description)&&(identical(other.userComment, userComment) || other.userComment == userComment)&&(identical(other.nextUrl, nextUrl) || other.nextUrl == nextUrl)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.favicon, favicon) || other.favicon == favicon)&&const DeepCollectionEquality().equals(other._authors, _authors)&&(identical(other.language, language) || other.language == language)&&(identical(other.expired, expired) || other.expired == expired)&&const DeepCollectionEquality().equals(other._hubs, _hubs)&&const DeepCollectionEquality().equals(other._items, _items)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,version,title,homePageUrl,feedUrl,description,userComment,nextUrl,icon,favicon,const DeepCollectionEquality().hash(_authors),language,expired,const DeepCollectionEquality().hash(_hubs),const DeepCollectionEquality().hash(_items)); + +@override +String toString() { + return 'JsonFeed(version: $version, title: $title, homePageUrl: $homePageUrl, feedUrl: $feedUrl, description: $description, userComment: $userComment, nextUrl: $nextUrl, icon: $icon, favicon: $favicon, authors: $authors, language: $language, expired: $expired, hubs: $hubs, items: $items)'; +} + + +} + +/// @nodoc +abstract mixin class _$JsonFeedCopyWith<$Res> implements $JsonFeedCopyWith<$Res> { + factory _$JsonFeedCopyWith(_JsonFeed value, $Res Function(_JsonFeed) _then) = __$JsonFeedCopyWithImpl; +@override @useResult +$Res call({ + String version, String title,@JsonKey(name: 'home_page_url') String? homePageUrl,@JsonKey(name: 'feed_url') String? feedUrl, String? description,@JsonKey(name: 'user_comment') String? userComment,@JsonKey(name: 'next_url') String? nextUrl, String? icon, String? favicon, List? authors, String? language, bool? expired, List? hubs, List items +}); + + + + +} +/// @nodoc +class __$JsonFeedCopyWithImpl<$Res> + implements _$JsonFeedCopyWith<$Res> { + __$JsonFeedCopyWithImpl(this._self, this._then); + + final _JsonFeed _self; + final $Res Function(_JsonFeed) _then; + +/// Create a copy of JsonFeed +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? version = null,Object? title = null,Object? homePageUrl = freezed,Object? feedUrl = freezed,Object? description = freezed,Object? userComment = freezed,Object? nextUrl = freezed,Object? icon = freezed,Object? favicon = freezed,Object? authors = freezed,Object? language = freezed,Object? expired = freezed,Object? hubs = freezed,Object? items = null,}) { + return _then(_JsonFeed( +version: null == version ? _self.version : version // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,homePageUrl: freezed == homePageUrl ? _self.homePageUrl : homePageUrl // ignore: cast_nullable_to_non_nullable +as String?,feedUrl: freezed == feedUrl ? _self.feedUrl : feedUrl // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,userComment: freezed == userComment ? _self.userComment : userComment // ignore: cast_nullable_to_non_nullable +as String?,nextUrl: freezed == nextUrl ? _self.nextUrl : nextUrl // ignore: cast_nullable_to_non_nullable +as String?,icon: freezed == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable +as String?,favicon: freezed == favicon ? _self.favicon : favicon // ignore: cast_nullable_to_non_nullable +as String?,authors: freezed == authors ? _self._authors : authors // ignore: cast_nullable_to_non_nullable +as List?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable +as String?,expired: freezed == expired ? _self.expired : expired // ignore: cast_nullable_to_non_nullable +as bool?,hubs: freezed == hubs ? _self._hubs : hubs // ignore: cast_nullable_to_non_nullable +as List?,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +// dart format on diff --git a/lib/src/models.g.dart b/lib/src/models.g.dart new file mode 100644 index 0000000..ddb30e3 --- /dev/null +++ b/lib/src/models.g.dart @@ -0,0 +1,126 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_Author _$AuthorFromJson(Map json) => _Author( + name: json['name'] as String?, + url: json['url'] as String?, + avatar: json['avatar'] as String?, +); + +Map _$AuthorToJson(_Author instance) => { + 'name': instance.name, + 'url': instance.url, + 'avatar': instance.avatar, +}; + +_Attachment _$AttachmentFromJson(Map json) => _Attachment( + url: json['url'] as String, + mimeType: json['mime_type'] as String, + title: json['title'] as String?, + sizeInBytes: (json['size_in_bytes'] as num?)?.toInt(), + durationInSeconds: (json['duration_in_seconds'] as num?)?.toDouble(), +); + +Map _$AttachmentToJson(_Attachment instance) => + { + 'url': instance.url, + 'mime_type': instance.mimeType, + 'title': instance.title, + 'size_in_bytes': instance.sizeInBytes, + 'duration_in_seconds': instance.durationInSeconds, + }; + +_Hub _$HubFromJson(Map json) => + _Hub(type: json['type'] as String, url: json['url'] as String); + +Map _$HubToJson(_Hub instance) => { + 'type': instance.type, + 'url': instance.url, +}; + +_Item _$ItemFromJson(Map json) => _Item( + id: json['id'] as String, + url: json['url'] as String?, + externalUrl: json['external_url'] as String?, + title: json['title'] as String?, + contentHtml: json['content_html'] as String?, + contentText: json['content_text'] as String?, + summary: json['summary'] as String?, + image: json['image'] as String?, + bannerImage: json['banner_image'] as String?, + datePublished: json['date_published'] as String?, + dateModified: json['date_modified'] as String?, + authors: (json['authors'] as List?) + ?.map((e) => Author.fromJson(e as Map)) + .toList(), + tags: (json['tags'] as List?)?.map((e) => e as String).toList(), + language: json['language'] as String?, + attachments: (json['attachments'] as List?) + ?.map((e) => Attachment.fromJson(e as Map)) + .toList(), +); + +Map _$ItemToJson(_Item instance) => { + 'id': instance.id, + 'url': instance.url, + 'external_url': instance.externalUrl, + 'title': instance.title, + 'content_html': instance.contentHtml, + 'content_text': instance.contentText, + 'summary': instance.summary, + 'image': instance.image, + 'banner_image': instance.bannerImage, + 'date_published': instance.datePublished, + 'date_modified': instance.dateModified, + 'authors': instance.authors, + 'tags': instance.tags, + 'language': instance.language, + 'attachments': instance.attachments, +}; + +_JsonFeed _$JsonFeedFromJson(Map json) => _JsonFeed( + version: json['version'] as String, + title: json['title'] as String, + homePageUrl: json['home_page_url'] as String?, + feedUrl: json['feed_url'] as String?, + description: json['description'] as String?, + userComment: json['user_comment'] as String?, + nextUrl: json['next_url'] as String?, + icon: json['icon'] as String?, + favicon: json['favicon'] as String?, + authors: (json['authors'] as List?) + ?.map((e) => Author.fromJson(e as Map)) + .toList(), + language: json['language'] as String?, + expired: json['expired'] as bool?, + hubs: (json['hubs'] as List?) + ?.map((e) => Hub.fromJson(e as Map)) + .toList(), + items: + (json['items'] as List?) + ?.map((e) => Item.fromJson(e as Map)) + .toList() ?? + const [], +); + +Map _$JsonFeedToJson(_JsonFeed instance) => { + 'version': instance.version, + 'title': instance.title, + 'home_page_url': instance.homePageUrl, + 'feed_url': instance.feedUrl, + 'description': instance.description, + 'user_comment': instance.userComment, + 'next_url': instance.nextUrl, + 'icon': instance.icon, + 'favicon': instance.favicon, + 'authors': instance.authors, + 'language': instance.language, + 'expired': instance.expired, + 'hubs': instance.hubs, + 'items': instance.items, +}; diff --git a/openai.md b/openai.md new file mode 100644 index 0000000..be7c00a --- /dev/null +++ b/openai.md @@ -0,0 +1,327 @@ +# json\_feed\_package + +A small Dart package that implements JSON Feed v1.1 models + parsing using `freezed` and `json_serializable`, ready for codegen, tests and CI. + +--- + +## File tree + +``` +json_feed/ +├── LICENSE +├── README.md +├── analysis_options.yaml +├── pubspec.yaml +├── lib/ +│ ├── json_feed.dart +│ └── src/ +│ └── models.dart +├── example/ +│ └── main.dart +├── test/ +│ └── json_feed_test.dart +└── .github/ + └── workflows/ + └── dart.yml +``` + +--- + +## LICENSE + +> MIT License + +(Include MIT license text in your repository.) + +--- + +## README.md + +A short README with usage and commands to run. + +--- + +## pubspec.yaml + +```yaml +name: json_feed +description: A Dart implementation of JSON Feed v1.1 with freezed/json_serializable codegen. +version: 0.1.0 +homepage: https://example.org/json_feed +environment: + sdk: '>=2.18.0 <4.0.0' + +dependencies: + freezed_annotation: ^2.2.0 + json_annotation: ^4.8.0 + +dev_dependencies: + build_runner: ^2.4.6 + freezed: ^2.2.0 + json_serializable: ^6.6.1 + test: ^1.24.0 + +# optional: classifier and authors + +``` + +--- + +## analysis\_options.yaml + +```yaml +include: package:pedantic/analysis_options.yaml + +# You can customize rules here if you prefer. +``` + +--- + +## lib/src/models.dart + +This file contains the `freezed` annotated models used by the package. It declares the `JsonFeed`, `Item`, `Author`, `Attachment`, and `Hub` classes. + +```dart +// lib/src/models.dart + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'models.freezed.dart'; +part 'models.g.dart'; + +@freezed +class JsonFeed with _$_JsonFeed { + @JsonSerializable(explicitToJson: true) + const factory JsonFeed({ + required String version, + required String title, + @JsonKey(name: 'home_page_url') String? homePageUrl, + @JsonKey(name: 'feed_url') String? feedUrl, + String? description, + @JsonKey(name: 'user_comment') String? userComment, + @JsonKey(name: 'next_url') String? nextUrl, + String? icon, + String? favicon, + @JsonKey(defaultValue: []) List authors, + String? language, + bool? expired, + @JsonKey(defaultValue: []) List hubs, + @JsonKey(defaultValue: []) List items, + // capture extension keys starting with '_' in a map + @JsonKey(name: '_extensions') @JsonKey(ignore: true) Map? extensions, + }) = _JsonFeed; + + factory JsonFeed.fromJson(Map json) => _$JsonFeedFromJson(json); +} + +@freezed +class Author with _$_Author { + @JsonSerializable() + const factory Author({ + String? name, + String? url, + String? avatar, + @JsonKey(ignore: true) Map? extras, + }) = _Author; + + factory Author.fromJson(Map json) => _$AuthorFromJson(json); +} + +@freezed +class Hub with _$_Hub { + @JsonSerializable() + const factory Hub({ + required String type, + required String url, + }) = _Hub; + + factory Hub.fromJson(Map json) => _$HubFromJson(json); +} + +@freezed +class Attachment with _$_Attachment { + @JsonSerializable() + const factory Attachment({ + required String url, + @JsonKey(name: 'mime_type') required String mimeType, + String? title, + @JsonKey(name: 'size_in_bytes') int? sizeInBytes, + @JsonKey(name: 'duration_in_seconds') double? durationInSeconds, + @JsonKey(ignore: true) Map? extras, + }) = _Attachment; + + factory Attachment.fromJson(Map json) => _$AttachmentFromJson(json); +} + +@freezed +class Item with _$_Item { + @JsonSerializable(explicitToJson: true) + const factory Item({ + required String id, + String? url, + @JsonKey(name: 'external_url') String? externalUrl, + String? title, + @JsonKey(name: 'content_html') String? contentHtml, + @JsonKey(name: 'content_text') String? contentText, + String? summary, + String? image, + @JsonKey(name: 'banner_image') String? bannerImage, + @JsonKey(name: 'date_published') String? datePublished, + @JsonKey(name: 'date_modified') String? dateModified, + @JsonKey(defaultValue: []) List authors, + @JsonKey(defaultValue: []) List tags, + String? language, + @JsonKey(defaultValue: []) List attachments, + @JsonKey(ignore: true) Map? extensions, + }) = _Item; + + factory Item.fromJson(Map json) => _$ItemFromJson(json); +} +``` + +Notes: + +* `extensions` / `extras` fields are marked `ignore` so that the generated `fromJson`/`toJson` don't expect them. We'll populate them manually in a top-level parsing helper if you want to preserve `_`-prefixed keys. +* Dates are kept as strings for simple parsing; you can add custom converters if you prefer `DateTime` fields. + +--- + +## lib/json\_feed.dart + +Top-level helpers to parse/validate the feed and to preserve `_`-prefixed extension keys. + +```dart +// lib/json_feed.dart + +library json_feed; + +export 'src/models.dart'; + +import 'dart:convert'; + +import 'src/models.dart'; + +class JsonFeedParseException implements Exception { + final String message; + JsonFeedParseException(this.message); + @override + String toString() => 'JsonFeedParseException: $message'; +} + +JsonFeed parseJsonFeed(String jsonString) { + final decoded = json.decode(jsonString); + if (decoded is! Map) throw JsonFeedParseException('Top-level JSON must be an object.'); + + // validate minimal requirements per spec + final version = decoded['version']; + if (version is! String || version.isEmpty) throw JsonFeedParseException('Missing required "version" string.'); + final items = decoded['items']; + if (items is! List) throw JsonFeedParseException('Missing required "items" array.'); + // authors fallback and extension extraction can be added here + + // Use generated fromJson + final feed = JsonFeed.fromJson(decoded); + + // TODO: populate ignored extensions/extras if needed by scanning keys starting with '_' + + return feed; +} +``` + +--- + +## example/main.dart + +```dart +import 'package:json_feed/json_feed.dart'; +import 'dart:io'; + +void main() { + final sample = File('example/sample_feed.json').readAsStringSync(); + final feed = parseJsonFeed(sample); + print('Feed title: ${feed.title}'); + print('Item count: ${feed.items.length}'); +} +``` + +--- + +## test/json\_feed\_test.dart + +A small unit test that runs the generator and validates parsing of a minimal JSON Feed. + +```dart +import 'package:test/test.dart'; +import 'package:json_feed/json_feed.dart'; + +void main() { + test('parse minimal feed', () { + final json = '''{ + "version": "https://jsonfeed.org/version/1.1", + "title": "Example Feed", + "items": [ + { + "id": "1", + "content_text": "Hello world" + } + ] + }'''; + + final feed = parseJsonFeed(json); + expect(feed.version, isNotEmpty); + expect(feed.items, isNotEmpty); + expect(feed.items.first.id, '1'); + }); +} +``` + +--- + +## .github/workflows/dart.yml + +```yaml +name: Dart CI + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1 + with: + sdk: 'stable' + + - name: Get dependencies + run: dart pub get + + - name: Run codegen (build_runner) + run: dart run build_runner build --delete-conflicting-outputs + + - name: Run tests + run: dart test -r expanded +``` + +--- + +## How to use locally + +1. `dart pub get` +2. `dart run build_runner build --delete-conflicting-outputs` (generates `*.g.dart` and `*.freezed.dart`) +3. `dart test` + +--- + +If you'd like I can: + +* include custom `JsonKey` `fromJson`/`toJson` converters for `DateTime` fields, +* implement preservation of `_`-prefixed extension keys into `extensions` maps, and +* produce `examples/sample_feed.json` with a fuller sample feed used by tests. + +Tell me which of those you'd like next and I will add them. diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..36660c4 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,16 @@ +name: json_feed +description: A Dart implementation of JSON Feed v1.1 with freezed/json_serializable codegen. +version: 0.1.0 +homepage: https://example.org/json_feed +environment: + sdk: '>=3.8.0<4.0.0' + +dependencies: + freezed_annotation: ^3.1.0 + json_annotation: ^4.9.0 + +dev_dependencies: + test: ^1.24.0 + build_runner: ^2.8.0 + freezed: ^3.2.3 + json_serializable: ^6.11.1 diff --git a/test/json_feed_test.dart b/test/json_feed_test.dart new file mode 100644 index 0000000..5636c44 --- /dev/null +++ b/test/json_feed_test.dart @@ -0,0 +1,129 @@ +import 'package:test/test.dart'; +import 'package:json_feed/json_feed.dart'; + +void main() { + test('parse minimal feed', () { + final json = '''{ + "version": "https://jsonfeed.org/version/1.1", + "title": "Example Feed", + "items": [ + { + "id": "1", + "content_text": "Hello world" + } + ] + }'''; + + final feed = parseJsonFeed(json); + expect(feed.version, isNotEmpty); + expect(feed.items, isNotEmpty); + expect(feed.items.first.id, '1'); + }); + + test('parse complete feed with all elements', () { + final json = '''{ + "version": "https://jsonfeed.org/version/1.1", + "title": "Complete Feed", + "home_page_url": "https://example.com/", + "feed_url": "https://example.com/feed.json", + "description": "A complete example feed", + "user_comment": "This is a user comment.", + "next_url": "https://example.com/feed.json?page=2", + "icon": "https://example.com/icon.png", + "favicon": "https://example.com/favicon.ico", + "authors": [ + { + "name": "Jane Doe", + "url": "https://example.com/jane", + "avatar": "https://example.com/jane.jpg" + } + ], + "language": "en", + "expired": false, + "hubs": [ + { + "type": "WebSub", + "url": "https://hub.example.com/" + } + ], + "items": [ + { + "id": "1", + "url": "https://example.com/1", + "external_url": "https://external.com/1", + "title": "Item 1", + "content_html": "

Hello world

", + "content_text": "Hello world", + "summary": "Summary of item 1", + "image": "https://example.com/image1.png", + "banner_image": "https://example.com/banner1.png", + "date_published": "2023-01-01T12:00:00Z", + "date_modified": "2023-01-02T12:00:00Z", + "authors": [ + { + "name": "Jane Doe", + "url": "https://example.com/jane", + "avatar": "https://example.com/jane.jpg" + } + ], + "tags": ["tag1", "tag2"], + "language": "en", + "attachments": [ + { + "url": "https://example.com/audio.mp3", + "mime_type": "audio/mpeg", + "title": "Audio", + "size_in_bytes": 123456, + "duration_in_seconds": 120.5 + } + ] + } + ] + }'''; + + final feed = parseJsonFeed(json); + expect(feed.version, equals("https://jsonfeed.org/version/1.1")); + expect(feed.title, equals("Complete Feed")); + expect(feed.homePageUrl, equals("https://example.com/")); + expect(feed.feedUrl, equals("https://example.com/feed.json")); + expect(feed.description, equals("A complete example feed")); + expect(feed.userComment, equals("This is a user comment.")); + expect(feed.nextUrl, equals("https://example.com/feed.json?page=2")); + expect(feed.icon, equals("https://example.com/icon.png")); + expect(feed.favicon, equals("https://example.com/favicon.ico")); + expect(feed.authors, isNotNull); + expect(feed.authors!.first.name, equals("Jane Doe")); + expect(feed.language, equals("en")); + expect(feed.expired, isFalse); + expect(feed.hubs, isNotNull); + expect(feed.hubs!.first.type, equals("WebSub")); + expect(feed.hubs!.first.url, equals("https://hub.example.com/")); + expect(feed.items, isNotEmpty); + + final item = feed.items.first; + expect(item.id, equals("1")); + expect(item.url, equals("https://example.com/1")); + expect(item.externalUrl, equals("https://external.com/1")); + expect(item.title, equals("Item 1")); + expect(item.contentHtml, equals("

Hello world

")); + expect(item.contentText, equals("Hello world")); + expect(item.summary, equals("Summary of item 1")); + expect(item.image, equals("https://example.com/image1.png")); + expect(item.bannerImage, equals("https://example.com/banner1.png")); + expect(item.datePublished, equals("2023-01-01T12:00:00Z")); + expect(item.dateModified, equals("2023-01-02T12:00:00Z")); + expect(item.authors, isNotNull); + expect(item.authors!.first.name, equals("Jane Doe")); + expect(item.tags, containsAll(["tag1", "tag2"])); + expect(item.language, equals("en")); + expect(item.attachments, isNotNull); + expect( + item.attachments!.first.url, + equals("https://example.com/audio.mp3"), + ); + expect(item.attachments!.first.mimeType, equals("audio/mpeg")); + expect(item.attachments!.first.title, equals("Audio")); + expect(item.attachments!.first.sizeInBytes, equals(123456)); + expect(item.attachments!.first.durationInSeconds, equals(120.5)); + }); +}