Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
nytamin committed Jun 28, 2024
1 parent aa5ac9d commit 28367ea
Show file tree
Hide file tree
Showing 14 changed files with 454 additions and 181 deletions.
14 changes: 14 additions & 0 deletions packages/helper/src/mosModel/__tests__/lib.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getHandleError } from '../lib'
import { getParseMosTypes } from '../parseMosTypes'

describe('lib', () => {
test('getHandleError types', () => {
const { mosString128 } = getParseMosTypes(true)
const handleError = getHandleError('unit test')

handleError(mosString128.createRequired, 'test', 'path')

// @ts-expect-error number is not a valid XML type, only string
handleError(mosString128.createRequired, 1234, 'path')
})
})
59 changes: 59 additions & 0 deletions packages/helper/src/mosModel/__tests__/parseMosTypes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { AnyXMLValue, AnyXMLValueSingular } from '@mos-connection/model'
import { getParseMosTypes } from '../parseMosTypes'

describe('parseMosTypes', () => {
test('createRequired types', () => {
const parseMosTypes = getParseMosTypes(true)

const testTypes = (xmlValue: AnyXMLValue, xmlValueSingular: AnyXMLValueSingular) => {
parseMosTypes.mosString128.createRequired(xmlValue)
parseMosTypes.mosTime.createRequired(xmlValue)
parseMosTypes.mosDuration.createRequired(xmlValue)

parseMosTypes.mosString128.createRequired(xmlValueSingular)
parseMosTypes.mosTime.createRequired(xmlValueSingular)
parseMosTypes.mosDuration.createRequired(xmlValueSingular)

// @ts-expect-error bad type
parseMosTypes.mosString128.createRequired({ somethingNonXml: 1234 })
// @ts-expect-error bad type
parseMosTypes.mosTime.createRequired({ somethingNonXml: 1234 })
// @ts-expect-error bad type
parseMosTypes.mosDuration.createRequired({ somethingNonXml: 1234 })
}
try {
testTypes('', '')
} catch {
// Ignore any errors
}

expect(1).toEqual(1)
})
test('createOptional types', () => {
const parseMosTypes = getParseMosTypes(true)

const testTypes = (xmlValue: AnyXMLValue, xmlValueSingular: AnyXMLValueSingular) => {
parseMosTypes.mosString128.createOptional(xmlValue)
parseMosTypes.mosTime.createOptional(xmlValue)
parseMosTypes.mosDuration.createOptional(xmlValue)

parseMosTypes.mosString128.createOptional(xmlValueSingular)
parseMosTypes.mosTime.createOptional(xmlValueSingular)
parseMosTypes.mosDuration.createOptional(xmlValueSingular)

// @ts-expect-error bad type
parseMosTypes.mosString128.createOptional({ somethingNonXml: 1234 })
// @ts-expect-error bad type
parseMosTypes.mosTime.createOptional({ somethingNonXml: 1234 })
// @ts-expect-error bad type
parseMosTypes.mosDuration.createOptional({ somethingNonXml: 1234 })
}
try {
testTypes('', '')
} catch {
// Ignore any errors
}

expect(1).toEqual(1)
})
})
2 changes: 1 addition & 1 deletion packages/helper/src/mosModel/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from './MosMessage'

export { AnyXML } from './lib'
export { AnyXMLObject as AnyXML } from './lib'
export * from './profile0'
export * from './profile1'
export * from './profile2'
Expand Down
8 changes: 6 additions & 2 deletions packages/helper/src/mosModel/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ export function numberOrUndefined(value: unknown): number | undefined {
return num
}

/** Just a generic type to use instead of "any", for xml objects */
export type AnyXML = { [key: string]: any }
export { AnyXMLObject, AnyXMLValue, AnyXMLValueSingular } from '@mos-connection/model'

/** Return true if the object has a property */
export function has(obj: unknown, property: string): boolean {
Expand All @@ -43,3 +42,8 @@ export function getHandleError(basePath: string): <V, R>(func: (val: V) => R, va

return handleError
}

export function ensureArray<T>(v: T | T[]): T[] {
if (typeof v === 'object' && Array.isArray(v)) return v
else return [v]
}
94 changes: 87 additions & 7 deletions packages/helper/src/mosModel/parseMosTypes.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
import { MosTypes, getMosTypes, MosType } from '@mos-connection/model'
import { AnyXMLValue, AnyXMLValueSingular } from './lib'

export function getParseMosTypes(strict: boolean): MosParseTypes {
const mosTypes = getMosTypes(strict)

const specialMosTypes = getSpecialMosTypes(strict)
return {
strict: mosTypes.strict,
mosString128: wrapParseMethods(mosTypes.mosString128, strict),
mosDuration: wrapParseMethods(mosTypes.mosDuration, strict),
mosTime: wrapParseMethods(mosTypes.mosTime, strict),

string: wrapParseMethods(specialMosTypes.string, strict),
stringEnum: wrapParseMethods(specialMosTypes.stringEnum, strict),
}
}
type MosParseTypes = {
[key in keyof MosTypes]: MosTypes[key] extends MosType<infer Serialized, infer Value, infer CreateValue>
? MosTypeParse<Serialized, Value, CreateValue>
: MosTypes[key]
} & {
string: MosTypeParse<string, string, AnyXMLValue>
stringEnum: MosTypeParse<any, string, { enum: { [key: string]: string }; value: AnyXMLValue }>
}
interface MosTypeParse<Serialized, Value, CreateValue> extends Omit<MosType<Serialized, Value, CreateValue>, 'create'> {
/**
* Used to parse data that is optional.
* If the data is missing, undefined is returned.
*/
createOptional: (anyValue: CreateValue) => Serialized | undefined
createOptional: (anyValue: CreateValue | AnyXMLValue) => Serialized | undefined
/**
* Used to parse data that is required.
* If in strict mode, the data must be present and parsable, otherwise an error is thrown.
* If not in strict mode, a fallback value will be used.
*/
createRequired: (anyValue: CreateValue) => Serialized
createRequired: (anyValue: CreateValue | AnyXMLValue) => Serialized
}

function wrapParseMethods<Serialized, Value, CreateValue>(
mosType: MosType<Serialized, Value, CreateValue>,
strict: boolean
): MosTypeParse<Serialized, Value, CreateValue> {
return {
createOptional: wrapParseMethodCreateOptional(mosType),
createOptional: wrapParseMethodCreateOptional(mosType, strict),
createRequired: wrapParseMethodCreateRequired(mosType, strict),
validate: mosType.validate,
valueOf: mosType.valueOf,
Expand All @@ -43,9 +51,10 @@ function wrapParseMethods<Serialized, Value, CreateValue>(
} as MosTypeParse<Serialized, Value, CreateValue>
}
function wrapParseMethodCreateOptional<Serialized, Value, CreateValue>(
mosType: MosType<Serialized, Value, CreateValue>
mosType: MosType<Serialized, Value, CreateValue>,
strict: boolean
): MosTypeParse<Serialized, Value, CreateValue>['createOptional'] {
return parseOptional(mosType.create)
return parseOptional(mosType.create, strict)
}
function wrapParseMethodCreateRequired<Serialized, Value, CreateValue>(
mosType: MosType<Serialized, Value, CreateValue>,
Expand All @@ -54,24 +63,35 @@ function wrapParseMethodCreateRequired<Serialized, Value, CreateValue>(
return parseRequired(mosType.create, mosType.fallback, strict)
}

export function parseOptional<V, R>(parser: (value: V) => R): (value: V) => R | undefined {
export function parseOptional<V, R>(
parser: (value: V) => R,
strict: boolean
): (value: V | AnyXMLValue) => R | undefined {
return (value: any) => {
// handle empty string:
if (typeof value === 'string' && !value.trim()) value = undefined
// handle empty object (can happen when parsing an empty xml tag):
if (typeof value === 'object' && Object.keys(value).length === 0) value = undefined

value = ensureSingular(value, strict)

if (value === undefined) return undefined
return parser(value)
}
}
export function parseRequired<V, R>(parser: (value: V) => R, fallback: () => R, strict: boolean): (value: V) => R {
export function parseRequired<V, R>(
parser: (value: V) => R,
fallback: () => R,
strict: boolean
): (value: V | AnyXMLValue) => R {
return (value: any) => {
// handle empty string:
if (typeof value === 'string' && !value.trim()) value = undefined
// handle empty object (can happen when parsing an empty xml tag):
if (typeof value === 'object' && Object.keys(value).length === 0) value = undefined

value = ensureSingular(value, strict)

if (value === undefined) {
// Something might be wrong. value is undefined, but should not be (?)
if (strict) {
Expand All @@ -85,3 +105,63 @@ export function parseRequired<V, R>(parser: (value: V) => R, fallback: () => R,
}
}
}

function ensureSingular(value: AnyXMLValue, strict: boolean): AnyXMLValueSingular {
if (typeof value === 'object') {
if (Array.isArray(value)) {
if (value.length === 0) return undefined
if (value.length > 1) {
if (strict)
throw new Error(`Expected only one value, got ${value.length} values: ${JSON.stringify(value)}`)
else return undefined
}

return ensureSingular(value[0], strict)
} else {
if (strict) throw new Error(`Expected only one value, got object: ${JSON.stringify(value)}`)
else return undefined
}
} else {
return value
}
}

function getSpecialMosTypes(strict: boolean) {
const string: MosType<string, string, AnyXMLValue> = {
create: (anyValue: AnyXMLValue) => {
if (typeof anyValue !== 'string') throw new Error(`Expected a string, got: "${anyValue}"`)
return anyValue
},
validate: (_value: string) => true,
valueOf: (value: string) => value,
stringify: (value: string) => value,
is: (value: string | any): value is string => typeof value !== 'string',
fallback: () => '',
}
const stringEnum: MosType<string, string, { enum: { [key: string]: string }; value: AnyXMLValue }> = {
create: (createValue) => {
if (!createValue.enum) throw new Error(`Expected an object with an "enum" key, got: "${createValue}"`)

const key = `${createValue.value}`
if (!key) throw new Error(`Expected a value, got: "${createValue.value}"`)

if (createValue.enum[key] === undefined) {
if (strict) {
throw new Error(`Unknown value, got: "${key}", possible values: ${Object.keys(createValue.enum)}`)
} else return ''
}

return key
},
validate: (_value: string) => true,
valueOf: (value: string) => value,
stringify: (value: string) => value,
is: (value: string | any): value is string => typeof value !== 'string',
fallback: () => '',
}

return {
string,
stringEnum,
}
}
79 changes: 70 additions & 9 deletions packages/helper/src/mosModel/profile0/xmlConversion.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import * as XMLBuilder from 'xmlbuilder'
import { IMOSListMachInfo, IMOSString128 } from '@mos-connection/model'
import { AnyXML, getHandleError } from '../lib'
import {
AnyXMLValue,
IMOSDefaultActiveX,
IMOSListMachInfo,
IMOSListMachInfoDefaultActiveXMode,
IMOSString128,
} from '@mos-connection/model'
import { AnyXMLObject, ensureArray, getHandleError } from '../lib'
import { addTextElementInternal } from '../../utils/Utils'
import { getParseMosTypes, parseOptional } from '../parseMosTypes'

/* eslint-disable @typescript-eslint/no-namespace */
export namespace XMLMosIDs {
export function fromXML(xml: AnyXML, strict: boolean): IMOSString128[] {
export function fromXML(xml: AnyXMLObject, strict: boolean): IMOSString128[] {
const mosTypes = getParseMosTypes(strict)

const arr: Array<IMOSString128> = []
Expand All @@ -22,7 +28,7 @@ export namespace XMLMosIDs {

/* eslint-disable @typescript-eslint/no-namespace */
export namespace XMLMosListMachInfo {
export function fromXML(xml: AnyXML, strict: boolean): IMOSListMachInfo {
export function fromXML(xml: AnyXMLObject, strict: boolean): IMOSListMachInfo {
const { mosString128, mosTime } = getParseMosTypes(strict)

const handleError = getHandleError('listMachInfo')
Expand All @@ -43,11 +49,8 @@ export namespace XMLMosListMachInfo {
xml.supportedProfiles,
'supportedProfiles'
),
defaultActiveX: handleError(
parseOptional((v) => v),
xml.defaultActiveX,
'defaultActiveX'
),
defaultActiveX: XMLDefaultActiveX.fromXML(xml.defaultActiveX, strict),

mosExternalMetaData: handleError((v) => v, xml.mosExternalMetaData, 'mosExternalMetaData'),
}

Expand All @@ -65,6 +68,7 @@ export namespace XMLMosListMachInfo {
if (info.opTime) addTextElementInternal(xmlListMachInfo, 'opTime', info.opTime, undefined, strict)
addTextElementInternal(xmlListMachInfo, 'mosRev', info.mosRev, undefined, strict)

XMLDefaultActiveX.toXML(xmlListMachInfo, info.defaultActiveX, strict)
// TODO: the supportedProfiles should be changed to an Array

const xmlSupportedProfiles = XMLBuilder.create('supportedProfiles')
Expand Down Expand Up @@ -104,3 +108,60 @@ function parseSupportedProfiles(xmlSupportedProfiles: any, strict: boolean): IMO

return parsed
}

export namespace XMLDefaultActiveX {
export function fromXML(xml: AnyXMLValue, strict: boolean): IMOSDefaultActiveX[] {
const mosTypes = getParseMosTypes(strict)
const handleError = getHandleError('defaultActiveX')

const defaultActiveX: IMOSDefaultActiveX[] = []
for (const xmlObj of ensureArray(xml)) {
if (!xmlObj) continue

if (typeof xmlObj !== 'object') {
if (strict) throw new Error(`defaultActiveX: Expected an object, got: "${xmlObj}"`)
else continue
}
if (typeof xmlObj === 'object' && Array.isArray(xmlObj)) {
if (strict)
throw new Error(`defaultActiveX: Expected an object, got an array: "${JSON.stringify(xmlObj)}"`)
else continue
}

const parsedObj: IMOSDefaultActiveX = {
mode: handleError(
mosTypes.stringEnum.createRequired,
{ enum: IMOSListMachInfoDefaultActiveXMode, value: xmlObj.mode },
'mode'
),
controlFileLocation: handleError(
mosTypes.string.createRequired,
xmlObj.controlFileLocation,
'controlFileLocation'
),
controlSlug: handleError(mosTypes.mosString128.createRequired, xml.controlSlug, 'controlSlug'),
controlName: handleError(mosTypes.string.createRequired, xmlObj.controlName, 'controlName'),
controlDefaultParams: handleError(
mosTypes.string.createRequired,
xmlObj.controlDefaultParams,
'controlDefaultParams'
),
}

defaultActiveX.push(parsedObj)
}
return defaultActiveX
}
export function toXML(xml: XMLBuilder.XMLElement, objs: IMOSDefaultActiveX[] | undefined, strict: boolean): void {
if (objs === undefined) return
for (const obj of objs) {
const xmlItem = addTextElementInternal(xml, 'defaultActiveX', undefined, undefined, strict)

addTextElementInternal(xmlItem, 'mode', obj.mode, undefined, strict)
addTextElementInternal(xmlItem, 'controlFileLocation', obj.controlFileLocation, undefined, strict)
addTextElementInternal(xmlItem, 'controlSlug', obj.controlSlug, undefined, strict)
addTextElementInternal(xmlItem, 'controlName', obj.controlName, undefined, strict)
addTextElementInternal(xmlItem, 'controlDefaultParams', obj.controlDefaultParams, undefined, strict)
}
}
}
Loading

0 comments on commit 28367ea

Please sign in to comment.