From 494f88cc52cbfd46f4f7aabe9c77eca7c7324c66 Mon Sep 17 00:00:00 2001 From: Alexandre Roux Date: Wed, 23 Oct 2024 11:46:31 +0200 Subject: [PATCH] feat: add tekartik_app_json_schema --- .github/dependabot.yml | 8 + app_cv_firestore/README.md | 2 +- app_cv_sembast/README.md | 2 +- app_date/README.md | 2 +- app_json_schema/.gitignore | 7 + app_json_schema/README.md | 11 + app_json_schema/analysis_options.yaml | 1 + app_json_schema/lib/cv_json_schema.dart | 13 + app_json_schema/lib/json_schema.dart | 2 + app_json_schema/lib/src/constant.dart | 14 + app_json_schema/lib/src/cv_json_schema.dart | 355 ++++++++++++++++++ app_json_schema/lib/src/json_schema.dart | 211 +++++++++++ app_json_schema/pubspec.yaml | 19 + app_json_schema/test/cv_json_schema_test.dart | 145 +++++++ app_json_schema/test/json_schema_test.dart | 142 +++++++ repo_support/pubspec.yaml | 5 + repo_support/tool/run_ci.dart | 4 +- repo_support/tool/setup_env.dart | 8 +- 18 files changed, 941 insertions(+), 10 deletions(-) create mode 100644 app_json_schema/.gitignore create mode 100644 app_json_schema/README.md create mode 100644 app_json_schema/analysis_options.yaml create mode 100644 app_json_schema/lib/cv_json_schema.dart create mode 100644 app_json_schema/lib/json_schema.dart create mode 100644 app_json_schema/lib/src/constant.dart create mode 100644 app_json_schema/lib/src/cv_json_schema.dart create mode 100644 app_json_schema/lib/src/json_schema.dart create mode 100644 app_json_schema/pubspec.yaml create mode 100644 app_json_schema/test/cv_json_schema_test.dart create mode 100644 app_json_schema/test/json_schema_test.dart diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 07fb94c..0bfa0db 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,10 @@ updates: directory: "app" schedule: interval: "monthly" + - package-ecosystem: "pub" + directory: "app_archive" + schedule: + interval: "monthly" - package-ecosystem: "pub" directory: "app_bloc" schedule: @@ -45,6 +49,10 @@ updates: directory: "app_intl" schedule: interval: "monthly" + - package-ecosystem: "pub" + directory: "app_json_schema" + schedule: + interval: "monthly" - package-ecosystem: "pub" directory: "app_lints" schedule: diff --git a/app_cv_firestore/README.md b/app_cv_firestore/README.md index 50cf503..fad2bb9 100644 --- a/app_cv_firestore/README.md +++ b/app_cv_firestore/README.md @@ -1,6 +1,6 @@ ## Setup -pubspect.yaml: +pubspec.yaml: ```yaml tekartik_app_cv_firestore: diff --git a/app_cv_sembast/README.md b/app_cv_sembast/README.md index 044e060..eaf5270 100644 --- a/app_cv_sembast/README.md +++ b/app_cv_sembast/README.md @@ -1,6 +1,6 @@ ## Setup -pubspect.yaml: +pubspec.yaml: ```yaml tekartik_app_cv_sembast: diff --git a/app_date/README.md b/app_date/README.md index 47c3e24..559e8cc 100644 --- a/app_date/README.md +++ b/app_date/README.md @@ -1,6 +1,6 @@ ## Setup -pubspect.yaml: +pubspec.yaml: ```yaml tekartik_app_date: diff --git a/app_json_schema/.gitignore b/app_json_schema/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/app_json_schema/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/app_json_schema/README.md b/app_json_schema/README.md new file mode 100644 index 0000000..5f1262d --- /dev/null +++ b/app_json_schema/README.md @@ -0,0 +1,11 @@ +## Setup + +pubspec.yaml: + +```yaml + tekartik_app_json_schema: + git: + url: https://github.com/tekartik/app_common_utils.dart + ref: dart3a + path: app_json_schema +``` \ No newline at end of file diff --git a/app_json_schema/analysis_options.yaml b/app_json_schema/analysis_options.yaml new file mode 100644 index 0000000..b54503f --- /dev/null +++ b/app_json_schema/analysis_options.yaml @@ -0,0 +1 @@ +include: package:tekartik_lints/package.yaml diff --git a/app_json_schema/lib/cv_json_schema.dart b/app_json_schema/lib/cv_json_schema.dart new file mode 100644 index 0000000..d4d80b8 --- /dev/null +++ b/app_json_schema/lib/cv_json_schema.dart @@ -0,0 +1,13 @@ +export 'package:cv/cv_json.dart'; +export 'json_schema.dart'; +export 'src/cv_json_schema.dart' + show + CvJsonSchema, + CvJsonSchemaBool, + CvJsonSchemaExt, + CvJsonSchemaInt, + CvJsonSchemaNum, + CvJsonSchemaString, + CvJsonSchemaEnumString, + CvJsonSchemaList, + CvJsonSchemaMap; diff --git a/app_json_schema/lib/json_schema.dart b/app_json_schema/lib/json_schema.dart new file mode 100644 index 0000000..8150a33 --- /dev/null +++ b/app_json_schema/lib/json_schema.dart @@ -0,0 +1,2 @@ +export 'src/constant.dart'; +export 'src/json_schema.dart' show JsonSchema, JsonSchemaType; diff --git a/app_json_schema/lib/src/constant.dart b/app_json_schema/lib/src/constant.dart new file mode 100644 index 0000000..5e982e3 --- /dev/null +++ b/app_json_schema/lib/src/constant.dart @@ -0,0 +1,14 @@ +/// String enum format +const jsonSchemaFormatEnum = 'enum'; + +/// Int32 +const jsonSchemaFormatInt32 = 'int32'; + +/// Int64 +const jsonSchemaFormatInt64 = 'int64'; + +/// Float +const jsonSchemaFormatFloat = 'float'; + +/// Double +const jsonSchemaFormatDouble = 'double'; diff --git a/app_json_schema/lib/src/cv_json_schema.dart b/app_json_schema/lib/src/cv_json_schema.dart new file mode 100644 index 0000000..edc7df4 --- /dev/null +++ b/app_json_schema/lib/src/cv_json_schema.dart @@ -0,0 +1,355 @@ +import 'package:cv/cv.dart'; +import 'package:tekartik_app_json_schema/json_schema.dart'; +import 'package:tekartik_common_utils/list_utils.dart'; + +const _typeKey = 'type'; + +/// JSON shema using the cv package +abstract class CvJsonSchema extends CvModelBase { + /// Description + CvField get description; + + /// Nullable + CvField get nullable; + + /// Type + CvField get type; +} + +/// JSON schema extension +extension CvJsonSchemaExt on CvJsonSchema { + /// Convert to JSON schema + JsonSchema toJsonSchema() { + switch (this) { + case CvJsonSchemaBool _: + return JsonSchema.boolean( + description: description.v, + nullable: nullable.v, + ); + + case CvJsonSchemaEnumString def: + return JsonSchema.enumString( + description: description.v, + nullable: nullable.v, + enumValues: def.enumValues.v!, + ); + case CvJsonSchemaWithFormat def: + return JsonSchema( + def.type.v!, + format: def.format.v, + description: description.v, + nullable: nullable.v, + ); + + case CvJsonSchemaList def: + return JsonSchema.array( + description: description.v, + nullable: nullable.v, + items: def.items.v!.toJsonSchema(), + ); + case CvJsonSchemaMap def: + return JsonSchema.object( + description: description.v, + nullable: nullable.v, + properties: def.properties.v! + .map((key, value) => MapEntry(key, value.toJsonSchema())), + optionalProperties: def.optionalProperties, + ); + default: + throw UnsupportedError('Unsupported type: ${type.v}'); + } + } + + /// Convert to JSON schema map + Model toSchemaJsonMap() { + return toMap(); + } +} + +/// Boolean JSON schema +abstract class CvJsonSchemaBool implements CvJsonSchema { + /// Constructor + factory CvJsonSchemaBool({ + String? description, + bool? nullable, + }) => + _CvJsonSchemaBool( + type: JsonSchemaType.boolean, + description: description, + nullable: nullable); +} + +/// Integer JSON schema +abstract class CvJsonSchemaInt implements CvJsonSchema, CvJsonSchemaNum { + /// Constructor + factory CvJsonSchemaInt({ + String? description, + bool? nullable, + String? format, + }) => + _CvJsonSchemaInt( + type: JsonSchemaType.integer, + description: description, + nullable: nullable, + format: format); +} + +/// Number JSON schema +abstract class CvJsonSchemaNum implements CvJsonSchema, CvJsonSchemaWithFormat { + /// Constructor + factory CvJsonSchemaNum({ + String? description, + bool? nullable, + String? format, + }) => + _CvJsonSchemaNum( + type: JsonSchemaType.number, + description: description, + nullable: nullable, + format: format); +} + +/// String JSON schema +abstract class CvJsonSchemaString + implements CvJsonSchema, CvJsonSchemaWithFormat { + /// Constructor + factory CvJsonSchemaString({ + String? description, + bool? nullable, + String? format, + }) => + _CvJsonSchemaString( + type: JsonSchemaType.string, + description: description, + nullable: nullable, + format: format); +} + +/// Enum String JSON schema +abstract class CvJsonSchemaEnumString implements CvJsonSchemaString { + /// Enum values + CvField> get enumValues; + + /// Constructor + factory CvJsonSchemaEnumString({ + String? description, + bool? nullable, + required List enumValues, + }) => + _CvJsonSchemaEnumString( + type: JsonSchemaType.string, + description: description, + nullable: nullable, + format: jsonSchemaFormatEnum, + enumValues: enumValues); +} + +/// List JSON schema +abstract class CvJsonSchemaList implements CvJsonSchema { + /// items type + CvModelField get items; + + /// Constructor + factory CvJsonSchemaList({ + String? description, + bool? nullable, + required CvJsonSchema items, + }) => + _CvJsonSchemaList( + type: JsonSchemaType.array, + description: description, + nullable: nullable, + items: items); +} + +/// Map (Object) JSON schema +abstract class CvJsonSchemaMap implements CvJsonSchema { + /// Properties + CvModelMapField get properties; + + /// Optional properties + CvListField get required; + + /// Constructor + factory CvJsonSchemaMap({ + String? description, + bool? nullable, + required Map properties, + List? optionalProperties, + }) => + _CvJsonSchemaMap( + type: JsonSchemaType.object, + description: description, + nullable: nullable, + properties: properties, + optionalProperties: optionalProperties); + + /// Optional properties getter + List? get optionalProperties; +} + +/// Has format +abstract class CvJsonSchemaWithFormat implements CvJsonSchema { + /// Format + CvField get format; +} + +abstract class _CvJsonSchemaBase extends CvModelBase implements CvJsonSchema { + _CvJsonSchemaBase({ + JsonSchemaType? type, + String? description, + bool? nullable, + }) : super() { + this.type.v = type; + this.description.setValue(description); + this.nullable.setValue(nullable); + } + @override + final description = CvField('description'); + @override + final nullable = CvField('nullable'); + @override + final type = CvField(_typeKey); + + @override + CvFields get fields => [type, description, nullable]; + + @override + Map toMap( + {List? columns, bool includeMissingValue = false}) { + var columnsNoType = List.of(columns ?? fields.map((field) => field.name)) + ..remove(_typeKey); + final map = super.toMap( + columns: columnsNoType, includeMissingValue: includeMissingValue); + if (type.v != null) { + map[_typeKey] = type.v!.toJson(); + } + return map; + } +} + +mixin _CvJsonSchemaWithFormatMixin implements _CvJsonSchemaBase { + final format = CvField('format'); +} + +class _CvJsonSchemaBool extends _CvJsonSchemaBase implements CvJsonSchemaBool { + _CvJsonSchemaBool({ + super.type, + super.description, + super.nullable, + }); +} + +class _CvJsonSchemaInt extends _CvJsonSchemaNum + with _CvJsonSchemaWithFormatMixin + implements CvJsonSchemaInt { + _CvJsonSchemaInt({ + super.type, + super.description, + super.nullable, + super.format, + }); +} + +class _CvJsonSchemaNum extends _CvJsonSchemaWithFormatBase + implements CvJsonSchemaNum { + _CvJsonSchemaNum({ + super.type, + super.description, + super.nullable, + super.format, + }); +} + +class _CvJsonSchemaString extends _CvJsonSchemaWithFormatBase + implements CvJsonSchemaString { + _CvJsonSchemaString({ + super.type, + super.description, + super.nullable, + super.format, + }); +} + +class _CvJsonSchemaEnumString extends _CvJsonSchemaString + implements CvJsonSchemaEnumString { + _CvJsonSchemaEnumString({ + super.type, + super.description, + super.nullable, + super.format, + required List enumValues, + }) { + this.enumValues.setValue(enumValues); + } + @override + final enumValues = CvField>('enum'); + + @override + CvFields get fields => [...super.fields, enumValues]; +} + +class _CvJsonSchemaWithFormatBase extends _CvJsonSchemaBase + with _CvJsonSchemaWithFormatMixin + implements CvJsonSchema, CvJsonSchemaWithFormat { + _CvJsonSchemaWithFormatBase({ + super.type, + super.description, + super.nullable, + String? format, + }) : super() { + this.format.setValue(format); + } + @override + CvFields get fields => [...super.fields, format]; +} + +class _CvJsonSchemaList extends _CvJsonSchemaBase implements CvJsonSchemaList { + _CvJsonSchemaList({ + super.type, + super.description, + super.nullable, + required CvJsonSchema items, + }) { + this.items.setValue(items); + } + @override + final items = CvModelField('items'); + + @override + CvFields get fields => [...super.fields, items]; +} + +List? _mapKeysWithout(Model map, List? without) { + var list = List.of(map.keys); + if (without != null) { + list.removeWhere((key) => without.contains(key)); + } + return list.nonEmpty(); +} + +class _CvJsonSchemaMap extends _CvJsonSchemaBase implements CvJsonSchemaMap { + _CvJsonSchemaMap({ + super.type, + super.description, + super.nullable, + required Map properties, + List? optionalProperties, + }) { + this.properties.setValue(properties); + + required.setValue(_mapKeysWithout(properties, optionalProperties)); + } + @override + final properties = CvModelMapField('properties'); + @override + final required = CvListField('required'); + + @override + CvFields get fields => [...super.fields, properties, required]; + + @override + List? get optionalProperties { + return _mapKeysWithout(properties.v!, required.v!); + } +} diff --git a/app_json_schema/lib/src/json_schema.dart b/app_json_schema/lib/src/json_schema.dart new file mode 100644 index 0000000..7666d2e --- /dev/null +++ b/app_json_schema/lib/src/json_schema.dart @@ -0,0 +1,211 @@ +/// Copied from Vertex AI SDK +library; + +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// The definition of an input or output data types. +/// +/// These types can be objects, but also primitives and arrays. +/// Represents a select subset of an +/// [OpenAPI 3.0 schema object](https://spec.openapis.org/oas/v3.0.3#schema). +final class JsonSchema { + /// Constructor + JsonSchema( + this.type, { + this.format, + this.description, + this.nullable, + this.enumValues, + this.items, + this.properties, + this.optionalProperties, + }); + + /// Construct a schema for an object with one or more properties. + JsonSchema.object({ + required Map properties, + List? optionalProperties, + String? description, + bool? nullable, + }) : this( + JsonSchemaType.object, + properties: properties, + optionalProperties: optionalProperties, + description: description, + nullable: nullable, + ); + + /// Construct a schema for an array of values with a specified type. + JsonSchema.array({ + required JsonSchema items, + String? description, + bool? nullable, + }) : this( + JsonSchemaType.array, + description: description, + nullable: nullable, + items: items, + ); + + /// Construct a schema for bool value. + JsonSchema.boolean({ + String? description, + bool? nullable, + }) : this( + JsonSchemaType.boolean, + description: description, + nullable: nullable, + ); + + /// Construct a schema for an integer number. + /// + /// The [format] may be "int32" or "int64". + JsonSchema.integer({ + String? description, + bool? nullable, + String? format, + }) : this( + JsonSchemaType.integer, + description: description, + nullable: nullable, + format: format, + ); + + /// Construct a schema for a non-integer number. + /// + /// The [format] may be "float" or "double". + JsonSchema.number({ + String? description, + bool? nullable, + String? format, + }) : this( + JsonSchemaType.number, + description: description, + nullable: nullable, + format: format, + ); + + /// Construct a schema for String value with enumerated possible values. + JsonSchema.enumString({ + required List enumValues, + String? description, + bool? nullable, + }) : this( + JsonSchemaType.string, + enumValues: enumValues, + description: description, + nullable: nullable, + format: 'enum', + ); + + /// Construct a schema for a String value. + JsonSchema.string({ + String? description, + bool? nullable, + String? format, + }) : this(JsonSchemaType.string, + description: description, nullable: nullable, format: format); + + /// The type of this value. + JsonSchemaType type; + + /// The format of the data. + /// + /// This is used only for primitive datatypes. + /// + /// Supported formats: + /// for [JsonSchemaType.number] type: float, double + /// for [JsonSchemaType.integer] type: int32, int64 + /// for [JsonSchemaType.string] type: enum. See [enumValues] + String? format; + + /// A brief description of the parameter. + /// + /// This could contain examples of use. + /// Parameter description may be formatted as Markdown. + String? description; + + /// Whether the value mey be null. + bool? nullable; + + /// Possible values if this is a [JsonSchemaType.string] with an enum format. + List? enumValues; + + /// Schema for the elements if this is a [JsonSchemaType.array]. + JsonSchema? items; + + /// Properties of this type if this is a [JsonSchemaType.object]. + Map? properties; + + /// Optional Properties if this is a [JsonSchemaType.object]. + /// + /// The keys from [properties] for properties that are optional if this is a + /// [JsonSchemaType.object]. Any properties that's not listed in optional will be + /// treated as required properties + List? optionalProperties; + + /// Convert to json object. + Map toJson() => { + 'type': type.toJson(), + if (format case final format?) 'format': format, + if (description case final description?) 'description': description, + if (nullable case final nullable?) 'nullable': nullable, + if (enumValues case final enumValues?) 'enum': enumValues, + if (items case final items?) 'items': items.toJson(), + if (properties case final properties?) + 'properties': { + for (final MapEntry(:key, :value) in properties.entries) + key: value.toJson() + }, + // Calculate required properties based on optionalProperties + if (properties != null) + 'required': optionalProperties != null + ? properties!.keys + .where((key) => !optionalProperties!.contains(key)) + .toList() + : properties!.keys.toList(), + }; +} + +/// The value type of a [JsonSchema]. +enum JsonSchemaType { + /// string type. + string, + + /// number type + number, + + /// integer type + integer, + + /// boolean type + boolean, + + /// array type + array, + + /// object type + object; + + /// Convert to json object. + String toJson() => switch (this) { + string => 'STRING', + number => 'NUMBER', + integer => 'INTEGER', + boolean => 'BOOLEAN', + array => 'ARRAY', + object => 'OBJECT', + }; +} diff --git a/app_json_schema/pubspec.yaml b/app_json_schema/pubspec.yaml new file mode 100644 index 0000000..1d6e85e --- /dev/null +++ b/app_json_schema/pubspec.yaml @@ -0,0 +1,19 @@ +name: tekartik_app_json_schema +description: Json schema definition +version: 1.0.0 +publish_to: none + +environment: + sdk: ^3.5.0 + +# Add regular dependencies here. +dependencies: + cv: ">=1.1.0+4" + tekartik_common_utils: + git: + url: https://github.com/tekartik/common_utils.dart + ref: dart3a + +dev_dependencies: + lints: ">=5.0.0" + test: ">=1.24.0" diff --git a/app_json_schema/test/cv_json_schema_test.dart b/app_json_schema/test/cv_json_schema_test.dart new file mode 100644 index 0000000..36f3309 --- /dev/null +++ b/app_json_schema/test/cv_json_schema_test.dart @@ -0,0 +1,145 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:tekartik_app_json_schema/cv_json_schema.dart'; +import 'package:test/test.dart'; + +typedef SchemaType = JsonSchemaType; +typedef Schema = JsonSchema; + +void _checkToJsonSchema(CvJsonSchema schema) { + expect( + schema.toJsonSchema().toJson(), + schema.toSchemaJsonMap(), + ); +} + +void main() { + group('Schema Tests', () { + // Test basic constructors and toJson() for primitive types + test('Schema.boolean', () { + final schema = + CvJsonSchemaBool(description: 'A boolean value', nullable: true); + expect(schema.type.v, SchemaType.boolean); + expect(schema.description.v, 'A boolean value'); + expect(schema.nullable.v, true); + expect(schema.toSchemaJsonMap(), { + 'type': 'BOOLEAN', + 'description': 'A boolean value', + 'nullable': true, + }); + _checkToJsonSchema(schema); + }); + + test('Schema.integer', () { + final schema = CvJsonSchemaInt(format: 'int32'); + expect(schema.type.v, SchemaType.integer); + expect(schema.format.v, 'int32'); + expect(schema.toSchemaJsonMap(), { + 'type': 'INTEGER', + 'format': 'int32', + }); + _checkToJsonSchema(schema); + }); + + test('Schema.number', () { + final schema = CvJsonSchemaNum(format: 'double', nullable: false); + expect(schema.type.v, SchemaType.number); + expect(schema.format.v, 'double'); + expect(schema.nullable.v, false); + expect(schema.toSchemaJsonMap(), { + 'type': 'NUMBER', + 'format': 'double', + 'nullable': false, + }); + _checkToJsonSchema(schema); + }); + + test('Schema.string', () { + final schema = CvJsonSchemaString(); + expect(schema.type.v, SchemaType.string); + expect(schema.toSchemaJsonMap(), {'type': 'STRING'}); + _checkToJsonSchema(schema); + }); + + test('Schema.enumString', () { + final schema = CvJsonSchemaEnumString(enumValues: ['value1', 'value2']); + expect(schema.type.v, SchemaType.string); + expect(schema.format.v, 'enum'); + expect(schema.enumValues.v, ['value1', 'value2']); + expect(schema.toSchemaJsonMap(), { + 'type': 'STRING', + 'format': 'enum', + 'enum': ['value1', 'value2'], + }); + _checkToJsonSchema(schema); + }); + + // Test constructors and toJson() for complex types + test('Schema.array', () { + final itemSchema = CvJsonSchemaString(); + final schema = CvJsonSchemaList(items: itemSchema); + expect(schema.type.v, SchemaType.array); + expect(schema.items.v, itemSchema); + expect(schema.toSchemaJsonMap(), { + 'type': 'ARRAY', + 'items': {'type': 'STRING'}, + }); + _checkToJsonSchema(schema); + }); + + test('Schema.object', () { + final properties = { + 'name': CvJsonSchemaString(), + 'age': CvJsonSchemaInt(), + }; + final schema = CvJsonSchemaMap( + properties: properties, + optionalProperties: ['age'], + ); + expect(schema.type.v, SchemaType.object); + expect(schema.properties.v, properties); + expect(schema.optionalProperties, ['age']); + expect(schema.toSchemaJsonMap(), { + 'type': 'OBJECT', + 'properties': { + 'name': {'type': 'STRING'}, + 'age': {'type': 'INTEGER'}, + }, + 'required': ['name'], + }); + _checkToJsonSchema(schema); + }); + + test('Schema.object with empty optionalProperties', () { + final properties = { + 'name': CvJsonSchemaString(), + 'age': CvJsonSchemaInt(), + }; + final schema = CvJsonSchemaMap( + properties: properties, + ); + expect(schema.type.v, SchemaType.object); + expect(schema.properties.v, properties); + expect(schema.toSchemaJsonMap(), { + 'type': 'OBJECT', + 'properties': { + 'name': {'type': 'STRING'}, + 'age': {'type': 'INTEGER'}, + }, + 'required': ['name', 'age'], + }); + }); + }); +} diff --git a/app_json_schema/test/json_schema_test.dart b/app_json_schema/test/json_schema_test.dart new file mode 100644 index 0000000..294ad67 --- /dev/null +++ b/app_json_schema/test/json_schema_test.dart @@ -0,0 +1,142 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:tekartik_app_json_schema/json_schema.dart'; +import 'package:test/test.dart'; + +typedef SchemaType = JsonSchemaType; +typedef Schema = JsonSchema; +void main() { + group('Schema Tests', () { + // Test basic constructors and toJson() for primitive types + test('Schema.boolean', () { + final schema = + Schema.boolean(description: 'A boolean value', nullable: true); + expect(schema.type, SchemaType.boolean); + expect(schema.description, 'A boolean value'); + expect(schema.nullable, true); + expect(schema.toJson(), { + 'type': 'BOOLEAN', + 'description': 'A boolean value', + 'nullable': true, + }); + }); + + test('Schema.integer', () { + final schema = Schema.integer(format: 'int32'); + expect(schema.type, SchemaType.integer); + expect(schema.format, 'int32'); + expect(schema.toJson(), { + 'type': 'INTEGER', + 'format': 'int32', + }); + }); + + test('Schema.number', () { + final schema = Schema.number(format: 'double', nullable: false); + expect(schema.type, SchemaType.number); + expect(schema.format, 'double'); + expect(schema.nullable, false); + expect(schema.toJson(), { + 'type': 'NUMBER', + 'format': 'double', + 'nullable': false, + }); + }); + + test('Schema.string', () { + final schema = Schema.string(); + expect(schema.type, SchemaType.string); + expect(schema.toJson(), {'type': 'STRING'}); + }); + + test('Schema.enumString', () { + final schema = Schema.enumString(enumValues: ['value1', 'value2']); + expect(schema.type, SchemaType.string); + expect(schema.format, 'enum'); + expect(schema.enumValues, ['value1', 'value2']); + expect(schema.toJson(), { + 'type': 'STRING', + 'format': 'enum', + 'enum': ['value1', 'value2'], + }); + }); + + // Test constructors and toJson() for complex types + test('Schema.array', () { + final itemSchema = Schema.string(); + final schema = Schema.array(items: itemSchema); + expect(schema.type, SchemaType.array); + expect(schema.items, itemSchema); + expect(schema.toJson(), { + 'type': 'ARRAY', + 'items': {'type': 'STRING'}, + }); + }); + + test('Schema.object', () { + final properties = { + 'name': Schema.string(), + 'age': Schema.integer(), + }; + final schema = Schema.object( + properties: properties, + optionalProperties: ['age'], + ); + expect(schema.type, SchemaType.object); + expect(schema.properties, properties); + expect(schema.optionalProperties, ['age']); + expect(schema.toJson(), { + 'type': 'OBJECT', + 'properties': { + 'name': {'type': 'STRING'}, + 'age': {'type': 'INTEGER'}, + }, + 'required': ['name'], + }); + }); + + test('Schema.object with empty optionalProperties', () { + final properties = { + 'name': Schema.string(), + 'age': Schema.integer(), + }; + final schema = Schema.object( + properties: properties, + ); + expect(schema.type, SchemaType.object); + expect(schema.properties, properties); + expect(schema.toJson(), { + 'type': 'OBJECT', + 'properties': { + 'name': {'type': 'STRING'}, + 'age': {'type': 'INTEGER'}, + }, + 'required': ['name', 'age'], + }); + }); + + // Test SchemaType.toJson() + test('SchemaType.toJson', () { + expect(SchemaType.string.toJson(), 'STRING'); + expect(SchemaType.number.toJson(), 'NUMBER'); + expect(SchemaType.integer.toJson(), 'INTEGER'); + expect(SchemaType.boolean.toJson(), 'BOOLEAN'); + expect(SchemaType.array.toJson(), 'ARRAY'); + expect(SchemaType.object.toJson(), 'OBJECT'); + }); + + // Add more tests as needed to cover other scenarios and edge cases + }); +} diff --git a/repo_support/pubspec.yaml b/repo_support/pubspec.yaml index 1cab9d9..077637a 100644 --- a/repo_support/pubspec.yaml +++ b/repo_support/pubspec.yaml @@ -9,6 +9,11 @@ environment: dependencies: path: ">=1.9.0" process_run: ">=1.2.1+1" + tekartik_ci: + git: + url: https://github.com/tekartik/ci.dart + ref: dart3a + path: ci dev_dependencies: test: ">=1.24.0" diff --git a/repo_support/tool/run_ci.dart b/repo_support/tool/run_ci.dart index 5918062..3c1e446 100644 --- a/repo_support/tool/run_ci.dart +++ b/repo_support/tool/run_ci.dart @@ -2,6 +2,8 @@ import 'package:dev_build/package.dart'; import 'package:path/path.dart'; Future main() async { + await packageRunCi(join('..'), options: PackageRunCiOptions(recursive: true)); + /* for (var dir in [ 'app_intl', 'app_cv_firestore', @@ -24,5 +26,5 @@ Future main() async { 'app_archive', ]) { await packageRunCi(join('..', dir)); - } + }*/ } diff --git a/repo_support/tool/setup_env.dart b/repo_support/tool/setup_env.dart index 70666e4..eb64671 100644 --- a/repo_support/tool/setup_env.dart +++ b/repo_support/tool/setup_env.dart @@ -1,9 +1,5 @@ -import 'dart:io'; -import 'package:process_run/shell_run.dart'; +import 'package:tekartik_ci/setup_ci_github.dart'; Future main() async { - if (Platform.isLinux) { - // Assuming ubuntu, to run as root, this is mainly for CI - await run('sudo apt-get -y install libsqlite3-0 libsqlite3-dev'); - } + await sudoSetupSqlite3Lib(); }