Skip to content

Commit

Permalink
Merge pull request #3 from jacoscaz/no-throw
Browse files Browse the repository at this point in the history
No throwing
  • Loading branch information
jacoscaz authored Sep 9, 2024
2 parents d2f9ac1 + 29267f8 commit 94c7cda
Show file tree
Hide file tree
Showing 23 changed files with 621 additions and 778 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm ci
- run: npm run codegen
- run: npm run build
- run: npm test
271 changes: 134 additions & 137 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@

# `typed-ocpp`

A library for type-aware parsing, serialization and validation of OCPP 1.6-J
and OCPP 2.0.1 messages, built against the official [JSON Schema][i2] documents
published by the [Open Charge Alliance][i1].
A library for fast, type-aware validation of OCPP 1.6-J and OCPP 2.0.1
messages, built against the official [JSON Schema][i2] documents published
by the [Open Charge Alliance][i1].

[i1]: https://openchargealliance.org
[i2]: https://json-schema.org

## Usage

### `setAjv()`

This library requires an instance of `Ajv` with support for string formats,
as provided by the `ajv-formats` plugin. See [https://npm.im/ajv][a1] and
[https://npm.im/ajv-formats][a2].

```typescript
import Ajv from 'ajv';
import formats from 'ajv-formats';
import { setAjv } from 'typed-ocpp';

setAjv(formats(new Ajv()));
```

[a1]: https://npm.im/ajv
[a2]: https://npm.im/ajv-formats

### `OCPP16` and `OCPP20` namespaces

This library exports all functions and typings related to OCPP 1.6 and
Expand All @@ -40,178 +23,194 @@ Both namespaces export identical APIs while typings and schemas differ
according to the differences in the respective OCPP versions. All of the
examples below apply to both namespaces.

### `parse()`
### `validate()`

The `parse()` function return a fully-typed and validated "view" of the
original array. No additional transformation is applied beside the eventual
`JSON.parse()` if a string is provided.
The `validate()` function is a [user-defined, validating type guard][v1] which
returns `true`if the provided value is a spec-compliant OCPP message and
`false` otherwise.

```typescript
importOCPP16 } from 'typed-ocpp';

const raw = '[2,"test","BootNotification",{"chargePointModel":"model","chargePointVendor":"vendor"}]';
const parsed = OCPP.parse(raw);

Array.isArray(parsed); // true
```

```typescript
importOCPP20 } from 'typed-ocpp';

const raw = '[2,"test","BootNotification",{ "chargingStation": { "model": "test", "vendorName": "test" }, "reason": "PowerUp"}]';
const parsed = OCPP.parse(raw);

Array.isArray(parsed); // true
const value = [2,"test","BootNotification",{"chargePointModel":"model","chargePointVendor":"vendor"}];
if (OCPP16.validate(value)) {
// valid
}
```

Values returned by `parse()` have one of the following types:

```typescript
OCPP16.Call // union of all types for Call messages
OCPP16.CallError // type for Call Error messages
OCPP16.UncheckedCallResult // generic type for Call Result messages
```
If `validate()` returns `true`, the TS compiler will infer the provided value
to be of one of the following types:

```typescript
OCPP20.Call // union of all types for Call messages
OCPP20.CallError // type for Call Error messages
OCPP20.UncheckedCallResult // generic type for Call Result messages
OCPP16.Call // Union of all types of Call messages
OCPP16.CallError // Type for Call Error messages
OCPP16.UncheckedCallResult // Type for "unchecked" Call Result messages
```

As returned values are fully-typed, the TS compiler can use known types to
infer others:
The TS compiler will then be able to use known types to infer others:

```typescript
if (OCPP16.isCall(parsed)) {
parsed[2]; // TS gives type "OCPP.Action"
if (parsed[2] === OCPP16.Action.BootNotification) {
// TS infers the shape of the call payload based on the action
parsed[3].chargePointModel; // TS gives type "string"
parsed[3].randomProp; // TS compilation error
const value = [2,"test","BootNotification",{"chargePointModel":"model","chargePointVendor":"vendor"}];
if (OCPP16.validate(value)) {
if (OCPP16.isCall(value)) {
value[2]; // TS gives type "OCPP.Action"
if (value[2] === OCPP16.Action.BootNotification) {
// TS infers the shape of the call payload based on the action
value[3].chargePointModel; // TS gives type "string"
value[3].randomProp; // TS compilation error
}
}
}
```

### `isCall()`, `isCallResult()` and `isCallError()`

The `isCall()`, `isCallResult()` and `isCallError()` functions are utility type
guards that facilitates identifying the type of a parsed message:
If validation fails and `validate()` returns `false`, validation errors will
be stored in the `validate.errors` array:

```typescript
const parsed = OCPP16.parse(message);

if (OCPP16.isCall(parsed)) {
// TS infers that parsed is of type OCPP16.Call
const value = 'foobar';
if (!OCPP16.validate(value)) {
// prints: [ 'Invalid OCPP message: invalid message type or not an array' ]
console.log(OCPP16.validate.errors);
}
```

if (OCPP16.isCallResult(message)) {
// TS infers that parsed is of type OCPP16.UncheckedCallResult
}
[v1]: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates

if (OCPP16.isCallError(message)) {
// TS infers that parsed is of type OCPP16.CallError
}
```
### `validateCall()`, `validateCallError()` and `validateCallResult()`

### `checkCallResult()`
The `validateCall()`, `validateCallError()` and `validateCallResult()`
functions are user-defined, validating type guards specific to each type of
message defined by the OCPP specs: **_call_**, **_call error_** and
**_call result_**.

Call Result messages are returned by `parse()` as the `UncheckedCallResult`
type and must be checked against the originating `Call` messages for further
validation and type-awareness.
These functions behave in the same way as the `validate()` function but only
return `true` when provided with values of the corresponding message type.
Validation errors can be retrieved via the `.errors` property, just as with
`validate()`.

If `checkCallResult()` does not throw the returned values are guaranteed to be
valid `CallResult` objects _matching the provided `Call` objects_:
### `isCall()`, `isCallResult()` and `isCallError()`

The `isCall()`, `isCallResult()` and `isCallError()` functions are
user-defined, _non-validating_ type guards that facilitate identifying the type
of a valid message:

```typescript
const call = '[2,"test","BootNotification",{"chargePointModel":"model","chargePointVendor":"vendor"}]';
const result = '[3, "test", { status: "Accepted", currentTime: "1970-01-01T00:00:00.000Z", interval: 10 }]';

const parsedCall = OCPP16.parse(call);
const parsedResult = OCPP16.parse(result);

if (OCPP16.isCall(parsedCall)) { // Narrows parsedCall to OCPP16.Call
if (OCPP16.isCallResult(parsedResult)) { // Narrows parsedResult to OCPP16.UncheckedCallResult
if (parsedCall[2] === OCPP16.Action.BootNotification) { // Narrows parsedCall to OCPP16.BootNotificationCall
const checkedResult = OCPP16.checkCallResult( // checkedResult inferred as having type
parsedResult, parsedCall // OCPP16.BootNotificationCallResult
);
checkedResult[2].status; // Inferred as "Accepted" | "Pending" | "Rejected"
}
if (OCPP16.validate(message)) {
if (OCPP16.isCall(message)) {
// TS infers that parsed is of type OCPP16.Call
}
if (OCPP16.isCallResult(message)) {
// TS infers that parsed is of type OCPP16.UncheckedCallResult
}
if (OCPP16.isCallError(message)) {
// TS infers that parsed is of type OCPP16.CallError
}
}
```

The `checkCallResult()` function returns values of the generic type
`CheckedCallResult<C extends Call>` which resolves to the type of Call Result
message that corresponds to the type of Call messages provided as the `C` type
argument. `CheckedCallResult<C extends Call>` may also be used on its own to
model Call Result messages matching a specific Call message. See below.
### `checkCallResult()`

### `stringify()`
Post-validation, **_call result_** messages are inferred by the TS compiler to
be of the `UncheckedCallResult` type, which is a generic type that does not
constrain the **_call result_**'s payload to any specific shape as doing so
requires matching against the originating **_call_** message.

Returns the JSON serialization of the provided OCPP object.
Complete validation of a **_call result_** message against its originating
**_call_** message can be done through the `checkCallResult()` user-defined,
validating type guard.

```typescript
const serialized = OCPP16.stringify(parsed);
```
If `checkCallResult()` returns `true`, the provided **_call result_** message
is guaranteed to match the provided **_call_** message in terms of:

- Matching the expected type, incl. the payload (for example: being of type
`OCPP16.BootNotificationCallResult` given an originating
`OCPP16.BootNotificationCall` essage).
- Sharing the same _call identifier_ with the originating **_call_** message.

```typescript
const serialized = OCPP20.stringify(parsed);
const call = [
2,
"test",
"BootNotification",
{"chargePointModel":"model","chargePointVendor":"vendor"},
] satisfies OCPP16.Call;

const result = [
3,
"test",
{ status: "Accepted", currentTime: "1970-01-01T00:00:00.000Z", interval: 10 },
] satisfies OCPP16.UncheckedCallResult<any>;

// Narrows the type of `call` to `OCPP16.BootNotificationCall`
if (call[2] === OCPP16.Action.BootNotification) {
// Validates `result` against `call`, narrowing the type of
// `result` to `OCPP16.BootNotificationCallResult`
if (OCPP16.checkCallResult(result, call)) {
// Inferred as "Accepted" | "Pending" | "Rejected"
result[2].status;
} else {
// The `result` message does not match the originating `call` message
console.log(OCPP16.checkCallResult.errors);
}
}
```

Just like `validate()`, when `checkCallResult()` returns `false` it stores
validation errors in the `checkCallResult.errors` array.

### Types

#### Primary types

Within both the `OCPP16` and `OCPP20` namespaces, `typed-ocpp` provides a set
of typings and schemas that covers most aspects of OCPP messages.

We've already mentioned the primary types returned by `parse()` and
`checkCallResult()`:

```typescript
OCPP16.Call // union type of all Call message types
OCPP16.CallResult // union type of all Call Result message types
OCPP16.CallError // type of Call Error messages
OCPP16.UncheckedCallResult // type of unchecked Call Result messages
OCPP16.CheckedCallResult< // generic type of that resolves to the specific
C extends OCPP16.Call> // type of Call Result message matching the
// provided type of Call message "C"
```
// Union of all types of Call messages
OCPP16.Call

```typescript
OCPP20.Call // union type of all Call message types
OCPP20.CallResult // union type of all Call Result message types
OCPP20.CallError // type of Call Error messages
OCPP20.UncheckedCallResult // type of unchecked Call Result messages
OCPP20.CheckedCallResult< // generic type of that resolves to the specific
C extends OCPP20.Call> // type of Call Result message matching the
// provided type of Call message "C"
```
// Union of all Call Result message types
OCPP16.CallResult

#### Message-specific types
// Type for Call Error messages
OCPP16.CallError

Specific types for _Call_ and _Call Result_ messages use the `Call` and
`CallResult` suffixes: `AuthorizeCall`, `AuthorizeCallResult`,
`MeterValuesCall`, `MeterValuesCallResult` and so on:
// Type for "unchecked" Call Result messages
OCPP16.UncheckedCallResult

```typescript
OCPP16.MeterValuesCall
OCPP16.MeterValuesCallResult
// Generic type of Call Result message that resolves to the specific type of
// Call Result message matching the provided type of Call message "C"
OCPP16.CheckedCallResult<C extends OCPP16.Call>

// Message-specific types
OCPP16.AuthorizationCall
OCPP16.AuthorizationCallResult
OCPP16.BootNotificationCall
OCPP16.BootNotificationCallResult
/* ... */
```
```

#### Types for specific messages

Specific types for **_Call_** and **_Call Result_** messages making up the
`OCPP16.Call` and `OCPP16.CallResult` unions use the `Call` and `CallResult`
suffixes:

```typescript
OCPP20.MeterValuesCall
OCPP20.MeterValuesCallResult
/* ... */
OCPP16.MeterValuesCall
OCPP16.MeterValuesCallResult
/* ... and so on ...*/
```

#### The `CheckedCallResult<C extends Call>` type

When returning `true`, the `checkCallResult()` function leads the TS compiler
to infer the generic type `CheckedCallResult<C extends Call>`, which resolves
to the specific type of **_call result_** message that corresponds to the type
of **_call_** message provided as the `C` type argument.

The generic `CheckedCallResult<C extends Call>` type can also be used on its
own to model a Call Result message after a known or inferred type of Call
message:
own to model a **_call result_** message after a known or inferred type of
**_call_** message:

```typescript
const result: OCPP16.CheckedCallResult<OCPP16.GetConfigurationCall> = [
Expand All @@ -230,8 +229,6 @@ const result: OCPP16.CheckedCallResult<OCPP16.GetConfigurationCall> = [

#### Utility enums

The following enumerations are used within these primary types:

```typescript
OCPP16.MessageType // enum of message types (CALL = 2, CALLRESULT = 3, CALLERROR = 4)
OCPP16.Action // enum of actions in Call messages ("Authorize", "BootNotification", ...)
Expand Down
Loading

0 comments on commit 94c7cda

Please sign in to comment.