Skip to content
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
101 changes: 12 additions & 89 deletions src/TypeSpec.Extension/Emitter.Csharp/src/lib/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ import {
DurationKnownEncoding,
EncodeData,
IntrinsicType,
Model,
Program,
Type,
getFormat
Model
} from "@typespec/compiler";
import { Logger } from "./logger.js";
import { getFullNamespaceString } from "./utils.js";
Expand Down Expand Up @@ -78,15 +75,13 @@ export function fromSdkType(
return fromSdkConstantType(sdkType, enums, literalTypeContext);
if (sdkType.kind === "union")
return fromUnionType(sdkType, context, models, enums);
if (sdkType.kind === "utcDateTime") return fromSdkDatetimeType(sdkType);
if (sdkType.kind === "duration")
return fromSdkDurationType(sdkType as SdkDurationType);
if (sdkType.kind === "bytes")
return fromBytesType(sdkType as SdkBuiltInType);
if (sdkType.kind === "string")
return fromStringType(context.program, sdkType);
// TODO: offsetDateTime
if (sdkType.kind === "utcDateTime" || sdkType.kind == "offsetDateTime")
return fromSdkDatetimeType(sdkType);
if (sdkType.kind === "duration") return fromSdkDurationType(sdkType);
if (sdkType.kind === "bytes") return fromBytesType(sdkType);
if (sdkType.kind === "string") return fromStringType(sdkType);
if (sdkType.kind === "tuple") return fromTupleType(sdkType);
// TODO -- refine the other types from TCGC
if (sdkType.__raw?.kind === "Scalar") return fromScalarType(sdkType);
// this happens for discriminator type, normally all other primitive types should be handled in scalar above
// TODO: can we improve the type in TCGC around discriminator
Expand All @@ -96,32 +91,6 @@ export function fromSdkType(
return {} as InputType;
}

// TODO -- this is workaround because TCGC ignore format, we need to remove this after a discussion on format.
// this function is only for the case when we get a type from a parameter, because in typespec, the parameters share the same type as the properties
export function fromSdkModelPropertyType(
propertyType: SdkModelPropertyType,
context: SdkContext,
models: Map<string, InputModelType>,
enums: Map<string, InputEnumType>,
literalTypeContext?: LiteralTypeContext
): InputType {
// when the type is string, we need to add the format
if (propertyType.type.kind === "string") {
return fromStringType(
context.program,
propertyType.type,
propertyType.__raw
);
}
return fromSdkType(
propertyType.type,
context,
models,
enums,
literalTypeContext
);
}

export function fromSdkModelType(
modelType: SdkModelType,
context: SdkContext,
Expand Down Expand Up @@ -418,41 +387,10 @@ function fromBytesType(bytesType: SdkBuiltInType): InputPrimitiveType {
};
}

function fromStringType(
program: Program,
stringType: SdkType,
// we need the extra raw here because the format decorator is added to the property/parameter, but the raw in stringType is the type itself, and it does not have the format decorator we want.
// only when we get the type from a parameter, we need to pass the the parameter as raw here to get the format
// TODO -- we should remove this entirely later because TCGC ignores format in these cases, we add it now because we have old test projects, and we did not discuss the impact yet
raw?: Type
): InputPrimitiveType {
function fromStringFormat(rawStringType?: Type): InputPrimitiveTypeKind {
if (!rawStringType) return InputPrimitiveTypeKind.String;

const format = getFormat(program, rawStringType);
switch (format) {
case "date":
// TODO: remove
return InputPrimitiveTypeKind.DateTime;
case "uri":
case "url":
return InputPrimitiveTypeKind.Uri;
case "uuid":
return InputPrimitiveTypeKind.Guid;
default:
if (format) {
Logger.getInstance().warn(
`Invalid string format '${format}'`
);
}
return InputPrimitiveTypeKind.String;
}
}

raw = raw ?? stringType.__raw;
function fromStringType(stringType: SdkType): InputPrimitiveType {
return {
Kind: InputTypeKind.Primitive,
Name: fromStringFormat(raw),
Name: InputPrimitiveTypeKind.String,
IsNullable: stringType.nullable
};
}
Expand Down Expand Up @@ -548,7 +486,6 @@ function fromScalarType(scalarType: SdkType): InputPrimitiveType {
return {
Kind: InputTypeKind.Primitive,
Name: getCSharpInputTypeKindByPrimitiveModelName(
scalarType.kind,
scalarType.kind,
undefined // To-DO: encode not compatible
),
Expand All @@ -557,7 +494,6 @@ function fromScalarType(scalarType: SdkType): InputPrimitiveType {

function getCSharpInputTypeKindByPrimitiveModelName(
name: string,
format?: string,
encode?: EncodeData
): InputPrimitiveTypeKind {
switch (name) {
Expand Down Expand Up @@ -602,22 +538,7 @@ function fromScalarType(scalarType: SdkType): InputPrimitiveType {
case "eTag":
return InputPrimitiveTypeKind.String;
case "string":
switch (format?.toLowerCase()) {
case "date":
return InputPrimitiveTypeKind.DateTime;
case "uri":
case "url":
return InputPrimitiveTypeKind.Uri;
case "uuid":
return InputPrimitiveTypeKind.Guid;
default:
if (format) {
Logger.getInstance().warn(
`invalid format ${format}`
);
}
return InputPrimitiveTypeKind.String;
}
return InputPrimitiveTypeKind.String;
case "boolean":
return InputPrimitiveTypeKind.Boolean;
case "date":
Expand Down Expand Up @@ -656,6 +577,8 @@ function fromScalarType(scalarType: SdkType): InputPrimitiveType {
encode.type?.name === "float32"
) {
return InputPrimitiveTypeKind.DurationSecondsFloat;
} else if (encode.type?.name === "float64") {
return InputPrimitiveTypeKind.DurationSecondsDouble;
} else {
return InputPrimitiveTypeKind.DurationSeconds;
}
Expand Down
27 changes: 2 additions & 25 deletions src/TypeSpec.Extension/Emitter.Csharp/src/lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { getLroMetadata } from "@azure-tools/typespec-azure-core";
import {
SdkContext,
getAllModels,
getClientType,
getSdkModelPropertyType
getClientType
} from "@azure-tools/typespec-client-generator-core";
import {
Model,
Expand All @@ -15,7 +14,6 @@ import {
Type,
UsageFlags,
getEffectiveModelType,
ignoreDiagnostics,
isArrayModelType,
isRecordModelType,
resolveUsages
Expand All @@ -28,12 +26,7 @@ import {
isStatusCode
} from "@typespec/http";
import { NetEmitterOptions } from "../options.js";
import {
fromSdkEnumType,
fromSdkModelPropertyType,
fromSdkModelType,
fromSdkType
} from "./converter.js";
import { fromSdkEnumType, fromSdkModelType, fromSdkType } from "./converter.js";
import {
InputEnumType,
InputModelType,
Expand Down Expand Up @@ -109,22 +102,6 @@ export function getInputType(
): InputType {
Logger.getInstance().debug(`getInputType for kind: ${type.kind}`);

// TODO -- we might could remove this workaround when we adopt getAllOperations
// or when we decide not to honor the `@format` decorators on parameters
// this is specifically dealing with the case of an operation parameter
if (type.kind === "ModelProperty") {
const propertyType = ignoreDiagnostics(
getSdkModelPropertyType(context, type, operation)
);
return fromSdkModelPropertyType(
propertyType,
context,
models,
enums,
literalTypeContext
);
}

const sdkType = getClientType(context, type, operation);
return fromSdkType(sdkType, context, models, enums, literalTypeContext);
}
Expand Down
133 changes: 0 additions & 133 deletions src/TypeSpec.Extension/Emitter.Csharp/test/Unit/string-format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,137 +88,4 @@ describe("Test string format", () => {
)
);
});

it("format uri on operation parameter", async () => {
const program = await typeSpecCompile(
`
op test(@path @format("uri")sourceUrl: string): void;
`,
runner
);
const context = createEmitterContext(program);
const sdkContext = createNetSdkContext(context);
const [services] = getAllHttpServices(program);
const modelMap = new Map<string, InputModelType>();
const enumMap = new Map<string, InputEnumType>();
const operation = loadOperation(
sdkContext,
services[0].operations[0],
"",
[],
services[0].namespace,
modelMap,
enumMap
);
assert(
isEqual(
{
Kind: InputTypeKind.Primitive,
Name: InputPrimitiveTypeKind.Uri,
IsNullable: false
},
operation.Parameters[0].Type
)
);
});

it("format uri on model property", async () => {
const program = await typeSpecCompile(
`
@doc("This is a model.")
model Foo {
@doc("The source url.")
@format("uri")
source: string
}
`,
runner
);
const context = createEmitterContext(program);
const sdkContext = createNetSdkContext(context);
const [services] = getAllHttpServices(program);
const modelMap = new Map<string, InputModelType>();
const enumMap = new Map<string, InputEnumType>();
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
const foo = modelMap.get("Foo");
assert(foo !== undefined);
assert(
isEqual(
{
Kind: InputTypeKind.Primitive,
Name: InputPrimitiveTypeKind.Uri,
IsNullable: false
},
foo.Properties[0].Type
),
`string property format is not correct. Got ${JSON.stringify(
foo.Properties[0].Type
)} `
);
});

it("format uuid on operation parameter", async () => {
const program = await typeSpecCompile(
`
op test(@path @format("uuid")subscriptionId: string): void;
`,
runner
);
const context = createEmitterContext(program);
const sdkContext = createNetSdkContext(context);
const [services] = getAllHttpServices(program);
const modelMap = new Map<string, InputModelType>();
const enumMap = new Map<string, InputEnumType>();
const operation = loadOperation(
sdkContext,
services[0].operations[0],
"",
[],
services[0].namespace,
modelMap,
enumMap
);
assert(
isEqual(
{
Kind: InputTypeKind.Primitive,
Name: InputPrimitiveTypeKind.Guid,
IsNullable: false
},
operation.Parameters[0].Type
)
);
});

it("format on model property", async () => {
const program = await typeSpecCompile(
`
@doc("This is a model.")
model Foo {
@doc("The subscription id.")
@format("uuid")
subscriptionId: string
}
`,
runner
);
const context = createEmitterContext(program);
const sdkContext = createNetSdkContext(context);
const [services] = getAllHttpServices(program);
const modelMap = new Map<string, InputModelType>();
const enumMap = new Map<string, InputEnumType>();
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
const foo = modelMap.get("Foo");
assert(foo !== undefined);
assert(
isEqual(
{
Kind: InputTypeKind.Primitive,
Name: InputPrimitiveTypeKind.Guid,
IsNullable: false
} as InputPrimitiveType,
foo.Properties[0].Type
)
);
});
});
12 changes: 5 additions & 7 deletions test/TestProjects/FirstTest-TypeSpec/FirstTest-TypeSpec.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,10 @@ union DaysOfWeekExtensibleEnum {

model ModelWithFormat {
@doc("url format")
@format("uri")
sourceUrl: string;
sourceUrl: url;

@doc("uuid format")
@format("uuid")
guid: string;
guid: uuid;
}

@doc("Hello world service")
Expand Down Expand Up @@ -311,7 +309,7 @@ namespace Hello.Demo2 {
@doc("top level method")
@get
@convenientAPI(true)
op topAction(@path @format("date") action: string): Thing;
op topAction(@path action: utcDateTime): Thing;

@route("/top2")
@doc("top level method2")
Expand All @@ -338,14 +336,14 @@ op anonymousBody(...Thing): Thing;
op friendlyModel(...NotFriend): NotFriend;

op addTimeHeader(
@header("Repeatability-First-Sent") repeatabilityFirstSent?: utcDateTime
@header("Repeatability-First-Sent") repeatabilityFirstSent?: utcDateTime
): void;

@route("/stringFormat")
@doc("paramete has string format.")
@post
@convenientAPI(true)
op stringFormat(@path @format("uuid") subscriptionId: string, @body body: ModelWithFormat): void;
op stringFormat(@path subscriptionId: uuid, @body body: ModelWithFormat): void;


@route("/projectedName")
Expand Down
Loading