Skip to content

Commit

Permalink
[core-http] Find additionalProperties in referenced mapper (Azure#11473)
Browse files Browse the repository at this point in the history
* Look for additional properties in referenced mapper

* Format
  • Loading branch information
joheredi authored and deyaaeldeen committed Sep 25, 2020
1 parent 9150101 commit 79a97d5
Show file tree
Hide file tree
Showing 6 changed files with 423 additions and 42 deletions.
74 changes: 55 additions & 19 deletions sdk/core/core-client/src/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
SequenceMapper,
CompositeMapper,
PolymorphicDiscriminator,
EnumMapper
EnumMapper,
BaseMapper
} from "./interfaces";

class SerializerImpl implements Serializer {
Expand Down Expand Up @@ -565,6 +566,52 @@ function serializeDictionaryType(
return tempDictionary;
}

/**
* Resolves the additionalProperties property from a referenced mapper
* @param serializer the serializer containing the entire set of mappers
* @param mapper the composite mapper to resolve
* @param objectName name of the object being serialized
*/
function resolveAdditionalProperties(
serializer: Serializer,
mapper: CompositeMapper,
objectName: string
): SequenceMapper | BaseMapper | CompositeMapper | DictionaryMapper | EnumMapper | undefined {
const additionalProperties = mapper.type.additionalProperties;

if (!additionalProperties && mapper.type.className) {
const modelMapper = resolveReferencedMapper(serializer, mapper, objectName);
return modelMapper?.type.additionalProperties;
}

return additionalProperties;
}

/**
* Finds the mapper referenced by className
* @param serializer the serializer containing the entire set of mappers
* @param mapper the composite mapper to resolve
* @param objectName name of the object being serialized
*/
function resolveReferencedMapper(
serializer: Serializer,
mapper: CompositeMapper,
objectName: string
): CompositeMapper | undefined {
const className = mapper.type.className;
if (!className) {
throw new Error(
`Class name for model "${objectName}" is not provided in the mapper "${JSON.stringify(
mapper,
undefined,
2
)}".`
);
}

return serializer.modelMappers[className];
}

/**
* Resolves a composite mapper's modelProperties.
* @param serializer the serializer containing the entire set of mappers
Expand All @@ -577,28 +624,17 @@ function resolveModelProperties(
): { [propertyName: string]: Mapper } {
let modelProps = mapper.type.modelProperties;
if (!modelProps) {
const className = mapper.type.className;
if (!className) {
throw new Error(
`Class name for model "${objectName}" is not provided in the mapper "${JSON.stringify(
mapper,
undefined,
2
)}".`
);
}

const modelMapper = serializer.modelMappers[className];
const modelMapper = resolveReferencedMapper(serializer, mapper, objectName);
if (!modelMapper) {
throw new Error(`mapper() cannot be null or undefined for model "${className}".`);
throw new Error(`mapper() cannot be null or undefined for model "${mapper.type.className}".`);
}
modelProps = modelMapper.type.modelProperties;
modelProps = modelMapper?.type.modelProperties;
if (!modelProps) {
throw new Error(
`modelProperties cannot be null or undefined in the ` +
`mapper "${JSON.stringify(
modelMapper
)}" of type "${className}" for object "${objectName}".`
`mapper "${JSON.stringify(modelMapper)}" of type "${
mapper.type.className
}" for object "${objectName}".`
);
}
}
Expand Down Expand Up @@ -695,7 +731,7 @@ function serializeCompositeType(
}
}

const additionalPropertiesMapper = mapper.type.additionalProperties;
const additionalPropertiesMapper = resolveAdditionalProperties(serializer, mapper, objectName);
if (additionalPropertiesMapper) {
const propNames = Object.keys(modelProps);
for (const clientPropName in object) {
Expand Down
34 changes: 34 additions & 0 deletions sdk/core/core-client/test/serviceClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,40 @@ describe("ServiceClient", function() {
});

describe("serializeRequestBody()", () => {
it("should serialize additional properties when the mapper is refd by className", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
const body = [
{
version: 1,
name: "Test",
time: new Date("2020-09-24T17:31:35.034Z"),
data: {
baseData: {
test: "Hello!",
extraProp: "FooBar"
}
}
}
];

serializeRequestBody(
httpRequest,
{
body
},
{
httpMethod: "POST",
requestBody: Mappers.body,
responses: { 200: {} },
serializer: createSerializer(Mappers)
}
);
assert.strictEqual(
httpRequest.body,
`[{"ver":1,"name":"Test","time":"2020-09-24T17:31:35.034Z","data":{"baseData":{"test":"Hello!","extraProp":"FooBar"}}}]`
);
});

it("should serialize a JSON String request body", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
Expand Down
116 changes: 116 additions & 0 deletions sdk/core/core-client/test/testMappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1018,4 +1018,120 @@ internalMappers.requestBody1 = {
mapper: CreateQueueBody
};

internalMappers.TelemetryItem = {
type: {
name: "Composite",
className: "TelemetryItem",
modelProperties: {
version: {
defaultValue: 1,
serializedName: "ver",
type: {
name: "Number"
}
},
name: {
serializedName: "name",
required: true,
type: {
name: "String"
}
},
time: {
serializedName: "time",
required: true,
type: {
name: "DateTime"
}
},
sampleRate: {
defaultValue: 100,
serializedName: "sampleRate",
type: {
name: "Number"
}
},
sequence: {
constraints: {
MaxLength: 64
},
serializedName: "seq",
type: {
name: "String"
}
},
instrumentationKey: {
serializedName: "iKey",
type: {
name: "String"
}
},
tags: {
serializedName: "tags",
type: {
name: "Dictionary",
value: { type: { name: "String" } }
}
},
data: {
serializedName: "data",
type: {
name: "Composite",
className: "MonitorBase"
}
}
}
}
};

internalMappers.MonitorBase = {
type: {
name: "Composite",
className: "MonitorBase",
modelProperties: {
baseType: {
serializedName: "baseType",
type: {
name: "String"
}
},
baseData: {
serializedName: "baseData",
type: {
name: "Composite",
className: "MonitorDomain"
}
}
}
}
};

internalMappers.MonitorDomain = {
type: {
name: "Composite",
className: "MonitorDomain",
additionalProperties: { type: { name: "Object" } },
modelProperties: {
test: {
serializedName: "test",
type: {
name: "String"
}
}
}
}
};

internalMappers.body = {
parameterPath: "body",
mapper: {
serializedName: "body",
required: true,
type: {
name: "Sequence",
element: { type: { name: "Composite", className: "TelemetryItem" } }
}
}
};

export const Mappers = internalMappers;
71 changes: 53 additions & 18 deletions sdk/core/core-http/src/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,52 @@ function serializeDictionaryType(
return tempDictionary;
}

/**
* Resolves the additionalProperties property from a referenced mapper
* @param serializer the serializer containing the entire set of mappers
* @param mapper the composite mapper to resolve
* @param objectName name of the object being serialized
*/
function resolveAdditionalProperties(
serializer: Serializer,
mapper: CompositeMapper,
objectName: string
): SequenceMapper | BaseMapper | CompositeMapper | DictionaryMapper | EnumMapper | undefined {
const additionalProperties = mapper.type.additionalProperties;

if (!additionalProperties && mapper.type.className) {
const modelMapper = resolveReferencedMapper(serializer, mapper, objectName);
return modelMapper?.type.additionalProperties;
}

return additionalProperties;
}

/**
* Finds the mapper referenced by className
* @param serializer the serializer containing the entire set of mappers
* @param mapper the composite mapper to resolve
* @param objectName name of the object being serialized
*/
function resolveReferencedMapper(
serializer: Serializer,
mapper: CompositeMapper,
objectName: string
): CompositeMapper | undefined {
const className = mapper.type.className;
if (!className) {
throw new Error(
`Class name for model "${objectName}" is not provided in the mapper "${JSON.stringify(
mapper,
undefined,
2
)}".`
);
}

return serializer.modelMappers[className];
}

/**
* Resolves a composite mapper's modelProperties.
* @param serializer the serializer containing the entire set of mappers
Expand All @@ -560,28 +606,17 @@ function resolveModelProperties(
): { [propertyName: string]: Mapper } {
let modelProps = mapper.type.modelProperties;
if (!modelProps) {
const className = mapper.type.className;
if (!className) {
throw new Error(
`Class name for model "${objectName}" is not provided in the mapper "${JSON.stringify(
mapper,
undefined,
2
)}".`
);
}

const modelMapper = serializer.modelMappers[className];
const modelMapper = resolveReferencedMapper(serializer, mapper, objectName);
if (!modelMapper) {
throw new Error(`mapper() cannot be null or undefined for model "${className}".`);
throw new Error(`mapper() cannot be null or undefined for model "${mapper.type.className}".`);
}
modelProps = modelMapper.type.modelProperties;
modelProps = modelMapper?.type.modelProperties;
if (!modelProps) {
throw new Error(
`modelProperties cannot be null or undefined in the ` +
`mapper "${JSON.stringify(
modelMapper
)}" of type "${className}" for object "${objectName}".`
`mapper "${JSON.stringify(modelMapper)}" of type "${
mapper.type.className
}" for object "${objectName}".`
);
}
}
Expand Down Expand Up @@ -678,7 +713,7 @@ function serializeCompositeType(
}
}

const additionalPropertiesMapper = mapper.type.additionalProperties;
const additionalPropertiesMapper = resolveAdditionalProperties(serializer, mapper, objectName);
if (additionalPropertiesMapper) {
const propNames = Object.keys(modelProps);
for (const clientPropName in object) {
Expand Down
Loading

0 comments on commit 79a97d5

Please sign in to comment.