Merge pull request #3980 from github/mario-campos/add-optional-to-json

Support `undefined`, `number` in JSON module
This commit is contained in:
Mario Campos
2026-07-08 13:05:24 +00:00
committed by GitHub
4 changed files with 82 additions and 32 deletions

32
lib/entry-points.js generated
View File

@@ -77637,14 +77637,14 @@ var require_reflection_json_writer = __commonJS({
/**
* Returns `null` as the default for google.protobuf.NullValue.
*/
enum(type, value, fieldName, optional2, emitDefaultValues, enumAsInteger) {
enum(type, value, fieldName, optional, emitDefaultValues, enumAsInteger) {
if (type[0] == "google.protobuf.NullValue")
return !emitDefaultValues && !optional2 ? void 0 : null;
return !emitDefaultValues && !optional ? void 0 : null;
if (value === void 0) {
assert_1.assert(optional2);
assert_1.assert(optional);
return void 0;
}
if (value === 0 && !emitDefaultValues && !optional2)
if (value === 0 && !emitDefaultValues && !optional)
return void 0;
assert_1.assert(typeof value == "number");
assert_1.assert(Number.isInteger(value));
@@ -77659,12 +77659,12 @@ var require_reflection_json_writer = __commonJS({
return options.emitDefaultValues ? null : void 0;
return type.internalJsonWrite(value, options);
}
scalar(type, value, fieldName, optional2, emitDefaultValues) {
scalar(type, value, fieldName, optional, emitDefaultValues) {
if (value === void 0) {
assert_1.assert(optional2);
assert_1.assert(optional);
return void 0;
}
const ed = emitDefaultValues || optional2;
const ed = emitDefaultValues || optional;
switch (type) {
// int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted.
case reflection_info_1.ScalarType.INT32:
@@ -144297,7 +144297,7 @@ var string = {
validate: isString,
required: true
};
function optional(validator) {
function optionalOrNull(validator) {
return {
validate: (val) => {
return val === void 0 || val === null || validator.validate(val);
@@ -161285,14 +161285,14 @@ var toolcache4 = __toESM(require_tool_cache());
// src/start-proxy/types.ts
var usernameSchema = {
/** The username needed to authenticate to the package registry, if any. */
username: optional(string)
username: optionalOrNull(string)
};
function hasUsername(config) {
return "username" in config;
}
var usernamePasswordSchema = {
/** The password needed to authenticate to the package registry, if any. */
password: optional(string),
password: optionalOrNull(string),
...usernameSchema
};
function hasUsernameAndPassword(config) {
@@ -161300,7 +161300,7 @@ function hasUsernameAndPassword(config) {
}
var tokenSchema = {
/** The token needed to authenticate to the package registry, if any. */
token: optional(string),
token: optionalOrNull(string),
...usernameSchema
};
function hasToken(config) {
@@ -161322,15 +161322,15 @@ var awsConfigSchema = {
"role-name": string,
domain: string,
"domain-owner": string,
audience: optional(string)
audience: optionalOrNull(string)
};
function isAWSConfig(config) {
return validateSchema(awsConfigSchema, config);
}
var jfrogConfigSchema = {
"jfrog-oidc-provider-name": string,
audience: optional(string),
"identity-mapping-name": optional(string)
audience: optionalOrNull(string),
"identity-mapping-name": optionalOrNull(string)
};
function isJFrogConfig(config) {
return validateSchema(jfrogConfigSchema, config);
@@ -161345,8 +161345,8 @@ function isCloudsmithConfig(config) {
}
var gcpConfigSchema = {
"workload-identity-provider": string,
"service-account": optional(string),
audience: optional(string)
"service-account": optionalOrNull(string),
audience: optionalOrNull(string)
};
function isGCPConfig(config) {
return validateSchema(gcpConfigSchema, config);

View File

@@ -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) => {
@@ -28,13 +28,36 @@ test("validateSchema - required properties are required", async (t) => {
t.true(json.validateSchema(testSchema, { requiredKey: "foo" }));
});
test("validateSchema - optional properties are optional", async (t) => {
test("validateSchema - optionalOrNullSchema properties are optional or null", async (t) => {
// Optional fields may be absent
t.true(json.validateSchema(optionalSchema, {}));
t.true(json.validateSchema(optionalSchema, { optionalKey: undefined }));
t.true(json.validateSchema(optionalSchema, { optionalKey: null }));
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 are optional", async (t) => {
// Optional fields may be absent or explicitly undefined
t.true(json.validateSchema(optionalSchema, {}));
t.true(json.validateSchema(optionalSchema, { optionalKey: undefined }));
// 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 }));

View File

@@ -30,6 +30,11 @@ export function isString(value: unknown): value is string {
return typeof value === "string";
}
/** Asserts that `value` is a number. */
export function isNumber(value: unknown): value is number {
return typeof value === "number";
}
/** Asserts that `value` is either a string or undefined. */
export function isStringOrUndefined(
value: unknown,
@@ -55,8 +60,17 @@ 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>;
/**
* 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);
@@ -65,6 +79,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>>;

View File

@@ -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. */
@@ -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;
@@ -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;
@@ -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. */
@@ -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. */
@@ -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. */