diff --git a/examples/query-database/src/main/scala/example/QueryDatabase.scala b/examples/query-database/src/main/scala/example/QueryDatabase.scala index 5d38d80a..8a90e0d7 100644 --- a/examples/query-database/src/main/scala/example/QueryDatabase.scala +++ b/examples/query-database/src/main/scala/example/QueryDatabase.scala @@ -16,7 +16,7 @@ object QueryDatabase extends ZIOAppDefault { ) def example: ZIO[Notion, NotionError, Unit] = { - val filter = number("Col1") >= 10 and date("Col2") <= LocalDate.of(2022, 2, 2) + val filter = $"Col1".asNumber >= 10 and $"Col2".asDate <= LocalDate.of(2022, 2, 2) val sorts = $"Col1".descending andThen createdTime for { diff --git a/examples/update-page/src/main/scala/example/UpdatePage.scala b/examples/update-page/src/main/scala/example/UpdatePage.scala index 85bb3073..916a6b60 100644 --- a/examples/update-page/src/main/scala/example/UpdatePage.scala +++ b/examples/update-page/src/main/scala/example/UpdatePage.scala @@ -4,8 +4,8 @@ import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend import zio._ import zio.notion._ +import zio.notion.dsl._ import zio.notion.model.page.Page -import zio.notion.model.page.patch.PatchedProperty._ import java.time.LocalDate @@ -20,8 +20,8 @@ object UpdatePage extends ZIOAppDefault { for { patch0 <- Right(page.patch) - patch1 <- patch0.updateProperty(PatchedNumber.ceil.on("Col1")) - patch2 <- patch1.updateProperty(PatchedDate.between(date, date.plusDays(14)).on("Col2")) + patch1 <- patch0.updateProperty($"col1".asNumber.patch.ceil) + patch2 <- patch1.updateProperty($"col2".asDate.patch.between(date, date.plusDays(14))) } yield patch2.archive } diff --git a/zio-notion-core/src/main/scala/zio/notion/Main.scala b/zio-notion-core/src/main/scala/zio/notion/Main.scala index 76f6607c..50838472 100644 --- a/zio-notion-core/src/main/scala/zio/notion/Main.scala +++ b/zio-notion-core/src/main/scala/zio/notion/Main.scala @@ -38,7 +38,7 @@ object Main extends ZIOAppDefault { val configuration: NotionConfiguration = NotionConfiguration(bearer = "secret_dnjrnOCfZBOiKsITF8AFDNL5QwYYHF5t7Rysbl0Mfzd") val sorts: Sorts = $"Name".ascending - val filter: Filter = title("Name").startsWith("a") + val filter: Filter = $"Name".asTitle.startsWith("a") def app: ZIO[Notion, NotionError, Unit] = for { diff --git a/zio-notion-core/src/main/scala/zio/notion/PropertyUpdater.scala b/zio-notion-core/src/main/scala/zio/notion/PropertyUpdater.scala index ca322ae3..544cd7ef 100644 --- a/zio-notion-core/src/main/scala/zio/notion/PropertyUpdater.scala +++ b/zio-notion-core/src/main/scala/zio/notion/PropertyUpdater.scala @@ -12,48 +12,20 @@ object PropertyUpdater { final case class One(key: String) extends ColumnMatcher } - final case class FieldSetter[T <: PatchedProperty](matcher: ColumnMatcher, value: T) extends PropertyUpdater[Nothing, T] - final case class FieldUpdater[+E, T <: PatchedProperty](matcher: ColumnMatcher, transform: T => Either[E, T]) - extends PropertyUpdater[E, T] - - type UTransformation[P <: PatchedProperty] = Transformation[Nothing, P] - - trait Patch[E, P <: PatchedProperty] - - trait Transformation[E, P <: PatchedProperty] extends Patch[E, P] { self => - def transform(property: P): Either[E, P] - - def map(f: P => P): Transformation[E, P] = Transformation(p => transform(p).map(f)) - - def andThen(next: Transformation[E, P]): Transformation[E, P] = date => transform(date).flatMap(curr => next.transform(curr)) - - def on(fieldName: String): FieldUpdater[E, P] = FieldUpdater(ColumnMatcher.One(fieldName), transform) - - def onAll: FieldUpdater[E, P] = FieldUpdater(ColumnMatcher.Predicate(_ => true), transform) - - def onAllMatching(predicate: String => Boolean): FieldUpdater[E, P] = FieldUpdater(ColumnMatcher.Predicate(predicate), transform) + final case class FieldSetter[P <: PatchedProperty](matcher: ColumnMatcher, value: P) extends PropertyUpdater[Nothing, P] { + def map(f: P => P): FieldSetter[P] = FieldSetter(matcher, f(value)) } - - object Transformation { - def apply[E, P <: PatchedProperty](f: P => Either[E, P]): Transformation[E, P] = (property: P) => f(property) - - def succeed[P <: PatchedProperty](f: P => P): UTransformation[P] = Transformation(p => Right(f(p))) + final case class FieldUpdater[+E, P <: PatchedProperty]( + matcher: ColumnMatcher, + f: P => Either[E, P] + ) extends PropertyUpdater[E, P] { + def map(g: P => P): FieldUpdater[E, P] = FieldUpdater(matcher, property => f(property).map(g)) } - trait Setter[P <: PatchedProperty] extends Patch[Nothing, P] { - protected def build(): P - final def value: P = build() - - def map(f: P => P): Setter[P] = Setter(f(value)) - - final def on(fieldName: String): FieldSetter[P] = FieldSetter(ColumnMatcher.One(fieldName), value) - - def onAll: FieldSetter[P] = FieldSetter(ColumnMatcher.Predicate(_ => true), value) - - def onAllMatching(predicate: String => Boolean): FieldSetter[P] = FieldSetter(ColumnMatcher.Predicate(predicate), value) - } + type UFieldUpdater[P <: PatchedProperty] = FieldUpdater[Nothing, P] - object Setter { - def apply[P <: PatchedProperty](p: P): Setter[P] = () => p + object FieldUpdater { + def succeed[E, P <: PatchedProperty](matcher: ColumnMatcher, f: P => P): FieldUpdater[E, P] = + FieldUpdater(matcher, property => Right(f(property))) } } diff --git a/zio-notion-core/src/main/scala/zio/notion/dsl/Column.scala b/zio-notion-core/src/main/scala/zio/notion/dsl/Column.scala index 73ec16bc..ed96f9b1 100644 --- a/zio-notion-core/src/main/scala/zio/notion/dsl/Column.scala +++ b/zio-notion-core/src/main/scala/zio/notion/dsl/Column.scala @@ -1,53 +1,282 @@ package zio.notion.dsl -import zio.notion.dsl.DatabaseQueryDSL._ +import zio.notion.PropertyUpdater.ColumnMatcher._ +import zio.notion.dsl.Column._ +import zio.notion.dsl.PatchedColumn._ +import zio.notion.model.database.query.PropertyFilter +import zio.notion.model.database.query.PropertyFilter._ +import zio.notion.model.database.query.PropertyFilter.DatePropertyFilter.{ + After, + Before, + NextMonth, + NextWeek, + NextYear, + OnOrAfter, + OnOrBefore, + PastMonth, + PastWeek +} +import zio.notion.model.database.query.PropertyFilter.NumberPropertyFilter.{GreaterThan, GreaterThanOrEqualTo, LessThan, LessThanOrEqualTo} +import zio.notion.model.database.query.PropertyFilter.TextPropertyFilter.{EndsWith, StartsWith} import zio.notion.model.database.query.Sorts.Sorting import zio.notion.model.database.query.Sorts.Sorting.Property +import java.time.LocalDate + final case class Column(colName: String) { + def definition: ColumnDefinition = colDefinition(colName) + // sorts def ascending: Sorting = Property(colName, ascending = true) def descending: Sorting = Property(colName, ascending = false) - def definition: ColumnDefinition = colDefinition(colName) - // filters - def asNumber: NumberFilterConstructor = NumberFilterConstructor(colName) + def asNumber: NumberDSLConstructor = NumberDSLConstructor(colName) - def asTitle: TitleFilterConstructor = TitleFilterConstructor(colName) + def asTitle: TitleDSLConstructor = TitleDSLConstructor(colName) - def asRichText: RichTextFilterConstructor = RichTextFilterConstructor(colName) + def asRichText: RichTextDSLConstructor = RichTextDSLConstructor(colName) - def asCheckbox: CheckboxFilterConstructor = CheckboxFilterConstructor(colName) + def asCheckbox: CheckboxDSLConstructor = CheckboxDSLConstructor(colName) - def asSelect: SelectFilterConstructor = SelectFilterConstructor(colName) + def asSelect: SelectDSLConstructor = SelectDSLConstructor(colName) - def asMultiSelect: MultiSelectFilterConstructor = MultiSelectFilterConstructor(colName) + def asMultiSelect: MultiSelectDSLConstructor = MultiSelectDSLConstructor(colName) - def asDate: DateFilterConstructor = DateFilterConstructor(colName) + def asDate: DateDSLConstructor = DateDSLConstructor(colName) - def asPeople: PeopleConstructor = PeopleConstructor(colName) + def asPeople: PeopleDSLConstructor = PeopleDSLConstructor(colName) - def asFiles: FilesConstructor = FilesConstructor(colName) + def asFiles: FilesDSLConstructor = FilesDSLConstructor(colName) - def asUrl: UrlFilterConstructor = UrlFilterConstructor(colName) + def asUrl: UrlDSLConstructor = UrlDSLConstructor(colName) - def asEmail: EmailFilterConstructor = EmailFilterConstructor(colName) + def asEmail: EmailDSLConstructor = EmailDSLConstructor(colName) - def asPhoneNumber: PhoneNumberFilterConstructor = PhoneNumberFilterConstructor(colName) + def asPhoneNumber: PhoneNumberDSLConstructor = PhoneNumberDSLConstructor(colName) - def asRelation: RelationFilterConstructor = RelationFilterConstructor(colName) + def asRelation: RelationDSLConstructor = RelationDSLConstructor(colName) - def asCreatedBy: CreatedByConstructor = CreatedByConstructor(colName) + def asCreatedBy: CreatedByDSLConstructor = CreatedByDSLConstructor(colName) - def asLastEditedBy: LastEditedByConstructor = LastEditedByConstructor(colName) + def asLastEditedBy: LastEditedByDSLConstructor = LastEditedByDSLConstructor(colName) - def asCreatedTime: CreatedTimeFilterConstructor = CreatedTimeFilterConstructor(colName) + def asCreatedTime: CreatedTimeDSLConstructor = CreatedTimeDSLConstructor(colName) - def asLastEditedTime: LastEditedTimeFilterConstructor = LastEditedTimeFilterConstructor(colName) + def asLastEditedTime: LastEditedTimeDSLConstructor = LastEditedTimeDSLConstructor(colName) + + // TODO Formula DSL + + // TODO Rollup DSL } -final case class Columns(predicate: String => Boolean) { - def definition: ColumnDefinitions = columnDefinitionsMatching(predicate) +object Column { + final case class TitleDSLConstructor private (property: String) { + def startsWith(string: String): Title = Title(property, StartsWith(string)) + def endsWith(string: String): Title = Title(property, EndsWith(string)) + def equals(string: String): Title = Title(property, Equals(string)) + def doesNotEqual(string: String): Title = Title(property, DoesNotEqual(string)) + def contains(string: String): Title = Title(property, Contains(string)) + def doesNotContain(string: String): Title = Title(property, DoesNotContain(string)) + def isEmpty: Title = Title(property, IsEmpty(true)) + def isNotEmpty: Title = Title(property, IsNotEmpty(true)) + + def patch: PatchedColumnTitle = PatchedColumnTitle(One(property)) + } + + final case class RichTextDSLConstructor private (property: String) { + def startsWith(string: String): RichText = RichText(property, StartsWith(string)) + def endsWith(string: String): RichText = RichText(property, EndsWith(string)) + def equals(string: String): RichText = RichText(property, Equals(string)) + def doesNotEqual(string: String): RichText = RichText(property, DoesNotEqual(string)) + def contains(string: String): RichText = RichText(property, Contains(string)) + def doesNotContain(string: String): RichText = RichText(property, DoesNotContain(string)) + def isEmpty: RichText = RichText(property, IsEmpty(true)) + def isNotEmpty: RichText = RichText(property, IsNotEmpty(true)) + + def patch: PatchedColumnRichText = PatchedColumnRichText(One(property)) + } + + final case class NumberDSLConstructor private (property: String) { + def equals(double: Double): Number = Number(property, NumberPropertyFilter.Equals(double)) + def doesNotEqual(double: Double): Number = Number(property, NumberPropertyFilter.DoesNotEqual(double)) + def greaterThan(double: Double): Number = Number(property, GreaterThan(double)) + def lessThan(double: Double): Number = Number(property, LessThan(double)) + def greaterThanOrEqualTo(double: Double): Number = Number(property, GreaterThanOrEqualTo(double)) + def lessThanOrEqualTo(double: Double): Number = Number(property, LessThanOrEqualTo(double)) + def isEmpty: Number = Number(property, IsEmpty(true)) + def isNotEmpty: Number = Number(property, IsNotEmpty(true)) + + def ==(double: Double): Number = equals(double) + def !=(double: Double): Number = doesNotEqual(double) + def >(double: Double): Number = greaterThan(double) + def <(double: Double): Number = lessThan(double) + def >=(double: Double): Number = greaterThanOrEqualTo(double) + def <=(double: Double): Number = lessThanOrEqualTo(double) + + def patch: PatchedColumnNumber = PatchedColumnNumber(One(property)) + } + + final case class CheckboxDSLConstructor private (property: String) { + def equals(boolean: Boolean): Checkbox = Checkbox(property, CheckboxPropertyFilter.Equals(boolean)) + def doesNotEqual(boolean: Boolean): Checkbox = Checkbox(property, CheckboxPropertyFilter.DoesNotEqual(boolean)) + + def isTrue: Checkbox = equals(true) + def isFalse: Checkbox = equals(false) + + def patch: PatchedColumnCheckbox = PatchedColumnCheckbox(One(property)) + } + + final case class SelectDSLConstructor private (property: String) { + def equals(string: String): Select = Select(property, Equals(string)) + def doesNotEqual(string: String): Select = Select(property, DoesNotEqual(string)) + def isEmpty: Select = Select(property, IsEmpty(true)) + def isNotEmpty: Select = Select(property, IsNotEmpty(true)) + + def patch: PatchedColumnSelect = PatchedColumnSelect(One(property)) + } + + final case class MultiSelectDSLConstructor private (property: String) { + def equals(string: String): MultiSelect = MultiSelect(property, Equals(string)) + def doesNotEqual(string: String): MultiSelect = MultiSelect(property, DoesNotEqual(string)) + def contains(string: String): MultiSelect = MultiSelect(property, Contains(string)) + def doesNotContain(string: String): MultiSelect = MultiSelect(property, DoesNotContain(string)) + def isEmpty: MultiSelect = MultiSelect(property, IsEmpty(true)) + def isNotEmpty: MultiSelect = MultiSelect(property, IsNotEmpty(true)) + + def patch: PatchedColumnMultiSelect = PatchedColumnMultiSelect(One(property)) + } + + final case class DateDSLConstructor private (property: String) { + def equals(date: LocalDate): Date = Date(property, Equals(date.toString)) + def before(date: LocalDate): Date = Date(property, Before(date.toString)) + def after(date: LocalDate): Date = Date(property, After(date.toString)) + def onOrBefore(date: LocalDate): Date = Date(property, OnOrBefore(date.toString)) + def onOrAfter(date: LocalDate): Date = Date(property, OnOrAfter(date.toString)) + def pastWeek: Date = Date(property, PastWeek) + def pastMonth: Date = Date(property, PastMonth) + def nextWeek: Date = Date(property, NextWeek) + def nextMonth: Date = Date(property, NextMonth) + def nextYear: Date = Date(property, NextYear) + def isEmpty: Date = Date(property, IsEmpty(true)) + def isNotEmpty: Date = Date(property, IsNotEmpty(true)) + + def >(date: LocalDate): Date = after(date) + def <(date: LocalDate): Date = before(date) + def >=(date: LocalDate): Date = onOrAfter(date) + def <=(date: LocalDate): Date = onOrBefore(date) + + def patch: PatchedColumnDate = PatchedColumnDate(One(property)) + } + + final case class PeopleDSLConstructor private (property: String) { + def contains(string: String): People = People(property, Contains(string)) + def doesNotContain(string: String): People = People(property, DoesNotContain(string)) + def isEmpty: People = People(property, IsEmpty(true)) + def isNotEmpty: People = People(property, IsNotEmpty(true)) + + def patch: PatchedColumnPeople = PatchedColumnPeople(One(property)) + } + + final case class FilesDSLConstructor private (property: String) { + def isEmpty: Files = Files(property, IsEmpty(true)) + def isNotEmpty: Files = Files(property, IsNotEmpty(true)) + + def patch: PatchedColumnFiles = PatchedColumnFiles(One(property)) + } + + final case class UrlDSLConstructor private (property: String) { + def equals(string: String): Url = Url(property, Equals(string)) + def doesNotEqual(string: String): Url = Url(property, DoesNotEqual(string)) + def contains(string: String): Url = Url(property, Contains(string)) + def doesNotContain(string: String): Url = Url(property, DoesNotContain(string)) + def isEmpty: Url = Url(property, IsEmpty(true)) + def isNotEmpty: Url = Url(property, IsNotEmpty(true)) + + def patch: PatchedColumnUrl = PatchedColumnUrl(One(property)) + } + + final case class EmailDSLConstructor private (property: String) { + def equals(string: String): Email = Email(property, Equals(string)) + def doesNotEqual(string: String): Email = Email(property, DoesNotEqual(string)) + def contains(string: String): Email = Email(property, Contains(string)) + def doesNotContain(string: String): Email = Email(property, DoesNotContain(string)) + def isEmpty: Email = Email(property, IsEmpty(true)) + def isNotEmpty: Email = Email(property, IsNotEmpty(true)) + + def patch: PatchedColumnEmail = PatchedColumnEmail(One(property)) + } + + final case class PhoneNumberDSLConstructor private (property: String) { + def equals(string: String): PhoneNumber = PhoneNumber(property, Equals(string)) + def doesNotEqual(string: String): PhoneNumber = PhoneNumber(property, DoesNotEqual(string)) + def contains(string: String): PhoneNumber = PhoneNumber(property, Contains(string)) + def doesNotContain(string: String): PhoneNumber = PhoneNumber(property, DoesNotContain(string)) + def isEmpty: PhoneNumber = PhoneNumber(property, IsEmpty(true)) + def isNotEmpty: PhoneNumber = PhoneNumber(property, IsNotEmpty(true)) + + def patch: PatchedColumnPhoneNumber = PatchedColumnPhoneNumber(One(property)) + } + + final case class RelationDSLConstructor private (property: String) { + def contains(string: String): Relation = Relation(property, Contains(string)) + def doesNotContain(string: String): Relation = Relation(property, DoesNotContain(string)) + def isEmpty: Relation = Relation(property, IsEmpty(true)) + def isNotEmpty: Relation = Relation(property, IsNotEmpty(true)) + } + + final case class CreatedByDSLConstructor private (property: String) { + def contains(string: String): CreatedBy = CreatedBy(property, Contains(string)) + def doesNotContain(string: String): CreatedBy = CreatedBy(property, DoesNotContain(string)) + def isEmpty: CreatedBy = CreatedBy(property, IsEmpty(true)) + def isNotEmpty: CreatedBy = CreatedBy(property, IsNotEmpty(true)) + } + + final case class LastEditedByDSLConstructor private (property: String) { + def contains(string: String): LastEditedBy = LastEditedBy(property, Contains(string)) + def doesNotContain(string: String): LastEditedBy = LastEditedBy(property, DoesNotContain(string)) + def isEmpty: LastEditedBy = LastEditedBy(property, IsEmpty(true)) + def isNotEmpty: LastEditedBy = LastEditedBy(property, IsNotEmpty(true)) + } + + final case class CreatedTimeDSLConstructor private (property: String) { + def equals(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, Equals(date.toString)) + def before(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, Before(date.toString)) + def after(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, After(date.toString)) + def onOrBefore(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, OnOrBefore(date.toString)) + def onOrAfter(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, OnOrAfter(date.toString)) + def pastWeek: CreatedTime = PropertyFilter.CreatedTime(property, PastWeek) + def pastMonth: CreatedTime = PropertyFilter.CreatedTime(property, PastMonth) + def nextWeek: CreatedTime = PropertyFilter.CreatedTime(property, NextWeek) + def nextMonth: CreatedTime = PropertyFilter.CreatedTime(property, NextMonth) + def nextYear: CreatedTime = PropertyFilter.CreatedTime(property, NextYear) + def isEmpty: CreatedTime = PropertyFilter.CreatedTime(property, IsEmpty(true)) + def isNotEmpty: CreatedTime = PropertyFilter.CreatedTime(property, IsNotEmpty(true)) + + def >(date: LocalDate): CreatedTime = after(date) + def <(date: LocalDate): CreatedTime = before(date) + def >=(date: LocalDate): CreatedTime = onOrAfter(date) + def <=(date: LocalDate): CreatedTime = onOrBefore(date) + } + + final case class LastEditedTimeDSLConstructor private (property: String) { + def equals(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, Equals(date.toString)) + def before(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, Before(date.toString)) + def after(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, After(date.toString)) + def onOrBefore(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, OnOrBefore(date.toString)) + def onOrAfter(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, OnOrAfter(date.toString)) + def pastWeek: LastEditedTime = PropertyFilter.LastEditedTime(property, PastWeek) + def pastMonth: LastEditedTime = PropertyFilter.LastEditedTime(property, PastMonth) + def nextWeek: LastEditedTime = PropertyFilter.LastEditedTime(property, NextWeek) + def nextMonth: LastEditedTime = PropertyFilter.LastEditedTime(property, NextMonth) + def nextYear: LastEditedTime = PropertyFilter.LastEditedTime(property, NextYear) + def isEmpty: LastEditedTime = PropertyFilter.LastEditedTime(property, IsEmpty(true)) + def isNotEmpty: LastEditedTime = PropertyFilter.LastEditedTime(property, IsNotEmpty(true)) + + def >(date: LocalDate): LastEditedTime = after(date) + def <(date: LocalDate): LastEditedTime = before(date) + def >=(date: LocalDate): LastEditedTime = onOrAfter(date) + def <=(date: LocalDate): LastEditedTime = onOrBefore(date) + } } diff --git a/zio-notion-core/src/main/scala/zio/notion/dsl/ColumnDefinition.scala b/zio-notion-core/src/main/scala/zio/notion/dsl/ColumnDefinition.scala index 02e34b87..70415c3b 100644 --- a/zio-notion-core/src/main/scala/zio/notion/dsl/ColumnDefinition.scala +++ b/zio-notion-core/src/main/scala/zio/notion/dsl/ColumnDefinition.scala @@ -1,24 +1,7 @@ package zio.notion.dsl -import zio.notion.PropertyUpdater.ColumnMatcher import zio.notion.model.database.patch.PatchPlan -import zio.notion.model.database.patch.PatchPlan.PropertyType -trait Definition { - def patchPlan: PatchPlan - def matcher: ColumnMatcher -} - -final case class ColumnDefinition(colName: String, patchPlan: PatchPlan) extends Definition { - def rename(name: String): ColumnDefinition = copy(patchPlan = patchPlan.copy(name = Some(name))) - - def as(propertyType: PropertyType): ColumnDefinition = copy(patchPlan = patchPlan.copy(propertyType = Some(propertyType))) - - override def matcher: ColumnMatcher = ColumnMatcher.One(colName) -} - -final case class ColumnDefinitions(predicate: String => Boolean, patchPlan: PatchPlan) extends Definition { - def as(propertyType: PropertyType): ColumnDefinitions = copy(patchPlan = patchPlan.copy(propertyType = Some(propertyType))) - - override def matcher: ColumnMatcher = ColumnMatcher.Predicate(predicate) +final case class ColumnDefinition(colName: String) { + def patch: PatchedColumnDefinition = PatchedColumnDefinition(colName, PatchPlan.unit) } diff --git a/zio-notion-core/src/main/scala/zio/notion/dsl/ColumnDefinitions.scala b/zio-notion-core/src/main/scala/zio/notion/dsl/ColumnDefinitions.scala new file mode 100644 index 00000000..3fec3bba --- /dev/null +++ b/zio-notion-core/src/main/scala/zio/notion/dsl/ColumnDefinitions.scala @@ -0,0 +1,7 @@ +package zio.notion.dsl + +import zio.notion.model.database.patch.PatchPlan + +final case class ColumnDefinitions(predicate: String => Boolean) { + def patch: PatchedColumnDefinitions = PatchedColumnDefinitions(predicate, PatchPlan.unit) +} diff --git a/zio-notion-core/src/main/scala/zio/notion/dsl/Columns.scala b/zio-notion-core/src/main/scala/zio/notion/dsl/Columns.scala new file mode 100644 index 00000000..5ffb85eb --- /dev/null +++ b/zio-notion-core/src/main/scala/zio/notion/dsl/Columns.scala @@ -0,0 +1,83 @@ +package zio.notion.dsl + +import zio.notion.PropertyUpdater.ColumnMatcher.Predicate +import zio.notion.dsl.Columns._ +import zio.notion.dsl.PatchedColumn._ + +final case class Columns(predicate: String => Boolean) { + def definition: ColumnDefinitions = columnDefinitionsMatching(predicate) + + def asNumber: NumberDSLConstructor = NumberDSLConstructor(predicate) + + def asTitle: TitleDSLConstructor = TitleDSLConstructor(predicate) + + def asRichText: RichTextDSLConstructor = RichTextDSLConstructor(predicate) + + def asCheckbox: CheckboxDSLConstructor = CheckboxDSLConstructor(predicate) + + def asSelect: SelectDSLConstructor = SelectDSLConstructor(predicate) + + def asMultiSelect: MultiSelectDSLConstructor = MultiSelectDSLConstructor(predicate) + + def asDate: DateDSLConstructor = DateDSLConstructor(predicate) + + def asPeople: PeopleDSLConstructor = PeopleDSLConstructor(predicate) + + def asFiles: FilesDSLConstructor = FilesDSLConstructor(predicate) + + def asUrl: UrlDSLConstructor = UrlDSLConstructor(predicate) + + def asEmail: EmailDSLConstructor = EmailDSLConstructor(predicate) + + def asPhoneNumber: PhoneNumberDSLConstructor = PhoneNumberDSLConstructor(predicate) +} + +object Columns { + final case class TitleDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnTitle = PatchedColumnTitle(Predicate(predicate)) + } + + final case class RichTextDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnRichText = PatchedColumnRichText(Predicate(predicate)) + } + + final case class NumberDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnNumber = PatchedColumnNumber(Predicate(predicate)) + } + + final case class CheckboxDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnCheckbox = PatchedColumnCheckbox(Predicate(predicate)) + } + + final case class SelectDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnSelect = PatchedColumnSelect(Predicate(predicate)) + } + + final case class MultiSelectDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnMultiSelect = PatchedColumnMultiSelect(Predicate(predicate)) + } + + final case class DateDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnDate = PatchedColumnDate(Predicate(predicate)) + } + + final case class PeopleDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnPeople = PatchedColumnPeople(Predicate(predicate)) + } + + final case class FilesDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnFiles = PatchedColumnFiles(Predicate(predicate)) + } + + final case class UrlDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnUrl = PatchedColumnUrl(Predicate(predicate)) + } + + final case class EmailDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnEmail = PatchedColumnEmail(Predicate(predicate)) + } + + final case class PhoneNumberDSLConstructor private (predicate: String => Boolean) { + def patch: PatchedColumnPhoneNumber = PatchedColumnPhoneNumber(Predicate(predicate)) + } +} diff --git a/zio-notion-core/src/main/scala/zio/notion/dsl/DatabaseQueryDSL.scala b/zio-notion-core/src/main/scala/zio/notion/dsl/DatabaseQueryDSL.scala index 6a524e4f..b6d0cbd7 100644 --- a/zio-notion-core/src/main/scala/zio/notion/dsl/DatabaseQueryDSL.scala +++ b/zio-notion-core/src/main/scala/zio/notion/dsl/DatabaseQueryDSL.scala @@ -1,16 +1,10 @@ package zio.notion.dsl import zio.notion.model.database.query.{Filter, PropertyFilter, Sorts} -import zio.notion.model.database.query.PropertyFilter._ -import zio.notion.model.database.query.PropertyFilter.DatePropertyFilter._ -import zio.notion.model.database.query.PropertyFilter.NumberPropertyFilter.{GreaterThan, GreaterThanOrEqualTo, LessThan, LessThanOrEqualTo} -import zio.notion.model.database.query.PropertyFilter.TextPropertyFilter.{EndsWith, StartsWith} import zio.notion.model.database.query.Sorts.Sorting import zio.notion.model.database.query.Sorts.Sorting._ import zio.notion.model.database.query.Sorts.Sorting.TimestampType.{CreatedTime, LastEditedTime} -import java.time.LocalDate - trait DatabaseQueryDSL { // Sort helpers @@ -27,228 +21,6 @@ trait DatabaseQueryDSL { // Filter helpers implicit def propertyFilterToFilter(propertyFilter: PropertyFilter): Filter = Filter.One(propertyFilter) - - case class TitleFilterConstructor private (property: String) { - def startsWith(string: String): Title = Title(property, StartsWith(string)) - def endsWith(string: String): Title = Title(property, EndsWith(string)) - def equals(string: String): Title = Title(property, Equals(string)) - def doesNotEqual(string: String): Title = Title(property, DoesNotEqual(string)) - def contains(string: String): Title = Title(property, Contains(string)) - def doesNotContain(string: String): Title = Title(property, DoesNotContain(string)) - def isEmpty: Title = Title(property, IsEmpty(true)) - def isNotEmpty: Title = Title(property, IsNotEmpty(true)) - } - - def title(propertyName: String): TitleFilterConstructor = TitleFilterConstructor(propertyName) - - case class RichTextFilterConstructor private (property: String) { - def startsWith(string: String): RichText = RichText(property, StartsWith(string)) - def endsWith(string: String): RichText = RichText(property, EndsWith(string)) - def equals(string: String): RichText = RichText(property, Equals(string)) - def doesNotEqual(string: String): RichText = RichText(property, DoesNotEqual(string)) - def contains(string: String): RichText = RichText(property, Contains(string)) - def doesNotContain(string: String): RichText = RichText(property, DoesNotContain(string)) - def isEmpty: RichText = RichText(property, IsEmpty(true)) - def isNotEmpty: RichText = RichText(property, IsNotEmpty(true)) - } - - def richText(propertyName: String): RichTextFilterConstructor = RichTextFilterConstructor(propertyName) - - case class NumberFilterConstructor private (property: String) { - def equals(double: Double): Number = Number(property, NumberPropertyFilter.Equals(double)) - def doesNotEqual(double: Double): Number = Number(property, NumberPropertyFilter.DoesNotEqual(double)) - def greaterThan(double: Double): Number = Number(property, GreaterThan(double)) - def lessThan(double: Double): Number = Number(property, LessThan(double)) - def greaterThanOrEqualTo(double: Double): Number = Number(property, GreaterThanOrEqualTo(double)) - def lessThanOrEqualTo(double: Double): Number = Number(property, LessThanOrEqualTo(double)) - def isEmpty: Number = Number(property, IsEmpty(true)) - def isNotEmpty: Number = Number(property, IsNotEmpty(true)) - - def ==(double: Double): Number = equals(double) - def !=(double: Double): Number = doesNotEqual(double) - def >(double: Double): Number = greaterThan(double) - def <(double: Double): Number = lessThan(double) - def >=(double: Double): Number = greaterThanOrEqualTo(double) - def <=(double: Double): Number = lessThanOrEqualTo(double) - } - - def number(propertyName: String): NumberFilterConstructor = NumberFilterConstructor(propertyName) - - case class CheckboxFilterConstructor private (property: String) { - def equals(boolean: Boolean): Checkbox = Checkbox(property, CheckboxPropertyFilter.Equals(boolean)) - def doesNotEqual(boolean: Boolean): Checkbox = Checkbox(property, CheckboxPropertyFilter.DoesNotEqual(boolean)) - - def isTrue: Checkbox = equals(true) - def isFalse: Checkbox = equals(false) - } - - def checkbox(propertyName: String): CheckboxFilterConstructor = CheckboxFilterConstructor(propertyName) - - case class SelectFilterConstructor private (property: String) { - def equals(string: String): Select = Select(property, Equals(string)) - def doesNotEqual(string: String): Select = Select(property, DoesNotEqual(string)) - def isEmpty: Select = Select(property, IsEmpty(true)) - def isNotEmpty: Select = Select(property, IsNotEmpty(true)) - } - - def select(propertyName: String): SelectFilterConstructor = SelectFilterConstructor(propertyName) - - case class MultiSelectFilterConstructor private (property: String) { - def equals(string: String): MultiSelect = MultiSelect(property, Equals(string)) - def doesNotEqual(string: String): MultiSelect = MultiSelect(property, DoesNotEqual(string)) - def contains(string: String): MultiSelect = MultiSelect(property, Contains(string)) - def doesNotContain(string: String): MultiSelect = MultiSelect(property, DoesNotContain(string)) - def isEmpty: MultiSelect = MultiSelect(property, IsEmpty(true)) - def isNotEmpty: MultiSelect = MultiSelect(property, IsNotEmpty(true)) - } - - def multiSelect(propertyName: String): MultiSelectFilterConstructor = MultiSelectFilterConstructor(propertyName) - - case class DateFilterConstructor private (property: String) { - def equals(date: LocalDate): Date = Date(property, Equals(date.toString)) - def before(date: LocalDate): Date = Date(property, Before(date.toString)) - def after(date: LocalDate): Date = Date(property, After(date.toString)) - def onOrBefore(date: LocalDate): Date = Date(property, OnOrBefore(date.toString)) - def onOrAfter(date: LocalDate): Date = Date(property, OnOrAfter(date.toString)) - def pastWeek: Date = Date(property, PastWeek) - def pastMonth: Date = Date(property, PastMonth) - def nextWeek: Date = Date(property, NextWeek) - def nextMonth: Date = Date(property, NextMonth) - def nextYear: Date = Date(property, NextYear) - def isEmpty: Date = Date(property, IsEmpty(true)) - def isNotEmpty: Date = Date(property, IsNotEmpty(true)) - - def >(date: LocalDate): Date = after(date) - def <(date: LocalDate): Date = before(date) - def >=(date: LocalDate): Date = onOrAfter(date) - def <=(date: LocalDate): Date = onOrBefore(date) - } - - def date(propertyName: String): DateFilterConstructor = DateFilterConstructor(propertyName) - - case class PeopleConstructor private (property: String) { - def contains(string: String): People = People(property, Contains(string)) - def doesNotContain(string: String): People = People(property, DoesNotContain(string)) - def isEmpty: People = People(property, IsEmpty(true)) - def isNotEmpty: People = People(property, IsNotEmpty(true)) - } - - def people(propertyName: String): PeopleConstructor = PeopleConstructor(propertyName) - - case class FilesConstructor private (property: String) { - def isEmpty: Files = Files(property, IsEmpty(true)) - def isNotEmpty: Files = Files(property, IsNotEmpty(true)) - } - - def files(propertyName: String): FilesConstructor = FilesConstructor(propertyName) - - case class UrlFilterConstructor private (property: String) { - def equals(string: String): Url = Url(property, Equals(string)) - def doesNotEqual(string: String): Url = Url(property, DoesNotEqual(string)) - def contains(string: String): Url = Url(property, Contains(string)) - def doesNotContain(string: String): Url = Url(property, DoesNotContain(string)) - def isEmpty: Url = Url(property, IsEmpty(true)) - def isNotEmpty: Url = Url(property, IsNotEmpty(true)) - } - - def url(propertyName: String): UrlFilterConstructor = UrlFilterConstructor(propertyName) - - case class EmailFilterConstructor private (property: String) { - def equals(string: String): Email = Email(property, Equals(string)) - def doesNotEqual(string: String): Email = Email(property, DoesNotEqual(string)) - def contains(string: String): Email = Email(property, Contains(string)) - def doesNotContain(string: String): Email = Email(property, DoesNotContain(string)) - def isEmpty: Email = Email(property, IsEmpty(true)) - def isNotEmpty: Email = Email(property, IsNotEmpty(true)) - } - - def email(propertyName: String): EmailFilterConstructor = EmailFilterConstructor(propertyName) - - case class PhoneNumberFilterConstructor private (property: String) { - def equals(string: String): PhoneNumber = PhoneNumber(property, Equals(string)) - def doesNotEqual(string: String): PhoneNumber = PhoneNumber(property, DoesNotEqual(string)) - def contains(string: String): PhoneNumber = PhoneNumber(property, Contains(string)) - def doesNotContain(string: String): PhoneNumber = PhoneNumber(property, DoesNotContain(string)) - def isEmpty: PhoneNumber = PhoneNumber(property, IsEmpty(true)) - def isNotEmpty: PhoneNumber = PhoneNumber(property, IsNotEmpty(true)) - } - - def phoneNumber(propertyName: String): PhoneNumberFilterConstructor = PhoneNumberFilterConstructor(propertyName) - - case class RelationFilterConstructor private (property: String) { - def contains(string: String): Relation = Relation(property, Contains(string)) - def doesNotContain(string: String): Relation = Relation(property, DoesNotContain(string)) - def isEmpty: Relation = Relation(property, IsEmpty(true)) - def isNotEmpty: Relation = Relation(property, IsNotEmpty(true)) - } - - def relation(propertyName: String): RelationFilterConstructor = RelationFilterConstructor(propertyName) - - case class CreatedByConstructor private (property: String) { - def contains(string: String): CreatedBy = CreatedBy(property, Contains(string)) - def doesNotContain(string: String): CreatedBy = CreatedBy(property, DoesNotContain(string)) - def isEmpty: CreatedBy = CreatedBy(property, IsEmpty(true)) - def isNotEmpty: CreatedBy = CreatedBy(property, IsNotEmpty(true)) - } - - def createdBy(propertyName: String): CreatedByConstructor = CreatedByConstructor(propertyName) - - case class LastEditedByConstructor private (property: String) { - def contains(string: String): LastEditedBy = LastEditedBy(property, Contains(string)) - def doesNotContain(string: String): LastEditedBy = LastEditedBy(property, DoesNotContain(string)) - def isEmpty: LastEditedBy = LastEditedBy(property, IsEmpty(true)) - def isNotEmpty: LastEditedBy = LastEditedBy(property, IsNotEmpty(true)) - } - - def lastEditedBy(propertyName: String): LastEditedByConstructor = LastEditedByConstructor(propertyName) - - case class CreatedTimeFilterConstructor private (property: String) { - def equals(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, Equals(date.toString)) - def before(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, Before(date.toString)) - def after(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, After(date.toString)) - def onOrBefore(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, OnOrBefore(date.toString)) - def onOrAfter(date: LocalDate): CreatedTime = PropertyFilter.CreatedTime(property, OnOrAfter(date.toString)) - def pastWeek: CreatedTime = PropertyFilter.CreatedTime(property, PastWeek) - def pastMonth: CreatedTime = PropertyFilter.CreatedTime(property, PastMonth) - def nextWeek: CreatedTime = PropertyFilter.CreatedTime(property, NextWeek) - def nextMonth: CreatedTime = PropertyFilter.CreatedTime(property, NextMonth) - def nextYear: CreatedTime = PropertyFilter.CreatedTime(property, NextYear) - def isEmpty: CreatedTime = PropertyFilter.CreatedTime(property, IsEmpty(true)) - def isNotEmpty: CreatedTime = PropertyFilter.CreatedTime(property, IsNotEmpty(true)) - - def >(date: LocalDate): CreatedTime = after(date) - def <(date: LocalDate): CreatedTime = before(date) - def >=(date: LocalDate): CreatedTime = onOrAfter(date) - def <=(date: LocalDate): CreatedTime = onOrBefore(date) - } - - def createdTime(propertyName: String): CreatedTimeFilterConstructor = CreatedTimeFilterConstructor(propertyName) - - case class LastEditedTimeFilterConstructor private (property: String) { - def equals(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, Equals(date.toString)) - def before(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, Before(date.toString)) - def after(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, After(date.toString)) - def onOrBefore(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, OnOrBefore(date.toString)) - def onOrAfter(date: LocalDate): LastEditedTime = PropertyFilter.LastEditedTime(property, OnOrAfter(date.toString)) - def pastWeek: LastEditedTime = PropertyFilter.LastEditedTime(property, PastWeek) - def pastMonth: LastEditedTime = PropertyFilter.LastEditedTime(property, PastMonth) - def nextWeek: LastEditedTime = PropertyFilter.LastEditedTime(property, NextWeek) - def nextMonth: LastEditedTime = PropertyFilter.LastEditedTime(property, NextMonth) - def nextYear: LastEditedTime = PropertyFilter.LastEditedTime(property, NextYear) - def isEmpty: LastEditedTime = PropertyFilter.LastEditedTime(property, IsEmpty(true)) - def isNotEmpty: LastEditedTime = PropertyFilter.LastEditedTime(property, IsNotEmpty(true)) - - def >(date: LocalDate): LastEditedTime = after(date) - def <(date: LocalDate): LastEditedTime = before(date) - def >=(date: LocalDate): LastEditedTime = onOrAfter(date) - def <=(date: LocalDate): LastEditedTime = onOrBefore(date) - } - - def lastEditedTime(propertyName: String): LastEditedTimeFilterConstructor = LastEditedTimeFilterConstructor(propertyName) - - // TODO Formula DSL - - // TODO Rollup DSL } object DatabaseQueryDSL extends DatabaseQueryDSL diff --git a/zio-notion-core/src/main/scala/zio/notion/dsl/PatchedColumn.scala b/zio-notion-core/src/main/scala/zio/notion/dsl/PatchedColumn.scala new file mode 100644 index 00000000..05d9d1db --- /dev/null +++ b/zio-notion-core/src/main/scala/zio/notion/dsl/PatchedColumn.scala @@ -0,0 +1,140 @@ +package zio.notion.dsl + +import zio.Clock +import zio.notion.PropertyUpdater._ +import zio.notion.model.common.UserId +import zio.notion.model.common.enumeration.Color +import zio.notion.model.common.richtext.{Annotations, RichTextData} +import zio.notion.model.page.patch.PatchedProperty._ +import zio.notion.model.page.property.Link + +import java.time.LocalDate + +object PatchedColumn { + final case class PatchedColumnTitle(matcher: ColumnMatcher) { + def set(title: Seq[RichTextData.Text]): FieldSetter[PatchedTitle] = FieldSetter(matcher, PatchedTitle(title)) + def set(title: String): FieldSetter[PatchedTitle] = set(Seq(RichTextData.default(title, Annotations.default))) + def update[E](f: Seq[RichTextData] => Seq[RichTextData]): FieldUpdater[E, PatchedTitle] = + FieldUpdater.succeed(matcher, property => property.copy(title = f(property.title))) + + def capitalize: UFieldUpdater[PatchedTitle] = + update(_.map { + case d: RichTextData.Text => d.copy(text = d.text.copy(content = d.text.content.capitalize), plainText = d.plainText.capitalize) + case d => d + }) + } + + final case class PatchedColumnRichText(matcher: ColumnMatcher) { + def set(title: Seq[RichTextData.Text]): FieldSetter[PatchedRichText] = FieldSetter(matcher, PatchedRichText(title)) + def set(title: String): FieldSetter[PatchedRichText] = set(Seq(RichTextData.default(title, Annotations.default))) + def update[E](f: Seq[RichTextData] => Seq[RichTextData]): FieldUpdater[E, PatchedRichText] = + FieldUpdater.succeed(matcher, property => property.copy(richText = f(property.richText))) + + def write(text: String, annotations: Annotations = Annotations.default): FieldSetter[PatchedRichText] = + set(List(RichTextData.default(text, annotations))) + def annotate(f: Annotations => Annotations): UFieldUpdater[PatchedRichText] = + update(_.map { + case d: RichTextData.Text => d.copy(annotations = f(d.annotations)) + case d: RichTextData.Mention => d.copy(annotations = f(d.annotations)) + case d: RichTextData.Equation => d.copy(annotations = f(d.annotations)) + }) + + def reset: UFieldUpdater[PatchedRichText] = annotate(_ => Annotations.default) + def bold: UFieldUpdater[PatchedRichText] = annotate(_.copy(bold = true)) + def italic: UFieldUpdater[PatchedRichText] = annotate(_.copy(italic = true)) + def strikethrough: UFieldUpdater[PatchedRichText] = annotate(_.copy(strikethrough = true)) + def underline: UFieldUpdater[PatchedRichText] = annotate(_.copy(underline = true)) + def code: UFieldUpdater[PatchedRichText] = annotate(_.copy(code = true)) + def color(color: Color): UFieldUpdater[PatchedRichText] = annotate(_.copy(color = color)) + } + + final case class PatchedColumnNumber(matcher: ColumnMatcher) { + def set(number: Double): FieldSetter[PatchedNumber] = FieldSetter(matcher, PatchedNumber(number)) + def update(f: Double => Double): UFieldUpdater[PatchedNumber] = maybeUpdate(p => Right(f(p))) + def maybeUpdate[E](f: Double => Either[E, Double]): FieldUpdater[E, PatchedNumber] = + FieldUpdater.apply(matcher, property => f(property.number).map(number => property.copy(number = number))) + + def divide(number: Double): UFieldUpdater[PatchedNumber] = update(_ / number) // TODO: Transform to maybe update + def add(number: Double): UFieldUpdater[PatchedNumber] = update(_ + number) + def minus(number: Double): UFieldUpdater[PatchedNumber] = update(_ - number) + def times(number: Double): UFieldUpdater[PatchedNumber] = update(_ * number) + def pow(number: Double): UFieldUpdater[PatchedNumber] = update(Math.pow(_, number)) + def floor: UFieldUpdater[PatchedNumber] = update(Math.floor) + def ceil: UFieldUpdater[PatchedNumber] = update(Math.ceil) + } + + final case class PatchedColumnCheckbox(matcher: ColumnMatcher) { + def set(checkbox: Boolean): FieldSetter[PatchedCheckbox] = FieldSetter(matcher, PatchedCheckbox(checkbox)) + def update(f: Boolean => Boolean): UFieldUpdater[PatchedCheckbox] = + FieldUpdater.succeed(matcher, property => property.copy(checkbox = f(property.checkbox))) + + def check: FieldSetter[PatchedCheckbox] = set(true) + def uncheck: FieldSetter[PatchedCheckbox] = set(false) + def reverse: UFieldUpdater[PatchedCheckbox] = update(!_) + } + + final case class PatchedColumnSelect(matcher: ColumnMatcher) { + def set(id: Option[String], name: Option[String]): FieldSetter[PatchedSelect] = FieldSetter(matcher, PatchedSelect(id, name)) + + def setUsingId(id: String): FieldSetter[PatchedSelect] = set(Some(id), None) + def setUsingName(name: String): FieldSetter[PatchedSelect] = set(None, Some(name)) + } + + final case class PatchedColumnMultiSelect(matcher: ColumnMatcher) { + def set(selects: List[PatchedSelect]): FieldSetter[PatchedMultiSelect] = FieldSetter(matcher, PatchedMultiSelect(selects)) + def update(f: List[PatchedSelect] => List[PatchedSelect]): UFieldUpdater[PatchedMultiSelect] = + FieldUpdater.succeed(matcher, property => property.copy(multiSelect = f(property.multiSelect))) + + def removeUsingIdIfExists(id: String): UFieldUpdater[PatchedMultiSelect] = update(_.filterNot(_.id.contains(id))) + def removeUsingNameIfExists(name: String): UFieldUpdater[PatchedMultiSelect] = update(_.filterNot(_.name.contains(name))) + def addUsingId(id: String): UFieldUpdater[PatchedMultiSelect] = update(_ :+ PatchedSelect(Some(id), None)) + def addUsingName(name: String): UFieldUpdater[PatchedMultiSelect] = update(_ :+ PatchedSelect(None, Some(name))) + } + + final case class PatchedColumnDate(matcher: ColumnMatcher) { + def set(start: LocalDate, end: Option[LocalDate], timeZone: Option[String]): FieldSetter[PatchedDate] = + FieldSetter(matcher, PatchedDate(start, end, timeZone)) + + def startAt(date: LocalDate): FieldSetter[PatchedDate] = set(date, None, None) + def endAt(f: LocalDate => LocalDate): UFieldUpdater[PatchedDate] = + FieldUpdater.succeed(matcher, property => property.copy(end = Some(f(property.start)))) + def endAt(date: LocalDate): UFieldUpdater[PatchedDate] = endAt(_ => date) + def between(start: LocalDate, end: LocalDate): FieldSetter[PatchedDate] = set(start, Some(end), None) + def today: zio.UIO[FieldSetter[PatchedDate]] = Clock.localDateTime.map(_.toLocalDate).map(startAt) + } + + final case class PatchedColumnPeople(matcher: ColumnMatcher) { + def set(people: Seq[UserId]): FieldSetter[PatchedPeople] = FieldSetter(matcher, PatchedPeople(people)) + def update(f: Seq[UserId] => Seq[UserId]): UFieldUpdater[PatchedPeople] = + FieldUpdater.succeed(matcher, property => property.copy(people = f(property.people))) + + def add(people: Seq[UserId]): UFieldUpdater[PatchedPeople] = update(_ ++ people) + def add(people: UserId): UFieldUpdater[PatchedPeople] = add(List(people)) + } + + final case class PatchedColumnFiles(matcher: ColumnMatcher) { + def set(files: Seq[Link]): FieldSetter[PatchedFiles] = FieldSetter(matcher, PatchedFiles(files)) + def update(f: Seq[Link] => Seq[Link]): UFieldUpdater[PatchedFiles] = + FieldUpdater.succeed(matcher, property => property.copy(files = f(property.files))) + + def add(files: Seq[Link]): UFieldUpdater[PatchedFiles] = update(_ ++ files) + def add(file: Link): UFieldUpdater[PatchedFiles] = add(List(file)) + def filter(predicate: Link => Boolean): UFieldUpdater[PatchedFiles] = update(_.filter(predicate)) + } + + final case class PatchedColumnUrl(matcher: ColumnMatcher) { + def set(url: String): FieldSetter[PatchedUrl] = FieldSetter(matcher, PatchedUrl(url)) + } + + final case class PatchedColumnEmail(matcher: ColumnMatcher) { + def set(email: String): FieldSetter[PatchedEmail] = FieldSetter(matcher, PatchedEmail(email)) + def update(f: String => String): UFieldUpdater[PatchedEmail] = + FieldUpdater.succeed(matcher, property => property.copy(email = f(property.email))) + } + + final case class PatchedColumnPhoneNumber(matcher: ColumnMatcher) { + def set(phoneNumber: String): FieldSetter[PatchedPhoneNumber] = FieldSetter(matcher, PatchedPhoneNumber(phoneNumber)) + def update(f: String => String): UFieldUpdater[PatchedPhoneNumber] = + FieldUpdater.succeed(matcher, property => property.copy(phoneNumber = f(property.phoneNumber))) + } +} diff --git a/zio-notion-core/src/main/scala/zio/notion/dsl/PatchedColumnDefinition.scala b/zio-notion-core/src/main/scala/zio/notion/dsl/PatchedColumnDefinition.scala new file mode 100644 index 00000000..eb658877 --- /dev/null +++ b/zio-notion-core/src/main/scala/zio/notion/dsl/PatchedColumnDefinition.scala @@ -0,0 +1,24 @@ +package zio.notion.dsl + +import zio.notion.PropertyUpdater.ColumnMatcher +import zio.notion.model.database.patch.PatchPlan +import zio.notion.model.database.patch.PatchPlan.PropertyType + +sealed trait PatchedDefinition { + def patchPlan: PatchPlan + def matcher: ColumnMatcher +} + +final case class PatchedColumnDefinition(colName: String, patchPlan: PatchPlan) extends PatchedDefinition { + def rename(name: String): PatchedColumnDefinition = copy(patchPlan = patchPlan.copy(name = Some(name))) + + def as(propertyType: PropertyType): PatchedColumnDefinition = copy(patchPlan = patchPlan.copy(propertyType = Some(propertyType))) + + override def matcher: ColumnMatcher = ColumnMatcher.One(colName) +} + +final case class PatchedColumnDefinitions(predicate: String => Boolean, patchPlan: PatchPlan) extends PatchedDefinition { + def as(propertyType: PropertyType): PatchedColumnDefinitions = copy(patchPlan = patchPlan.copy(propertyType = Some(propertyType))) + + override def matcher: ColumnMatcher = ColumnMatcher.Predicate(predicate) +} diff --git a/zio-notion-core/src/main/scala/zio/notion/dsl/package.scala b/zio-notion-core/src/main/scala/zio/notion/dsl/package.scala index ffcf1038..670bcf93 100644 --- a/zio-notion-core/src/main/scala/zio/notion/dsl/package.scala +++ b/zio-notion-core/src/main/scala/zio/notion/dsl/package.scala @@ -1,7 +1,5 @@ package zio.notion -import zio.notion.model.database.patch.PatchPlan - package object dsl { implicit class ColumnContext(val sc: StringContext) { def $(args: Any*): Column = col(sc.s(args: _*)) @@ -13,11 +11,11 @@ package object dsl { val allColumns: Columns = columnsMatching(_ => true) - def columnDefinitionsMatching(predicate: String => Boolean): ColumnDefinitions = ColumnDefinitions(predicate, PatchPlan.unit) + def columnDefinitionsMatching(predicate: String => Boolean): ColumnDefinitions = ColumnDefinitions(predicate) val allColumnDefinitions: ColumnDefinitions = columnDefinitionsMatching(_ => true) def col(colName: String): Column = Column(colName) - def colDefinition(colName: String): ColumnDefinition = ColumnDefinition(colName, PatchPlan.unit) + def colDefinition(colName: String): ColumnDefinition = ColumnDefinition(colName) } diff --git a/zio-notion-core/src/main/scala/zio/notion/model/database/Database.scala b/zio-notion-core/src/main/scala/zio/notion/model/database/Database.scala index 9ee697df..98233b0f 100644 --- a/zio-notion-core/src/main/scala/zio/notion/model/database/Database.scala +++ b/zio-notion-core/src/main/scala/zio/notion/model/database/Database.scala @@ -6,7 +6,7 @@ import io.circe.generic.extras.ConfiguredJsonCodec import zio.notion.NotionError import zio.notion.NotionError.PropertyNotExist import zio.notion.PropertyUpdater.ColumnMatcher -import zio.notion.dsl.ColumnDefinition +import zio.notion.dsl.PatchedDefinition import zio.notion.model.common.{Cover, Icon, Parent, UserId} import zio.notion.model.common.richtext.{Annotations, RichTextData} import zio.notion.model.database.description.PropertyDescription @@ -40,11 +40,11 @@ object Database { properties: Map[String, Option[PatchPlan]] ) { self => def updateProperty( - columnDefinition: ColumnDefinition + patchedDefinition: PatchedDefinition )(implicit manifest: Manifest[PropertyDescription.Title]): Patch = { - val patchPlan = columnDefinition.patchPlan + val patchPlan = patchedDefinition.patchPlan - columnDefinition.matcher match { + patchedDefinition.matcher match { case ColumnMatcher.Predicate(f) => val properties: Iterable[(String, Option[PatchPlan])] = database.properties.collect { diff --git a/zio-notion-core/src/main/scala/zio/notion/model/page/patch/PatchedProperty.scala b/zio-notion-core/src/main/scala/zio/notion/model/page/patch/PatchedProperty.scala index 0b8d8e2e..3f1c1bc7 100644 --- a/zio-notion-core/src/main/scala/zio/notion/model/page/patch/PatchedProperty.scala +++ b/zio-notion-core/src/main/scala/zio/notion/model/page/patch/PatchedProperty.scala @@ -2,11 +2,8 @@ package zio.notion.model.page.patch import io.circe.{Encoder, Json} -import zio.Clock -import zio.notion.PropertyUpdater.{Setter, Transformation, UTransformation} import zio.notion.model.common.UserId -import zio.notion.model.common.enumeration.Color -import zio.notion.model.common.richtext.{Annotations, RichTextData} +import zio.notion.model.common.richtext.RichTextData import zio.notion.model.magnolia.{NoDiscriminantNoNullEncoderDerivation, PatchedPropertyEncoderDerivation} import zio.notion.model.page.property.Link @@ -16,71 +13,39 @@ sealed trait PatchedProperty // TODO: Add formula and rollup patches object PatchedProperty { - final case class PatchedNumber(number: Double) extends PatchedProperty - - object PatchedNumber { - def set(number: Double): Setter[PatchedNumber] = Setter(PatchedNumber(number)) - - def update(f: Double => Double): UTransformation[PatchedNumber] = - Transformation.succeed(number => number.copy(number = f(number.number))) - - def maybeUpdate[E](f: Double => Either[E, Double]): Transformation[E, PatchedNumber] = - Transformation.apply(number => f(number.number).map(double => PatchedNumber(double))) - - def add(number: Double): UTransformation[PatchedNumber] = update(_ + number) - - def minus(number: Double): UTransformation[PatchedNumber] = update(_ - number) - - def times(number: Double): UTransformation[PatchedNumber] = update(_ * number) + final case class PatchedTitle(title: Seq[RichTextData]) extends PatchedProperty - // Transform to maybe update - def divide(number: Double): UTransformation[PatchedNumber] = update(_ / number) + object PatchedTitle { + implicit val encoder: Encoder[PatchedTitle] = PatchedPropertyEncoderDerivation.gen[PatchedTitle] + } - def pow(number: Double): UTransformation[PatchedNumber] = update(Math.pow(_, number)) + final case class PatchedRichText(richText: Seq[RichTextData]) extends PatchedProperty - def floor: UTransformation[PatchedNumber] = update(Math.floor) + object PatchedRichText { + implicit val encoder: Encoder[PatchedRichText] = PatchedPropertyEncoderDerivation.gen[PatchedRichText] + } - def ceil: UTransformation[PatchedNumber] = update(Math.ceil) + final case class PatchedNumber(number: Double) extends PatchedProperty + object PatchedNumber { implicit val encoder: Encoder[PatchedNumber] = PatchedPropertyEncoderDerivation.gen[PatchedNumber] } - final case class PatchedUrl(url: String) extends PatchedProperty - - object PatchedUrl { - def set(url: String): Setter[PatchedUrl] = Setter(PatchedUrl(url)) + final case class PatchedCheckbox(checkbox: Boolean) extends PatchedProperty - implicit val encoder: Encoder[PatchedUrl] = PatchedPropertyEncoderDerivation.gen[PatchedUrl] + object PatchedCheckbox { + implicit val encoder: Encoder[PatchedCheckbox] = PatchedPropertyEncoderDerivation.gen[PatchedCheckbox] } final case class PatchedSelect(id: Option[String], name: Option[String]) extends PatchedProperty object PatchedSelect { - def set(id: Option[String], name: Option[String]): Setter[PatchedSelect] = Setter(PatchedSelect(id, name)) - - def setUsingId(id: String): Setter[PatchedSelect] = set(Some(id), None) - - def setUsingName(name: String): Setter[PatchedSelect] = set(None, Some(name)) - implicit val encoder: Encoder[PatchedSelect] = PatchedPropertyEncoderDerivation.gen[PatchedSelect] } final case class PatchedMultiSelect(multiSelect: List[PatchedSelect]) extends PatchedProperty object PatchedMultiSelect { - def set(selects: List[PatchedSelect]): Setter[PatchedMultiSelect] = Setter(PatchedMultiSelect(selects)) - - def update(f: List[PatchedSelect] => List[PatchedSelect]): UTransformation[PatchedMultiSelect] = - Transformation.succeed(multiSelect => multiSelect.copy(multiSelect = f(multiSelect.multiSelect))) - - def removeUsingIdIfExists(id: String): UTransformation[PatchedMultiSelect] = update(_.filterNot(_.id.contains(id))) - - def removeUsingNameIfExists(name: String): UTransformation[PatchedMultiSelect] = update(_.filterNot(_.name.contains(name))) - - def addUsingId(id: String): UTransformation[PatchedMultiSelect] = update(_ :+ PatchedSelect(Some(id), None)) - - def addUsingName(name: String): UTransformation[PatchedMultiSelect] = update(_ :+ PatchedSelect(None, Some(name))) - implicit val encoder: Encoder[PatchedMultiSelect] = (property: PatchedMultiSelect) => { val encodedMultiSelect = property.multiSelect.map(NoDiscriminantNoNullEncoderDerivation.gen[PatchedSelect].apply) @@ -91,140 +56,37 @@ object PatchedProperty { final case class PatchedDate(start: LocalDate, end: Option[LocalDate], timeZone: Option[String]) extends PatchedProperty object PatchedDate { - def set(start: LocalDate, end: Option[LocalDate], timeZone: Option[String]): Setter[PatchedDate] = - Setter(PatchedDate(start, end, timeZone)) - - def startAt(date: LocalDate): Setter[PatchedDate] = set(date, None, None) - - def endAt(f: LocalDate => LocalDate): UTransformation[PatchedDate] = - Transformation.succeed(property => property.copy(end = Some(f(property.start)))) - - def endAt(date: LocalDate): UTransformation[PatchedDate] = endAt(_ => date) - - def between(start: LocalDate, end: LocalDate): Setter[PatchedDate] = set(start, Some(end), None) - - def today: zio.UIO[Setter[PatchedDate]] = Clock.localDateTime.map(_.toLocalDate).map(startAt) - implicit val encoder: Encoder[PatchedDate] = PatchedPropertyEncoderDerivation.gen[PatchedDate] } - final case class PatchedEmail(email: String) extends PatchedProperty - - object PatchedEmail { - def set(email: String): Setter[PatchedEmail] = Setter(PatchedEmail(email)) - - def update(f: String => String): UTransformation[PatchedEmail] = - Transformation.succeed(property => property.copy(email = f(property.email))) - - implicit val encoder: Encoder[PatchedEmail] = PatchedPropertyEncoderDerivation.gen[PatchedEmail] - } - - final case class PatchedPhoneNumber(phoneNumber: String) extends PatchedProperty - - object PatchedPhoneNumber { - def set(phoneNumber: String): Setter[PatchedPhoneNumber] = Setter(PatchedPhoneNumber(phoneNumber)) - - def update(f: String => String): UTransformation[PatchedPhoneNumber] = - Transformation.succeed(property => property.copy(phoneNumber = f(property.phoneNumber))) - - implicit val encoder: Encoder[PatchedPhoneNumber] = PatchedPropertyEncoderDerivation.gen[PatchedPhoneNumber] - } - - final case class PatchedCheckbox(checkbox: Boolean) extends PatchedProperty - - object PatchedCheckbox { - def set(checkbox: Boolean): Setter[PatchedCheckbox] = Setter(PatchedCheckbox(checkbox)) - - def update(f: Boolean => Boolean): UTransformation[PatchedCheckbox] = - Transformation.succeed(property => property.copy(checkbox = f(property.checkbox))) - - def check: Setter[PatchedCheckbox] = set(true) - - def uncheck: Setter[PatchedCheckbox] = set(false) - - def reverse: UTransformation[PatchedCheckbox] = update(!_) + final case class PatchedPeople(people: Seq[UserId]) extends PatchedProperty - implicit val encoder: Encoder[PatchedCheckbox] = PatchedPropertyEncoderDerivation.gen[PatchedCheckbox] + object PatchedPeople { + implicit val encoder: Encoder[PatchedPeople] = PatchedPropertyEncoderDerivation.gen[PatchedPeople] } final case class PatchedFiles(files: Seq[Link]) extends PatchedProperty object PatchedFiles { - def set(files: Seq[Link]): Setter[PatchedFiles] = Setter(PatchedFiles(files)) - - def update(f: Seq[Link] => Seq[Link]): UTransformation[PatchedFiles] = - Transformation.succeed(property => property.copy(files = f(property.files))) - - def add(files: Seq[Link]): UTransformation[PatchedFiles] = update(_ ++ files) - - def add(file: Link): UTransformation[PatchedFiles] = add(List(file)) - - def filter(predicate: Link => Boolean): UTransformation[PatchedFiles] = update(_.filter(predicate)) - implicit val encoder: Encoder[PatchedFiles] = PatchedPropertyEncoderDerivation.gen[PatchedFiles] } - final case class PatchedTitle(title: Seq[RichTextData]) extends PatchedProperty - - object PatchedTitle { - def set(title: Seq[RichTextData.Text]): Setter[PatchedTitle] = Setter(PatchedTitle(title)) - - def set(title: String): Setter[PatchedTitle] = set(Seq(RichTextData.default(title, Annotations.default))) - - def update(f: Seq[RichTextData] => Seq[RichTextData]): UTransformation[PatchedTitle] = - Transformation.succeed(property => property.copy(title = f(property.title))) - - def capitalize: UTransformation[PatchedTitle] = - update(_.map { - case d: RichTextData.Text => d.copy(text = d.text.copy(content = d.text.content.capitalize), plainText = d.plainText.capitalize) - case d => d - }) + final case class PatchedUrl(url: String) extends PatchedProperty - implicit val encoder: Encoder[PatchedTitle] = PatchedPropertyEncoderDerivation.gen[PatchedTitle] + object PatchedUrl { + implicit val encoder: Encoder[PatchedUrl] = PatchedPropertyEncoderDerivation.gen[PatchedUrl] } - final case class PatchedRichText(richText: Seq[RichTextData]) extends PatchedProperty - - object PatchedRichText { - def set(richText: List[RichTextData]): Setter[PatchedRichText] = Setter(PatchedRichText(richText)) - - def write(text: String, annotations: Annotations = Annotations.default): Setter[PatchedRichText] = - set(List(RichTextData.default(text, annotations))) - - def update(f: Seq[RichTextData] => Seq[RichTextData]): UTransformation[PatchedRichText] = - Transformation.succeed(property => property.copy(richText = f(property.richText))) - - def annotate(f: Annotations => Annotations): UTransformation[PatchedRichText] = - update(_.map { - case d: RichTextData.Text => d.copy(annotations = f(d.annotations)) - case d: RichTextData.Mention => d.copy(annotations = f(d.annotations)) - case d: RichTextData.Equation => d.copy(annotations = f(d.annotations)) - }) - - def reset: UTransformation[PatchedRichText] = annotate(_ => Annotations.default) - def bold: UTransformation[PatchedRichText] = annotate(_.copy(bold = true)) - def italic: UTransformation[PatchedRichText] = annotate(_.copy(italic = true)) - def strikethrough: UTransformation[PatchedRichText] = annotate(_.copy(strikethrough = true)) - def underline: UTransformation[PatchedRichText] = annotate(_.copy(underline = true)) - def code: UTransformation[PatchedRichText] = annotate(_.copy(code = true)) - def color(color: Color): UTransformation[PatchedRichText] = annotate(_.copy(color = color)) + final case class PatchedEmail(email: String) extends PatchedProperty - implicit val encoder: Encoder[PatchedRichText] = PatchedPropertyEncoderDerivation.gen[PatchedRichText] + object PatchedEmail { + implicit val encoder: Encoder[PatchedEmail] = PatchedPropertyEncoderDerivation.gen[PatchedEmail] } - final case class PatchedPeople(people: Seq[UserId]) extends PatchedProperty - - object PatchedPeople { - def set(people: Seq[UserId]): Setter[PatchedPeople] = Setter(PatchedPeople(people)) - - def update(f: Seq[UserId] => Seq[UserId]): UTransformation[PatchedPeople] = - Transformation.succeed(property => property.copy(people = f(property.people))) - - def add(people: Seq[UserId]): UTransformation[PatchedPeople] = update(_ ++ people) - - def add(people: UserId): UTransformation[PatchedPeople] = add(List(people)) + final case class PatchedPhoneNumber(phoneNumber: String) extends PatchedProperty - implicit val encoder: Encoder[PatchedPeople] = PatchedPropertyEncoderDerivation.gen[PatchedPeople] + object PatchedPhoneNumber { + implicit val encoder: Encoder[PatchedPhoneNumber] = PatchedPropertyEncoderDerivation.gen[PatchedPhoneNumber] } implicit val encoder: Encoder[PatchedProperty] = PatchedPropertyEncoderDerivation.gen[PatchedProperty] diff --git a/zio-notion-core/src/test/scala/zio/notion/PropertyUpdaterSpec.scala b/zio-notion-core/src/test/scala/zio/notion/PropertyUpdaterSpec.scala index 93215fa5..f647a4ef 100644 --- a/zio-notion-core/src/test/scala/zio/notion/PropertyUpdaterSpec.scala +++ b/zio-notion-core/src/test/scala/zio/notion/PropertyUpdaterSpec.scala @@ -3,6 +3,7 @@ package zio.notion import zio.Scope import zio.notion.Faker.fakeLocalDate import zio.notion.PropertyUpdater.{FieldSetter, FieldUpdater} +import zio.notion.dsl._ import zio.notion.model.page.patch.PatchedProperty.{PatchedDate, PatchedNumber} import zio.test._ @@ -11,20 +12,18 @@ object PropertyUpdaterSpec extends ZIOSpecDefault { suite("Property Updater suite")( test("We can map a setter") { val patch: FieldSetter[PatchedDate] = - PatchedDate + allColumns.asDate.patch .startAt(fakeLocalDate) .map(property => property.copy(start = property.start.plusDays(5))) - .onAll assertTrue(patch.value.start == fakeLocalDate.plusDays(5)) }, test("We can map a transformation") { val patch: FieldUpdater[Nothing, PatchedNumber] = - PatchedNumber.ceil + allColumns.asNumber.patch.ceil .map(property => property.copy(number = property.number + 10)) - .onAll - assertTrue(patch.transform(PatchedNumber(2.5)).map(_.number) == Right(13d)) + assertTrue(patch.f(PatchedNumber(2.5)).map(_.number) == Right(13d)) } ) diff --git a/zio-notion-core/src/test/scala/zio/notion/dsl/ColumnDefinitionSpec.scala b/zio-notion-core/src/test/scala/zio/notion/dsl/ColumnDefinitionSpec.scala index 1790bf02..3554928c 100644 --- a/zio-notion-core/src/test/scala/zio/notion/dsl/ColumnDefinitionSpec.scala +++ b/zio-notion-core/src/test/scala/zio/notion/dsl/ColumnDefinitionSpec.scala @@ -1,7 +1,6 @@ package zio.notion.dsl import zio.Scope -import zio.notion.model.database.patch.PatchPlan import zio.test._ object ColumnDefinitionSpec extends ZIOSpecDefault { @@ -10,12 +9,12 @@ object ColumnDefinitionSpec extends ZIOSpecDefault { test("I can generate a column definition using string context") { val columnDefinition = $$"col1" - assertTrue(columnDefinition == ColumnDefinition("col1", PatchPlan.unit)) + assertTrue(columnDefinition == ColumnDefinition("col1")) }, test("I can generate a column definition using colDefinition function") { val columnDefinition = colDefinition("col1") - assertTrue(columnDefinition == ColumnDefinition("col1", PatchPlan.unit)) + assertTrue(columnDefinition == ColumnDefinition("col1")) } ) } diff --git a/zio-notion-core/src/test/scala/zio/notion/dsl/ColumnSpec.scala b/zio-notion-core/src/test/scala/zio/notion/dsl/ColumnSpec.scala index 9d5923c6..148ecff6 100644 --- a/zio-notion-core/src/test/scala/zio/notion/dsl/ColumnSpec.scala +++ b/zio-notion-core/src/test/scala/zio/notion/dsl/ColumnSpec.scala @@ -2,7 +2,6 @@ package zio.notion.dsl import zio.Scope import zio.notion.Faker._ -import zio.notion.model.database.patch.PatchPlan import zio.notion.model.database.query.PropertyFilter._ import zio.notion.model.database.query.PropertyFilter.DatePropertyFilter.Before import zio.test._ @@ -91,7 +90,7 @@ object ColumnSpec extends ZIOSpecDefault { }, test("I can convert a column into a column definition") { val columnDefinition = col("col1").definition - assertTrue(columnDefinition == ColumnDefinition("col1", PatchPlan.unit)) + assertTrue(columnDefinition == ColumnDefinition("col1")) } ) } diff --git a/zio-notion-core/src/test/scala/zio/notion/dsl/DatabaseQueryDSLSpec.scala b/zio-notion-core/src/test/scala/zio/notion/dsl/DatabaseQueryDSLSpec.scala index b1d07354..144e4c94 100644 --- a/zio-notion-core/src/test/scala/zio/notion/dsl/DatabaseQueryDSLSpec.scala +++ b/zio-notion-core/src/test/scala/zio/notion/dsl/DatabaseQueryDSLSpec.scala @@ -18,13 +18,13 @@ object DatabaseQueryDSLSpec extends ZIOSpecDefault { def filterSpec: Spec[TestEnvironment with Scope, Any] = suite("Filter dsl helper functions suite")( test("We can use a PropertyFilter as a Filter") { - val filter: Filter = title("Title").startsWith("Toto") + val filter: Filter = $"Title".asTitle.startsWith("Toto") val expected: Filter = Filter.One(Title("Title", StartsWith("Toto"))) assertTrue(filter == expected) }, test("We can use a dsl to express date filters") { - val filter: Date = date("Date").before(LocalDate.of(2022, 2, 2)) + val filter: Date = $"Date".asDate.before(LocalDate.of(2022, 2, 2)) assertTrue(filter.date == Before("2022-02-02")) } diff --git a/zio-notion-core/src/test/scala/zio/notion/model/page/patch/PatchedPropertySpec.scala b/zio-notion-core/src/test/scala/zio/notion/dsl/PatchedColumnSpec.scala similarity index 71% rename from zio-notion-core/src/test/scala/zio/notion/model/page/patch/PatchedPropertySpec.scala rename to zio-notion-core/src/test/scala/zio/notion/dsl/PatchedColumnSpec.scala index 478a582a..7c26da1f 100644 --- a/zio-notion-core/src/test/scala/zio/notion/model/page/patch/PatchedPropertySpec.scala +++ b/zio-notion-core/src/test/scala/zio/notion/dsl/PatchedColumnSpec.scala @@ -1,22 +1,35 @@ -package zio.notion.model.page.patch +package zio.notion.dsl import io.circe.syntax.EncoderOps import zio.{Scope, UIO} import zio.notion.Faker._ -import zio.notion.PropertyUpdater._ +import zio.notion.PropertyUpdater.{FieldSetter, FieldUpdater, UFieldUpdater} import zio.notion.model.common.{richtext, Url, UserId} import zio.notion.model.common.enumeration.Color import zio.notion.model.common.richtext.RichTextData -import zio.notion.model.page.patch.PatchedProperty._ +import zio.notion.model.page.patch.PatchedProperty.{ + PatchedCheckbox, + PatchedDate, + PatchedEmail, + PatchedFiles, + PatchedMultiSelect, + PatchedNumber, + PatchedPeople, + PatchedPhoneNumber, + PatchedRichText, + PatchedSelect, + PatchedTitle, + PatchedUrl +} import zio.notion.model.page.property.Link import zio.notion.model.page.property.Link.External import zio.notion.model.printer -import zio.test._ +import zio.test.{assertTrue, Spec, TestEnvironment, TestResult, ZIOSpecDefault} import java.time.LocalDate -object PatchedPropertySpec extends ZIOSpecDefault { +object PatchedColumnSpec extends ZIOSpecDefault { override def spec: Spec[TestEnvironment with Scope, Any] = suite("Test property patching action")( specPatchedNumber, @@ -34,46 +47,46 @@ object PatchedPropertySpec extends ZIOSpecDefault { def specPatchedNumber: Spec[TestEnvironment with Scope, Any] = { def assertUpdate[E](updater: FieldUpdater[Nothing, PatchedNumber], initial: Double, excepted: Double): TestResult = - assertTrue(updater.transform(PatchedNumber(initial)).map(_.number) == Right(excepted)) + assertTrue(updater.f(PatchedNumber(initial)).map(_.number) == Right(excepted)) suite("Test patching numbers")( test("We can set a new number") { - val patch: FieldSetter[PatchedNumber] = PatchedNumber.set(10).onAll + val patch: FieldSetter[PatchedNumber] = allColumns.asNumber.patch.set(10) assertTrue(patch.value.number == 10) }, test("We can add by a number") { - val patch: FieldUpdater[Nothing, PatchedNumber] = PatchedNumber.add(10).onAll + val patch: FieldUpdater[Nothing, PatchedNumber] = allColumns.asNumber.patch.add(10) assertUpdate(patch, 20, 30) }, test("We can subtract by a number") { - val patch: FieldUpdater[Nothing, PatchedNumber] = PatchedNumber.minus(10).onAll + val patch: FieldUpdater[Nothing, PatchedNumber] = allColumns.asNumber.patch.minus(10) assertUpdate(patch, 20, 10) }, test("We can multiply by a number") { - val patch: FieldUpdater[Nothing, PatchedNumber] = PatchedNumber.times(2).onAll + val patch: FieldUpdater[Nothing, PatchedNumber] = allColumns.asNumber.patch.times(2) assertUpdate(patch, 5, 10) }, test("We can divide by a number") { - val patch: FieldUpdater[Nothing, PatchedNumber] = PatchedNumber.divide(2).onAll + val patch: FieldUpdater[Nothing, PatchedNumber] = allColumns.asNumber.patch.divide(2) assertUpdate(patch, 5, 2.5) }, test("We can pow by a number") { - val patch: FieldUpdater[Nothing, PatchedNumber] = PatchedNumber.pow(2).onAll + val patch: FieldUpdater[Nothing, PatchedNumber] = allColumns.asNumber.patch.pow(2) assertUpdate(patch, 5, 25) }, test("We can ceil a number") { - val patch: FieldUpdater[Nothing, PatchedNumber] = PatchedNumber.ceil.onAll + val patch: FieldUpdater[Nothing, PatchedNumber] = allColumns.asNumber.patch.ceil assertUpdate(patch, 5.4, 6) }, test("We can floor a number") { - val patch: FieldUpdater[Nothing, PatchedNumber] = PatchedNumber.floor.onAll + val patch: FieldUpdater[Nothing, PatchedNumber] = allColumns.asNumber.patch.floor assertUpdate(patch, 5.4, 5) } @@ -83,7 +96,7 @@ object PatchedPropertySpec extends ZIOSpecDefault { def specPatchedUrl: Spec[TestEnvironment with Scope, Any] = suite("Test patching urls")( test("We can set a new url") { - val patch: FieldSetter[PatchedUrl] = PatchedUrl.set(fakeUrl).onAll + val patch: FieldSetter[PatchedUrl] = allColumns.asUrl.patch.set(fakeUrl) assertTrue(patch.value.url == fakeUrl) } @@ -92,12 +105,12 @@ object PatchedPropertySpec extends ZIOSpecDefault { def specPatchedSelect: Spec[TestEnvironment with Scope, Any] = suite("Test patching selects")( test("We can set a new select using its name") { - val patch: FieldSetter[PatchedSelect] = PatchedSelect.setUsingName("name").onAll + val patch: FieldSetter[PatchedSelect] = allColumns.asSelect.patch.setUsingName("name") assertTrue(patch.value.name.contains("name") && patch.value.id.isEmpty) }, test("We can set a new select using its id") { - val patch: FieldSetter[PatchedSelect] = PatchedSelect.setUsingId("id").onAll + val patch: FieldSetter[PatchedSelect] = allColumns.asSelect.patch.setUsingId("id") assertTrue(patch.value.id.contains("id") && patch.value.name.isEmpty) } @@ -108,37 +121,37 @@ object PatchedPropertySpec extends ZIOSpecDefault { updater: FieldUpdater[Nothing, PatchedMultiSelect], initial: List[PatchedSelect], expected: List[PatchedSelect] - ): TestResult = assertTrue(updater.transform(PatchedMultiSelect(initial)).map(_.multiSelect) == Right(expected)) + ): TestResult = assertTrue(updater.f(PatchedMultiSelect(initial)).map(_.multiSelect) == Right(expected)) suite("Test patching multi selects")( test("We can set a new multi select") { val multiSelect: List[PatchedSelect] = List(PatchedSelect(None, Some("name"))) - val patch: FieldSetter[PatchedMultiSelect] = PatchedMultiSelect.set(multiSelect).onAll + val patch: FieldSetter[PatchedMultiSelect] = allColumns.asMultiSelect.patch.set(multiSelect) assertTrue(patch.value.multiSelect == multiSelect) }, test("We can remove a select using the name") { val multiSelect: List[PatchedSelect] = List(PatchedSelect(None, Some("name")), PatchedSelect(None, Some("other"))) - val patch: FieldUpdater[Nothing, PatchedMultiSelect] = PatchedMultiSelect.removeUsingNameIfExists("name").onAll + val patch: FieldUpdater[Nothing, PatchedMultiSelect] = allColumns.asMultiSelect.patch.removeUsingNameIfExists("name") assertUpdate(patch, multiSelect, List(PatchedSelect(None, Some("other")))) }, test("We can remove a select using the id") { val multiSelect: List[PatchedSelect] = List(PatchedSelect(Some("id"), None), PatchedSelect(Some("other"), None)) - val patch: FieldUpdater[Nothing, PatchedMultiSelect] = PatchedMultiSelect.removeUsingIdIfExists("id").onAll + val patch: FieldUpdater[Nothing, PatchedMultiSelect] = allColumns.asMultiSelect.patch.removeUsingIdIfExists("id") assertUpdate(patch, multiSelect, List(PatchedSelect(Some("other"), None))) }, test("We can add a select using the name") { - val patch: FieldUpdater[Nothing, PatchedMultiSelect] = PatchedMultiSelect.addUsingName("name").onAll + val patch: FieldUpdater[Nothing, PatchedMultiSelect] = allColumns.asMultiSelect.patch.addUsingName("name") assertUpdate(patch, List.empty, List(PatchedSelect(None, Some("name")))) }, test("We can add a select using the id") { - val patch: FieldUpdater[Nothing, PatchedMultiSelect] = PatchedMultiSelect.addUsingId("id").onAll + val patch: FieldUpdater[Nothing, PatchedMultiSelect] = allColumns.asMultiSelect.patch.addUsingId("id") assertUpdate(patch, List.empty, List(PatchedSelect(Some("id"), None))) } @@ -148,22 +161,22 @@ object PatchedPropertySpec extends ZIOSpecDefault { def specPatchedDate: Spec[TestEnvironment with Scope, Any] = suite("Test patching dates")( test("We can set a start date") { - val patch: FieldSetter[PatchedDate] = PatchedDate.startAt(fakeLocalDate).onAll + val patch: FieldSetter[PatchedDate] = allColumns.asDate.patch.startAt(fakeLocalDate) assertTrue(patch.value.start == fakeLocalDate) }, test("We can add an end date") { - val patch: FieldUpdater[Nothing, PatchedDate] = PatchedDate.endAt(fakeLocalDate.plusDays(2)).onAll + val patch: FieldUpdater[Nothing, PatchedDate] = allColumns.asDate.patch.endAt(fakeLocalDate.plusDays(2)) - assertTrue(patch.transform(PatchedDate(fakeLocalDate, None, None)).map(_.end) == Right(Some(fakeLocalDate.plusDays(2)))) + assertTrue(patch.f(PatchedDate(fakeLocalDate, None, None)).map(_.end) == Right(Some(fakeLocalDate.plusDays(2)))) }, test("We can set a date between two dates") { - val patch: FieldSetter[PatchedDate] = PatchedDate.between(fakeLocalDate, fakeLocalDate.plusDays(2)).onAll + val patch: FieldSetter[PatchedDate] = allColumns.asDate.patch.between(fakeLocalDate, fakeLocalDate.plusDays(2)) assertTrue(patch.value.start == fakeLocalDate && patch.value.end.contains(fakeLocalDate.plusDays(2))) }, test("We can set a start date to today") { - val patch: UIO[FieldSetter[PatchedDate]] = PatchedDate.today.map(_.onAll) + val patch: UIO[FieldSetter[PatchedDate]] = allColumns.asDate.patch.today patch.map(p => assertTrue(p.value.start == LocalDate.of(1970, 1, 1))) } @@ -172,7 +185,7 @@ object PatchedPropertySpec extends ZIOSpecDefault { def specPatchedEmail: Spec[TestEnvironment with Scope, Any] = suite("Test patching emails")( test("We can set a new email") { - val patch: FieldSetter[PatchedEmail] = PatchedEmail.set(fakeEmail).onAll + val patch: FieldSetter[PatchedEmail] = allColumns.asEmail.patch.set(fakeEmail) assertTrue(patch.value.email == fakeEmail) } @@ -181,7 +194,7 @@ object PatchedPropertySpec extends ZIOSpecDefault { def specPatchedPhoneNumber: Spec[TestEnvironment with Scope, Any] = suite("Test patching phone numbers")( test("We can set a new phone number") { - val patch: FieldSetter[PatchedPhoneNumber] = PatchedPhoneNumber.set(fakePhoneNumber).onAll + val patch: FieldSetter[PatchedPhoneNumber] = allColumns.asPhoneNumber.patch.set(fakePhoneNumber) assertTrue(patch.value.phoneNumber == fakePhoneNumber) } @@ -190,19 +203,19 @@ object PatchedPropertySpec extends ZIOSpecDefault { def specPatchedCheckbox: Spec[TestEnvironment with Scope, Any] = suite("Test patching checkbox")( test("We can check a checkbox") { - val patch: FieldSetter[PatchedCheckbox] = PatchedCheckbox.check.onAll + val patch: FieldSetter[PatchedCheckbox] = allColumns.asCheckbox.patch.check assertTrue(patch.value.checkbox) }, test("We can uncheck a checkbox") { - val patch: FieldSetter[PatchedCheckbox] = PatchedCheckbox.uncheck.onAll + val patch: FieldSetter[PatchedCheckbox] = allColumns.asCheckbox.patch.uncheck assertTrue(!patch.value.checkbox) }, test("We can reverse a checkbox") { - val patch: FieldUpdater[Nothing, PatchedCheckbox] = PatchedCheckbox.reverse.onAll + val patch: FieldUpdater[Nothing, PatchedCheckbox] = allColumns.asCheckbox.patch.reverse - assertTrue(patch.transform(PatchedCheckbox(false)).map(_.checkbox) == Right(true)) + assertTrue(patch.f(PatchedCheckbox(false)).map(_.checkbox) == Right(true)) } ) @@ -211,46 +224,46 @@ object PatchedPropertySpec extends ZIOSpecDefault { test("We can set a list of files") { val files: List[Link] = List(External("name", Url(fakeUrl))) - val patch: FieldSetter[PatchedFiles] = PatchedFiles.set(files).onAll + val patch: FieldSetter[PatchedFiles] = allColumns.asFiles.patch.set(files) assertTrue(patch.value.files == files) }, test("We can set a new file") { - val patch: FieldUpdater[Nothing, PatchedFiles] = PatchedFiles.add(External("name", Url(fakeUrl))).onAll + val patch: FieldUpdater[Nothing, PatchedFiles] = allColumns.asFiles.patch.add(External("name", Url(fakeUrl))) - assertTrue(patch.transform(PatchedFiles(Seq.empty)).map(_.files) == Right(Seq(External("name", Url(fakeUrl))))) + assertTrue(patch.f(PatchedFiles(Seq.empty)).map(_.files) == Right(Seq(External("name", Url(fakeUrl))))) }, test("We can filter files") { val files: List[Link] = List(External("name", Url(fakeUrl))) val patch: FieldUpdater[Nothing, PatchedFiles] = - PatchedFiles.filter { + allColumns.asFiles.patch.filter { case Link.File(name, _) => name != "name" case External(name, _) => name != "name" - }.onAll + } - assertTrue(patch.transform(PatchedFiles(files)).map(_.files) == Right(Seq.empty)) + assertTrue(patch.f(PatchedFiles(files)).map(_.files) == Right(Seq.empty)) } ) def specPatchedTitle: Spec[TestEnvironment with Scope, Any] = suite("Test patching title")( test("We can set a new title") { - val patch: FieldSetter[PatchedTitle] = PatchedTitle.set("Title").onAll + val patch: FieldSetter[PatchedTitle] = allColumns.asTitle.patch.set("Title") assertTrue(patch.value.title.head.asInstanceOf[RichTextData.Text].plainText == "Title") }, test("We can capitalize a title") { - val patch: FieldUpdater[Nothing, PatchedTitle] = PatchedTitle.capitalize.onAll + val patch: FieldUpdater[Nothing, PatchedTitle] = allColumns.asTitle.patch.capitalize val source = PatchedTitle(List(RichTextData.default("title", richtext.Annotations.default))) - assertTrue(patch.transform(source).map(_.title.head.asInstanceOf[RichTextData.Text].plainText) == Right("Title")) + assertTrue(patch.f(source).map(_.title.head.asInstanceOf[RichTextData.Text].plainText) == Right("Title")) } ) def specPatchedRichText: Spec[TestEnvironment with Scope, Any] = { - def testAnnotation(name: String, transformation: UTransformation[PatchedRichText], expected: richtext.Annotations => Boolean) = + def testAnnotation(name: String, patch: UFieldUpdater[PatchedRichText], expected: richtext.Annotations => Boolean) = test(s"We can use $name on a every rich text") { val default: PatchedRichText = PatchedRichText( @@ -264,26 +277,24 @@ object PatchedPropertySpec extends ZIOSpecDefault { ) ) - val patch: FieldUpdater[Nothing, PatchedRichText] = transformation.onAll - assertTrue( - patch.transform(default).map(_.richText.map(_.asInstanceOf[RichTextData.Text].annotations).forall(expected)) == Right(true) + patch.f(default).map(_.richText.map(_.asInstanceOf[RichTextData.Text].annotations).forall(expected)) == Right(true) ) } suite("Test patching rich text")( test("We can write a new rich text") { - val patch: FieldSetter[PatchedRichText] = PatchedRichText.write("A new content").onAll + val patch: FieldSetter[PatchedRichText] = allColumns.asRichText.patch.write("A new content") assertTrue(patch.value.richText.headOption.map(_.asInstanceOf[RichTextData.Text].plainText).contains("A new content")) }, - testAnnotation("reset", PatchedRichText.reset, _ == richtext.Annotations.default), - testAnnotation("bold", PatchedRichText.bold, _.bold), - testAnnotation("italic", PatchedRichText.italic, _.italic), - testAnnotation("strikethrough", PatchedRichText.strikethrough, _.strikethrough), - testAnnotation("underline", PatchedRichText.underline, _.underline), - testAnnotation("code", PatchedRichText.code, _.code), - testAnnotation("color", PatchedRichText.color(Color.BlueBackground), _.color == Color.BlueBackground) + testAnnotation("reset", allColumns.asRichText.patch.reset, _ == richtext.Annotations.default), + testAnnotation("bold", allColumns.asRichText.patch.bold, _.bold), + testAnnotation("italic", allColumns.asRichText.patch.italic, _.italic), + testAnnotation("strikethrough", allColumns.asRichText.patch.strikethrough, _.strikethrough), + testAnnotation("underline", allColumns.asRichText.patch.underline, _.underline), + testAnnotation("code", allColumns.asRichText.patch.code, _.code), + testAnnotation("color", allColumns.asRichText.patch.color(Color.BlueBackground), _.color == Color.BlueBackground) ) } @@ -292,14 +303,14 @@ object PatchedPropertySpec extends ZIOSpecDefault { test("We can set a list of people") { val people: List[UserId] = List(UserId(fakeUUID)) - val patch: FieldSetter[PatchedPeople] = PatchedPeople.set(people).onAll + val patch: FieldSetter[PatchedPeople] = allColumns.asPeople.patch.set(people) assertTrue(patch.value.people == people) }, test("We can set a new person") { - val patch: FieldUpdater[Nothing, PatchedPeople] = PatchedPeople.add(UserId(fakeUUID)).onAll + val patch: FieldUpdater[Nothing, PatchedPeople] = allColumns.asPeople.patch.add(UserId(fakeUUID)) - assertTrue(patch.transform(PatchedPeople(Seq.empty)).map(_.people) == Right(Seq(UserId(fakeUUID)))) + assertTrue(patch.f(PatchedPeople(Seq.empty)).map(_.people) == Right(Seq(UserId(fakeUUID)))) } ) diff --git a/zio-notion-core/src/test/scala/zio/notion/model/database/DatabaseSpec.scala b/zio-notion-core/src/test/scala/zio/notion/model/database/DatabaseSpec.scala index b3066731..e5438cac 100644 --- a/zio-notion-core/src/test/scala/zio/notion/model/database/DatabaseSpec.scala +++ b/zio-notion-core/src/test/scala/zio/notion/model/database/DatabaseSpec.scala @@ -189,7 +189,7 @@ object DatabaseSpec extends ZIOSpecDefault { def patchSpec: Spec[TestEnvironment with Scope, Any] = suite("Database update suite")( test("We should be able to update one property") { - val patch = fakeDatabase.patch.updateProperty($$"Test".as(date).rename("Date")) + val patch = fakeDatabase.patch.updateProperty($$"Test".patch.as(date).rename("Date")) val expected = """{ @@ -206,7 +206,7 @@ object DatabaseSpec extends ZIOSpecDefault { assertTrue(printer.print(patch.asJson) == expected) }, test("We should be able to create a new property description") { - val patch = fakeDatabase.patch.updateProperty($$"New field".as(date)) + val patch = fakeDatabase.patch.updateProperty($$"New field".patch.as(date)) val expected = """{ @@ -222,7 +222,7 @@ object DatabaseSpec extends ZIOSpecDefault { assertTrue(printer.print(patch.asJson) == expected) }, test("We should be able to rename a newly created property description (if we rename on create, we have to use the rename value)") { - val patch = fakeDatabase.patch.updateProperty($$"New field".as(date).rename("Date")) + val patch = fakeDatabase.patch.updateProperty($$"New field".patch.as(date).rename("Date")) val expected = """{ diff --git a/zio-notion-core/src/test/scala/zio/notion/model/page/PageSpec.scala b/zio-notion-core/src/test/scala/zio/notion/model/page/PageSpec.scala index f30f081e..16502665 100644 --- a/zio-notion-core/src/test/scala/zio/notion/model/page/PageSpec.scala +++ b/zio-notion-core/src/test/scala/zio/notion/model/page/PageSpec.scala @@ -5,9 +5,9 @@ import io.circe.syntax.EncoderOps import zio.Scope import zio.notion.Faker._ +import zio.notion.dsl._ import zio.notion.model.common._ import zio.notion.model.common.Icon.Emoji -import zio.notion.model.page.patch.PatchedProperty._ import zio.notion.model.printer import zio.test._ import zio.test.Assertion.isRight @@ -171,7 +171,7 @@ object PageSpec extends ZIOSpecDefault { def patchSpec: Spec[TestEnvironment with Scope, Any] = suite("Page update suite")( test("We should be able to update one property") { - val maybePatch = fakePage.patch.updateProperty(PatchedCheckbox.check.on("Checkbox")) + val maybePatch = fakePage.patch.updateProperty($"Checkbox".asCheckbox.patch.check) val expected = """{ @@ -268,8 +268,8 @@ object PageSpec extends ZIOSpecDefault { test("Updating a property twice should apply the second update on the first one") { val maybePatch = for { - p1 <- fakePage.patch.updateProperty(PatchedCheckbox.check.on("Checkbox")) - p2 <- p1.updateProperty(PatchedCheckbox.reverse.onAll) + p1 <- fakePage.patch.updateProperty($"Checkbox".asCheckbox.patch.check) + p2 <- p1.updateProperty(allColumns.asCheckbox.patch.reverse) } yield p2 val expected =