Skip to content

Commit

Permalink
Pre release 0.2.0 (#10)
Browse files Browse the repository at this point in the history
* Added travis yml and updated readme with build badge

* Readme update

* Renamed prohecy to generate

* Updated folder structure

* Added test and file manipulation dependencies

* Added tests for writClassFile

* Library Renamed to apollo-prophecy

pythian -> prophecy,
Pythian* -> Prophetic

* 0.1.1

* Added version related scripts

* Fixed build errors

Added express types as dev dependency

* 0.1.0

* Added mocha to dev dependencies

* 0.1.0 pre relase clean

* 0.1.0 pre release build

* Update output message

& moved "@types/graphql" to "devDependencies"

* Readme update

* Added readme logo 🔮

* Feature/ask command/#1 (#8)

* Added Ask/Fetch command

* Code generation clean

* Updated structure and added tests

* Fix: added handle method to PropheticErrorHandled

* Updated readme with ask explanation

* Added Table of Contents

* Readme enhenced

* Added nyc for test code coverage

* Feature/yargs/#3 (#9)

* Replaced minimist with yargs closes #3

* post release clean

* Readme occurence removed

* Readme update on Table of Contents

* Restructured error generation

* Updated readme accordingly to recent updates

* Fixed tests
  • Loading branch information
theGlenn authored Jun 14, 2018
1 parent 7beef29 commit 56daaca
Show file tree
Hide file tree
Showing 28 changed files with 2,437 additions and 198 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ node_modules
*.log
_generated/
_generated/Errors.ts
.nyc_output
coverage
Errors.ts
errors.json
77 changes: 77 additions & 0 deletions CLIENT-ERRORS-SAMPLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
Here you will find an example of client-side generated `Errors.ts`;

```ts
/* tslint:disable */
import { ApolloError } from "apollo-client";
import { GraphQLError } from "graphql";

export enum PropheticErrorCode {
CodeLessError = 'NONE',
UnknownError = "UNKNOWN",
ForbiddenError = "FORBIDDEN",
AuthenticationRequiredError = "AUTH_REQUIRED"
}

export class PropheticError {
constructor(public codes: string[]){}

private inCodes(code: PropheticErrorCode){ return this.codes.indexOf(code) > -1; }

get isCodeLessError() { return this.inCodes(PropheticErrorCode.CodeLessError); }
get isUnknownError() { return this.inCodes(PropheticErrorCode.UnknownError); }
get isForbiddenError() { return this.inCodes(PropheticErrorCode.ForbiddenError); }
get isAuthenticationRequiredError() { return this.inCodes(PropheticErrorCode.AuthenticationRequiredError); }
}

export interface Handler {
(): any
}

export class PropheticErrorHandled {
private handler: Handler = () => {}

constructor(public codes: string[]){}

private inCodes(code: PropheticErrorCode, handler: Handler){
if(this.codes.indexOf(code) > -1){
this.handler = handler
}

return this;
}

CodeLessError(handler: Handler) { return this.inCodes(PropheticErrorCode.CodeLessError, handler); }
UnknownError(handler: Handler) { return this.inCodes(PropheticErrorCode.UnknownError, handler); }
ForbiddenError(handler: Handler) { return this.inCodes(PropheticErrorCode.ForbiddenError, handler); }
AuthenticationRequiredError(handler: Handler) { return this.inCodes(PropheticErrorCode.AuthenticationRequiredError, handler); }
handle() { return this.handler(); }
}

const CODE_LESS_EXTENSION = { code: 'NONE'};
const findCodes = (error: ApolloError | GraphQLError): PropheticErrorCode[] => {
if(error instanceof ApolloError) {
return error.graphQLErrors.map((gError) => findCodes(gError)[0]);
} else if(error.extensions) {
const { extensions: { code } = CODE_LESS_EXTENSION } = error;
return [code];
}

return [PropheticErrorCode.CodeLessError];
}

export const errorHere = (error: ApolloError | GraphQLError | undefined ) => {
if(!error) {
return new PropheticError([]);
}
const codes = findCodes(error);
return new PropheticError(codes);
}

export const isThis = (error: ApolloError | GraphQLError | undefined) => {
if(!error) {
return new PropheticErrorHandled([]);
}
const codes = findCodes(error);
return new PropheticErrorHandled(codes);
}
```
82 changes: 82 additions & 0 deletions Errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* tslint:disable */
import { ApolloError } from "apollo-client";
import { GraphQLError } from "graphql";

export enum PropheticErrorCode {
CodeLessError = 'NONE',
UnknownError = "CAN_NOT_FETCH_BY_ID",
ForbiddenError = "null",
AuthenticationRequiredError = "AUTH_REQUIRED",
MagicTokenExpiredError = "MAGIC_TOKEN_EXPIRED",
UserNotFoundError = "USER_NOT_FOUND",
UserAlreadyExist = "USER_ALREADY_EXISTS"
}

export class PropheticError {
constructor(public codes: string[]){}

private inCodes(code: PropheticErrorCode) { return this.codes.indexOf(code) > -1; }

get isCodeLessError() { return this.inCodes(PropheticErrorCode.CodeLessError); }
get isUnknownError() { return this.inCodes(PropheticErrorCode.UnknownError); }
get isForbiddenError() { return this.inCodes(PropheticErrorCode.ForbiddenError); }
get isAuthenticationRequiredError() { return this.inCodes(PropheticErrorCode.AuthenticationRequiredError); }
get isMagicTokenExpiredError() { return this.inCodes(PropheticErrorCode.MagicTokenExpiredError); }
get isUserNotFoundError() { return this.inCodes(PropheticErrorCode.UserNotFoundError); }
get isUserAlreadyExist() { return this.inCodes(PropheticErrorCode.UserAlreadyExist); }
}

export interface Handler {
(): any
}

export class PropheticErrorHandled {
private handler: Handler = () => {}

constructor(public codes: string[]){}

private inCodes(code: PropheticErrorCode, handler: Handler){
if(this.codes.indexOf(code) > -1){
this.handler = handler
}

return this;
}

CodeLessError(handler: Handler) { return this.inCodes(PropheticErrorCode.CodeLessError, handler); }
UnknownError(handler: Handler) { return this.inCodes(PropheticErrorCode.UnknownError, handler); }
ForbiddenError(handler: Handler) { return this.inCodes(PropheticErrorCode.ForbiddenError, handler); }
AuthenticationRequiredError(handler: Handler) { return this.inCodes(PropheticErrorCode.AuthenticationRequiredError, handler); }
MagicTokenExpiredError(handler: Handler) { return this.inCodes(PropheticErrorCode.MagicTokenExpiredError, handler); }
UserNotFoundError(handler: Handler) { return this.inCodes(PropheticErrorCode.UserNotFoundError, handler); }
UserAlreadyExist(handler: Handler) { return this.inCodes(PropheticErrorCode.UserAlreadyExist, handler); }
handle() { return this.handler(); }
}

const CODE_LESS_EXTENSION = { code: 'NONE'};
const findCodes = (error: ApolloError | GraphQLError): PropheticErrorCode[] => {
if(error instanceof ApolloError) {
return error.graphQLErrors.map((gError) => findCodes(gError)[0]);
} else if(error.extensions) {
const { extensions: { code } = CODE_LESS_EXTENSION } = error;
return [code];
}

return [PropheticErrorCode.CodeLessError];
}

export const errorHere = (error: ApolloError | GraphQLError | undefined ) => {
if(!error) {
return new PropheticError([]);
}
const codes = findCodes(error);
return new PropheticError(codes);
}

export const isThis = (error: ApolloError | GraphQLError | undefined) => {
if(!error) {
return new PropheticErrorHandled([]);
}
const codes = findCodes(error);
return new PropheticErrorHandled(codes);
}
149 changes: 120 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<h1 align="center">Apollo Prophecy</h1>

<div align="center">
🙏📟🙏
👁📟👁
<br/><strong>You shall fail... successfully</strong>
</div>

<div align="center">
Expand All @@ -22,13 +23,24 @@
</a>
</div>

## 📟 Features

* Generate **Server-side** **throwable** errors in your resolvers like `throw new NotAProphetError()`
* Expose **machine readable** graphql errors through your api documentation
* Generate **Client-side** Apollo errors **consumable** like `errorHere(error).isNotAProphetError ?`

## 📟 Features
* Generate **throwable** Apollo errors for your server from a json file
* Expose spec compliant graphql errors through your api documentation
# 📋 Table of Contents

* [Installation](#installation)
* [Usage](#usage)
* [Server Side](#server)
* [Client Side](#client)
* [Todo](#todo)
* [Contribute](#contribute)
* [Test and Miscellaneous](#run-tests)

## Installation

## Install
First, install `apollo-prophecy` globaly

```sh
Expand All @@ -41,23 +53,26 @@ npm install -g apollo-prophecy
Usage: apollo-prophecy [command]
Commands:
apollo-prophecy generate [--file] [--out]
apollo-prophecy ask-errors <graphql endpoint> [--query]
apollo-prophecy generate <json file> [--out]
apollo-prophecy ask <graphql endpoint> [--field] [--out]
Options:
-h, --help Show help [boolean]
-v, --version Display version number [boolean]
```

### Server
#### `generate`
This command creates the `Error.ts` file from a `Json` definition file, using `--out` param you can change the name and location.

#### `generate` command

This command creates the `Error.ts` file from a `JSON` input file, using `--out` param you can change the name and location.
Input file should at least contains the keys `message` and `code`

```sh
apollo-prophecy generate errors.json
```

For example given the following `errors.json`:
For example given the following `errors.json` as input:

```json
{
Expand All @@ -72,7 +87,7 @@ For example given the following `errors.json`:
}
```

Apollo Errorgen will generate the following `Errors.ts`
Apollo Prophecy will generate the following `Errors.ts`

```ts
export class AuthenticationRequiredError extends ProphecyError {
Expand All @@ -88,48 +103,124 @@ export class UserNotFoundError extends ProphecyError {
}
```

Now you can use it the following way `throw new UserNotFoundError()`
Now you can use it the following way `throw new UserNotFoundError()` in your resolvers.

`apollo-errorgen` also exposes a `definitions` and a graphql type named `ProphecyError` so that you can expose all your errors descriptions through resolvers, [go see Client](###client).
`apollo-prophecy` also exposes a `definitions` object and a graphql type definition named `PropheticError` so that you can expose all your errors descriptions through resolvers, [go see Client](###client).

```ts
...
export const definitions = {
"AuthenticationRequiredError": {
export const definitions = [{
"name": "AuthenticationRequiredError"
"message": "You must be logged in to do this",
"code": "AUTH_REQUIRED"
},
"UserNotFoundError": {
"extensions": {
"code": "AUTH_REQUIRED"
}
}, {
"name": "UserNotFoundError"
"message": "No user found",
"code": "USER_NOT_FOUND"
"extensions": {
"code": "USER_NOT_FOUND"
}
}
};
}];

export const errorType = `
type ProphecyError {
message: String
type PropheticErrorExtensions {
code: String
}
type PropheticError {
message: String?
extensions: PropheticErrorExtensions
}
`;
...
```

### Client
*TODO*

## TODO
* See [#1][i1]: Client `apollo-errorgen ask` command
#### `ask` command

This command queries the `errors` field on a graphql endpoint and creates an `Errors.ts` file containing **helpers** for all the errors exposed through the server api documentation.

```sh
apollo-prophecy ask http://localhost:3000/graphql
```

#### Usage

In order to easily handle erros with **Apollo-Client**, the generated `Errors.ts` exposes two methods `errorHere` and `isThis`, both takes one paramater of type `ApolloError` or `GraphQLError`.

##### `errorHere()` function

`errorHere` returns an object that has a **property** for each errors.
You can perform a simple `boolean` check on the `error` argument by calling the approiate *key*.

```ts
import { errorHere } from `./_generated/Errors.ts`;

...(error) => {
if(errorHere(error).isUserNotFoundError){
// Do something
} else if(errorHere(error).isNotAProphetError){
// Do something else
}
}
```

##### `isThis()` function

`isThis` returns an object that has a **handler** method for each errors.
It perfoms a simple check on the `error` argument, if the it succeed the corresponding handler is called otherwise nothing happens.

Note: Handlers can return a values.

```ts
import { isThis } from `./_generated/Errors.ts`;

...(error) => {
isThis(error)
.UserNotFoundError(() => ...)
.NotAProphetError(() => ...)
.handle()
}
```

React example:

```tsx
import { isThis } from `./_generated/Errors.ts`;

...(error) => {
return <p style={{color: '#FF495C'}}>
{
isThis(error)
.UserNotFoundError(() => <span>Could not find a user with tha name</span>)
.NotAProphetError(() => <span>Only Prophets can perfom this kind of actions...</span>)
.handle();
}
<p style={{color: '#FF495C'}}>
}
```

## Contributing

[![Build status](https://travis-ci.com/theGlenn/apollo-prophecy.svg?branch=master&style=flat-square)](https://travis-ci.com/theGlenn/apollo-prophecy)

<div align="center">
✊ Grab an issue -> 🍴 fork **develop** -> 👨‍💻 Code -> 🛠 Test -> 📩 Pull Request -> 💥💥💥
</div>

### TODO

* See [#2][i2]: Add support for third party libraries errors like [apollo-errors](https://github.com/thebigredgeek/apollo-errors)
* See [#3][i3]: Use [Yargs](https://github.com/yargs/yargs) for arguments parsing

[i1]: https://github.com/theGlenn/apollo-prophecy/issues/1
[i2]: https://github.com/theGlenn/apollo-prophecy/issues/2
[i3]: https://github.com/theGlenn/apollo-prophecy/issues/3

## Contribute
Take an issue fork `/develop` -> work -> test -> pull request -> 💥
### Running tests locally:

## Run tests
```sh
npm test
```
Expand Down
Loading

0 comments on commit 56daaca

Please sign in to comment.