diff --git a/README.md b/README.md index c4b057d..0ddefaa 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Scalist is a client library for [Todoist API](https://developer.todoist.com/), w Scalist works on Scala 2.11 with Java 7/8. **Warning:** -Project is at early stages now. Implemented feature set is not complete and there can be performance problems. Also, a great work should be done on clearing out what types should be made private/public. +Project is at early stages now. Most major features are implemented, but there can be performance problems. Also, a great work should be done on clearing out what types should be made private/public. 1. [Getting started](#getting-started) @@ -28,7 +28,8 @@ Project is at early stages now. Implemented feature set is not complete and ther 2. [API dependencies](#api-dependencies) 3. [Type safety](#type-safety) 3. [Documentation](#documentation) -4. [Contributing](#contributing) +4. [Supported resources and commands](#supported-resources-and-commands) +5. [Contributing](#contributing) ## Getting started @@ -37,7 +38,7 @@ Project is at early stages now. Implemented feature set is not complete and ther Currently, there's only one API implementation, based on [Dispatch HTTP](https://github.com/dispatch/reboot) and [Circe JSON](https://github.com/travisbrown/circe) libraries. To get it, include this in your `build.sbt`: ```scala -libraryDependencies += "ru.vpavkin" %% "scalist-dispatch-circe" % "0.1.0" +libraryDependencies += "ru.vpavkin" %% "scalist-dispatch-circe" % "0.2.0" ``` Next, import the API toolkit where you need it: @@ -98,7 +99,10 @@ Build a single command request: val addProject = api.perform(AddProject("Learn scalist", Some(ProjectColor.color18))) ``` -Build a typesafe multiple command request (multiple ways): +Build a typesafe multiple command request (multiple ways). +Notice the usage of `projectId` tagger to mark raw `UUID` as a project id. +All entity ids have tagged types for additional compile time safety. + ```scala import java.util.UUID @@ -133,7 +137,7 @@ val addProjectWithTasks = api.performAll( ) ``` -Note that resource ids, while being `UUID`s under the hood, are tagged with corresponding phantom types, so you won't be able to write things like this: +Note, that tagged ids help to avoid misuse here: for instance, you won't be able to create `AddTask` command with a temp id of a label: ```scala // labelId and taskId have differently tagged types val invalidCommand = AddLabel("Label").andForIt(AddTask("Task1", _)) @@ -141,7 +145,9 @@ val invalidCommand = AddLabel("Label").andForIt(AddTask("Task1", _)) #### Request execution -To send the request just call `execute` method on the request object: +Everything we created in [Queries](#queries) and [Commands](#commands) sections examples are just request definitions: no requests were actually executed yet. + +Given a request definition we can send the request by just calling `execute` method on the request definition instance: ```scala projectsRequest.execute @@ -240,6 +246,12 @@ result.resultFor(addProject.uuid) result.resultFor(UUID.randomUUID) // returns None at runtime ``` +- `isSuccess` allows to quickly find out, if all the commands in the request finished successfully: + +```scala +result.isSuccess // true or false +``` + ## Design Scalist is designed with three correlated requirements: @@ -277,13 +289,54 @@ Some type level tricks, that were used within the Scalist DSL will be described ## Documentation -Full API documentation is under development. +Full API documentation is under development. For now, please, check the [Getting started](#getting-started) guide or [file an issue](https://github.com/vpavkin/scalist/issues/new) with a question. -Scaladocs are located [here](http://vpavkin.github.io/scalist/api/#package). +Also, all methods that can be used by library user are documented in the source. +Full scaladocs are located [here](http://vpavkin.github.io/scalist/api/#package). Model classes are good to study right in the [source](https://github.com/vpavkin/scalist/tree/master/core/src/main/scala/ru/pavkin/todoist/api/core/model). +## Supported resources and commands: + +Currently supported resources: + +- Project +- Label +- Filter +- Task +- Note +- Reminder +- User + +Full list of commands, currently supported by Scalist: + +- AddAbsoluteTimeBasedReminder +- AddFilter +- AddLabel +- AddLocationBasedReminder +- AddNote +- AddProject +- AddRelativeTimeBasedReminder +- AddTask +- AddTaskToInbox +- ArchiveProjects +- CloseTask +- DeleteFilter +- DeleteLabel +- DeleteNote +- DeleteProjects +- DeleteReminder +- DeleteTasks +- MoveTasks +- UnarchiveProjects +- UncompleteTasks +- UpdateFilter +- UpdateLabel +- UpdateNote +- UpdateProject +- UpdateTask + ## Contributing Any contribution is welcome! :) If you want to, please, don't hesitate to: diff --git a/build.sbt b/build.sbt index 76efd08..2081188 100644 --- a/build.sbt +++ b/build.sbt @@ -125,7 +125,7 @@ lazy val tests = project.in(file("tests")) dispatchCirce ) -lazy val noDocProjects: Seq[ProjectReference] = Seq.empty +lazy val noDocProjects: Seq[ProjectReference] = Seq(scalist) lazy val docSettings = site.settings ++ ghpages.settings ++ unidocSettings ++ Seq( site.addMappingsToSiteDir(mappings in(ScalaUnidoc, packageDoc), "api"), diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/AuthorizedAPI.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/AuthorizedAPI.scala index a0d730f..a0f5f00 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/AuthorizedAPI.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/AuthorizedAPI.scala @@ -7,22 +7,120 @@ import ru.pavkin.todoist.api.utils.IsDistinctConstraint import shapeless._ import shapeless.ops.hlist.Reverse +/** + * Authorized API client that is entitled to perform most of the API calls. + * + * Methods are separated into two groups: + * + * - `get` methods, for querying resources + * - `perform` methods, for sending commands + * + * All the implicit parameters for those methods are usually supplied by the imported API suite, e.g.: + * + * {{{ + * import ru.pavkin.todoist.api.dispatch.circe.default._ + * }}} + * + * All methods return request definition containers, that are not yet executed. + * To execute a request definition, you have to call `execute` method on it. + * + * Request definitions can be chained together to compose a multiple entity request. + * Chaining is done with `and` method, e.g.: + * + * {{{ + * api.get[Projects].and[Labels].and[Tasks] + * api.getAll[Projects :: Labels :: HNil].and[Tasks] + * api.perform(AddProject("p1")).and(AddProject("p2")) + * api.performAll(AddProject("p1") :+ AddProject("p2")) + * }}} + */ trait AuthorizedAPI[F[_], P[_], Base] { + + /** + * Returns a single resource request definition, that after being executed will return + * the resource of type `R` + */ def get[R](implicit IR: HasRawRequest[R], parser: SingleResponseDecoder[P, Base, R]): SingleQueryDefinition[F, P, R, Base] + /** + * Returns a multiple resources request definition, that after being executed will return + * an `HList` of resources, specified in phantom type parameter `R` + * + * Example usage: + * {{{ + * api.getAll[Projects :: Labels :: Tasks :: HNil] + * // will return List[Projects] :: List[Labels] :: List[Tasks] :: HNil upon execution + * }}} + * + * Syntax helpers are available for working with multiple resources response. + * After handling the API effect, you can call these methods on the result: + * + * {{{ + * res.projects // returns List[Project] + * res.labels // returns List[Label] + * // ... + * // etc, but only for resources that were requested + * }}} + * + * For syntax helpers to be available, you should import the syntax toolkit, for example: + * + * {{{ + * import ru.pavkin.todoist.api.dispatch.circe.default.syntax._ + * }}} + * + * @note Doesn't allow to specify duplicate resources. + */ def getAll[R <: HList](implicit IR: HasRawRequest[R], ID: IsDistinctConstraint[R], parser: MultipleResponseDecoder[P, Base, R]): MultipleQueryDefinition[F, P, R, Base] + /** Returns a single command request definition, that when being executed + * performs a supplied `command: C` and returns command result of type `R`. + * + * All command results are successors of [[ru.pavkin.todoist.api.core.model.TodoistCommandResult]]: + * + * - For [[ru.pavkin.todoist.api.core.model.SimpleCommand]] + * returns [[ru.pavkin.todoist.api.core.model.CommandResult]] + * - For [[ru.pavkin.todoist.api.core.model.TempIdCommand]] + * returns [[ru.pavkin.todoist.api.core.model.TempIdCommandResult]] + * + * @param command Command to execute within the request + */ def perform[C, R](command: C) (implicit trr: ToRawRequest[C], cr: CommandReturns.Aux[C, R], parser: SingleCommandResponseDecoder.Aux[P, C, Base, R]): SingleCommandDefinition[F, P, C, R, Base] + /** + * Returns a multiple commands request definition, that when being executed + * performs all supplied `commands` and returns an `HList` of corresponding command results. + * See [[AuthorizedAPI.perform]] method docs for command results details + * + * Syntax helpers are available for multiple command results response. + * After handling the API effect, you can call these methods on the result: + * + * {{{ + * res.resultFor(_0) // returns strictly typed result of the first command on the list + * res.resultFor(_1) // returns strictly typed result of the seconds command on the list + * // ... + * // and so on, but only for the amount of commands that was actually sent + * + * res.resultFor(uuid:UUID) // tries to find result for command with specific uuid + * // returns an Option[TodoistCommandResult] + * }}} + * + * For syntax helpers to be available, you should import the syntax toolkit, for example: + * + * {{{ + * import ru.pavkin.todoist.api.dispatch.circe.default.syntax._ + * }}} + * + * @param commands `HList` of commands to execute + */ def performAll[C <: HList, R <: HList, CR <: HList](commands: C) (implicit R: Reverse.Aux[C, CR], diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/OAuthAPI.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/OAuthAPI.scala index aef3eff..8412d58 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/OAuthAPI.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/OAuthAPI.scala @@ -4,15 +4,30 @@ import ru.pavkin.todoist.api._ import ru.pavkin.todoist.api.core.decoder.SingleResponseDecoder import ru.pavkin.todoist.api.core.model.{TokenExchange, TokenScope} +/** + * Supplies helper methods for Todoist authorization API + */ trait OAuthAPI[F[_], P[_], Base] { + /** + * Returns an oAuth step 3 request definition, that upon execution + * exchanges security code on API token. + * + * @param request Exchange request parameters + */ def oAuthStep3(request: TokenExchange) (implicit trr: ToRawRequest[TokenExchange], parser: SingleResponseDecoder[P, Base, model.AccessToken]) : RequestDefinition[F, P, model.AccessToken, Base] - + /** + * Constructs a url for oAuth step 1 request, based on supplied parameters + * + * @param clientId client id + * @param scopes a set of scopes that are required by authorizing application + * @param state unique state parameter + */ def oAuthStep1URL(clientId: String, scopes: Set[TokenScope], state: String): String = s"$oAuthURL?" + List( "client_id" -> clientId, diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/RequestDefinition.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/RequestDefinition.scala index 3abdd62..23428cf 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/RequestDefinition.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/RequestDefinition.scala @@ -1,6 +1,10 @@ package ru.pavkin.todoist.api.core trait RequestDefinition[F[_], P[_], R, Base] { + + /** + * Executes this request definition and returns the result wrapped with the API effect + */ def execute: F[R] } diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/UnauthorizedAPI.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/UnauthorizedAPI.scala index 663d273..0056f24 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/UnauthorizedAPI.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/UnauthorizedAPI.scala @@ -2,10 +2,24 @@ package ru.pavkin.todoist.api.core import ru.pavkin.todoist.api.Token +/** + * Unauthorized API provides two methods: `withToken(token: String)` that returns Authorized API + * and `auth`, that returns a wrapper for OAuth related helpers + */ trait UnauthorizedAPI[F[_], P[_], Base] { + /** + * Creates an authorized API Client that has access to most of the API calls like + * querying resources and performing commands + * + * @param token User API token + * @return Authorized API Client + */ def withToken(token: Token): AuthorizedAPI[F, P, Base] + /** + * Returns a client for OAuth related helpers + */ def auth: OAuthAPI[F, P, Base] } diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/command/MultipleCommandDefinition.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/command/MultipleCommandDefinition.scala index fec532e..b8c2e66 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/command/MultipleCommandDefinition.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/command/MultipleCommandDefinition.scala @@ -5,9 +5,22 @@ import ru.pavkin.todoist.api.core.decoder.{SingleCommandResponseDecoder, SingleR import ru.pavkin.todoist.api.core.{CommandReturns, ToRawRequest, RequestDefinition} import shapeless._ +/** + * A definition of a multiple commands request. + * + * Call `execute` to perform the commands and get the results `HList` (under the effect) + */ trait MultipleCommandDefinition[F[_], P[_], C <: HList, R <: HList, Base] extends RequestDefinition[F, P, R, Base] { + /** + * Returns a new command request definition, that after execution will + * execute all the commands from this definition plus the added one + * and return an `HList` of corresponding results + * + * See [[ru.pavkin.todoist.api.core.AuthorizedAPI.performAll]] for details on working with + * multiple commands response + */ def and[CC, RR](command: CC) (implicit FM: FlatMap[P], diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/command/SingleCommandDefinition.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/command/SingleCommandDefinition.scala index 83d8e3b..8c8697a 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/command/SingleCommandDefinition.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/command/SingleCommandDefinition.scala @@ -5,9 +5,24 @@ import ru.pavkin.todoist.api.core.decoder.{SingleCommandResponseDecoder, SingleR import ru.pavkin.todoist.api.core.{CommandReturns, RequestDefinition, ToRawRequest} import shapeless._ +/** + * A definition of a single command request. + * + * Call `execute` to perform the command and get the result (under the effect) + */ trait SingleCommandDefinition[F[_], P[_], C, R, Base] extends RequestDefinition[F, P, R, Base] { + /** + * Returns a new command request definition, that after execution will + * execute both commands and return an `HList` of corresponding results + * + * Equivalent of calling: + * {{{api.performAll(command1 :+ command2)}}} + * + * See [[ru.pavkin.todoist.api.core.AuthorizedAPI.performAll]] for details on working with + * multiple commands response + */ def and[CC, RR](command: CC) (implicit FM: FlatMap[P], diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/model/package.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/model/package.scala index 798c2f1..aaae6fe 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/model/package.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/model/package.scala @@ -1,5 +1,8 @@ package ru.pavkin.todoist.api.core +/** + * Contains all command and resource model classes + */ package object model { type Item = Task } diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/CombineCommands.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/CombineCommands.scala index 56b2eb7..81a6d94 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/CombineCommands.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/CombineCommands.scala @@ -9,16 +9,50 @@ import shapeless.{HList, ::, HNil} object CombineCommands { trait Syntax { implicit class CombineCommandsOps[C <: Command](command: C) { + /** + * Combines two commands into an `HList` + * + * @example {{{command1 :+ command2}}} + */ def :+[A <: Command](other: A): C :: A :: HNil = command :: other :: HNil } implicit class TempIdProduceCommandsOps[Tag, C](command: C)(implicit ev: C <:< TempIdCommand[Tag]) { + /** + * Creates a dependant command that uses the `tempId` of this command. + * Returns '''only''' the dependant command. + * + * @param factory A function that receives a tempId and creates a command. + * @return The dependant command '''only''' + * @example {{{AddProject("p").forIt(AddTask("t",_) // AddTask}}} + */ def forIt[T](factory: UUID @@ Tag => T): T = factory(command.tempId) + /** + * Creates a dependant command that uses the `tempId` of this command '''and + * stacks it to this command'''. + * Returns an `HList` of this command and the dependant command. + * + * @param factory A function that receives a tempId and creates a command. + * @return An `HList` of this command and the dependant command. + * @example {{{AddProject("p").andForIt(AddTask("t",_) // AddProject :: AddTask :: HNil}}} + */ def andForIt[T](factory: UUID @@ Tag => T): C :: T :: HNil = command :: factory(command.tempId) :: HNil + /** + * Creates multiple dependant commands that use the `tempId` of this command '''and + * stacks them to this command'''. + * Returns an `HList` of this command and the dependant commands. + * + * @param commands A function that receives a tempId and creates an `HList` of commands. + * @return An `HList` of this command and the dependant commands. + * @example {{{ + * AddProject("p").andForItAll(id => AddTask("t1",id) :+ AddTask("t2",id)) + * // AddProject :: AddTask :: AddTask :: HNil + * }}} + */ def andForItAll[T <: HList](commands: UUID @@ Tag => T): C :: T = command :: commands(command.tempId) } diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/CommandResultHList.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/CommandResultHList.scala index 52b17d4..b79a431 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/CommandResultHList.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/CommandResultHList.scala @@ -6,7 +6,16 @@ import ru.pavkin.todoist.api.core.model.TodoistCommandResult import shapeless.{HNil, HList, ::} trait CommandResultHList[T <: HList] { + /** + * Returns `true` if all commands in request were successfully executed, `false` otherwise + */ def isSuccess(result: T): Boolean + /** + * Returns result for the command with specified `uuid` + * + * @param uuid the uuid of the command the result for which is requested + * @return `Some(result)` if it exists, `None` otherwise + */ def resultFor(result: T)(uuid: UUID): Option[TodoistCommandResult] } @@ -14,7 +23,19 @@ object CommandResultHList { trait Syntax { implicit class CommandResultHListOps[A <: HList](a: A)(implicit ev: CommandResultHList[A]) { + /** + * Returns result for the command with specified `uuid` + * + * @param uuid the uuid of the command the result for which is requested + * @return `Some(result)` if it exists, `None` otherwise + * @example {{{res.resultFor(command.uuid)}}} + */ def resultFor(uuid: UUID): Option[TodoistCommandResult] = ev.resultFor(a)(uuid) + /** + * Returns `true` if all commands in request were successfully executed, `false` otherwise + * + * @example {{{res.isSuccess}}} + */ def isSuccess: Boolean = ev.isSuccess(a) } } diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/ReversedAtSyntax.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/ReversedAtSyntax.scala index 7112e72..4a0725f 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/ReversedAtSyntax.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/model/util/ReversedAtSyntax.scala @@ -7,6 +7,18 @@ trait ReversedAtSyntax extends Nats { val _0: _0 = new _0 implicit class ReversedAtHListOps[A <: HList](a: A) { + + /** + * Returns the result of the command that is located under specified index in the request + * + * - For [[ru.pavkin.todoist.api.core.model.SimpleCommand]] + * returns [[ru.pavkin.todoist.api.core.model.CommandResult]] + * - For [[ru.pavkin.todoist.api.core.model.TempIdCommand]] + * returns [[ru.pavkin.todoist.api.core.model.TempIdCommandResult]] + * + * @param index the zero based command index, prefixed by an underscore, e.g. `_0` or `_12` + * @example {{{res.resultFor(_0)}}} + */ def resultFor[R <: HList, Out](index: Nat) (implicit R: Reverse.Aux[A, R], A: At.Aux[R, index.N, Out]): Out = A(a.reverse) } diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/query/MultipleQueryDefinition.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/query/MultipleQueryDefinition.scala index 1630597..62f4cbc 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/query/MultipleQueryDefinition.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/query/MultipleQueryDefinition.scala @@ -6,8 +6,21 @@ import ru.pavkin.todoist.api.core.{HasRawRequest, RequestDefinition} import ru.pavkin.todoist.api.utils.NotContainsConstraint import shapeless.{::, HList} +/** + * A definition of a multiple resources query. + * The execution result is an HList of requested resources + * + * Call `execute` to perform the request and get the result (under the effect) + */ trait MultipleQueryDefinition[F[_], P[_], R <: HList, Base] extends RequestDefinition[F, P, R, Base] { + /** + * Returns a new request definition, that after execution will return + * all originally requested resources plus the added one (in a form of an `HList`) + * + * See [[ru.pavkin.todoist.api.core.AuthorizedAPI.getAll]] for details on working with + * multiple resources response + */ def and[RR](implicit FM: FlatMap[P], NC: R NotContainsConstraint RR, diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/query/SingleQueryDefinition.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/query/SingleQueryDefinition.scala index a5de9a9..df4dbfa 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/query/SingleQueryDefinition.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/query/SingleQueryDefinition.scala @@ -5,8 +5,23 @@ import ru.pavkin.todoist.api.core.decoder.SingleResponseDecoder import ru.pavkin.todoist.api.core.{HasRawRequest, RequestDefinition} import shapeless.{::, <:!<, HNil} +/** + * A definition of a single resource query. + * + * Call `execute` to perform the request and get the result (under the effect) + */ trait SingleQueryDefinition[F[_], P[_], R, Base] extends RequestDefinition[F, P, R, Base] { + /** + * Returns a new request definition, that after execution will return + * both resources (in a form of an `HList`) + * + * Equivalent of calling: + * {{{api.getAll[R2 :: R1 :: HNil]}}} + * + * See [[ru.pavkin.todoist.api.core.AuthorizedAPI.getAll]] for details on working with + * multiple resources response + */ def and[RR](implicit FM: FlatMap[P], NEQ: RR <:!< R, diff --git a/core/src/main/scala/ru/pavkin/todoist/api/core/tags.scala b/core/src/main/scala/ru/pavkin/todoist/api/core/tags.scala index c9946d6..8939f79 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/core/tags.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/core/tags.scala @@ -4,35 +4,109 @@ import shapeless.tag import shapeless.tag._ object tags { + /** + * Tag for project ids + */ trait ProjectId + /** + * Tag for label ids + */ trait LabelId + /** + * Tag for task ids + */ trait TaskId + /** + * Tag for user ids + */ trait UserId + /** + * Tag for note ids + */ trait NoteId + /** + * Tag for filter ids + */ trait FilterId + /** + * Tag for reminder ids + */ trait ReminderId - trait Projects - trait Labels - trait Syntax { implicit class ResourceIdTagOps[A: IsResourceId](a: A) { + /** + * Tags raw value with [[ru.pavkin.todoist.api.core.tags.ProjectId]] tag + * so that it can be used with the modal classes + */ def projectId: A @@ tags.ProjectId = tag[tags.ProjectId](a) + /** + * Tags raw value with [[ru.pavkin.todoist.api.core.tags.LabelId]] tag + * so that it can be used with the modal classes + */ def labelId: A @@ tags.LabelId = tag[tags.LabelId](a) + /** + * Tags raw value with [[ru.pavkin.todoist.api.core.tags.TaskId]] tag + * so that it can be used with the modal classes + */ def taskId: A @@ tags.TaskId = tag[tags.TaskId](a) + /** + * Tags raw value with [[ru.pavkin.todoist.api.core.tags.UserId]] tag + * so that it can be used with the modal classes + */ def userId: A @@ tags.UserId = tag[tags.UserId](a) + /** + * Tags raw value with [[ru.pavkin.todoist.api.core.tags.NoteId]] tag + * so that it can be used with the modal classes + */ def noteId: A @@ tags.NoteId = tag[tags.NoteId](a) + /** + * Tags raw value with [[ru.pavkin.todoist.api.core.tags.FilterId]] tag + * so that it can be used with the modal classes + */ def filterId: A @@ tags.FilterId = tag[tags.FilterId](a) + /** + * Tags raw value with [[ru.pavkin.todoist.api.core.tags.ReminderId]] tag + * so that it can be used with the modal classes + */ def reminderId: A @@ tags.ReminderId = tag[tags.ReminderId](a) } implicit class ResourceIdListTagOps[A: IsResourceId](a: List[A]) { + /** + * Tags a list of raw values with [[ru.pavkin.todoist.api.core.tags.ProjectId]] tag + * so that it can be used with the modal classes + */ def projectIds: List[A @@ tags.ProjectId] = a.map(_.projectId) + /** + * Tags a list of raw values with [[ru.pavkin.todoist.api.core.tags.LabelId]] tag + * so that it can be used with the modal classes + */ def labelIds: List[A @@ tags.LabelId] = a.map(_.labelId) + /** + * Tags a list of raw values with [[ru.pavkin.todoist.api.core.tags.TaskId]] tag + * so that it can be used with the modal classes + */ def taskIds: List[A @@ tags.TaskId] = a.map(_.taskId) + /** + * Tags a list of raw values with [[ru.pavkin.todoist.api.core.tags.UserId]] tag + * so that it can be used with the modal classes + */ def userIds: List[A @@ tags.UserId] = a.map(_.userId) + /** + * Tags a list of raw values with [[ru.pavkin.todoist.api.core.tags.NoteId]] tag + * so that it can be used with the modal classes + */ def noteIds: List[A @@ tags.NoteId] = a.map(_.noteId) + /** + * Tags a list of raw values with [[ru.pavkin.todoist.api.core.tags.FilterId]] tag + * so that it can be used with the modal classes + */ def filterIds: List[A @@ tags.FilterId] = a.map(_.filterId) + /** + * Tags a list of raw values with [[ru.pavkin.todoist.api.core.tags.ReminderId]] tag + * so that it can be used with the modal classes + */ def reminderIds: List[A @@ tags.ReminderId] = a.map(_.reminderId) } } diff --git a/core/src/main/scala/ru/pavkin/todoist/api/suite/FutureBasedAPISuite.scala b/core/src/main/scala/ru/pavkin/todoist/api/suite/FutureBasedAPISuite.scala index fc9c37c..b813790 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/suite/FutureBasedAPISuite.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/suite/FutureBasedAPISuite.scala @@ -8,6 +8,12 @@ import scala.concurrent.ExecutionContext trait FutureBasedAPISuite[F[_], P[_], Base] extends FutureInstances { + /** + * Returns Unauthorized API client instance + * + * @param ec Execution context that will be used internally for all API calls + * + */ def todoist(implicit ec: ExecutionContext): UnauthorizedAPI[F, P, Base] } diff --git a/core/src/main/scala/ru/pavkin/todoist/api/suite/ModelAPISuite.scala b/core/src/main/scala/ru/pavkin/todoist/api/suite/ModelAPISuite.scala index 10bb49a..b23c956 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/suite/ModelAPISuite.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/suite/ModelAPISuite.scala @@ -92,6 +92,18 @@ trait ModelAPISuite[F[_], P[_], Base] def dtoToAccessToken(implicit M: Monad[P]): SingleResponseDecoder[P, dto.AccessToken, model.AccessToken] = SingleResponseDecoder.using(d => M.pure(d.toModel)) + /** + * Syntax helpers for this API suite + * + * @see [[ru.pavkin.todoist.api.suite.QueryAPISuite.QuerySyntax.HListQueryOps.projects]] + * @see [[ru.pavkin.todoist.api.core.model.util.ReversedAtSyntax.ReversedAtHListOps.resultFor]] + * @see [[ru.pavkin.todoist.api.core.model.util.CommandResultHList.Syntax.CommandResultHListOps.resultFor]] + * @see [[ru.pavkin.todoist.api.core.model.util.CommandResultHList.Syntax.CommandResultHListOps.isSuccess]] + * @see [[ru.pavkin.todoist.api.core.model.util.CombineCommands.Syntax.CombineCommandsOps.:+]] + * @see [[ru.pavkin.todoist.api.core.model.util.CombineCommands.Syntax.TempIdProduceCommandsOps.forIt]] + * @see [[ru.pavkin.todoist.api.core.model.util.CombineCommands.Syntax.TempIdProduceCommandsOps.andForIt]] + * @see [[ru.pavkin.todoist.api.core.model.util.CombineCommands.Syntax.TempIdProduceCommandsOps.andForItAll]] + */ object syntax extends QuerySyntax with ReversedAtSyntax diff --git a/core/src/main/scala/ru/pavkin/todoist/api/suite/QueryAPISuite.scala b/core/src/main/scala/ru/pavkin/todoist/api/suite/QueryAPISuite.scala index 670995d..913c750 100644 --- a/core/src/main/scala/ru/pavkin/todoist/api/suite/QueryAPISuite.scala +++ b/core/src/main/scala/ru/pavkin/todoist/api/suite/QueryAPISuite.scala @@ -6,13 +6,37 @@ import shapeless.{HList, ::, HNil} trait QueryAPISuite { + /** + * Collection of `Project` entities + */ type Projects + /** + * Collection of `Label` entities + */ type Labels + /** + * Collection of `Item` entities + */ type Tasks + /** + * Collection of `Note` entities + */ type Notes + /** + * Collection of `Filter` entities + */ type Filters + /** + * Collection of `Reminder` entities + */ type Reminders + /** + * `User` entity + */ type User + /** + * All resources that can be requested + */ type All = User :: Reminders :: Filters :: Notes :: Tasks :: Projects :: Labels :: HNil implicit val tasks = HasRawRequest.resource[Tasks](List("items")) @@ -26,12 +50,35 @@ trait QueryAPISuite { trait QuerySyntax { implicit class HListQueryOps[L <: HList](l: L) { + /** + * Returns the list of projects contained in this response + * + * @example {{{res.projects}}} + */ def projects(implicit S: Selector[L, Projects]): Projects = S(l) + /** + * Returns the list of labels contained in this response + */ def labels(implicit S: Selector[L, Labels]): Labels = S(l) + /** + * Returns the list of tasks contained in this response + */ def tasks(implicit S: Selector[L, Tasks]): Tasks = S(l) + /** + * Returns the list of notes contained in this response + */ def notes(implicit S: Selector[L, Notes]): Notes = S(l) + /** + * Returns the list of filters contained in this response + */ def filters(implicit S: Selector[L, Filters]): Filters = S(l) + /** + * Returns the list of reminders contained in this response + */ def reminders(implicit S: Selector[L, Reminders]): Reminders = S(l) + /** + * Returns the user object contained in this response + */ def user(implicit S: Selector[L, User]): User = S(l) } } diff --git a/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/circe/CirceAPISuite.scala b/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/circe/CirceAPISuite.scala index d25b1c9..9bb2250 100644 --- a/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/circe/CirceAPISuite.scala +++ b/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/circe/CirceAPISuite.scala @@ -47,7 +47,7 @@ trait CirceAPISuite executor ) - def auth: OAuthAPI[DispatchAPI.Result, CirceDecoder.Result, Json] = + override val auth: OAuthAPI[DispatchAPI.Result, CirceDecoder.Result, Json] = new DispatchOAuthAPI( new DispatchOAuthRequestFactory, executor diff --git a/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/circe/default.scala b/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/circe/default.scala index d4f7fca..efffa95 100644 --- a/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/circe/default.scala +++ b/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/circe/default.scala @@ -1,3 +1,14 @@ package ru.pavkin.todoist.api.dispatch.circe +/** + * Default asynchronous API client implementation. + * + * API effect is `Future[ Xor[DispatchAPI.Error, T] ]` + * + * Uses Dispatch HTTP client and Circe Json under the hood + * + * @see [[scala.concurrent.Future]] + * @see [[cats.data.Xor]] + * @see [[ru.pavkin.todoist.api.dispatch.impl.circe.DispatchAPI.Error]] + */ object default extends CirceModelAPISuite diff --git a/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/impl/circe/DispatchAuthorizedAPI.scala b/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/impl/circe/DispatchAuthorizedAPI.scala index 71a0fc5..83a6f3c 100644 --- a/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/impl/circe/DispatchAuthorizedAPI.scala +++ b/dispatch-circe/src/main/scala/ru/pavkin/todoist/api/dispatch/impl/circe/DispatchAuthorizedAPI.scala @@ -14,8 +14,24 @@ import scala.concurrent.{ExecutionContext, Future} object DispatchAPI extends FutureInstances with ComposeApply { + /** + * Represents error, occured during HTTP request. + * Can be either [[HTTPError]] or [[DecodingError]] + */ sealed trait Error + /** + * Messages that an error occured during result decoding, + * which usually means inconsistency between the actual API and implementation + * + * @param underlying the Circe error, describing the actual failure + */ case class DecodingError(underlying: io.circe.Error) extends Error + /** + * Represents HTTP response with not 2xx result code + * + * @param code HTTP response code + * @param body optional response body + */ case class HTTPError(code: Int, body: Option[String]) extends Error type L[T] = DispatchJsonRequestExecutor.Result[T] @@ -54,6 +70,6 @@ class DispatchAuthorizedAPI(override val requestFactory: AuthorizedRequestFactor extends DispatchAPI(executor) with ExecutedAuthorizedAPI[Result, L, P, Req, Json] class DispatchOAuthAPI(override val requestFactory: RawRequest Produce Req, - override val executor: RequestExecutor.Aux[Req, DispatchJsonRequestExecutor.Result, Json]) - (override implicit val ec: ExecutionContext) + override val executor: RequestExecutor.Aux[Req, DispatchJsonRequestExecutor.Result, Json]) + (override implicit val ec: ExecutionContext) extends DispatchAPI(executor) with ExecutedOAuthAPI[Result, L, P, Req, Json]