Skip to content

Commit

Permalink
Merge pull request #45 from vpavkin/0.2.0-docs
Browse files Browse the repository at this point in the history
Release 0.2.0 docs
  • Loading branch information
vpavkin committed Mar 5, 2016
2 parents 6473ef1 + 8bf99fa commit 721f17a
Show file tree
Hide file tree
Showing 21 changed files with 492 additions and 16 deletions.
69 changes: 61 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -133,15 +137,17 @@ 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", _))
```

#### 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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
98 changes: 98 additions & 0 deletions core/src/main/scala/ru/pavkin/todoist/api/core/AuthorizedAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
17 changes: 16 additions & 1 deletion core/src/main/scala/ru/pavkin/todoist/api/core/OAuthAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]

}
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package ru.pavkin.todoist.api.core

/**
* Contains all command and resource model classes
*/
package object model {
type Item = Task
}
Loading

0 comments on commit 721f17a

Please sign in to comment.