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
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,22 @@ public String toModelImport(String name) {
return "#include \"" + folder + name + ".h\"";
}

/**
* Resolve a schema reference. If the schema has a $ref, return the referenced schema.
* This is for nested maps.
*/
private Schema resolveSchema(Schema schema) {
if (schema == null) {
return null;
}
if (StringUtils.isNotEmpty(schema.get$ref())) {
String ref = ModelUtils.getSimpleRef(schema.get$ref());
Schema resolved = ModelUtils.getSchema(openAPI, ref);
return resolved != null ? resolved : schema;
}
return schema;
}

/**
* Optional - type declaration. This is a String which is used by the templates to instantiate your
* types. There is typically special handling for different property types
Expand All @@ -185,15 +201,22 @@ public String toModelImport(String name) {
@Override
@SuppressWarnings("rawtypes")
public String getTypeDeclaration(Schema p) {
String openAPIType = getSchemaType(p);
// Resolve the schema to check for nested maps/arrays - refs that point to map schemas
Schema resolved = resolveSchema(p);

if (ModelUtils.isArraySchema(p)) {
Schema inner = ModelUtils.getSchemaItems(p);
if (ModelUtils.isArraySchema(resolved)) {
Schema inner = ModelUtils.getSchemaItems(resolved);
return getSchemaType(p) + "<" + getTypeDeclaration(inner) + ">";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return getSchemaType(p) + "<QString, " + getTypeDeclaration(inner) + ">";
} else if (ModelUtils.isBinarySchema(p)) {
} else if (ModelUtils.isMapSchema(resolved)) {
Schema inner = ModelUtils.getAdditionalProperties(resolved);
// inner can be null if additionalProperties is a boolean or not present
String innerType = inner != null ? getTypeDeclaration(inner) : PREFIX + "Object";
return getSchemaType(p) + "<QString, " + innerType + ">";
}

// For non-containers, use the original schema to preserve model names
String openAPIType = getSchemaType(p);
if (ModelUtils.isBinarySchema(p)) {
return getSchemaType(p);
} else if (ModelUtils.isFileSchema(p)) {
return getSchemaType(p);
Expand All @@ -210,29 +233,32 @@ public String getTypeDeclaration(Schema p) {
@Override
@SuppressWarnings("rawtypes")
public String toDefaultValue(Schema p) {
if (ModelUtils.isBooleanSchema(p)) {
Schema schema = resolveSchema(p);
if (ModelUtils.isBooleanSchema(schema)) {
return "false";
} else if (ModelUtils.isDateSchema(p)) {
} else if (ModelUtils.isDateSchema(schema)) {
return "NULL";
} else if (ModelUtils.isDateTimeSchema(p)) {
} else if (ModelUtils.isDateTimeSchema(schema)) {
return "NULL";
} else if (ModelUtils.isNumberSchema(p)) {
if (SchemaTypeUtil.FLOAT_FORMAT.equals(p.getFormat())) {
} else if (ModelUtils.isNumberSchema(schema)) {
if (SchemaTypeUtil.FLOAT_FORMAT.equals(schema.getFormat())) {
return "0.0f";
}
return "0.0";
} else if (ModelUtils.isIntegerSchema(p)) {
if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) {
} else if (ModelUtils.isIntegerSchema(schema)) {
if (SchemaTypeUtil.INTEGER64_FORMAT.equals(schema.getFormat())) {
return "0L";
}
return "0";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return "QMap<QString, " + getTypeDeclaration(inner) + ">()";
} else if (ModelUtils.isArraySchema(p)) {
Schema inner = ModelUtils.getSchemaItems(p);
} else if (ModelUtils.isMapSchema(schema)) {
Schema inner = ModelUtils.getAdditionalProperties(schema);
// inner can be null if additionalProperties is a boolean or not present
String innerType = inner != null ? getTypeDeclaration(inner) : PREFIX + "Object";
return "QMap<QString, " + innerType + ">()";
} else if (ModelUtils.isArraySchema(schema)) {
Schema inner = ModelUtils.getSchemaItems(schema);
return "QList<" + getTypeDeclaration(inner) + ">()";
} else if (ModelUtils.isStringSchema(p)) {
} else if (ModelUtils.isStringSchema(schema)) {
return "QString(\"\")";
} else if (!StringUtils.isEmpty(p.get$ref())) {
return toModelName(ModelUtils.getSimpleRef(p.get$ref())) + "()";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,15 @@ public CodegenModel fromModel(String name, Schema model) {
}
}

// Handle additionalProperties for models that have both properties and additionalProperties
Schema addlProps = ModelUtils.getAdditionalProperties(model);
if (addlProps != null && model.getProperties() != null && !model.getProperties().isEmpty()) {
// This model has both defined properties AND additionalProperties
codegenModel.additionalPropertiesType = getTypeDeclaration(addlProps);
// Add import for web::json::value which is used to store additional properties
codegenModel.imports.add("#include <cpprest/json.h>");
}

return codegenModel;
}

Expand Down Expand Up @@ -362,6 +371,34 @@ public String toApiFilename(String name) {
return toApiName(name);
}

/**
* Resolve a schema reference. If the schema has a $ref, return the referenced schema.
* This is for nested maps.
*/
private Schema resolveSchema(Schema schema) {
if (schema == null) {
return null;
}
if (StringUtils.isNotEmpty(schema.get$ref())) {
String ref = ModelUtils.getSimpleRef(schema.get$ref());
Schema resolved = ModelUtils.getSchema(openAPI, ref);
return resolved != null ? resolved : schema;
}
return schema;
}

/**
* Check if a schema is a pure map (has additionalProperties but no defined properties).
* Schemas with both properties and additionalProperties should be treated as models, not maps.
*/
private boolean isPureMapSchema(Schema schema) {
if (!ModelUtils.isMapSchema(schema)) {
return false;
}
// If the schema has defined properties, it's not a pure map
return schema.getProperties() == null || schema.getProperties().isEmpty();
}

/**
* Optional - type declaration. This is a String which is used by the
* templates to instantiate your types. There is typically special handling
Expand All @@ -372,15 +409,23 @@ public String toApiFilename(String name) {
*/
@Override
public String getTypeDeclaration(Schema p) {
String openAPIType = getSchemaType(p);
// Resolve the schema to check for nested maps/arrays - refs that point to map schemas
Schema resolved = resolveSchema(p);

if (ModelUtils.isArraySchema(p)) {
Schema inner = ModelUtils.getSchemaItems(p);
if (ModelUtils.isArraySchema(resolved)) {
Schema inner = ModelUtils.getSchemaItems(resolved);
return getSchemaType(p) + "<" + getTypeDeclaration(inner) + ">";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return getSchemaType(p) + "<utility::string_t, " + getTypeDeclaration(inner) + ">";
} else if (ModelUtils.isFileSchema(p) || ModelUtils.isBinarySchema(p)) {
} else if (isPureMapSchema(resolved)) {
// Only treat as map if it has additionalProperties but NO defined properties
Schema inner = ModelUtils.getAdditionalProperties(resolved);
// inner can be null if additionalProperties is a boolean or not present
String innerType = inner != null ? getTypeDeclaration(inner) : "std::shared_ptr<Object>";
return getSchemaType(p) + "<utility::string_t, " + innerType + ">";
}

// For non-containers, use the original schema to preserve model names
String openAPIType = getSchemaType(p);
if (ModelUtils.isFileSchema(p) || ModelUtils.isBinarySchema(p)) {
return "std::shared_ptr<" + openAPIType + ">";
} else if (ModelUtils.isStringSchema(p)
|| ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p)
Expand Down Expand Up @@ -414,8 +459,10 @@ public String toDefaultValue(Schema p) {
return "0L";
}
return "0";
} else if (ModelUtils.isMapSchema(p)) {
String inner = getSchemaType(ModelUtils.getAdditionalProperties(p));
} else if (isPureMapSchema(p)) {
Schema innerSchema = ModelUtils.getAdditionalProperties(p);
// innerSchema can be null if additionalProperties is a boolean or not present
String inner = innerSchema != null ? getSchemaType(innerSchema) : "std::shared_ptr<Object>";
return "std::map<utility::string_t, " + inner + ">()";
} else if (ModelUtils.isArraySchema(p)) {
String inner = getSchemaType(ModelUtils.getSchemaItems(p));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,23 @@ public String getTypeDeclaration(String str) {
return str;
}

/**
* Resolve a schema reference. If the schema has a $ref, return the referenced schema.
* This is for nested maps.
*/
private Schema resolveSchema(Schema schema) {
if (schema == null) {
return null;
}
if (StringUtils.isNotEmpty(schema.get$ref())) {
String ref = ModelUtils.getSimpleRef(schema.get$ref());
Schema resolved = ModelUtils.getSchema(openAPI, ref);
return resolved != null ? resolved : schema;
}
return schema;
}

private void makeTypeMappings() {
// Types
String cpp_array_type = "std::list";
typeMapping = new HashMap<>();

typeMapping.put("string", "std::string");
Expand All @@ -219,7 +233,8 @@ private void makeTypeMappings() {
typeMapping.put("long", "long");
typeMapping.put("boolean", "bool");
typeMapping.put("double", "double");
typeMapping.put("array", cpp_array_type);
typeMapping.put("array", "std::list");
typeMapping.put("map", "std::map");
typeMapping.put("number", "long");
typeMapping.put("binary", "std::string");
typeMapping.put("password", "std::string");
Expand Down Expand Up @@ -255,6 +270,19 @@ public String toInstantiationType(Schema p) {

@Override
public String getTypeDeclaration(Schema p) {
// Only resolve for nested maps - check if a $ref points to a map schema
Schema resolved = resolveSchema(p);

// Handle nested maps: if a $ref resolves to a map schema, build the nested type
if (ModelUtils.isMapSchema(resolved)) {
Schema inner = ModelUtils.getAdditionalProperties(resolved);
// inner can be null if additionalProperties is a boolean or not present
String innerType = inner != null ? getTypeDeclaration(inner) : "std::string";
return getSchemaType(p) + "<std::string, " + innerType + ">";
}

// For everything else (including arrays), use the original behavior
// The templates handle adding array item types themselves
String openAPIType = getSchemaType(p);
if (languageSpecificPrimitives.contains(openAPIType)) {
return toModelName(openAPIType);
Expand Down Expand Up @@ -296,7 +324,7 @@ public String toModelImport(String name) {
return "#include <string>";
} else if (name.equals("std::list")) {
return "#include <list>";
} else if (name.equals("Map")) {
} else if (name.equals("Map") || name.equals("std::map")) {
return "#include <map>";
}
return "#include \"" + name + ".h\"";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,23 @@ public:
{{/isInherited}}

{{/vars}}
{{#additionalPropertiesType}}

/// <summary>
/// Get additional properties (properties not defined in the schema)
/// </summary>
std::map<utility::string_t, web::json::value> getAdditionalProperties() const;
bool additionalPropertiesIsSet() const;
void unsetAdditionalProperties();
/// <summary>
/// Set additional properties
/// </summary>
void setAdditionalProperties(const std::map<utility::string_t, web::json::value>& value);
/// <summary>
/// Add a single additional property
/// </summary>
void addAdditionalProperty(const utility::string_t& key, const web::json::value& value);
{{/additionalPropertiesType}}

protected:
{{#vars}}
Expand All @@ -285,6 +302,10 @@ protected:
{{/isInherited}}

{{/vars}}
{{#additionalPropertiesType}}
std::map<utility::string_t, web::json::value> m_AdditionalProperties;
bool m_AdditionalPropertiesIsSet;
{{/additionalPropertiesType}}
};

{{/isEnum}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ void {{classname}}::setValue({{classname}}::e{{classname}} const value)
{{/isNullable}}
{{/isInherited}}
{{/vars}}
{{#additionalPropertiesType}}
m_AdditionalPropertiesIsSet = false;
{{/additionalPropertiesType}}
}

{{classname}}::~{{classname}}()
Expand Down Expand Up @@ -262,6 +265,16 @@ web::json::value {{classname}}::toJson() const
{{/isNullable}}
{{/isInherited}}
{{/vars}}
{{#additionalPropertiesType}}
// Serialize additional properties
if(m_AdditionalPropertiesIsSet)
{
for(const auto& item : m_AdditionalProperties)
{
val[item.first] = item.second;
}
}
{{/additionalPropertiesType}}

return val;
}
Expand Down Expand Up @@ -295,6 +308,24 @@ bool {{classname}}::fromJson(const web::json::value& val)
}
{{/isInherited}}
{{/vars}}
{{#additionalPropertiesType}}
// Capture additional properties (keys not defined in the schema)
if(val.is_object())
{
for(const auto& item : val.as_object())
{
// Skip known properties
{{#vars}}
{{^isInherited}}
if(item.first == utility::conversions::to_string_t(_XPLATSTR("{{baseName}}"))) continue;
{{/isInherited}}
{{/vars}}
// This is an additional property
m_AdditionalProperties[item.first] = item.second;
m_AdditionalPropertiesIsSet = true;
}
}
{{/additionalPropertiesType}}
return ok;
}

Expand Down Expand Up @@ -515,6 +546,36 @@ void {{classname}}::unset{{name}}()
{{/isNullable}}
}
{{/isInherited}}{{/vars}}
{{#additionalPropertiesType}}

std::map<utility::string_t, web::json::value> {{classname}}::getAdditionalProperties() const
{
return m_AdditionalProperties;
}

void {{classname}}::setAdditionalProperties(const std::map<utility::string_t, web::json::value>& value)
{
m_AdditionalProperties = value;
m_AdditionalPropertiesIsSet = true;
}

void {{classname}}::addAdditionalProperty(const utility::string_t& key, const web::json::value& value)
{
m_AdditionalProperties[key] = value;
m_AdditionalPropertiesIsSet = true;
}

bool {{classname}}::additionalPropertiesIsSet() const
{
return m_AdditionalPropertiesIsSet;
}

void {{classname}}::unsetAdditionalProperties()
{
m_AdditionalProperties.clear();
m_AdditionalPropertiesIsSet = false;
}
{{/additionalPropertiesType}}
{{/isEnum}}
{{/oneOf}}
{{#modelNamespaceDeclarations}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <cpprest/json.h>

#include <map>
#include <memory>
#include <set>
#include <vector>

Expand Down
Loading
Loading