Skip to content

Commit

Permalink
feat: request options (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
AuHau authored Sep 20, 2021
1 parent f62a6e9 commit 9eac5cd
Show file tree
Hide file tree
Showing 9 changed files with 789 additions and 210 deletions.
220 changes: 148 additions & 72 deletions src/bee-debug.ts

Large diffs are not rendered by default.

220 changes: 146 additions & 74 deletions src/bee.ts

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions src/feed/json.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FeedWriter, FeedReader, AnyJson, BatchId, Reference } from '../types'
import { FeedWriter, FeedReader, AnyJson, BatchId, Reference, RequestOptions } from '../types'
import { Bee } from '../bee'

function serializeJson(data: AnyJson): Uint8Array {
Expand All @@ -24,9 +24,10 @@ export async function setJsonData(
writer: FeedWriter,
postageBatchId: BatchId,
data: AnyJson,
options?: RequestOptions,
): Promise<Reference> {
const serializedData = serializeJson(data)
const { reference } = await bee.uploadData(postageBatchId, serializedData)
const { reference } = await bee.uploadData(postageBatchId, serializedData, options)

return writer.upload(postageBatchId, reference)
}
4 changes: 2 additions & 2 deletions src/types/debug.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PublicKey, NumberString, Reference, TransactionHash } from './index'
import { PublicKey, NumberString, Reference, TransactionHash, RequestOptions } from './index'
import { HexEthAddress } from '../utils/eth'

/**
Expand Down Expand Up @@ -86,7 +86,7 @@ export interface ChequebookBalanceResponse {
availableBalance: NumberString
}

export interface CashoutOptions {
export interface CashoutOptions extends RequestOptions {
/**
* Gas price for the cashout transaction in WEI
*/
Expand Down
32 changes: 27 additions & 5 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,27 @@ export type BatchId = HexString<typeof BATCH_ID_HEX_LENGTH>
*/
export type AddressPrefix = HexString

export interface BeeOptions {
export interface RequestOptions {
/**
* Timeout of requests in milliseconds
*/
timeout?: number

/**
* Configure backoff mechanism for requests retries.
* Specifies how many retries will be performed before failing a request.
* Retries are performed for GET, PUT, HEAD, DELETE, OPTIONS and TRACE requests.
* Default is 2.
*/
retry?: number

/**
* User defined Fetch compatible function
*/
fetch?: Fetch
}

export interface BeeOptions extends RequestOptions {
/**
* Signer object or private key of the Signer in form of either hex string or Uint8Array that will be default signer for the instance.
*/
Expand Down Expand Up @@ -115,7 +135,7 @@ export interface UploadResult {
tagUid?: number
}

export interface UploadOptions {
export interface UploadOptions extends RequestOptions {
/**
* Will pin the data locally in the Bee node as well.
*
Expand Down Expand Up @@ -222,7 +242,7 @@ export interface Tag {
startedAt: string
}

export interface AllTagsOptions {
export interface AllTagsOptions extends RequestOptions {
limit?: number
offset?: number
}
Expand Down Expand Up @@ -344,7 +364,7 @@ export interface FeedReader {
download(options?: FeedUpdateOptions): Promise<FetchFeedUpdateResponse>
}

export interface JsonFeedOptions {
export interface JsonFeedOptions extends RequestOptions {
/**
* Valid only for `get` action, where either this `address` or `signer` has
* to be specified.
Expand Down Expand Up @@ -464,7 +484,7 @@ export interface TransactionInfo {
/**
* Options for creation of postage batch
*/
export interface PostageBatchOptions {
export interface PostageBatchOptions extends RequestOptions {
/**
* Sets label for the postage batch
*/
Expand Down Expand Up @@ -542,3 +562,5 @@ interface JsonMap {
[key: string]: AnyJson
}
type JsonArray = Array<AnyJson>

type Fetch = (input: RequestInfo, init?: RequestInit) => Promise<Response>
94 changes: 85 additions & 9 deletions src/utils/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import {
PSS_TARGET_HEX_LENGTH_MAX,
UploadOptions,
TransactionHash,
RequestOptions,
PostageBatchOptions,
CashoutOptions,
} from '../types'
import { BeeArgumentError } from './error'
import { isFile } from './file'
Expand Down Expand Up @@ -59,16 +62,23 @@ export function isStrictlyObject(value: unknown): value is object {
return isObject(value) && !Array.isArray(value)
}

export function assertBoolean(value: unknown): asserts value is boolean {
if (value !== true && value !== false) throw new TypeError('value is not boolean')
// eslint-disable-next-line @typescript-eslint/ban-types
export function assertStrictlyObject(value: unknown, name = 'value'): asserts value is object {
if (!isStrictlyObject(value)) {
throw new TypeError(`${name} has to be an object that is not null nor array!`)
}
}

export function assertBoolean(value: unknown, name = 'value'): asserts value is boolean {
if (value !== true && value !== false) throw new TypeError(`${name} is not boolean`)
}

export function assertInteger(value: unknown): asserts value is number | NumberString {
if (!isInteger(value)) throw new TypeError('value is not integer')
export function assertInteger(value: unknown, name = 'value'): asserts value is number | NumberString {
if (!isInteger(value)) throw new TypeError(`${name} is not integer`)
}

export function assertNonNegativeInteger(value: unknown, name = 'Value'): asserts value is number | NumberString {
assertInteger(value)
assertInteger(value, name)

if (Number(value) < 0) throw new BeeArgumentError(`${name} has to be bigger or equal to zero`, value)
}
Expand All @@ -89,11 +99,37 @@ export function assertBatchId(value: unknown): asserts value is BatchId {
assertHexString(value, BATCH_ID_HEX_LENGTH, 'BatchId')
}

export function assertRequestOptions(value: unknown, name = 'RequestOptions'): asserts value is RequestOptions {
if (value === undefined) {
return
}

if (!isStrictlyObject(value)) {
throw new TypeError(`${name} has to be an object!`)
}

const options = value as RequestOptions

if (options.retry) {
assertNonNegativeInteger(options.retry, `${name}.retry`)
}

if (options.timeout) {
assertNonNegativeInteger(options.timeout, `${name}.timeout`)
}

if (options.fetch && typeof options.fetch !== 'function') {
throw new TypeError(`${name}.fetch has to be a function or undefined!`)
}
}

export function assertUploadOptions(value: unknown, name = 'UploadOptions'): asserts value is UploadOptions {
if (!isStrictlyObject(value)) {
throw new TypeError(`${name} has to be an object!`)
}

assertRequestOptions(value, name)

const options = value as UploadOptions

if (options.pin && typeof options.pin !== 'boolean') {
Expand Down Expand Up @@ -220,6 +256,44 @@ export function assertPublicKey(value: unknown): asserts value is PublicKey {
assertHexString(value, PUBKEY_HEX_LENGTH, 'PublicKey')
}

export function assertPostageBatchOptions(value: unknown): asserts value is PostageBatchOptions {
if (value === undefined) {
return
}

assertStrictlyObject(value)

const options = value as PostageBatchOptions
assertRequestOptions(options, 'PostageBatchOptions')

if (options?.gasPrice) {
assertNonNegativeInteger(options.gasPrice)
}

if (options?.immutableFlag !== undefined) {
assertBoolean(options.immutableFlag)
}
}

export function assertCashoutOptions(value: unknown): asserts value is CashoutOptions {
if (value === undefined) {
return
}

assertStrictlyObject(value)

const options = value as CashoutOptions
assertRequestOptions(options, 'PostageBatchOptions')

if (options?.gasLimit) {
assertNonNegativeInteger(options.gasLimit)
}

if (options?.gasPrice) {
assertNonNegativeInteger(options.gasPrice)
}
}

/**
* Check whether the given parameter is valid data to upload
* @param value
Expand Down Expand Up @@ -251,24 +325,26 @@ export function assertAllTagsOptions(entry: unknown): asserts entry is AllTagsOp
throw new TypeError('options has to be an object or undefined!')
}

assertRequestOptions(entry, 'AllTagsOptions')

const options = entry as AllTagsOptions

if (options?.limit !== undefined) {
if (typeof options.limit !== 'number') {
throw new TypeError('options.limit has to be a number or undefined!')
throw new TypeError('AllTagsOptions.limit has to be a number or undefined!')
}

if (options.limit < TAGS_LIMIT_MIN) {
throw new BeeArgumentError(`options.limit has to be at least ${TAGS_LIMIT_MIN}`, options.limit)
throw new BeeArgumentError(`AllTagsOptions.limit has to be at least ${TAGS_LIMIT_MIN}`, options.limit)
}

if (options.limit > TAGS_LIMIT_MAX) {
throw new BeeArgumentError(`options.limit has to be at most ${TAGS_LIMIT_MAX}`, options.limit)
throw new BeeArgumentError(`AllTagsOptions.limit has to be at most ${TAGS_LIMIT_MAX}`, options.limit)
}
}

if (options?.offset !== undefined) {
assertNonNegativeInteger(options.offset, 'options.offset')
assertNonNegativeInteger(options.offset, 'AllTagsOptions.offset')
}
}

Expand Down
97 changes: 96 additions & 1 deletion test/unit/assertions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable */
import { BeeArgumentError } from '../../src'
import { BeeArgumentError, BeeOptions } from '../../src'
import { makeBytes } from '../../src/utils/bytes'

export function testBatchIdAssertion(executor: (input: unknown) => void): void {
Expand Down Expand Up @@ -74,6 +74,101 @@ export function testUploadOptionsAssertions(executor: (input: unknown) => void):
})
}

export function testRequestOptionsAssertions(
executor: (input: unknown, beeOptions?: BeeOptions) => void,
testFetch = true,
): void {
it('should throw exception for bad RequestOptions', async () => {
await expect(() => executor(1)).rejects.toThrow(TypeError)
await expect(() => executor(true)).rejects.toThrow(TypeError)
await expect(() => executor([])).rejects.toThrow(TypeError)
await expect(() => executor(() => {})).rejects.toThrow(TypeError)
await expect(() => executor('string')).rejects.toThrow(TypeError)

await expect(() => executor({ timeout: 'plur' })).rejects.toThrow(TypeError)
await expect(() => executor({ timeout: true })).rejects.toThrow(TypeError)
await expect(() => executor({ timeout: {} })).rejects.toThrow(TypeError)
await expect(() => executor({ timeout: [] })).rejects.toThrow(TypeError)
await expect(() => executor({ timeout: -1 })).rejects.toThrow(BeeArgumentError)

await expect(() => executor({ retry: 'plur' })).rejects.toThrow(TypeError)
await expect(() => executor({ retry: true })).rejects.toThrow(TypeError)
await expect(() => executor({ retry: {} })).rejects.toThrow(TypeError)
await expect(() => executor({ retry: [] })).rejects.toThrow(TypeError)
await expect(() => executor({ retry: -1 })).rejects.toThrow(BeeArgumentError)

await expect(() => executor({ fetch: 'plur' })).rejects.toThrow(TypeError)
await expect(() => executor({ fetch: true })).rejects.toThrow(TypeError)
await expect(() => executor({ fetch: {} })).rejects.toThrow(TypeError)
await expect(() => executor({ fetch: [] })).rejects.toThrow(TypeError)
await expect(() => executor({ fetch: -1 })).rejects.toThrow(TypeError)
await expect(() => executor({ fetch: 1 })).rejects.toThrow(TypeError)
})

if (testFetch) {
it('should use per-call request options instead of instance request options', async () => {
const instanceFetch = jest.fn()
const instanceMessage = 'instance error'
const instanceError = { message: instanceMessage }
instanceFetch.mockRejectedValue(instanceError)
await expect(() => executor({}, { retry: 0, fetch: instanceFetch })).rejects.toThrow(instanceMessage)
expect(instanceFetch.mock.calls.length).toEqual(1)

const callFetch = jest.fn()
const callMessage = 'call error'
const callError = { message: callMessage }
callFetch.mockRejectedValue(callError)
await expect(() => executor({ fetch: callFetch }, { retry: 0, fetch: instanceFetch })).rejects.toThrow(
callMessage,
)
expect(instanceFetch.mock.calls.length).toEqual(1) // The count did not change from last call
expect(callFetch.mock.calls.length).toEqual(1)
})
}
}

export function testPostageBatchOptionsAssertions(executor: (input: unknown) => void): void {
it('should throw exception for bad PostageBatch', async () => {
await expect(() => executor(1)).rejects.toThrow(TypeError)
await expect(() => executor(true)).rejects.toThrow(TypeError)
await expect(() => executor([])).rejects.toThrow(TypeError)
await expect(() => executor('string')).rejects.toThrow(TypeError)

await expect(() => executor({ gasPrice: 'plur' })).rejects.toThrow(TypeError)
await expect(() => executor({ gasPrice: true })).rejects.toThrow(TypeError)
await expect(() => executor({ gasPrice: {} })).rejects.toThrow(TypeError)
await expect(() => executor({ gasPrice: [] })).rejects.toThrow(TypeError)
await expect(() => executor({ gasPrice: -1 })).rejects.toThrow(BeeArgumentError)

await expect(() => executor({ immutableFlag: 'plur' })).rejects.toThrow(TypeError)
await expect(() => executor({ immutableFlag: 1 })).rejects.toThrow(TypeError)
await expect(() => executor({ immutableFlag: null })).rejects.toThrow(TypeError)
await expect(() => executor({ immutableFlag: {} })).rejects.toThrow(TypeError)
await expect(() => executor({ immutableFlag: [] })).rejects.toThrow(TypeError)
})
}

export function testCashoutOptionsAssertions(executor: (input: unknown) => void): void {
it('should throw exception for bad CashoutOptions', async () => {
await expect(() => executor(1)).rejects.toThrow(TypeError)
await expect(() => executor(true)).rejects.toThrow(TypeError)
await expect(() => executor([])).rejects.toThrow(TypeError)
await expect(() => executor('string')).rejects.toThrow(TypeError)

await expect(() => executor({ gasPrice: 'plur' })).rejects.toThrow(TypeError)
await expect(() => executor({ gasPrice: true })).rejects.toThrow(TypeError)
await expect(() => executor({ gasPrice: {} })).rejects.toThrow(TypeError)
await expect(() => executor({ gasPrice: [] })).rejects.toThrow(TypeError)
await expect(() => executor({ gasPrice: -1 })).rejects.toThrow(BeeArgumentError)

await expect(() => executor({ gasLimit: 'plur' })).rejects.toThrow(TypeError)
await expect(() => executor({ gasLimit: true })).rejects.toThrow(TypeError)
await expect(() => executor({ gasLimit: {} })).rejects.toThrow(TypeError)
await expect(() => executor({ gasLimit: [] })).rejects.toThrow(TypeError)
await expect(() => executor({ gasLimit: -1 })).rejects.toThrow(BeeArgumentError)
})
}

export function testFileUploadOptionsAssertions(executor: (input: unknown) => void): void {
it('should throw exception for bad FileUploadOptions', async () => {
await expect(() => executor({ contentType: true })).rejects.toThrow(TypeError)
Expand Down
Loading

0 comments on commit 9eac5cd

Please sign in to comment.