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

feat: adds support for oneOf and anyOf as UnionModel #899

Merged
merged 5 commits into from
Sep 28, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Should be able to render python models and should log expected output to console 1`] = `
Array [
"class Root:
kennethaasan marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, input):
if hasattr(input, 'email'):
self._email = input.email
@property
def email(self):
return self._email
@email.setter
def email(self, email):
self._email = email
",
]
`;
12 changes: 6 additions & 6 deletions examples/rust-generate-crate/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ Array [
"// Members represents a union of types: String, f64, bool
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Members {
#[serde(rename=\\"Members0\\")]
Members0(String),
#[serde(rename=\\"Members1\\")]
Members1(f64),
#[serde(rename=\\"Members2\\")]
Members2(bool),
#[serde(rename=\\"MembersOneOf_00\\")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might have to take a double check down the line after this PR, #869, and #876 how we can properly name schemas for models.

cc @leigh-johnson

MembersOneOf_00(String),
#[serde(rename=\\"MembersOneOf_11\\")]
MembersOneOf_11(f64),
#[serde(rename=\\"MembersOneOf_22\\")]
MembersOneOf_22(bool),
}

",
Expand Down
15 changes: 13 additions & 2 deletions src/helpers/CommonModelToMetaModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,23 @@ export function convertToMetaModel(jsonSchemaModel: CommonModel, alreadySeenMode
return new AnyModel(modelName, jsonSchemaModel.originalInput);
}
export function convertToUnionModel(jsonSchemaModel: CommonModel, name: string, alreadySeenModels: Map<CommonModel, MetaModel>): UnionModel | undefined {
if (!Array.isArray(jsonSchemaModel.type) || jsonSchemaModel.type.length <= 1) {
const containsUnions = Array.isArray(jsonSchemaModel.union);
const containsSimpleTypeUnion = Array.isArray(jsonSchemaModel.type) && jsonSchemaModel.type.length > 1;
if (!containsSimpleTypeUnion && !containsUnions) {
return undefined;
}
// Has multiple types, so convert to union
const unionModel = new UnionModel(name, jsonSchemaModel.originalInput, []);
alreadySeenModels.set(jsonSchemaModel, unionModel);

// Has multiple types, so convert to union
if (containsUnions && jsonSchemaModel.union) {
for (const unionCommonModel of jsonSchemaModel.union) {
const unionMetaModel = convertToMetaModel(unionCommonModel, alreadySeenModels);
unionModel.union.push(unionMetaModel);
}
return unionModel;
}
// Has simple union types
const enumModel = convertToEnumModel(jsonSchemaModel, name);
if (enumModel !== undefined) {
unionModel.union.push(enumModel);
Expand Down
21 changes: 21 additions & 0 deletions src/interpreter/InterpretAnyOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CommonModel } from '../models/CommonModel';
import { Interpreter, InterpreterOptions, InterpreterSchemaType } from './Interpreter';

/**
* Interpreter function for anyOf keyword.
*
* It puts the schema reference into the items field.
*
* @param schema
* @param model
* @param interpreter
* @param interpreterOptions to control the interpret process
*/
export default function interpretAnyOf(schema: InterpreterSchemaType, model: CommonModel, interpreter : Interpreter, interpreterOptions: InterpreterOptions = Interpreter.defaultInterpreterOptions): void {
if (typeof schema === 'boolean' || schema.anyOf === undefined) { return; }
for (const anyOfSchema of schema.anyOf) {
const anyOfModel = interpreter.interpret(anyOfSchema, interpreterOptions);
if (anyOfModel === undefined) { continue; }
model.addItemUnion(anyOfModel);
}
}
21 changes: 21 additions & 0 deletions src/interpreter/InterpretOneOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CommonModel } from '../models/CommonModel';
import { Interpreter, InterpreterOptions, InterpreterSchemaType } from './Interpreter';

/**
* Interpreter function for oneOf keyword.
*
* It puts the schema reference into the items field.
*
* @param schema
* @param model
* @param interpreter
* @param interpreterOptions to control the interpret process
*/
export default function interpretOneOf(schema: InterpreterSchemaType, model: CommonModel, interpreter : Interpreter, interpreterOptions: InterpreterOptions = Interpreter.defaultInterpreterOptions): void {
if (typeof schema === 'boolean' || schema.oneOf === undefined) { return; }
for (const oneOfSchema of schema.oneOf) {
const oneOfModel = interpreter.interpret(oneOfSchema, interpreterOptions);
if (oneOfModel === undefined) { continue; }
model.addItemUnion(oneOfModel);
}
}
21 changes: 4 additions & 17 deletions src/interpreter/Interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import interpretPatternProperties from './InterpretPatternProperties';
import interpretNot from './InterpretNot';
import interpretDependencies from './InterpretDependencies';
import interpretAdditionalItems from './InterpretAdditionalItems';
import interpretOneOf from './InterpretOneOf';
import interpretAnyOf from './InterpretAnyOf';

export type InterpreterOptions = {
allowInheritance?: boolean
Expand Down Expand Up @@ -78,12 +80,12 @@ export class Interpreter {
interpretItems(schema, model, this, interpreterOptions);
interpretProperties(schema, model, this, interpreterOptions);
interpretAllOf(schema, model, this, interpreterOptions);
interpretOneOf(schema, model, this, interpreterOptions);
interpretAnyOf(schema, model, this, interpreterOptions);
interpretDependencies(schema, model, this, interpreterOptions);
interpretConst(schema, model);
interpretEnum(schema, model);

this.interpretAndCombineMultipleSchemas(schema.oneOf, model, schema, interpreterOptions);
this.interpretAndCombineMultipleSchemas(schema.anyOf, model, schema, interpreterOptions);
if (!(schema instanceof Draft4Schema) && !(schema instanceof Draft6Schema)) {
this.interpretAndCombineSchema(schema.then, model, schema, interpreterOptions);
this.interpretAndCombineSchema(schema.else, model, schema, interpreterOptions);
Expand All @@ -110,19 +112,4 @@ export class Interpreter {
CommonModel.mergeCommonModels(currentModel, model, rootSchema);
}
}

/**
* Go through multiple schemas and combine the interpreted models together.
*
* @param schema to go through
* @param currentModel the current output
* @param rootSchema the root schema to use as original schema when merged
* @param interpreterOptions to control the interpret process
*/
interpretAndCombineMultipleSchemas(schema: InterpreterSchemaType[] | undefined, currentModel: CommonModel, rootSchema: any, interpreterOptions: InterpreterOptions = Interpreter.defaultInterpreterOptions): void {
if (!Array.isArray(schema)) { return; }
for (const forEachSchema of schema) {
this.interpretAndCombineSchema(forEachSchema, currentModel, rootSchema, interpreterOptions);
}
}
}
14 changes: 14 additions & 0 deletions src/models/CommonModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class CommonModel {
$ref?: string;
required?: string[];
additionalItems?: CommonModel;
union?: CommonModel[]

/**
* Takes a deep copy of the input object and converts it to an instance of CommonModel.
Expand Down Expand Up @@ -189,6 +190,19 @@ export class CommonModel {
this.items = modelItems;
}

/**
* Adds a union model to the model.
*
* @param unionModel
*/
addItemUnion(unionModel: CommonModel): void {
if (Array.isArray(this.union)) {
this.union.push(unionModel);
} else {
this.union = [unionModel];
}
}

/**
* Add enum value to the model.
*
Expand Down
131 changes: 131 additions & 0 deletions test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,134 @@ exports[`PythonGenerator Class should not render reserved keyword 1`] = `
self._reservedDel = reservedDel
"
`;

exports[`PythonGenerator Class should render \`class\` type 1`] = `
"class Address:
def __init__(self, input):
self._streetName = input.streetName
self._city = input.city
self._state = input.state
self._houseNumber = input.houseNumber
if hasattr(input, 'marriage'):
self._marriage = input.marriage
if hasattr(input, 'members'):
self._members = input.members
self._arrayType = input.arrayType
if hasattr(input, 'additionalProperties'):
self._additionalProperties = input.additionalProperties

@property
def streetName(self):
return self._streetName
@streetName.setter
def streetName(self, streetName):
self._streetName = streetName

@property
def city(self):
return self._city
@city.setter
def city(self, city):
self._city = city

@property
def state(self):
return self._state
@state.setter
def state(self, state):
self._state = state

@property
def houseNumber(self):
return self._houseNumber
@houseNumber.setter
def houseNumber(self, houseNumber):
self._houseNumber = houseNumber

@property
def marriage(self):
return self._marriage
@marriage.setter
def marriage(self, marriage):
self._marriage = marriage

@property
def members(self):
return self._members
@members.setter
def members(self, members):
self._members = members

@property
def arrayType(self):
return self._arrayType
@arrayType.setter
def arrayType(self, arrayType):
self._arrayType = arrayType

@property
def additionalProperties(self):
return self._additionalProperties
@additionalProperties.setter
def additionalProperties(self, additionalProperties):
self._additionalProperties = additionalProperties
"
`;

exports[`PythonGenerator Class should work with custom preset for \`class\` type 1`] = `
"class CustomClass:
test1

test1


def __init__(self, input):
if hasattr(input, 'property'):
self._property = input.property
if hasattr(input, 'additionalProperties'):
self._additionalProperties = input.additionalProperties

test2
@property
def property(self):
return self._property
test3
@property.setter
def property(self, property):
self._property = property

test2
@property
def additionalProperties(self):
return self._additionalProperties
test3
@additionalProperties.setter
def additionalProperties(self, additionalProperties):
self._additionalProperties = additionalProperties
"
`;

exports[`PythonGenerator Enum should render \`enum\` with mixed types (union type) 1`] = `
"class Things(Enum):
TEXAS = \\"Texas\\"
NUMBER_1 = 1
RESERVED_NUMBER_1 = \\"1\\"
RESERVED_FALSE = \\"false\\"
CURLYLEFT_QUOTATION_TEST_QUOTATION_COLON_QUOTATION_TEST_QUOTATION_CURLYRIGHT = \\"{\\\\\\"test\\\\\\":\\\\\\"test\\\\\\"}\\""
`;

exports[`PythonGenerator Enum should render enums with translated special characters 1`] = `
"class States(Enum):
TEST_PLUS = \\"test+\\"
DOLLAR_TEST = \\"$test\\"
TEST_MINUS = \\"test-\\"
TEST_QUESTION_EXCLAMATION = \\"test?!\\"
ASTERISK_TEST = \\"*test\\""
`;

exports[`PythonGenerator Enum should work custom preset for \`enum\` type 1`] = `
"class CustomEnum(Enum):
TEXAS = \\"Texas\\"
ALABAMA = \\"Alabama\\"
CALIFORNIA = \\"California\\""
`;
24 changes: 12 additions & 12 deletions test/generators/rust/__snapshots__/RustGenerator.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@ exports[`RustGenerator Struct & Complete Models Should render complete models 2`
"// Members represents a union of types: String, f64, bool
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Members {
#[serde(rename=\\"Members0\\")]
Members0(String),
#[serde(rename=\\"Members1\\")]
Members1(f64),
#[serde(rename=\\"Members2\\")]
Members2(bool),
#[serde(rename=\\"MembersOneOf_00\\")]
MembersOneOf_00(String),
#[serde(rename=\\"MembersOneOf_11\\")]
MembersOneOf_11(f64),
#[serde(rename=\\"MembersOneOf_22\\")]
MembersOneOf_22(bool),
}

"
Expand Down Expand Up @@ -200,12 +200,12 @@ exports[`RustGenerator Struct & Complete Models should render \`struct\` type 2
"// Members represents a union of types: String, f64, bool
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Members {
#[serde(rename=\\"Members0\\")]
Members0(String),
#[serde(rename=\\"Members1\\")]
Members1(f64),
#[serde(rename=\\"Members2\\")]
Members2(bool),
#[serde(rename=\\"MembersOneOf_00\\")]
MembersOneOf_00(String),
#[serde(rename=\\"MembersOneOf_11\\")]
MembersOneOf_11(f64),
#[serde(rename=\\"MembersOneOf_22\\")]
MembersOneOf_22(bool),
}

"
Expand Down
Loading