Skip to content
Merged
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
87 changes: 87 additions & 0 deletions packages/quicktype-core/src/attributes/DefaultValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type {
JSONSchemaAttributes,
JSONSchemaType,
Ref,
} from "../input/JSONSchemaInput.js";
import type { JSONSchema } from "../input/JSONSchemaStore.js";
import type { Type } from "../Type/Type.js";

import { TypeAttributeKind } from "./TypeAttributes.js";

export type DefaultValue = boolean | number | string | null;

class DefaultValueTypeAttributeKind extends TypeAttributeKind<DefaultValue> {
public constructor() {
super("defaultValue");
}

public get inIdentity(): boolean {
return true;
}

public combine(values: DefaultValue[]): DefaultValue | undefined {
const first = values[0];
return values.every((value) => value === first) ? first : undefined;
}

public makeInferred(_: DefaultValue): undefined {
return undefined;
}

public addToSchema(
schema: { [name: string]: unknown },
_t: Type,
defaultValue: DefaultValue,
): void {
schema.default = defaultValue;
}

public stringify(defaultValue: DefaultValue): string {
return JSON.stringify(defaultValue);
}
}

export const defaultValueTypeAttributeKind: TypeAttributeKind<DefaultValue> =
new DefaultValueTypeAttributeKind();

export function defaultValueAttributeProducer(
schema: JSONSchema,
_ref: Ref,
types: Set<JSONSchemaType>,
): JSONSchemaAttributes | undefined {
if (typeof schema !== "object") return undefined;

const defaultValue = schema.default;
if (
defaultValue !== null &&
typeof defaultValue !== "boolean" &&
typeof defaultValue !== "number" &&
typeof defaultValue !== "string"
) {
return undefined;
}

const attributes =
defaultValueTypeAttributeKind.makeAttributes(defaultValue);
if (typeof defaultValue === "number") {
if (!types.has("number") && !types.has("integer")) return undefined;
return { forNumber: attributes };
}

if (typeof defaultValue === "string") {
if (!types.has("string")) return undefined;
return { forString: attributes };
}

if (typeof defaultValue === "boolean") {
if (!types.has("boolean")) return undefined;
return { forBoolean: attributes };
}

if (!types.has("null")) return undefined;
return { forNull: attributes };
}

export function defaultValueForType(t: Type): DefaultValue | undefined {
return defaultValueTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}
16 changes: 15 additions & 1 deletion packages/quicktype-core/src/input/JSONSchemaInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
minMaxLengthAttributeProducer,
patternAttributeProducer,
} from "../attributes/Constraints.js";
import { defaultValueAttributeProducer } from "../attributes/DefaultValue.js";
import { descriptionAttributeProducer } from "../attributes/Description.js";
import { enumValuesAttributeProducer } from "../attributes/EnumValues.js";
import { StringTypes } from "../attributes/StringTypes.js";
Expand Down Expand Up @@ -651,7 +652,9 @@ const schemaTypes = Object.getOwnPropertyNames(

export interface JSONSchemaAttributes {
forArray?: TypeAttributes;
forBoolean?: TypeAttributes;
forCases?: TypeAttributes[];
forNull?: TypeAttributes;
forNumber?: TypeAttributes;
forObject?: TypeAttributes;
forString?: TypeAttributes;
Expand Down Expand Up @@ -1288,6 +1291,12 @@ async function addTypesInSchema(
const numberAttributes = combineProducedAttributes(
({ forNumber }) => forNumber,
);
const booleanAttributes = combineProducedAttributes(
({ forBoolean }) => forBoolean,
);
const nullAttributes = combineProducedAttributes(
({ forNull }) => forNull,
);

for (const [name, kind] of [
["null", "null"],
Expand All @@ -1299,7 +1308,11 @@ async function addTypesInSchema(

const attributes = isNumberTypeKind(kind)
? numberAttributes
: undefined;
: kind === "bool"
? booleanAttributes
: kind === "null"
? nullAttributes
: undefined;
unionTypes.push(typeBuilder.getPrimitiveType(kind, attributes));
}

Expand Down Expand Up @@ -1556,6 +1569,7 @@ export class JSONSchemaInput implements Input<JSONSchemaSourceData> {
this._attributeProducers = [
descriptionAttributeProducer,
accessorNamesAttributeProducer,
defaultValueAttributeProducer,
enumValuesAttributeProducer,
uriSchemaAttributesProducer,
minMaxAttributeProducer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
nullTypeIssueAnnotation,
} from "../../Annotation.js";
import { getAccessorName } from "../../attributes/AccessorNames.js";
import { defaultValueForType } from "../../attributes/DefaultValue.js";
import { enumCaseValues } from "../../attributes/EnumValues.js";
import {
ConvenienceRenderer,
Expand Down Expand Up @@ -917,8 +918,41 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
return this._memberNameStyle(`${jsonName}Constraint`);
}

protected emitMember(cppType: Sourcelike, name: Sourcelike): void {
this.emitLine(cppType, " ", name, ";");
protected emitMember(
cppType: Sourcelike,
name: Sourcelike,
defaultValue?: Sourcelike,
): void {
this.emitLine(
cppType,
" ",
name,
defaultValue === undefined ? [] : [" = ", defaultValue],
";",
);
}

protected cppDefaultValue(t: Type): Sourcelike | undefined {
const defaultValue = defaultValueForType(t);
if (defaultValue === undefined) return undefined;

if (defaultValue === null) return "nullptr";
if (typeof defaultValue === "boolean") {
return defaultValue ? "true" : "false";
}

if (typeof defaultValue === "number") return String(defaultValue);
if (t instanceof EnumType && t.cases.has(defaultValue)) {
return [
this.nameForNamedType(t),
"::",
this.nameForEnumCase(t, defaultValue),
];
}

return this._stringType.createStringLiteral([
stringEscape(defaultValue),
]);
}

protected emitClassMembers(
Expand All @@ -942,6 +976,7 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
property.isOptional,
),
name,
this.cppDefaultValue(property.type),
);
if (constraints?.has(jsonName)) {
/** FIXME!!! NameStyle will/can collide with other Names */
Expand Down Expand Up @@ -972,6 +1007,7 @@ export class CPlusPlusRenderer extends ConvenienceRenderer {
property.isOptional,
),
name,
this.cppDefaultValue(property.type),
);
} else {
const [getterName, mutableGetterName, setterName] = defined(
Expand Down
2 changes: 2 additions & 0 deletions test/inputs/schema/default-value.1.fail.no-defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
3 changes: 3 additions & 0 deletions test/inputs/schema/default-value.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": 0
}
13 changes: 13 additions & 0 deletions test/inputs/schema/default-value.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "An object with a scalar default",
"type": "object",
"properties": {
"id": {
"type": "integer",
"default": 0
}
},
"required": ["id"],
"additionalProperties": false
}
13 changes: 13 additions & 0 deletions test/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,9 @@ export const CJSONLanguage: Language = {
/* Required properties absent are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */
"intersection.schema",
"required.schema",
// The default-value fail sample also relies on required-property
// enforcement, which cJSON does not do.
"default-value.schema",
/* Pure Any type not supported (for the current implementation, can be added later, should manage a callback to provide the final application a way to handle it at parsing and creation of cJSON) */
"any.schema",
"direct-union.schema",
Expand Down Expand Up @@ -969,6 +972,8 @@ export const SwiftLanguage: Language = {
// This works on macOS, but on Linux one of the failure test cases doesn't fail
...skipsUntypedUnions,
"required.schema",
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"multi-type-enum.schema",
"intersection.schema",
...skipsMapValueValidation,
Expand Down Expand Up @@ -1897,6 +1902,8 @@ export const HaskellLanguage: Language = {
"keyword-unions.schema",
"optional-any.schema",
"required.schema",
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"required-non-properties.schema",
],
rendererOptions: {},
Expand Down Expand Up @@ -2067,6 +2074,8 @@ export const TypeScriptZodLanguage: Language = {
"optional-any.schema",
"recursive-union-flattening.schema",
"required.schema",
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"required-non-properties.schema",
],
rendererOptions: {},
Expand Down Expand Up @@ -2183,6 +2192,8 @@ export const TypeScriptEffectSchemaLanguage: Language = {
"keyword-unions.schema",
"optional-any.schema",
"required.schema",
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"required-non-properties.schema",
],
rendererOptions: {},
Expand Down Expand Up @@ -2241,6 +2252,8 @@ export const ElixirLanguage: Language = {
// Struct keys cannot be enforced at runtime in Elixir and their values will just be set to null.
"strict-optional.schema",
"required.schema",
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"boolean-subschema.schema",
"intersection.schema",
"optional-any.schema",
Expand Down
Loading