Skip to content

Commit

Permalink
Merge pull request #39 from Nirhoshan/add_xml_support
Browse files Browse the repository at this point in the history
Add xml support
  • Loading branch information
nadheesh committed Feb 6, 2024
2 parents fe0fcb0 + 207dcaa commit ba779f9
Show file tree
Hide file tree
Showing 7 changed files with 3,357 additions and 65 deletions.
3 changes: 3 additions & 0 deletions ballerina/constants.bal
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ const ACTION_KEY = "action";
const ACTION_NAME_KEY = "name";
const ACTION_ARGUEMENTS_KEY = "arguments";
final string:RegExp ACTION_INPUT_REGEX = re `^action.?input`;
const XML_NAMESPACE = "@xmlns";
const XML_CONTENT = "#content";
final string:RegExp XML_MEDIA = re `application/.*xml`;
22 changes: 22 additions & 0 deletions ballerina/http_utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
// under the License.
import ballerina/http;
import ballerina/lang.regexp;
import ballerina/log;
import ballerina/mime;
import ballerina/url;
import ballerina/xmldata;

type QueryParamEncoding record {
EncodingStyle style = FORM;
Expand Down Expand Up @@ -294,3 +296,23 @@ isolated function getContentLength(http:Response response) returns int|error? {
return int:fromString(contentLengthHeader);
}

isolated function getRequestMessage(string? mediaType, HttpInput httpInput) returns json|xml|error {
json|xml message;
if mediaType is string && mediaType.matches(XML_MEDIA) {
message = check xmldata:fromJson(httpInput?.requestBody);
} else {
message = httpInput?.requestBody;
}
return message;
}

isolated function getHttpParameters(map<HttpTool> httpTools, string httpMethod, HttpInput httpInput, boolean writeOperation) returns HttpParameters|error {
HttpTool httpTool = httpTools.get(string `${httpInput.path.toString()}:${httpMethod}`);
string path = check getParamEncodedPath(httpTool, httpInput?.parameters);
log:printDebug(string `HTTP ${httpMethod} ${path} ${httpInput?.requestBody.toString()}`);
if httpInput?.requestBody is () {
return {path: path, message: ()};
}
json|xml message = check getRequestMessage(httpTool.requestBody?.mediaType, httpInput);
return {path: path, message: message};
}
17 changes: 17 additions & 0 deletions ballerina/openapi_types.bal
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ public type BaseSchema record {
json default?;
# Whether the value is nullable
boolean nullable?;
# Xml schema
XmlSchema 'xml?;
# Not allowed $ref property
never \$ref?;
};
Expand Down Expand Up @@ -325,12 +327,27 @@ public type ObjectSchemaType2 record {
# Defines an object schema.
public type ObjectSchema ObjectSchemaType1|ObjectSchemaType2;

public type XmlSchema record {|
# Replaces the name of the element/attribute used for the described schema property.
string name?;
# The URI of the namespace definition.
string namespace?;
# The prefix to be used for the name.
string prefix?;
# Declares whether the property definition translates to an attribute instead of an element.
boolean attribute?;
# May be used only for an array definition.
boolean wrapped?;
|};

# Defines a reference object.
public type Reference record {
# Reference to a component
string \$ref;
# Short description of the target component
string summary?;
# Xml schema
XmlSchema 'xml?;
# Detailed description of the target component
string description?;
};
Expand Down
159 changes: 125 additions & 34 deletions ballerina/openapi_utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// specific language governing permissions and limitations
// under the License.
import ballerina/io;
import ballerina/lang.regexp;
import ballerina/log;
import ballerina/yaml;

Expand Down Expand Up @@ -202,22 +203,36 @@ class OpenApiSpecVisitor {
private isolated function visitContent(map<MediaType> content) returns record {|string mediaType; Schema schema;|}|error {
// check for json content
foreach [string, MediaType] [key, value] in content.entries() {
if key.trim().matches(re `(application/.*json|text/.*plain|\*/\*)`) {
if key.trim().matches(re `(application/.*json|${XML_MEDIA}|text/.*plain|\*/\*)`) {
return {
mediaType: key,
schema: value.schema
};
}
}
return error UnsupportedMediaTypeError("Only json or text content is supported.", availableContentTypes = content.keys());
return error UnsupportedMediaTypeError("Only json, xml or text content is supported.", availableContentTypes = content.keys());
}

private isolated function visitRequestBody(RequestBody requestBody) returns RequestBodySchema|error {
map<MediaType> content = requestBody.content;
string mediaType;
Schema schema;
{mediaType, schema} = check self.visitContent(content);
return {mediaType, schema: check self.visitSchema(schema)};
if !mediaType.matches(re `${XML_MEDIA}`) {
return {
mediaType,
schema: check self.visitSchema(schema)
};
}
if schema is Reference {
string refName = regexp:split(re `/`, schema.\$ref).pop();
schema = {'type: OBJECT, properties: {[refName] : schema}};
}
return {
mediaType,
schema: check self.visitSchema(schema, true)
};

}

private isolated function visitParameters((Parameter|Reference)[]? parameters) returns map<ParameterSchema>?|error {
Expand Down Expand Up @@ -284,40 +299,79 @@ class OpenApiSpecVisitor {
if component is Reference {
return self.visitReference(component);
}
if component !is Schema {
return component;
}
string? xmlName = component.'xml?.name;
string? xmlPrefix = component.'xml?.prefix;
if xmlName !is () {
reference.'xml.name = xmlName;
}
if xmlPrefix !is () {
reference.'xml.prefix = xmlPrefix;
}
return component;
}

private isolated function visitSchema(Schema schema) returns JsonSubSchema|error {
private isolated function visitSchema(Schema schema, boolean isXml = false) returns JsonSubSchema|error {
if schema is ObjectSchema {
return self.visitObjectSchema(schema);
return self.visitObjectSchema(schema, isXml);
}
if schema is ArraySchema {
return self.visitArraySchema(schema);
return self.visitArraySchema(schema, isXml);
}
if schema is PrimitiveTypeSchema {
return self.visitPrimitiveTypeSchema(schema);
return self.visitPrimitiveTypeSchema(schema, isXml);
}
if schema is AnyOfSchema {
return self.visitAnyOfSchema(schema);
return self.visitAnyOfSchema(schema, isXml);
}
if schema is OneOfSchema {
return self.visitOneOfSchema(schema);
return self.visitOneOfSchema(schema, isXml);
}
if schema is AllOfSchema {
return self.visitAllOfSchema(schema);
return self.visitAllOfSchema(schema, isXml);
}
if schema is NotSchema {
return self.visitNotSchema(schema);
return self.visitNotSchema(schema, isXml);
}
Schema resolvedSchema = check self.visitReference(<Reference>schema).ensureType();
return check self.visitSchema(resolvedSchema);
return check self.visitSchema(resolvedSchema, isXml);
}

private isolated function wrapObjectSchema(string? xmlName, string? xmlNamespace, string? xmlPrefix, string? refName, ObjectInputSchema|ArrayInputSchema|PrimitiveInputSchema inputSchema) returns ObjectInputSchema|error {
ObjectInputSchema outerObjectSchema = {
'type: OBJECT,
properties: {}
};
if xmlName is string {
outerObjectSchema.properties[self.getPropertyName(xmlName, xmlPrefix)] = inputSchema;
} else if refName is string {
outerObjectSchema.properties[self.getPropertyName(refName, xmlPrefix)] = inputSchema;
}
if xmlNamespace is string {
string namespaceProperty = xmlPrefix is string ? string `${XML_NAMESPACE}:${xmlPrefix}` : XML_NAMESPACE;
outerObjectSchema.properties[namespaceProperty] = {'const: xmlNamespace};
}
if inputSchema is PrimitiveInputSchema {
outerObjectSchema.properties[XML_CONTENT] = inputSchema;
}
return outerObjectSchema;
}

private isolated function visitObjectSchema(ObjectSchema schema) returns ObjectInputSchema|error {
private isolated function visitObjectSchema(ObjectSchema schema, boolean isXml) returns ObjectInputSchema|error {
ObjectInputSchema objectSchema = {
'type: OBJECT,
properties: {}
};
if isXml {
string? xmlNamespace = schema.'xml?.namespace;
string? xmlPrefix = schema.'xml?.prefix;
if xmlNamespace is string {
string namespaceProperty = xmlPrefix is string ? string `${XML_NAMESPACE}:${xmlPrefix}` : XML_NAMESPACE;
objectSchema.properties[namespaceProperty] = {'const: xmlNamespace};
}
}

if schema?.properties == () {
return objectSchema;
Expand All @@ -329,7 +383,24 @@ class OpenApiSpecVisitor {
}

foreach [string, Schema] [propertyName, property] in properties.entries() {
objectSchema.properties[propertyName] = check self.visitSchema(property);
if property.'xml?.name is () {
property.'xml.name = propertyName;
}
JsonSubSchema resolvedPropertySchema = check self.visitSchema(property, isXml);
if !isXml {
objectSchema.properties[propertyName] = resolvedPropertySchema;
continue;
}
string? innerXmlName = property.'xml?.name;
boolean? xmlAttribute = property.'xml?.attribute;
string? innerXmlPrefix = property.'xml?.prefix;
string xmlName = propertyName;
if innerXmlName is string {
xmlName = innerXmlName;
}
string attributePrefix = xmlAttribute is boolean && xmlAttribute ? "@" : "";
string resolvedPropertyName = self.getPropertyName(xmlName, innerXmlPrefix);
objectSchema.properties[string `${attributePrefix}${resolvedPropertyName}`] = resolvedPropertySchema;
}
boolean|string[]? required = schema?.required;
if required is string[] {
Expand All @@ -338,23 +409,33 @@ class OpenApiSpecVisitor {
return objectSchema;
}

private isolated function visitArraySchema(ArraySchema schema) returns ArrayInputSchema|error {
return {
private isolated function visitArraySchema(ArraySchema schema, boolean isXml) returns ArrayInputSchema|ObjectInputSchema|error {
ArrayInputSchema arraySchema = {
'type: ARRAY,
items: check self.visitSchema(schema.items)
items: check self.visitSchema(schema.items, isXml)
};

if isXml {
boolean? xmlWrapped = schema?.'xml?.wrapped;
string? xmlNamespace = schema?.'xml?.namespace;
string? xmlPrefix = schema?.'xml?.prefix;
if xmlWrapped is boolean && xmlWrapped {
return self.wrapObjectSchema(schema.items.'xml?.name, xmlNamespace, xmlPrefix, schema.'xml?.name, arraySchema);
}
}
return arraySchema;
}

private isolated function visitPrimitiveTypeSchema(PrimitiveTypeSchema schema) returns PrimitiveInputSchema|error {
PrimitiveInputSchema inputSchmea = {
private isolated function visitPrimitiveTypeSchema(PrimitiveTypeSchema schema, boolean isXml) returns PrimitiveInputSchema|ObjectInputSchema|error {
PrimitiveInputSchema inputSchema = {
'type: schema.'type
};

if self.additionalInfoFlags.extractDescription {
inputSchmea.description = schema.description;
inputSchema.description = schema.description;
}
if self.additionalInfoFlags.extractDefault {
inputSchmea.default = check schema?.default.ensureType();
inputSchema.default = check schema?.default.ensureType();
}

if schema is StringSchema {
Expand All @@ -369,43 +450,53 @@ class OpenApiSpecVisitor {
}
}

inputSchmea.format = format;
inputSchmea.pattern = pattern;
inputSchmea.'enum = schema.'enum;
inputSchema.format = format;
inputSchema.pattern = pattern;
inputSchema.'enum = schema.'enum;
}
if schema is NumberSchema {
inputSchmea.'type = FLOAT;
inputSchema.'type = FLOAT;
}
if isXml {
string? xmlNamespace = schema.'xml?.namespace;
string? xmlPrefix = schema.'xml?.prefix;
if xmlNamespace is string {
return self.wrapObjectSchema((), xmlNamespace, xmlPrefix, (), inputSchema);
}
}
return inputSchmea;
return inputSchema;
}

private isolated function visitAnyOfSchema(AnyOfSchema schema) returns AnyOfInputSchema|error {
private isolated function visitAnyOfSchema(AnyOfSchema schema, boolean isXml) returns AnyOfInputSchema|error {
JsonSubSchema[] anyOf = from Schema element in schema.anyOf
select check self.visitSchema(element).ensureType();
select check self.visitSchema(element, isXml).ensureType();
return {
anyOf
};
}

private isolated function visitAllOfSchema(AllOfSchema schema) returns AllOfInputSchema|error {
private isolated function visitAllOfSchema(AllOfSchema schema, boolean isXml) returns AllOfInputSchema|error {
JsonSubSchema[] allOf = from Schema element in schema.allOf
select check self.visitSchema(element).ensureType();
select check self.visitSchema(element, isXml).ensureType();
return {
allOf
};
}

private isolated function visitOneOfSchema(OneOfSchema schema) returns OneOfInputSchema|error {
private isolated function visitOneOfSchema(OneOfSchema schema, boolean isXml) returns OneOfInputSchema|error {
JsonSubSchema[] oneOf = from Schema element in schema.oneOf
select check self.visitSchema(element);
select check self.visitSchema(element, isXml);
return {
oneOf
};
}

private isolated function visitNotSchema(NotSchema schema) returns NotInputSchema|error {
private isolated function visitNotSchema(NotSchema schema, boolean isXml) returns NotInputSchema|error {
return {
not: check self.visitSchema(schema.not)
not: check self.visitSchema(schema.not, isXml)
};
}

isolated function getPropertyName(string name, string? prefix) returns string =>
prefix is () ? name : string `${prefix}:${name}`;
}
Loading

0 comments on commit ba779f9

Please sign in to comment.