/* * Copyright 2021 Juan Cruz Viotti * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { strict as assert } from 'assert' import { range } from 'lodash' import { JSONValue } from '../json' import { BaseEncodingDefinition } from './base-encoding-definition' import { EncodingType } from '../encoder' import { Encoding, getStates, getEncoding } from './index' import { TypedFloorOptions, TypedRoofOptions, TypedBoundedOptions, SizeTypedOptions } from '../encoder/array/options' import { UINT8_MAX } from '../utils/limits' import { generatePermutations } from '../utils/permutation' import { EnumEncodingNames, EnumEncoding, getEnumEncoding } from './enum' import { ArrayEncodingSchema, EncodingSchema } from '../schema' export interface FIXED_TYPED_ARRAY_ENCODING extends BaseEncodingDefinition { readonly type: EncodingType.Array; readonly encoding: 'FIXED_TYPED_ARRAY'; readonly options: SizeTypedOptions; } export interface BOUNDED_8BITS_TYPED_LENGTH_PREFIX_ENCODING extends BaseEncodingDefinition { readonly type: EncodingType.Array; readonly encoding: 'BOUNDED_8BITS_TYPED_LENGTH_PREFIX'; readonly options: TypedBoundedOptions; } export interface BOUNDED_TYPED_LENGTH_PREFIX_ENCODING extends BaseEncodingDefinition { readonly type: EncodingType.Array; readonly encoding: 'BOUNDED_TYPED_LENGTH_PREFIX'; readonly options: TypedBoundedOptions; } export interface FLOOR_TYPED_LENGTH_PREFIX_ENCODING extends BaseEncodingDefinition { readonly type: EncodingType.Array; readonly encoding: 'FLOOR_TYPED_LENGTH_PREFIX'; readonly options: TypedFloorOptions; } export interface ROOF_TYPED_LENGTH_PREFIX_ENCODING extends BaseEncodingDefinition { readonly type: EncodingType.Array; readonly encoding: 'ROOF_TYPED_LENGTH_PREFIX'; readonly options: TypedRoofOptions; } export type ArrayEncodingNames = EnumEncodingNames | 'FIXED_TYPED_ARRAY' | 'BOUNDED_8BITS_TYPED_LENGTH_PREFIX' | 'BOUNDED_TYPED_LENGTH_PREFIX' | 'FLOOR_TYPED_LENGTH_PREFIX' | 'ROOF_TYPED_LENGTH_PREFIX' export type ArrayEncoding = EnumEncoding | FIXED_TYPED_ARRAY_ENCODING | BOUNDED_8BITS_TYPED_LENGTH_PREFIX_ENCODING | BOUNDED_TYPED_LENGTH_PREFIX_ENCODING | FLOOR_TYPED_LENGTH_PREFIX_ENCODING | ROOF_TYPED_LENGTH_PREFIX_ENCODING export const getArrayStates = (schema: ArrayEncodingSchema): number | JSONValue[] => { if (typeof schema.maxItems === 'number' && (typeof schema.items !== 'undefined' || typeof schema.prefixItems !== 'undefined')) { const choices: number | JSONValue[][] = range(0, schema.maxItems).reduce((accumulator: number | JSONValue[][], index: number) => { const states: number | JSONValue[] = getStates((schema.prefixItems ?? [])[index] ?? schema.items ?? {}) if (Array.isArray(accumulator) && Array.isArray(states)) { accumulator.push(states) } else if (typeof accumulator !== 'number' && !Array.isArray(states)) { return states + accumulator.reduce((subaccumulator: number, choice: JSONValue[]) => { return subaccumulator + choice.length }, 0) } else if (!Array.isArray(accumulator)) { return accumulator + (Array.isArray(states) ? states.length : states) } return accumulator }, []) if (typeof choices === 'number') { return choices } return range(schema.minItems ?? 0, schema.maxItems + 1) .reduce((accumulator: JSONValue[][], maximum: number) => { return accumulator.concat(generatePermutations(...choices.slice(0, maximum))) }, []) } return Infinity } export const getArrayEncoding = (schema: ArrayEncodingSchema, level: number): ArrayEncoding => { const states: number | JSONValue[] = getArrayStates(schema) if (Array.isArray(states)) { return getEnumEncoding({ enum: states }, level) } const prefixEncodings: Encoding[] = (schema.prefixItems ?? []).map((subschema: EncodingSchema) => { return getEncoding(subschema, level + 1) }) // Canonicalizations rules ensure this assert(typeof schema.minItems === 'number') assert(schema.minItems >= 0) assert(typeof schema.items !== 'undefined') if (typeof schema.maxItems !== 'undefined' && schema.minItems === 0 && schema.maxItems > UINT8_MAX) { return { type: EncodingType.Array, encoding: 'ROOF_TYPED_LENGTH_PREFIX', options: { maximum: schema.maxItems, prefixEncodings, encoding: getEncoding(schema.items, level + 1) } } } if (typeof schema.maxItems !== 'undefined' && (schema.minItems !== 0 || schema.maxItems <= UINT8_MAX)) { return { type: EncodingType.Array, encoding: (schema.maxItems - schema.minItems <= UINT8_MAX) ? 'BOUNDED_8BITS_TYPED_LENGTH_PREFIX' : 'BOUNDED_TYPED_LENGTH_PREFIX', options: { minimum: schema.minItems, maximum: schema.maxItems, prefixEncodings, encoding: getEncoding(schema.items, level + 1) } } } return { type: EncodingType.Array, encoding: 'FLOOR_TYPED_LENGTH_PREFIX', options: { minimum: schema.minItems, prefixEncodings, encoding: getEncoding(schema.items, level + 1) } } }