Skip to content
Open
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
32 changes: 16 additions & 16 deletions lib/entry-points.js

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

31 changes: 27 additions & 4 deletions src/json/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const testSchema = {
requiredKey: json.string,
};

const optionalSchema = {
optionalKey: json.optional(json.string),
const optionalOrNullSchema = {
optionalKey: json.optionalOrNull(json.string),
};

test("validateSchema - required properties are required", async (t) => {
Expand All @@ -30,11 +30,34 @@ test("validateSchema - required properties are required", async (t) => {

test("validateSchema - optional properties are optional", async (t) => {
// Optional fields may be absent
t.true(json.validateSchema(optionalOrNullSchema, {}));
t.true(json.validateSchema(optionalOrNullSchema, { optionalKey: undefined }));
t.true(json.validateSchema(optionalOrNullSchema, { optionalKey: null }));

// But, if present, should have the expected type
t.false(json.validateSchema(optionalOrNullSchema, { optionalKey: 0 }));
t.false(json.validateSchema(optionalOrNullSchema, { optionalKey: 123 }));
t.false(json.validateSchema(optionalOrNullSchema, { optionalKey: false }));
t.false(json.validateSchema(optionalOrNullSchema, { optionalKey: true }));
t.false(json.validateSchema(optionalOrNullSchema, { optionalKey: [] }));
t.false(json.validateSchema(optionalOrNullSchema, { optionalKey: {} }));
t.true(json.validateSchema(optionalOrNullSchema, { optionalKey: "" }));
t.true(json.validateSchema(optionalOrNullSchema, { optionalKey: "foo" }));
});

const optionalSchema = {
optionalKey: json.optional(json.string),
};

test("validateSchema - optional properties may be absent or undefined, but reject null", async (t) => {
// Optional fields may be absent or explicitly undefined
t.true(json.validateSchema(optionalSchema, {}));
t.true(json.validateSchema(optionalSchema, { optionalKey: undefined }));
t.true(json.validateSchema(optionalSchema, { optionalKey: null }));

// But, if present, should have the expected type
// But should reject null
t.false(json.validateSchema(optionalSchema, { optionalKey: null }));

// And, if present, should have the expected type
t.false(json.validateSchema(optionalSchema, { optionalKey: 0 }));
t.false(json.validateSchema(optionalSchema, { optionalKey: 123 }));
t.false(json.validateSchema(optionalSchema, { optionalKey: false }));
Expand Down
36 changes: 34 additions & 2 deletions src/json/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export function isString(value: unknown): value is string {
return typeof value === "string";
}

export function isNumber(value: unknown): value is number {
return typeof value === "number" && Number.isFinite(value);
}

/** Asserts that `value` is either a string or undefined. */
export function isStringOrUndefined(
value: unknown,
Expand All @@ -55,8 +59,23 @@ export const string = {
required: true,
} as const satisfies Validator<string>;

/** Transforms a validator to be optional. */
export function optional<T>(validator: Validator<T>) {
/** A validator for number fields in schemas. */
export const number = {
validate: isNumber,
required: true,
} as const satisfies Validator<number>;
Comment on lines +62 to +66

/** A validator for object fields in schemas. */
export const object = {
validate: isObject<any>,
required: true,
} as const satisfies Validator<UnvalidatedObject<any>>;

/**
* Transforms a validator to be optional, accepting `undefined` or `null` for an
* absent value.
*/
export function optionalOrNull<T>(validator: Validator<T>) {
return {
validate: (val: unknown) => {
return val === undefined || val === null || validator.validate(val);
Expand All @@ -65,6 +84,19 @@ export function optional<T>(validator: Validator<T>) {
} as const satisfies Validator<T | undefined | null>;
}

/**
* Transforms a validator to be optional, accepting `undefined` for an absent
* value but, unlike `optionalOrNull`, rejecting `null`.
*/
export function optional<T>(validator: Validator<T>) {
return {
validate: (val: unknown): val is T | undefined => {
return val === undefined || validator.validate(val);
},
required: false,
} as const satisfies Validator<T | undefined>;
}

/** Represents an arbitrary object schema. */
export type Schema = Record<string, Validator<any>>;

Expand Down
16 changes: 8 additions & 8 deletions src/start-proxy/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type RawCredential = UnvalidatedObject<Credential>;
/** A schema for credential objects with a username. */
export const usernameSchema = {
/** The username needed to authenticate to the package registry, if any. */
username: json.optional(json.string),
username: json.optionalOrNull(json.string),
} as const satisfies json.Schema;

/** Usernames may be present for both authentication with tokens or passwords. */
Expand All @@ -29,7 +29,7 @@ export function hasUsername(config: AuthConfig): config is Username {
/** A schema for credential objects with a username and password. */
export const usernamePasswordSchema = {
/** The password needed to authenticate to the package registry, if any. */
password: json.optional(json.string),
password: json.optionalOrNull(json.string),
...usernameSchema,
} as const satisfies json.Schema;

Expand All @@ -52,7 +52,7 @@ export function hasUsernameAndPassword(
/** A schema for credential objects for token-based authentication. */
export const tokenSchema = {
/** The token needed to authenticate to the package registry, if any. */
token: json.optional(json.string),
token: json.optionalOrNull(json.string),
...usernameSchema,
} as const satisfies json.Schema;

Expand Down Expand Up @@ -100,7 +100,7 @@ export const awsConfigSchema = {
"role-name": json.string,
domain: json.string,
"domain-owner": json.string,
audience: json.optional(json.string),
audience: json.optionalOrNull(json.string),
} as const satisfies json.Schema;

/** Configuration for AWS OIDC. */
Expand All @@ -116,8 +116,8 @@ export function isAWSConfig(
/** A schema for JFrog OIDC configurations. */
export const jfrogConfigSchema = {
"jfrog-oidc-provider-name": json.string,
audience: json.optional(json.string),
"identity-mapping-name": json.optional(json.string),
audience: json.optionalOrNull(json.string),
"identity-mapping-name": json.optionalOrNull(json.string),
} as const satisfies json.Schema;

/** Configuration for JFrog OIDC. */
Expand Down Expand Up @@ -150,8 +150,8 @@ export function isCloudsmithConfig(
/** A schema for GCP OIDC configurations. */
export const gcpConfigSchema = {
"workload-identity-provider": json.string,
"service-account": json.optional(json.string),
audience: json.optional(json.string),
"service-account": json.optionalOrNull(json.string),
audience: json.optionalOrNull(json.string),
} as const satisfies json.Schema;

/** Configuration for GCP OIDC. */
Expand Down
Loading