@typespec/http-server-csharp
Version:
TypeSpec service code generator for c-sharp
422 lines • 14.9 kB
JavaScript
import { getEncode, getFormat, getMaxItems, getMaxValue, getMaxValueExclusive, getMinItems, getMinValue, getMinValueExclusive, isArrayModelType, resolveEncodedName, } from "@typespec/compiler";
import { camelCase } from "change-case";
import { Attribute, AttributeType, BooleanValue, CSharpType, HelperNamespace, NumericValue, Parameter, RawValue, StringValue, } from "./interfaces.js";
import { getEnumType, getStringConstraint, isArrayType } from "./type-helpers.js";
import { getCSharpTypeForScalar, isStringEnumType } from "./utils.js";
export const JsonNamespace = "System.Text.Json";
export function getEncodingValue(program, type) {
const value = getEncode(program, type);
return value ? value.encoding : undefined;
}
export function getFormatValue(program, type) {
return getFormat(program, type);
}
/**
* Return name encoding attributes
* @param program The program being processed
* @param type The type to check
* @returns The attributes associated with the type, or none
*/
export function getEncodedNameAttribute(program, type, cSharpName) {
const encodedName = resolveEncodedName(program, type, "application/json");
if (encodedName !== type.name) {
const attr = new Attribute(new AttributeType({
name: "JsonPropertyName",
namespace: JsonNamespace,
}), []);
attr.parameters.push(new Parameter({
name: "name",
value: new StringValue(encodedName),
optional: false,
type: new CSharpType({
name: "string",
namespace: "System",
isBuiltIn: true,
isValueType: true,
}),
}));
return attr;
}
if (cSharpName && type.name !== camelCase(cSharpName)) {
const attr = new Attribute(new AttributeType({
name: "JsonPropertyName",
namespace: JsonNamespace,
}), []);
attr.parameters.push(new Parameter({
name: "name",
value: new StringValue(type.name),
optional: false,
type: new CSharpType({
name: "string",
namespace: "System",
isBuiltIn: true,
isValueType: true,
}),
}));
return attr;
}
return undefined;
}
/**
* Return the encoding attributes for model properties
* @param program The program being processed
* @param type The type to check
* @returns The appropriate serialization attributes for the type, or none
*/
export function getEncodingAttributes(program, type) {
const result = [];
const propertyType = getScalarType(program, type);
if (propertyType !== undefined) {
switch (propertyType.scalar.name) {
case "unixTimestamp32":
result.push(getJsonConverterAttribute("UnixEpochDateTimeOffsetConverter"));
break;
case "duration":
result.push(getJsonConverterAttribute("TimeSpanDurationConverter"));
break;
case "bytes":
if (propertyType.encoding !== undefined &&
propertyType.encoding.name.toLowerCase() === "base64url") {
result.push(getJsonConverterAttribute("Base64UrlJsonConverter"));
}
break;
case "utcDateTime":
case "offsetDateTime":
if (propertyType.encoding !== undefined &&
propertyType.encoding.name.toLowerCase() === "unixTimestamp") {
result.push(getJsonConverterAttribute("UnixEpochDateTimeOffsetConverter"));
}
break;
}
}
return result;
}
function getScalarType(program, property) {
function getScalarEncoding(scalar) {
const encode = getEncode(program, scalar);
if (encode === undefined) {
switch (scalar.kind) {
case "ModelProperty":
if (scalar.type.kind === "Scalar") {
return getScalarEncoding(scalar.type);
}
return undefined;
case "Scalar":
if (scalar.baseScalar) {
return getScalarEncoding(scalar.baseScalar);
}
return undefined;
}
}
return { name: encode.encoding ?? "string", wireType: encode.type };
}
function getStdScalarType(scalar) {
if (program.checker.isStdType(scalar))
return scalar;
if (scalar.baseScalar)
return getStdScalarType(scalar.baseScalar);
return undefined;
}
if (property.type.kind !== "Scalar")
return undefined;
const stdScalar = getStdScalarType(property.type);
if (stdScalar === undefined)
return undefined;
const encoding = getScalarEncoding(property);
return { scalar: stdScalar, encoding: encoding };
}
function getTypeType() {
return new CSharpType({ name: "Type", namespace: "System", isValueType: false, isBuiltIn: true });
}
function getJsonConverterAttributeType() {
return new CSharpType({
name: "JsonConverter",
namespace: "System.Text.Json",
isValueType: false,
isBuiltIn: true,
});
}
function getJsonConverterAttribute(converterType) {
return new Attribute(getJsonConverterAttributeType(), [
new Parameter({
name: "",
type: getTypeType(),
optional: false,
value: new RawValue(`typeof(${converterType})`),
}),
]);
}
/**
* Return min and max length attributes for string
* @param program The program being processed
* @param type The type to check
* @returns The attributes associated with the type, or none
*/
export function getStringConstraintAttribute(program, type) {
const constraint = getStringConstraint(program, type);
if (constraint === undefined)
return undefined;
const minLength = constraint.minLength;
const maxLength = constraint.maxLength;
const pattern = constraint.pattern;
if (minLength === undefined && maxLength === undefined && pattern === undefined)
return undefined;
const attr = new Attribute(new AttributeType({
name: "StringConstraint",
namespace: HelperNamespace,
}), []);
if (minLength !== undefined) {
attr.parameters.push(new Parameter({
name: "MinLength",
value: new NumericValue(minLength),
optional: true,
type: new CSharpType({
name: "int",
namespace: "System",
isBuiltIn: true,
isValueType: true,
}),
}));
}
if (maxLength !== undefined) {
attr.parameters.push(new Parameter({
name: "MaxLength",
value: new NumericValue(maxLength),
optional: true,
type: new CSharpType({
name: "int",
namespace: "System",
isBuiltIn: true,
isValueType: true,
}),
}));
}
if (pattern !== undefined) {
attr.parameters.push(new Parameter({
name: "Pattern",
value: new StringValue(pattern),
optional: true,
type: new CSharpType({
name: "string",
namespace: "System",
isBuiltIn: true,
isValueType: true,
}),
}));
}
return attr;
}
/**
* Return min and max length attributes for string
* @param program The program being processed
* @param type The type to check
* @returns The attributes associated with the type, or none
*/
export function getArrayConstraintAttribute(program, type) {
if (!isArrayType(program, type))
return undefined;
const minItems = getMinItems(program, type);
const maxItems = getMaxItems(program, type);
if (minItems === undefined && maxItems === undefined)
return undefined;
if (type.kind !== "ModelProperty" || type.type.kind !== "Model")
return undefined;
if (!isArrayModelType(type.type))
return undefined;
const arrayType = type.type;
const elementType = arrayType.indexer.value;
if (elementType.kind !== "Scalar")
return undefined;
const scalarType = getCSharpTypeForScalar(program, elementType);
if (scalarType === undefined)
return undefined;
const attr = new Attribute(new AttributeType({
name: `ArrayConstraint<${scalarType.getTypeReference()}>`,
namespace: HelperNamespace,
}), []);
if (minItems !== undefined) {
attr.parameters.push(new Parameter({
name: "MinItems",
value: new NumericValue(minItems),
optional: true,
type: new CSharpType({
name: "int",
namespace: "System",
isBuiltIn: true,
isValueType: true,
}),
}));
}
if (maxItems !== undefined) {
attr.parameters.push(new Parameter({
name: "MaxItems",
value: new NumericValue(maxItems),
optional: true,
type: new CSharpType({
name: "int",
namespace: "System",
isBuiltIn: true,
isValueType: true,
}),
}));
}
return attr;
}
/**
* Return min and max length attributes for string
* @param program The program being processed
* @param type The type to check
* @returns The attributes associated with the type, or none
*/
export function getNumericConstraintAttribute(program, type) {
if (type.kind === "Scalar" || type.type.kind !== "Scalar")
return undefined;
const minValue = getMinValue(program, type);
const maxValue = getMaxValue(program, type);
const minValueExclusive = getMinValueExclusive(program, type);
const maxValueExclusive = getMaxValueExclusive(program, type);
if (minValue === undefined &&
maxValue === undefined &&
minValueExclusive === undefined &&
maxValueExclusive === undefined)
return undefined;
const scalarType = getCSharpTypeForScalar(program, type.type);
if (scalarType === undefined)
return undefined;
const attr = new Attribute(new AttributeType({
name: `NumericConstraint<${scalarType.getTypeReference()}>`,
namespace: HelperNamespace,
}), []);
const actualMin = minValue === undefined ? minValueExclusive : minValue;
if (actualMin !== undefined) {
attr.parameters.push(new Parameter({
name: "MinValue",
value: new NumericValue(actualMin),
optional: true,
type: scalarType,
}));
}
const actualMax = maxValue === undefined ? maxValueExclusive : maxValue;
if (actualMax !== undefined) {
attr.parameters.push(new Parameter({
name: "MaxValue",
value: new NumericValue(actualMax),
optional: true,
type: scalarType,
}));
}
if (minValueExclusive !== undefined) {
attr.parameters.push(new Parameter({
name: "MinValueExclusive",
value: new BooleanValue(true),
optional: true,
type: new CSharpType({
name: "bool",
namespace: "System",
isBuiltIn: true,
isValueType: true,
}),
}));
}
if (maxValueExclusive !== undefined) {
attr.parameters.push(new Parameter({
name: "MaxValueExclusive",
value: new BooleanValue(true),
optional: true,
type: new CSharpType({
name: "bool",
namespace: "System",
isBuiltIn: true,
isValueType: true,
}),
}));
}
return attr;
}
export function getSafeIntAttribute(type) {
if (type.name.toLowerCase() !== "safeint")
return undefined;
const attr = new Attribute(new AttributeType({
name: `NumericConstraint<long>`,
namespace: HelperNamespace,
}), []);
attr.parameters.push(new Parameter({
name: "MinValue",
value: new NumericValue(-9007199254740991),
optional: true,
type: new CSharpType({
name: "long",
namespace: "System",
isBuiltIn: true,
isValueType: true,
isNullable: false,
}),
}));
attr.parameters.push(new Parameter({
name: "MaxValue",
value: new NumericValue(9007199254740991),
optional: true,
type: new CSharpType({
name: "long",
namespace: "System",
isBuiltIn: true,
isValueType: true,
isNullable: false,
}),
}));
return attr;
}
function getEnumAttribute(type, cSharpName) {
return new Attribute(new AttributeType({
name: `JsonConverter(typeof(JsonStringEnumConverter))`,
namespace: "System.Text.Json.Serialization",
}), []);
}
export function getAttributes(program, type, cSharpName) {
const result = new Set();
switch (type.kind) {
case "Enum":
if (getEnumType(type) === "string")
result.add(getEnumAttribute(type, cSharpName));
break;
case "Union":
if (isStringEnumType(program, type)) {
result.add(getEnumAttribute(type, cSharpName));
}
break;
case "Model":
break;
case "ModelProperty": {
const arrayAttr = getArrayConstraintAttribute(program, type);
const stringAttr = getStringConstraintAttribute(program, type);
const numberAttr = getNumericConstraintAttribute(program, type);
const name = getEncodedNameAttribute(program, type, cSharpName);
if (arrayAttr)
result.add(arrayAttr);
if (stringAttr)
result.add(stringAttr);
if (numberAttr)
result.add(numberAttr);
if (name)
result.add(name);
const encodingAttributes = getEncodingAttributes(program, type);
for (const encoder of encodingAttributes) {
result.add(encoder);
}
const typeAttributes = getAttributes(program, type.type);
for (const typeAttribute of typeAttributes) {
result.add(typeAttribute);
}
break;
}
case "Scalar":
{
const safeInt = getSafeIntAttribute(type);
if (safeInt)
result.add(safeInt);
}
break;
}
return [...result.values()];
}
//# sourceMappingURL=attributes.js.map