Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation errors for ph libs #34

Open
Richard-Seaman opened this issue May 24, 2024 · 0 comments
Open

Validation errors for ph libs #34

Richard-Seaman opened this issue May 24, 2024 · 0 comments

Comments

@Richard-Seaman
Copy link

Hi.

First off, thanks for all of the work that has gone into this brilliant library.

I have been using Haystack for a number of years now (mostly via Skyspark) but am only really starting to explore the power of defs.

I’m toying around with adding some custom libs and normalising them into a namespace. It’s straightforward to do this using the existing HNormalizer.spec.ts and simply including any additional custom libs here:

libs = [
await readLib('ph'),
await readLib('phScience'),
await readLib('phIoT'),
lib,
]

I wanted to validate my custom libs. I found the relevant existing unit tests here:
https://github.com/j2inn/haystack-core/blob/29425dbf2e489f6b13f2b59f15605c985bec1bf2/spec/core/HNormalizer.spec.ts#L600C13-L600C65

I added the following unit test:

it('ph libs have no validation errors', async function (): Promise<void> {
	await normalizer.normalize()
	expect(logger.error).not.toHaveBeenCalled()
})

I was surprised to see that errors were being generated from the standard ph libraries:

  ● HNormalizer › #normalize() › validate › verifies tag values match the def's declared types › ph libs have no validation errors

    expect(jest.fn()).not.toHaveBeenCalled()

    Expected number of calls: 0
    Received number of calls: 12

    1: "Tag 'geoState.enum' should be 'dict'", "tagIsNotKind", {"defName": "geoState", "kind": "dict", "tag": "enum"}
    2: "Tag 'geoCountry.enum' should be 'dict'", "tagIsNotKind", {"defName": "geoCountry", "kind": "dict", "tag": "enum"}
    3: "Tag 'kind.enum' should be 'dict'", "tagIsNotKind", {"defName": "kind", "kind": "dict", "tag": "enum"}

The first three examples above all relate to the enum tag. Its def is as follows:

def:^enum
doc:
 Defines an eumeration of string key names.  The range may also be applied
 to format a Bool ordered as "false,true".  The string value of this tag
 may be formatted in one of four ways:
  - dict of dicts keyed by name and Dict values for meta such as 'doc' tag
  - comma separated names on one line
  - names separated by a "\n" newline character
  - markdown unordered list formatted as a series of "- name: description\n"
 
 Enum names *should* follow valid tag naming rules (start with lowercase
 ASCII letter and contain only ASCII letters and numbers).  However in
 cases where mapping directly to external enumerations enum names can
 contain arbitrary characters such as space.
is:[^str,^dict]
lib:^lib:ph
tagOn:[^def,^point]

I think the handling of multiple inheritance (is:[^str,^dict]) within verifyValuesMatchDefTypes may be the source of the problem:

private verifyValuesMatchDefTypes(
tagName: string,
tagDefNode: DefTreeNode,
tagValue: HVal,
defName: string
): void {
for (const kind of Object.values(Kind)) {
if (tagDefNode.extends(kind) && !valueIsKind(tagValue, kind)) {
// If the value does not match then test to see if this
// is an accumulated value (i.e. a list of the values).
const accumuatedOk =
tagDefNode.def.has('accumulate') &&
valueIsKind<HList>(tagValue, Kind.List) &&
(tagValue as HList)
.toArray()
.every((val) => val?.isKind(kind))
if (!accumuatedOk) {
this.error('tagIsNotKind', {
defName: defName,
tag: tagName,
kind,
})
}
}
}
}

Should the desired behaviour be that the tag's value matches one of the kinds which it extends, rather than every one?
If this is the case, I'd be happy to raise a PR if you're open to contributions.

Note that it also seems to impact some of the existing tests as I think they rely on there being no existing validation errors. For example, the following test still passes:

it('logs an error for a bool value that is a string type', async function (): Promise<void> {
	// addDefs(
	// 	HDict.make({
	// 		def: HSymbol.make('foo'),
	// 		is: HSymbol.make(Kind.Str),
	// 	}),
	// 	HDict.make({
	// 		def: HSymbol.make('foo2'),
	// 		is: HSymbol.make(Kind.Str),
	// 		foo: HBool.make(true),
	// 	})
	// )

	await normalizer.normalize()
	expect(logger.error).toHaveBeenCalled()
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant