@typespec/http-client-java
Version:
TypeSpec library for emitting Java client from the TypeSpec REST protocol binding
423 lines • 14.4 kB
JavaScript
import { getUnionAsEnum } from "@azure-tools/typespec-azure-core";
import { isSdkFloatKind, isSdkIntKind, } from "@azure-tools/typespec-client-generator-core";
import { getTypeName, isNullType, isTemplateDeclaration, isTemplateInstance, isTypeSpecValueTypeOf, } from "@typespec/compiler";
import { SchemaContext } from "./common/schemas/usage.js";
import { getNamespace } from "./utils.js";
export const DURATION_KNOWN_ENCODING = ["ISO8601", "seconds"];
export const DATETIME_KNOWN_ENCODING = ["rfc3339", "rfc7231", "unixTimestamp"];
export const BYTES_KNOWN_ENCODING = ["base64", "base64url"];
/** Acts as a cache for processing inputs.
*
* If the input is undefined, the output is always undefined.
* for a given input, the process is only ever called once.
*/
export class ProcessingCache {
transform;
results = new Map();
constructor(transform) {
this.transform = transform;
}
has(original) {
return !!original && !!this.results.get(original);
}
set(original, result) {
this.results.set(original, result);
return result;
}
get(original) {
return this.results.get(original);
}
process(original, ...args) {
if (original) {
const result = this.results.get(original) || this.transform(original, ...args);
this.results.set(original, result);
return result;
}
return undefined;
}
}
/** adds only if the item is not in the collection already
*
* @note While this isn't very efficient, it doesn't disturb the original
* collection, so you won't get inadvertent side effects from using Set, etc.
*/
export function pushDistinct(targetArray, ...items) {
for (const i of items) {
if (!targetArray.includes(i)) {
targetArray.push(i);
}
}
return targetArray;
}
export function modelContainsDerivedModel(model) {
return (!isTemplateDeclaration(model) &&
!(isTemplateInstance(model) && model.derivedModels.length === 0));
}
export function isModelReferredInTemplate(template, target) {
return (template === target ||
(template?.templateMapper?.args?.some((it) => "kind" in it && (it.kind === "Model" || it.kind === "Union")
? isModelReferredInTemplate(it, target)
: false) ??
false));
}
export function isNullableType(type) {
if (type.kind === "Union") {
const nullVariants = Array.from(type.variants.values()).filter((it) => isNullType(it.type));
return nullVariants.length >= 1;
}
else {
return false;
}
}
export function getNonNullSdkType(type) {
return type.kind === "nullable" ? type.type : type;
}
export function getDefaultValue(value) {
if (value) {
switch (value.valueKind) {
case "StringValue":
return value.value;
case "NumericValue":
return value.value;
case "BooleanValue":
return value.value;
}
}
return undefined;
}
export function getDurationFormat(type) {
let format = "duration-rfc3339";
// duration encoded as seconds
if (type.encode === "seconds") {
if (isSdkIntKind(type.wireType.kind)) {
format = "seconds-integer";
}
else if (isSdkFloatKind(type.wireType.kind)) {
format = "seconds-number";
}
else {
throw new Error(`Unrecognized scalar type used by duration encoded as seconds: '${type.kind}'.`);
}
}
return format;
}
export function hasScalarAsBase(type, scalarName) {
let scalarType = type;
while (scalarType) {
if (scalarType.name === scalarName) {
return true;
}
scalarType = scalarType.baseScalar;
}
return false;
}
export function unionReferredByType(program, type, cache) {
if (cache.has(type)) {
const ret = cache.get(type);
if (ret) {
return ret;
}
else {
return null;
}
}
cache.set(type, undefined);
if (type.kind === "Union") {
// ref CodeModelBuilder.processUnionSchema
const nonNullVariants = Array.from(type.variants.values()).filter((it) => !isNullType(it.type));
if (nonNullVariants.length === 1) {
// Type | null, follow that Type
const ret = unionReferredByType(program, nonNullVariants[0], cache);
if (ret) {
cache.set(type, ret);
return ret;
}
}
else if (getUnionAsEnum(type)) {
// "literal1" | "literal2" -> Enum
cache.set(type, null);
return null;
}
else {
// found Union
cache.set(type, type);
return type;
}
}
else if (type.kind === "Model") {
if (type.indexer) {
// follow indexer (for Array/Record)
const ret = unionReferredByType(program, type.indexer.value, cache);
if (ret) {
cache.set(type, ret);
return ret;
}
}
// follow properties
for (const property of type.properties.values()) {
const ret = unionReferredByType(program, property.type, cache);
if (ret) {
cache.set(type, ret);
return ret;
}
}
cache.set(type, null);
return null;
}
cache.set(type, null);
return null;
}
export function getUnionDescription(union, typeNameOptions) {
let name = union.name;
if (!name) {
const names = [];
union.variants.forEach((it) => {
names.push(getTypeName(it.type, typeNameOptions));
});
name = names.join(" | ");
}
return name;
}
export function modelIs(model, name, namespace) {
// use raw model because SdkModelType does not have sourceModel information
let currentModel = model.__raw;
while (currentModel) {
if (currentModel.name === name && getNamespace(currentModel) === namespace) {
return true;
}
currentModel = currentModel.sourceModel;
}
return false;
}
export function getAccess(type, accessCache) {
if (type &&
(type.kind === "Model" ||
type.kind === "Operation" ||
type.kind === "Enum" ||
type.kind === "Union" ||
type.kind === "Namespace")) {
let access = getDecoratorScopedValue(type, "$access", (it) => {
const value = it.args[0].value;
if ("kind" in value && value.kind === "EnumMember") {
return value.name;
}
else {
return undefined;
}
});
if (!access && type.namespace) {
// check (parent) namespace
if (accessCache.has(type.namespace)) {
access = accessCache.get(type.namespace);
}
else {
access = getAccess(type.namespace, accessCache);
accessCache.set(type.namespace, access);
}
}
return access;
}
else {
return undefined;
}
}
export function isAllValueInteger(values) {
return values.every((it) => Number.isInteger(it));
}
export function getUsage(type, usageCache) {
if (type &&
(type.kind === "Model" ||
type.kind === "Operation" ||
type.kind === "Enum" ||
type.kind === "Union" ||
type.kind === "Namespace")) {
let usage = getDecoratorScopedValue(type, "$usage", (it) => {
const value = it.args[0].value;
const values = [];
const ret = [];
if ("kind" in value && value.kind === "EnumMember") {
values.push(value);
}
else if ("kind" in value && value.kind === "Union") {
for (const v of value.variants.values()) {
values.push(v.type);
}
}
else {
return undefined;
}
for (const v of values) {
switch (v.name) {
case "input":
ret.push(SchemaContext.Input);
break;
case "output":
ret.push(SchemaContext.Output);
break;
}
}
if (ret.length === 0) {
return undefined;
}
return ret;
});
if (!usage && type.namespace) {
// check (parent) namespace
if (usageCache.has(type.namespace)) {
usage = usageCache.get(type.namespace);
}
else {
usage = getUsage(type.namespace, usageCache);
usageCache.set(type.namespace, usage);
}
}
return usage;
}
else {
return undefined;
}
}
/**
* Check if a given model or model property is an ARM common type.
* This is copied from typespec-azure-resource-manager. We don't want to depend on this package since it now has weird dependency on typespec-autorest.
*
* @param {Type} entity - The entity to be checked.
* @return {boolean} - A boolean value indicating whether an entity is an ARM common type.
*/
export function isArmCommonType(entity) {
const commonDecorators = ["$armCommonDefinition", "$armCommonParameter"];
if (isTypeSpecValueTypeOf(entity, ["Model", "ModelProperty"])) {
return commonDecorators.some((commonDecorator) => entity.decorators.some((d) => d.decorator.name === commonDecorator));
}
return false;
}
/**
* Get the serialized name of a property, based on either JSON, or XML, or Multipart.
*
* @param property the model property.
* @returns the serialized name of the property.
*/
export function getPropertySerializedName(property) {
// still fallback to "property.name", as for orphan model, serializationOptions.json is undefined
return (property.serializationOptions.json?.name ??
property.serializationOptions.xml?.name ??
property.serializationOptions.multipart?.name ??
property.__raw?.name ??
property.name);
}
/**
* Get the XML serialization format for a type or property.
*
* @param type the type or model property.
* @returns the XML serialization format, or undefined if not applicable.
*/
export function getXmlSerializationFormat(type) {
if (!type.serializationOptions.xml) {
return undefined;
}
// "unwrapped" from xml lib can be applied to both array and string
let propertyTypeIsArray = false;
let propertyTypeIsText = false;
if (type.kind === "property") {
propertyTypeIsArray = type.type.kind === "array";
propertyTypeIsText =
type.type.kind !== "array" && type.type.kind !== "dict" && type.type.kind !== "model";
}
// name, namespace and prefix on type and property
// attribute, wrapped, text on property
return {
name: type.serializationOptions.xml.name ?? undefined,
namespace: type.serializationOptions.xml.ns?.namespace ?? undefined,
prefix: type.serializationOptions.xml.ns?.prefix ?? undefined,
attribute: type.serializationOptions.xml.attribute ?? false,
wrapped: propertyTypeIsArray ? !(type.serializationOptions.xml.unwrapped ?? true) : false,
text: propertyTypeIsText ? (type.serializationOptions.xml.unwrapped ?? false) : false,
};
}
function getDecoratorScopedValue(type, decorator, mapFunc) {
// check for decorator that contains "java" scope, e.g. "java" or "python,java"
let value = type.decorators
.filter((it) => it.decorator.name === decorator &&
it.args.length === 2 &&
scopeExplicitlyIncludeJava(it.args[1].value.value))
.map((it) => mapFunc(it))
.find(() => true);
if (value) {
return value;
}
// check for decorator that contains negative non-"java" scope, e.g. "!python"
value = type.decorators
.filter((it) => it.decorator.name === decorator &&
it.args.length === 2 &&
scopeImplicitlyIncludeJava(it.args[1].value.value))
.map((it) => mapFunc(it))
.find(() => true);
if (value) {
return value;
}
// check for decorator that does not have scope
value = type.decorators
.filter((it) => it.decorator.name === decorator && it.args.length === 1)
.map((it) => mapFunc(it))
.find(() => true);
if (value) {
return value;
}
return undefined;
}
/**
* Tests that the scope explicitly includes "java". This is of higher priority than scope with negation.
*
* @param scope the scope.
* @returns scope explicitly includes "java".
*/
export function scopeExplicitlyIncludeJava(scope) {
if (scopeIsNegationOfMultiple(scope)) {
return false;
}
return scope
.split(",")
.map((s) => s.trim())
.includes("java");
}
/**
* Tests that the scope implicitly includes "java" by having a negation of other languages.
* E.g. "!python" or "!(python,csharp)".
*
* @param scope the scope.
* @returns scope implicitly includes "java".
*/
export function scopeImplicitlyIncludeJava(scope) {
if (scopeIsNegationOfMultiple(scope)) {
const scopeInNegation = scope.trim().slice(2, -1).trim(); // remove "!(" and ")"
return !scopeInNegation
.split(",")
.map((s) => s.trim())
.includes("java");
}
else {
return scope
.split(",")
.map((s) => s.trim())
.some((s) => s.startsWith("!") && s !== "!java");
}
}
function scopeIsNegationOfMultiple(scope) {
const trimmedScope = scope.trim();
return trimmedScope.startsWith("!(") && trimmedScope.endsWith(")");
}
/**
* Gets the Java simple class name of the ExternalType type.
* @param type the type.
* @returns the Java simple class name.
*/
export function getExternalJavaClassName(type) {
if (type.external) {
const fullyQualifiedClassName = type.external.identity;
return fullyQualifiedClassName.substring(fullyQualifiedClassName.lastIndexOf(".") + 1);
}
else {
throw new Error(`Type ${type.name} is not an ExternalType.`);
}
}
//# sourceMappingURL=type-utils.js.map