Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@athenna/resource",
"version": "5.2.0",
"version": "5.3.0",
"description": "Add transformation layers between your application data transfer.",
"license": "MIT",
"author": "João Lenon <lenon@athenna.io>",
Expand Down Expand Up @@ -53,7 +53,7 @@
},
"devDependencies": {
"@athenna/artisan": "^5.7.0",
"@athenna/common": "^5.17.0",
"@athenna/common": "^5.23.0",
"@athenna/config": "^5.4.0",
"@athenna/ioc": "^5.2.0",
"@athenna/logger": "^5.10.0",
Expand Down
127 changes: 111 additions & 16 deletions src/resource/BaseResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/

import { Is } from '@athenna/common'
import { Is, Json } from '@athenna/common'

export abstract class BaseResource<R = any, I = any> {
/**
Expand All @@ -19,6 +19,12 @@ export abstract class BaseResource<R = any, I = any> {
// TODO Convert to symbol
private items: I[]

private defaultValues: {
key: string
value?: any
closure?: (item?: I) => any
}[]

/**
* Indicates if the item received in the constructor
* was an array of not. This is important to validate
Expand All @@ -28,6 +34,7 @@ export abstract class BaseResource<R = any, I = any> {
private readonly isArray: boolean

public constructor(data: I | I[]) {
this.defaultValues = []
this.isArray = Is.Array(data)
this.items = this.isArray ? (data as I[]) : [data as I]
}
Expand All @@ -38,35 +45,123 @@ export abstract class BaseResource<R = any, I = any> {
*/
protected abstract schema(item: I): R

public toJSON(): R[]
public toJSON(): R

/**
* Transform the data of the resource to a valid JSON.
*
* @example
* ```ts
* const items = [...]
* const json = new MyResource(items).toJSON<any>()
* ```
*/
public toJSON<T = any>(): T {
const transformed = this.items.map(item => {
const json = this.schema(item)

if (this.defaultValues.length) {
Object.keys(json).forEach(key => {
const defaultValue = this.defaultValues.find(v => v.key === key)

if (!defaultValue) {
return
}

if (defaultValue.closure) {
Json.set(json, key, defaultValue.closure(item))

return
}

Json.set(json, key, defaultValue.value)
})
}

return json
})

return this.isArray ? (transformed as any) : (transformed[0] as any as T)
}

/**
* Get values from a key defined inside the items of the resource.
* Items are the raw value set for the resource used to build the
* JSON structure.
*
* @example
* ```ts
* const items = [{ hello: { world: 'hello' }}]
* const hellos = new MyResource(items).getFromItem('hello.world') // ['hello']
* ```
*/
public toJSON(): R | R[] {
const transformed = this.items.map(item => this.schema(item))
public getFromItem<T = any>(key: string): T {
const values = this.items.map(item => Json.get(item, key))

return this.isArray ? transformed : transformed[0]
return this.isArray ? (values as any) : (values[0] as any as T)
}

/**
* Change the value of an item inside your resource.
* If your resource is an array, all keys in the array will
* be changed.
* Get values from a key defined inside the JSON of the resource.
* The JSON of the resource are the items parsed using the structure
* you have defined in the `schema()` method.
*
* @example
* ```ts
* const items = [{ hello: { world: 'hello' }}]
* const hellos = new MyResource(items).getFromJson('schemaHello') // ['hello']
* ```
*/
public setValue(key: string, value: any) {
public getFromJson<T = any>(key: string): T {
const json = this.toJSON()

if (!json) {
return undefined
}

if (this.isArray) {
this.items = this.items.map(item => {
item[key] = value
return json.map(json => Json.get(json, key))
}

return item
})
return Json.get(json, key)
}

/**
* Set a value that will overwritte the original value defined in
* your original item structure. This method is useful when you let
* your framework manually call the `toJSON()` method and want to
* transform.
*
* @example
* ```ts
* const items = [{ hello: { world: 'hello' }}]
* new MyResource(items).setInItem('hello.world', 'hello2')
* ```
*/
public setInItem(key: string, value: any) {
this.items.map(item => Json.set(item, key, value))

return this
}

/**
* Set a value that will overwritte the original value created by
* your original structure defined in your `schema()` method. This
* method is useful when you want to change the value directly from
* your JSON structure.
*
* @example
* ```ts
* const items = [{ hello: { world: 'hello' }}]
* new MyResource(items).setInJson('schemaHello', 'hello2')
* ```
*/
public setInJson(key: string, value: any) {
if (Is.Function(value)) {
this.defaultValues.push({ key, closure: value })

return this
}

this.items[key] = value
this.defaultValues.push({ key, value })

return this
}
Expand Down
92 changes: 92 additions & 0 deletions tests/unit/resource/BaseResourceTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,96 @@ export default class BaseResourceTest {
}
])
}

@Test()
public async shouldBeAbleToChangeItemValuesFromResourceClass({ assert }: Context) {
const item = {
_id: '1',
_name: 'John Doe',
_email: 'john.doe@example.com'
}

const resource = new UserResource(item)

resource.setInItem('_id', 2)

assert.instanceOf(resource, UserResource)
assert.deepEqual(resource.toJSON(), {
id: 2,
name: 'John Doe',
email: 'john.doe@example.com'
})
}

@Test()
public async shouldBeAbleToGetItemValuesFromResourceClass({ assert }: Context) {
const item = {
_id: '1',
_name: 'John Doe',
_email: 'john.doe@example.com'
}

const resource = new UserResource(item)

assert.deepEqual(resource.getFromItem('_id'), '1')
}

@Test()
public async shouldReturnUndefinedIfCannotFindValueInsideItemFromResourceClass({ assert }: Context) {
const item = {
_id: '1',
_name: 'John Doe',
_email: 'john.doe@example.com'
}

const resource = new UserResource(item)

assert.deepEqual(resource.getFromItem('__id'), undefined)
}

@Test()
public async shouldBeAbleToChangeJsonValuesFromResourceClass({ assert }: Context) {
const item = {
_id: '1',
_name: 'John Doe',
_email: 'john.doe@example.com'
}

const resource = new UserResource(item)

resource.setInJson('id', 2)

assert.instanceOf(resource, UserResource)
assert.deepEqual(resource.toJSON(), {
id: 2,
name: 'John Doe',
email: 'john.doe@example.com'
})
}

@Test()
public async shouldBeAbleToGetJsonValuesFromResourceClass({ assert }: Context) {
const item = {
_id: '1',
_name: 'John Doe',
_email: 'john.doe@example.com'
}

const resource = new UserResource(item)

assert.deepEqual(resource.getFromJson('id'), '1')
}

@Test()
public async shouldReturnUndefinedIfCannotFindValueInsideJsonFromResourceClass({ assert }: Context) {
const item = {
_id: '1',
_name: 'John Doe',
_email: 'john.doe@example.com'
}

const resource = new UserResource(item)

assert.deepEqual(resource.getFromJson('_id'), undefined)
}
}
Loading