nestjs-openapi 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +8 -96
- package/dist/index.d.ts +8 -96
- package/dist/index.mjs +2 -2
- package/dist/internal.d.mts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.mjs +3 -2
- package/dist/shared/{nestjs-openapi.CDtSgIYg.d.mts → nestjs-openapi.-SW6E7E-.d.mts} +363 -272
- package/dist/shared/{nestjs-openapi.CDtSgIYg.d.ts → nestjs-openapi.-SW6E7E-.d.ts} +363 -272
- package/dist/shared/{nestjs-openapi.CU_zMKfF.mjs → nestjs-openapi.zDlSJGHz.mjs} +511 -374
- package/package.json +1 -1
|
@@ -850,88 +850,89 @@ const resolveEnumMemberValue = (initializerText, nextValue) => {
|
|
|
850
850
|
const numericValue = parseNumber(initializerText);
|
|
851
851
|
return numericValue === void 0 ? { value: initializerText, nextValue } : { value: numericValue, nextValue: numericValue + 1 };
|
|
852
852
|
};
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
862
|
-
const stringLiteral = initializer.asKind?.(ts.SyntaxKind.StringLiteral);
|
|
863
|
-
if (stringLiteral) {
|
|
864
|
-
return {
|
|
865
|
-
values: [...state.values, stringLiteral.getLiteralValue()],
|
|
866
|
-
nextValue: state.nextValue
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
const numericLiteral = initializer.asKind?.(ts.SyntaxKind.NumericLiteral);
|
|
870
|
-
if (numericLiteral) {
|
|
871
|
-
const numericValue = numericLiteral.getLiteralValue();
|
|
872
|
-
return {
|
|
873
|
-
values: [...state.values, numericValue],
|
|
874
|
-
nextValue: numericValue + 1
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
const resolved = resolveEnumMemberValue(
|
|
878
|
-
initializer.getText(),
|
|
879
|
-
state.nextValue
|
|
880
|
-
);
|
|
881
|
-
return {
|
|
882
|
-
values: [...state.values, resolved.value],
|
|
883
|
-
nextValue: resolved.nextValue
|
|
884
|
-
};
|
|
885
|
-
},
|
|
886
|
-
{
|
|
887
|
-
values: [],
|
|
888
|
-
nextValue: 0
|
|
889
|
-
}
|
|
890
|
-
).values;
|
|
891
|
-
const resolveEnumFromIdentifier = (identifier) => {
|
|
892
|
-
const symbol = identifier.getSymbol();
|
|
893
|
-
if (!symbol) return void 0;
|
|
894
|
-
const declarations = symbol.getDeclarations();
|
|
895
|
-
if (!declarations || declarations.length === 0) return void 0;
|
|
896
|
-
const directEnumDecl = declarations.find(
|
|
897
|
-
(decl) => Node.isEnumDeclaration(decl)
|
|
898
|
-
);
|
|
899
|
-
if (directEnumDecl) {
|
|
900
|
-
return extractEnumValues(directEnumDecl);
|
|
853
|
+
const appendEnumValue = (state, value, nextValue) => ({
|
|
854
|
+
values: [...state.values, value],
|
|
855
|
+
nextValue
|
|
856
|
+
});
|
|
857
|
+
const resolveEnumInitializerValue = (initializer, nextValue) => {
|
|
858
|
+
const stringLiteral = initializer.asKind?.(ts.SyntaxKind.StringLiteral);
|
|
859
|
+
if (stringLiteral) {
|
|
860
|
+
return { value: stringLiteral.getLiteralValue(), nextValue };
|
|
901
861
|
}
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
862
|
+
const numericLiteral = initializer.asKind?.(ts.SyntaxKind.NumericLiteral);
|
|
863
|
+
if (numericLiteral) {
|
|
864
|
+
const value = numericLiteral.getLiteralValue();
|
|
865
|
+
return { value, nextValue: value + 1 };
|
|
866
|
+
}
|
|
867
|
+
return resolveEnumMemberValue(initializer.getText(), nextValue);
|
|
906
868
|
};
|
|
869
|
+
const resolveEnumMember = (state, member) => Option.fromNullable(member.getInitializer()).pipe(
|
|
870
|
+
Option.match({
|
|
871
|
+
onNone: () => appendEnumValue(state, state.nextValue, state.nextValue + 1),
|
|
872
|
+
onSome: (initializer) => {
|
|
873
|
+
const resolved = resolveEnumInitializerValue(
|
|
874
|
+
initializer,
|
|
875
|
+
state.nextValue
|
|
876
|
+
);
|
|
877
|
+
return appendEnumValue(state, resolved.value, resolved.nextValue);
|
|
878
|
+
}
|
|
879
|
+
})
|
|
880
|
+
);
|
|
881
|
+
const extractEnumValues = (enumDecl) => enumDecl.getMembers().reduce(resolveEnumMember, {
|
|
882
|
+
values: [],
|
|
883
|
+
nextValue: 0
|
|
884
|
+
}).values;
|
|
885
|
+
const findDirectEnumDeclaration = (declarations) => declarations.find(
|
|
886
|
+
(decl) => Node.isEnumDeclaration(decl)
|
|
887
|
+
);
|
|
888
|
+
const findImportedEnumSpecifierDeclaration = (declarations) => declarations.filter(Node.isImportSpecifier).flatMap(
|
|
889
|
+
(decl) => decl.getSymbol()?.getAliasedSymbol()?.getDeclarations() ?? []
|
|
890
|
+
).find((decl) => Node.isEnumDeclaration(decl));
|
|
891
|
+
const findEnumDeclaration = (declarations) => findDirectEnumDeclaration(declarations) ?? findImportedEnumSpecifierDeclaration(declarations);
|
|
892
|
+
const resolveEnumFromIdentifier = (identifier) => Option.fromNullable(identifier.getSymbol()).pipe(
|
|
893
|
+
Option.map((symbol) => symbol.getDeclarations()),
|
|
894
|
+
Option.filter((declarations) => declarations.length > 0),
|
|
895
|
+
Option.flatMap(
|
|
896
|
+
(declarations) => Option.fromNullable(findEnumDeclaration(declarations))
|
|
897
|
+
),
|
|
898
|
+
Option.map(extractEnumValues),
|
|
899
|
+
Option.getOrUndefined
|
|
900
|
+
);
|
|
907
901
|
const resolveEnumFromDecorator = (decorator) => {
|
|
908
902
|
const firstArg = decorator.getCallExpression()?.getArguments()?.[0];
|
|
909
903
|
return firstArg && Node.isIdentifier(firstArg) ? resolveEnumFromIdentifier(firstArg) : void 0;
|
|
910
904
|
};
|
|
911
905
|
const getPropertyInitializer = (objLit, name) => objLit.getProperty(name)?.asKind?.(ts.SyntaxKind.PropertyAssignment)?.getInitializer();
|
|
912
906
|
const getStringProp = (objLit, name) => getPropertyInitializer(objLit, name)?.asKind?.(ts.SyntaxKind.StringLiteral)?.getLiteralValue();
|
|
907
|
+
const readNumericInitializer = (initializer) => {
|
|
908
|
+
const numericLiteral = initializer.asKind?.(ts.SyntaxKind.NumericLiteral);
|
|
909
|
+
if (numericLiteral) {
|
|
910
|
+
return numericLiteral.getLiteralValue();
|
|
911
|
+
}
|
|
912
|
+
return Node.isPrefixUnaryExpression(initializer) ? parseNumber(initializer.getText()) : void 0;
|
|
913
|
+
};
|
|
913
914
|
const getNumericProp = (objLit, name) => {
|
|
914
915
|
const initializer = getPropertyInitializer(objLit, name);
|
|
915
|
-
|
|
916
|
-
if (numericLiteral) return numericLiteral.getLiteralValue();
|
|
917
|
-
return initializer && Node.isPrefixUnaryExpression(initializer) ? parseNumber(initializer.getText()) : void 0;
|
|
916
|
+
return initializer ? readNumericInitializer(initializer) : void 0;
|
|
918
917
|
};
|
|
919
918
|
const getBooleanProp = (objLit, name) => {
|
|
920
919
|
const text = getPropertyInitializer(objLit, name)?.getText();
|
|
921
920
|
return text === "true" ? true : text === "false" ? false : void 0;
|
|
922
921
|
};
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
922
|
+
const PRIMITIVE_LITERAL_VALUES = {
|
|
923
|
+
true: true,
|
|
924
|
+
false: false,
|
|
925
|
+
null: null
|
|
926
|
+
};
|
|
927
|
+
const readPrimitiveInitializer = (initializer) => {
|
|
926
928
|
if (Node.isStringLiteral(initializer) || Node.isNumericLiteral(initializer)) {
|
|
927
929
|
return initializer.getLiteralValue();
|
|
928
930
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
return primitiveLiterals[initializer.getText()];
|
|
931
|
+
return PRIMITIVE_LITERAL_VALUES[initializer.getText()];
|
|
932
|
+
};
|
|
933
|
+
const getPrimitiveValue = (objLit, name) => {
|
|
934
|
+
const initializer = getPropertyInitializer(objLit, name);
|
|
935
|
+
return initializer ? readPrimitiveInitializer(initializer) : void 0;
|
|
935
936
|
};
|
|
936
937
|
const API_STRING_KEYS = ["description", "title", "format", "pattern"];
|
|
937
938
|
const API_NUMBER_KEYS = [
|
|
@@ -954,65 +955,94 @@ const API_BOOLEAN_KEYS = [
|
|
|
954
955
|
"isArray"
|
|
955
956
|
];
|
|
956
957
|
const API_PRIMITIVE_KEYS = ["example", "default"];
|
|
958
|
+
const API_TYPE_IDENTIFIERS = {
|
|
959
|
+
String: { type: "string" },
|
|
960
|
+
Number: { type: "number" },
|
|
961
|
+
Boolean: { type: "boolean" },
|
|
962
|
+
Object: { type: "object" },
|
|
963
|
+
Array: { type: "array" },
|
|
964
|
+
Date: { type: "string", format: "date-time" }
|
|
965
|
+
};
|
|
957
966
|
const buildConstraintsFromKeys = (objectLiteral, keys, read) => Object.fromEntries(
|
|
958
967
|
keys.flatMap((key) => {
|
|
959
968
|
const value = read(objectLiteral, key);
|
|
960
969
|
return value === void 0 ? [] : [[key, value]];
|
|
961
970
|
})
|
|
962
971
|
);
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
|
|
972
|
+
const readEnumArrayElement = (element) => {
|
|
973
|
+
if (Node.isStringLiteral(element) || Node.isNumericLiteral(element)) {
|
|
974
|
+
return element.getLiteralValue();
|
|
975
|
+
}
|
|
976
|
+
return Node.isPrefixUnaryExpression(element) ? parseNumber(element.getText()) : void 0;
|
|
977
|
+
};
|
|
978
|
+
const readEnumArrayLiteral = (initializer) => {
|
|
966
979
|
const arrayLiteral = initializer.asKind?.(
|
|
967
980
|
ts.SyntaxKind.ArrayLiteralExpression
|
|
968
981
|
);
|
|
969
|
-
if (arrayLiteral) {
|
|
970
|
-
|
|
971
|
-
if (Node.isStringLiteral(element)) {
|
|
972
|
-
return [...acc, element.getLiteralValue()];
|
|
973
|
-
}
|
|
974
|
-
if (Node.isNumericLiteral(element)) {
|
|
975
|
-
return [...acc, element.getLiteralValue()];
|
|
976
|
-
}
|
|
977
|
-
if (Node.isPrefixUnaryExpression(element)) {
|
|
978
|
-
const numericValue = parseNumber(element.getText());
|
|
979
|
-
return numericValue === void 0 ? acc : [...acc, numericValue];
|
|
980
|
-
}
|
|
981
|
-
return acc;
|
|
982
|
-
}, []);
|
|
983
|
-
return values.length > 0 ? values : void 0;
|
|
982
|
+
if (!arrayLiteral) {
|
|
983
|
+
return void 0;
|
|
984
984
|
}
|
|
985
|
-
|
|
985
|
+
const values = arrayLiteral.getElements().map(readEnumArrayElement).filter((value) => value !== void 0);
|
|
986
|
+
return values.length > 0 ? values : void 0;
|
|
986
987
|
};
|
|
987
|
-
const
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
objectLiteral,
|
|
995
|
-
API_STRING_KEYS,
|
|
996
|
-
(obj, key) => getStringProp(obj, key)
|
|
997
|
-
),
|
|
998
|
-
...buildConstraintsFromKeys(
|
|
999
|
-
objectLiteral,
|
|
1000
|
-
API_NUMBER_KEYS,
|
|
1001
|
-
(obj, key) => getNumericProp(obj, key)
|
|
1002
|
-
),
|
|
1003
|
-
...buildConstraintsFromKeys(
|
|
1004
|
-
objectLiteral,
|
|
1005
|
-
API_BOOLEAN_KEYS,
|
|
1006
|
-
(obj, key) => getBooleanProp(obj, key)
|
|
1007
|
-
),
|
|
1008
|
-
...buildConstraintsFromKeys(
|
|
1009
|
-
objectLiteral,
|
|
1010
|
-
API_PRIMITIVE_KEYS,
|
|
1011
|
-
(obj, key) => getPrimitiveValue(obj, key)
|
|
1012
|
-
)
|
|
1013
|
-
};
|
|
1014
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
988
|
+
const readEnumIdentifier = (initializer) => Node.isIdentifier(initializer) ? resolveEnumFromIdentifier(initializer) : void 0;
|
|
989
|
+
const extractApiPropertyEnum = (objLit) => {
|
|
990
|
+
const initializer = getPropertyInitializer(objLit, "enum");
|
|
991
|
+
if (!initializer) {
|
|
992
|
+
return void 0;
|
|
993
|
+
}
|
|
994
|
+
return readEnumArrayLiteral(initializer) ?? readEnumIdentifier(initializer);
|
|
1015
995
|
};
|
|
996
|
+
const readApiTypeIdentifier = (initializer) => Node.isIdentifier(initializer) ? API_TYPE_IDENTIFIERS[initializer.getText()] : void 0;
|
|
997
|
+
const readApiTypeArrayElement = (initializer) => {
|
|
998
|
+
const firstArrayElement = initializer.asKind?.(ts.SyntaxKind.ArrayLiteralExpression)?.getElements()[0];
|
|
999
|
+
if (!firstArrayElement || !Node.isIdentifier(firstArrayElement)) {
|
|
1000
|
+
return void 0;
|
|
1001
|
+
}
|
|
1002
|
+
const itemType = API_TYPE_IDENTIFIERS[firstArrayElement.getText()];
|
|
1003
|
+
return itemType ? { ...itemType, isArray: true } : void 0;
|
|
1004
|
+
};
|
|
1005
|
+
const extractApiPropertyType = (objLit) => {
|
|
1006
|
+
const initializer = getPropertyInitializer(objLit, "type");
|
|
1007
|
+
if (!initializer) {
|
|
1008
|
+
return void 0;
|
|
1009
|
+
}
|
|
1010
|
+
return readApiTypeIdentifier(initializer) ?? readApiTypeArrayElement(initializer);
|
|
1011
|
+
};
|
|
1012
|
+
const extractApiPropertyConstraints = (decorator) => Option.fromNullable(
|
|
1013
|
+
decorator.getCallExpression()?.getArguments()[0]?.asKind?.(ts.SyntaxKind.ObjectLiteralExpression)
|
|
1014
|
+
).pipe(
|
|
1015
|
+
Option.map((objectLiteral) => {
|
|
1016
|
+
const enumValues = extractApiPropertyEnum(objectLiteral);
|
|
1017
|
+
const typeConstraints = extractApiPropertyType(objectLiteral);
|
|
1018
|
+
return {
|
|
1019
|
+
...enumValues ? { enum: enumValues } : {},
|
|
1020
|
+
...typeConstraints ?? {},
|
|
1021
|
+
...buildConstraintsFromKeys(
|
|
1022
|
+
objectLiteral,
|
|
1023
|
+
API_STRING_KEYS,
|
|
1024
|
+
(obj, key) => getStringProp(obj, key)
|
|
1025
|
+
),
|
|
1026
|
+
...buildConstraintsFromKeys(
|
|
1027
|
+
objectLiteral,
|
|
1028
|
+
API_NUMBER_KEYS,
|
|
1029
|
+
(obj, key) => getNumericProp(obj, key)
|
|
1030
|
+
),
|
|
1031
|
+
...buildConstraintsFromKeys(
|
|
1032
|
+
objectLiteral,
|
|
1033
|
+
API_BOOLEAN_KEYS,
|
|
1034
|
+
(obj, key) => getBooleanProp(obj, key)
|
|
1035
|
+
),
|
|
1036
|
+
...buildConstraintsFromKeys(
|
|
1037
|
+
objectLiteral,
|
|
1038
|
+
API_PRIMITIVE_KEYS,
|
|
1039
|
+
(obj, key) => getPrimitiveValue(obj, key)
|
|
1040
|
+
)
|
|
1041
|
+
};
|
|
1042
|
+
}),
|
|
1043
|
+
Option.filter((result) => Object.keys(result).length > 0),
|
|
1044
|
+
Option.getOrUndefined
|
|
1045
|
+
);
|
|
1016
1046
|
const compactConstraints = (constraints) => Object.fromEntries(
|
|
1017
1047
|
Object.entries(constraints).filter(([, value]) => value !== void 0)
|
|
1018
1048
|
);
|
|
@@ -1099,21 +1129,93 @@ const collectHiddenProperties = (propertyConstraints) => new Set(
|
|
|
1099
1129
|
([propertyName, constraints]) => constraints.hidden ? [propertyName] : []
|
|
1100
1130
|
)
|
|
1101
1131
|
);
|
|
1132
|
+
const cleanPropertyConstraints = (constraints) => {
|
|
1133
|
+
const {
|
|
1134
|
+
hidden: _hidden,
|
|
1135
|
+
isArray: _isArray,
|
|
1136
|
+
...cleanConstraints
|
|
1137
|
+
} = constraints;
|
|
1138
|
+
return cleanConstraints;
|
|
1139
|
+
};
|
|
1140
|
+
const hasNullableArraySchema = (schema) => schema.anyOf?.some((member) => member.type === "array") === true;
|
|
1141
|
+
const isItemTypeOverride = (typeOverride) => typeof typeOverride === "string" && typeOverride !== "array";
|
|
1142
|
+
const shouldApplyNullableArrayItemConstraints = (propertySchema, isArray, typeOverride, enumValues) => hasNullableArraySchema(propertySchema) && (isArray === true && isItemTypeOverride(typeOverride) || enumValues !== void 0);
|
|
1143
|
+
const applyNullableArrayItemConstraints = (propertySchema, isArray, typeOverride, enumValues, restConstraints) => {
|
|
1144
|
+
const hasItemTypeOverride = isArray === true && isItemTypeOverride(typeOverride);
|
|
1145
|
+
const itemConstraints = {
|
|
1146
|
+
...hasItemTypeOverride ? { type: typeOverride } : {},
|
|
1147
|
+
...enumValues ? { enum: enumValues } : {}
|
|
1148
|
+
};
|
|
1149
|
+
return {
|
|
1150
|
+
...propertySchema,
|
|
1151
|
+
...restConstraints,
|
|
1152
|
+
anyOf: propertySchema.anyOf?.map(
|
|
1153
|
+
(member) => member.type === "array" ? {
|
|
1154
|
+
...member,
|
|
1155
|
+
items: {
|
|
1156
|
+
...hasItemTypeOverride ? {} : member.items,
|
|
1157
|
+
...itemConstraints
|
|
1158
|
+
}
|
|
1159
|
+
} : member
|
|
1160
|
+
)
|
|
1161
|
+
};
|
|
1162
|
+
};
|
|
1163
|
+
const shouldApplyArrayItemConstraints = (propertySchema, typeOverride, enumValues) => propertySchema.type === "array" && (isItemTypeOverride(typeOverride) || enumValues !== void 0);
|
|
1164
|
+
const applyArrayItemConstraints = (propertySchema, typeOverride, enumValues, restConstraints) => {
|
|
1165
|
+
const hasItemTypeOverride = isItemTypeOverride(typeOverride);
|
|
1166
|
+
return {
|
|
1167
|
+
...propertySchema,
|
|
1168
|
+
...restConstraints,
|
|
1169
|
+
items: {
|
|
1170
|
+
...hasItemTypeOverride ? {} : propertySchema.items,
|
|
1171
|
+
...hasItemTypeOverride ? { type: typeOverride } : {},
|
|
1172
|
+
...enumValues ? { enum: enumValues } : {}
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
};
|
|
1176
|
+
const applyDirectPropertyConstraints = (propertySchema, typeOverride, enumValues, restConstraints) => ({
|
|
1177
|
+
...propertySchema,
|
|
1178
|
+
...typeOverride === void 0 ? {} : { type: typeOverride },
|
|
1179
|
+
...restConstraints,
|
|
1180
|
+
...enumValues === void 0 ? {} : { enum: enumValues }
|
|
1181
|
+
});
|
|
1102
1182
|
const applyPropertyConstraintsToSchema = (propertySchema, constraints) => Option.fromNullable(constraints).pipe(
|
|
1103
1183
|
Option.map((propertyConstraints) => {
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1184
|
+
const isArray = propertyConstraints.isArray;
|
|
1185
|
+
const {
|
|
1186
|
+
type: typeOverride,
|
|
1187
|
+
enum: enumValues,
|
|
1188
|
+
...restConstraints
|
|
1189
|
+
} = cleanPropertyConstraints(propertyConstraints);
|
|
1190
|
+
if (shouldApplyNullableArrayItemConstraints(
|
|
1191
|
+
propertySchema,
|
|
1192
|
+
isArray,
|
|
1193
|
+
typeOverride,
|
|
1194
|
+
enumValues
|
|
1195
|
+
)) {
|
|
1196
|
+
return applyNullableArrayItemConstraints(
|
|
1197
|
+
propertySchema,
|
|
1198
|
+
isArray,
|
|
1199
|
+
typeOverride,
|
|
1200
|
+
enumValues,
|
|
1201
|
+
restConstraints
|
|
1202
|
+
);
|
|
1112
1203
|
}
|
|
1113
|
-
return
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1204
|
+
return shouldApplyArrayItemConstraints(
|
|
1205
|
+
propertySchema,
|
|
1206
|
+
typeOverride,
|
|
1207
|
+
enumValues
|
|
1208
|
+
) ? applyArrayItemConstraints(
|
|
1209
|
+
propertySchema,
|
|
1210
|
+
typeOverride,
|
|
1211
|
+
enumValues,
|
|
1212
|
+
restConstraints
|
|
1213
|
+
) : applyDirectPropertyConstraints(
|
|
1214
|
+
propertySchema,
|
|
1215
|
+
typeOverride,
|
|
1216
|
+
enumValues,
|
|
1217
|
+
restConstraints
|
|
1218
|
+
);
|
|
1117
1219
|
}),
|
|
1118
1220
|
Option.getOrElse(() => propertySchema)
|
|
1119
1221
|
);
|
|
@@ -1179,8 +1281,8 @@ const mergeValidationConstraints = (schemas, classConstraints, classRequired) =>
|
|
|
1179
1281
|
);
|
|
1180
1282
|
return { definitions };
|
|
1181
1283
|
};
|
|
1182
|
-
const
|
|
1183
|
-
"
|
|
1284
|
+
const serviceExtractClassValidationInfo = Effect.fn(
|
|
1285
|
+
"ValidationMapperService.extractClassValidationInfo"
|
|
1184
1286
|
)(function* (classDecl) {
|
|
1185
1287
|
const className = classDecl.getName() ?? "<anonymous>";
|
|
1186
1288
|
const filePath = classDecl.getSourceFile().getFilePath();
|
|
@@ -1200,8 +1302,8 @@ const extractClassValidationInfoEffect = Effect.fn(
|
|
|
1200
1302
|
);
|
|
1201
1303
|
return info;
|
|
1202
1304
|
});
|
|
1203
|
-
const
|
|
1204
|
-
"
|
|
1305
|
+
const serviceMergeValidationConstraints = Effect.fn(
|
|
1306
|
+
"ValidationMapperService.mergeValidationConstraints"
|
|
1205
1307
|
)(function* (schemas, classConstraints, classRequired) {
|
|
1206
1308
|
const merged = mergeValidationConstraints(
|
|
1207
1309
|
schemas,
|
|
@@ -1223,6 +1325,18 @@ const mergeValidationConstraintsEffect = Effect.fn(
|
|
|
1223
1325
|
yield* Effect.annotateCurrentSpan("classRequiredCount", classRequired.size);
|
|
1224
1326
|
return merged;
|
|
1225
1327
|
});
|
|
1328
|
+
class ValidationMapperService extends Effect.Service()(
|
|
1329
|
+
"ValidationMapperService",
|
|
1330
|
+
{
|
|
1331
|
+
effect: Effect.succeed({
|
|
1332
|
+
extractClassValidationInfo: serviceExtractClassValidationInfo,
|
|
1333
|
+
mergeValidationConstraints: serviceMergeValidationConstraints
|
|
1334
|
+
})
|
|
1335
|
+
}
|
|
1336
|
+
) {
|
|
1337
|
+
}
|
|
1338
|
+
const extractClassValidationInfoEffect = serviceExtractClassValidationInfo;
|
|
1339
|
+
const mergeValidationConstraintsEffect = serviceMergeValidationConstraints;
|
|
1226
1340
|
|
|
1227
1341
|
const methodInfoCache = /* @__PURE__ */ new WeakMap();
|
|
1228
1342
|
const returnTypeInfoCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -2021,152 +2135,200 @@ class MethodExtractionService extends Effect.Service()(
|
|
|
2021
2135
|
) {
|
|
2022
2136
|
}
|
|
2023
2137
|
|
|
2024
|
-
const buildSecurityRequirements = (requirements) =>
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
}
|
|
2030
|
-
return [combined];
|
|
2031
|
-
};
|
|
2138
|
+
const buildSecurityRequirements = (requirements) => requirements.length === 0 ? void 0 : [
|
|
2139
|
+
Object.fromEntries(
|
|
2140
|
+
requirements.map((req) => [req.schemeName, [...req.scopes]])
|
|
2141
|
+
)
|
|
2142
|
+
];
|
|
2032
2143
|
const getRequestContentTypes = (methodInfo) => methodInfo.consumes.length > 0 ? methodInfo.consumes : ["application/json"];
|
|
2033
2144
|
const getResponseContentTypes = (methodInfo) => methodInfo.produces.length > 0 ? methodInfo.produces : ["application/json"];
|
|
2034
2145
|
const buildContentObject = (contentTypes, schema) => Object.fromEntries(contentTypes.map((type) => [type, { schema }]));
|
|
2035
|
-
const
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2146
|
+
const INLINE_OBJECT_BOUNDARY_PATTERN = /^\s*\{[\s\S]*\}\s*$/;
|
|
2147
|
+
const INLINE_OBJECT_SEPARATORS = /* @__PURE__ */ new Set([";", ","]);
|
|
2148
|
+
const BRACE_DEPTH_CHANGE = {
|
|
2149
|
+
"{": 1,
|
|
2150
|
+
"}": -1
|
|
2151
|
+
};
|
|
2152
|
+
const pushInlinePart = (parts, part) => {
|
|
2153
|
+
const trimmed = part.trim();
|
|
2154
|
+
if (trimmed) {
|
|
2155
|
+
parts.push(trimmed);
|
|
2043
2156
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2157
|
+
};
|
|
2158
|
+
const splitInlineObjectParts = (content) => {
|
|
2046
2159
|
const parts = [];
|
|
2047
2160
|
let current = "";
|
|
2048
2161
|
let braceDepth = 0;
|
|
2049
2162
|
for (const char of content) {
|
|
2050
|
-
if (char ===
|
|
2051
|
-
|
|
2052
|
-
current += char;
|
|
2053
|
-
} else if (char === "}") {
|
|
2054
|
-
braceDepth--;
|
|
2055
|
-
current += char;
|
|
2056
|
-
} else if ((char === ";" || char === ",") && braceDepth === 0) {
|
|
2057
|
-
if (current.trim()) {
|
|
2058
|
-
parts.push(current.trim());
|
|
2059
|
-
}
|
|
2163
|
+
if (INLINE_OBJECT_SEPARATORS.has(char) && braceDepth === 0) {
|
|
2164
|
+
pushInlinePart(parts, current);
|
|
2060
2165
|
current = "";
|
|
2061
|
-
|
|
2062
|
-
current += char;
|
|
2166
|
+
continue;
|
|
2063
2167
|
}
|
|
2168
|
+
current += char;
|
|
2169
|
+
braceDepth += BRACE_DEPTH_CHANGE[char] ?? 0;
|
|
2064
2170
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2171
|
+
pushInlinePart(parts, current);
|
|
2172
|
+
return parts;
|
|
2173
|
+
};
|
|
2174
|
+
const parseInlineProperty = (part) => {
|
|
2175
|
+
const colonIndex = part.indexOf(":");
|
|
2176
|
+
const rawName = colonIndex === -1 ? "" : part.slice(0, colonIndex).trim();
|
|
2177
|
+
const type = colonIndex === -1 ? "" : part.slice(colonIndex + 1).trim();
|
|
2178
|
+
const isOptional = rawName.endsWith("?");
|
|
2179
|
+
const name = (isOptional ? rawName.slice(0, -1) : rawName).trim();
|
|
2180
|
+
return name && type ? { name, type, isOptional } : void 0;
|
|
2181
|
+
};
|
|
2182
|
+
const parseInlineObjectType = (typeStr) => {
|
|
2183
|
+
const trimmed = typeStr.trim();
|
|
2184
|
+
if (!INLINE_OBJECT_BOUNDARY_PATTERN.test(trimmed)) {
|
|
2185
|
+
return null;
|
|
2186
|
+
}
|
|
2187
|
+
const content = trimmed.slice(1, -1).trim();
|
|
2188
|
+
const properties = splitInlineObjectParts(content).map(parseInlineProperty).filter((property) => property !== void 0);
|
|
2189
|
+
return {
|
|
2190
|
+
properties: Object.fromEntries(
|
|
2191
|
+
properties.map((property) => [
|
|
2192
|
+
property.name,
|
|
2193
|
+
tsTypeToOpenApiSchema(property.type)
|
|
2194
|
+
])
|
|
2195
|
+
),
|
|
2196
|
+
required: properties.flatMap(
|
|
2197
|
+
(property) => property.isOptional ? [] : [property.name]
|
|
2198
|
+
)
|
|
2199
|
+
};
|
|
2200
|
+
};
|
|
2201
|
+
const OBJECT_SCHEMA = { type: "object" };
|
|
2202
|
+
const PRIMITIVE_TYPE_SCHEMAS = {
|
|
2203
|
+
string: { type: "string" },
|
|
2204
|
+
number: { type: "number" },
|
|
2205
|
+
boolean: { type: "boolean" },
|
|
2206
|
+
date: { type: "string", format: "date-time" },
|
|
2207
|
+
void: OBJECT_SCHEMA,
|
|
2208
|
+
undefined: OBJECT_SCHEMA,
|
|
2209
|
+
never: OBJECT_SCHEMA,
|
|
2210
|
+
null: OBJECT_SCHEMA,
|
|
2211
|
+
unknown: OBJECT_SCHEMA,
|
|
2212
|
+
any: OBJECT_SCHEMA,
|
|
2213
|
+
object: OBJECT_SCHEMA
|
|
2214
|
+
};
|
|
2215
|
+
const BINARY_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
2216
|
+
"StreamableFile",
|
|
2217
|
+
"Buffer",
|
|
2218
|
+
"Readable",
|
|
2219
|
+
"ReadableStream"
|
|
2220
|
+
]);
|
|
2221
|
+
const RECORD_TYPE_PATTERN = /^Record<string,\s*(.+)>$/;
|
|
2222
|
+
const TYPE_REFERENCE_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$]*(<[^>]+>)?$/;
|
|
2223
|
+
const buildInlineObjectSchema = (parsed) => ({
|
|
2224
|
+
type: "object",
|
|
2225
|
+
properties: parsed.properties,
|
|
2226
|
+
...parsed.required.length > 0 ? { required: parsed.required } : {}
|
|
2227
|
+
});
|
|
2228
|
+
const withNullable = (schema) => schema.$ref ? { allOf: [{ $ref: schema.$ref }], nullable: true } : { ...schema, nullable: true };
|
|
2229
|
+
const resolveInlineObjectSchema = (tsType) => Option.fromNullable(parseInlineObjectType(tsType)).pipe(
|
|
2230
|
+
Option.map(buildInlineObjectSchema),
|
|
2231
|
+
Option.getOrUndefined
|
|
2232
|
+
);
|
|
2233
|
+
const parseUnionType = (tsType) => {
|
|
2234
|
+
if (!tsType.includes(" | ")) {
|
|
2235
|
+
return void 0;
|
|
2236
|
+
}
|
|
2237
|
+
const members = tsType.split(" | ").map((member) => member.trim());
|
|
2238
|
+
return {
|
|
2239
|
+
hasNull: members.includes("null"),
|
|
2240
|
+
types: members.filter(
|
|
2241
|
+
(member) => member !== "undefined" && member !== "null"
|
|
2242
|
+
)
|
|
2243
|
+
};
|
|
2244
|
+
};
|
|
2245
|
+
const buildUnionSchema = (types) => {
|
|
2246
|
+
if (types.length === 0) {
|
|
2247
|
+
return { type: "object" };
|
|
2248
|
+
}
|
|
2249
|
+
if (types.length === 1) {
|
|
2250
|
+
return tsTypeToOpenApiSchema(types[0]);
|
|
2082
2251
|
}
|
|
2083
|
-
return {
|
|
2252
|
+
return { oneOf: types.map((type) => tsTypeToOpenApiSchema(type)) };
|
|
2084
2253
|
};
|
|
2254
|
+
const resolveUnionSchema = (tsType) => Option.fromNullable(parseUnionType(tsType)).pipe(
|
|
2255
|
+
Option.map(({ types, hasNull }) => {
|
|
2256
|
+
const schema = buildUnionSchema(types);
|
|
2257
|
+
return hasNull && types.length > 0 ? withNullable(schema) : schema;
|
|
2258
|
+
}),
|
|
2259
|
+
Option.getOrUndefined
|
|
2260
|
+
);
|
|
2261
|
+
const resolvePrimitiveSchema = (tsType) => Option.fromNullable(PRIMITIVE_TYPE_SCHEMAS[tsType.toLowerCase()]).pipe(
|
|
2262
|
+
Option.map((schema) => ({ ...schema })),
|
|
2263
|
+
Option.getOrUndefined
|
|
2264
|
+
);
|
|
2265
|
+
const resolveBinarySchema = (tsType) => BINARY_TYPE_NAMES.has(tsType) ? { type: "string", format: "binary" } : void 0;
|
|
2266
|
+
const resolveArraySchema = (tsType) => tsType.endsWith("[]") ? {
|
|
2267
|
+
type: "array",
|
|
2268
|
+
items: tsTypeToOpenApiSchema(tsType.slice(0, -2))
|
|
2269
|
+
} : void 0;
|
|
2270
|
+
const resolveRecordSchema = (tsType) => RECORD_TYPE_PATTERN.test(tsType) ? { type: "object" } : void 0;
|
|
2271
|
+
const resolveReferenceSchema = (tsType) => TYPE_REFERENCE_PATTERN.test(tsType) ? { $ref: `#/components/schemas/${tsType}` } : void 0;
|
|
2272
|
+
const TYPE_SCHEMA_RESOLVERS = [
|
|
2273
|
+
resolveInlineObjectSchema,
|
|
2274
|
+
resolveUnionSchema,
|
|
2275
|
+
resolvePrimitiveSchema,
|
|
2276
|
+
resolveBinarySchema,
|
|
2277
|
+
resolveArraySchema,
|
|
2278
|
+
resolveRecordSchema,
|
|
2279
|
+
resolveReferenceSchema
|
|
2280
|
+
];
|
|
2085
2281
|
const tsTypeToOpenApiSchema = (tsType) => {
|
|
2086
2282
|
const trimmed = tsType.trim();
|
|
2087
|
-
|
|
2088
|
-
const
|
|
2089
|
-
if (
|
|
2090
|
-
const schema = {
|
|
2091
|
-
type: "object",
|
|
2092
|
-
properties: parsed.properties
|
|
2093
|
-
};
|
|
2094
|
-
if (parsed.required.length > 0) {
|
|
2095
|
-
return { ...schema, required: parsed.required };
|
|
2096
|
-
}
|
|
2283
|
+
for (const resolver of TYPE_SCHEMA_RESOLVERS) {
|
|
2284
|
+
const schema = resolver(trimmed);
|
|
2285
|
+
if (schema !== void 0) {
|
|
2097
2286
|
return schema;
|
|
2098
2287
|
}
|
|
2099
2288
|
}
|
|
2100
|
-
if (trimmed.includes(" | ")) {
|
|
2101
|
-
const allMembers = trimmed.split(" | ").map((t) => t.trim());
|
|
2102
|
-
const hasNull = allMembers.includes("null");
|
|
2103
|
-
const types = allMembers.filter((t) => t !== "undefined" && t !== "null");
|
|
2104
|
-
if (types.length === 0) return { type: "object" };
|
|
2105
|
-
const schema = types.length === 1 ? tsTypeToOpenApiSchema(types[0]) : { oneOf: types.map((type) => tsTypeToOpenApiSchema(type)) };
|
|
2106
|
-
if (!hasNull) return schema;
|
|
2107
|
-
if (schema.$ref) {
|
|
2108
|
-
return { allOf: [{ $ref: schema.$ref }], nullable: true };
|
|
2109
|
-
}
|
|
2110
|
-
return { ...schema, nullable: true };
|
|
2111
|
-
}
|
|
2112
|
-
switch (trimmed.toLowerCase()) {
|
|
2113
|
-
case "string":
|
|
2114
|
-
return { type: "string" };
|
|
2115
|
-
case "number":
|
|
2116
|
-
return { type: "number" };
|
|
2117
|
-
case "boolean":
|
|
2118
|
-
return { type: "boolean" };
|
|
2119
|
-
case "date":
|
|
2120
|
-
return { type: "string", format: "date-time" };
|
|
2121
|
-
case "void":
|
|
2122
|
-
case "undefined":
|
|
2123
|
-
case "never":
|
|
2124
|
-
case "null":
|
|
2125
|
-
return { type: "object" };
|
|
2126
|
-
case "unknown":
|
|
2127
|
-
case "any":
|
|
2128
|
-
case "object":
|
|
2129
|
-
return { type: "object" };
|
|
2130
|
-
}
|
|
2131
|
-
if (trimmed === "StreamableFile" || trimmed === "Buffer" || trimmed === "Readable" || trimmed === "ReadableStream") {
|
|
2132
|
-
return { type: "string", format: "binary" };
|
|
2133
|
-
}
|
|
2134
|
-
if (trimmed.endsWith("[]")) {
|
|
2135
|
-
const itemType = trimmed.slice(0, -2);
|
|
2136
|
-
return {
|
|
2137
|
-
type: "array",
|
|
2138
|
-
items: tsTypeToOpenApiSchema(itemType)
|
|
2139
|
-
};
|
|
2140
|
-
}
|
|
2141
|
-
const recordMatch = trimmed.match(/^Record<string,\s*(.+)>$/);
|
|
2142
|
-
if (recordMatch) {
|
|
2143
|
-
return {
|
|
2144
|
-
type: "object"
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
|
-
if (trimmed.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*(<[^>]+>)?$/)) {
|
|
2148
|
-
return { $ref: `#/components/schemas/${trimmed}` };
|
|
2149
|
-
}
|
|
2150
2289
|
return { type: "object" };
|
|
2151
2290
|
};
|
|
2152
2291
|
const isNodeModulesPath = (filePath) => filePath.includes("/node_modules/") || filePath.includes("\\node_modules\\");
|
|
2153
2292
|
const shouldFallbackExternalReturnRef = (returnType) => Option.isSome(returnType.filePath) && isNodeModulesPath(returnType.filePath.value);
|
|
2154
2293
|
const responseSchemaOrObjectFallback = (schema) => schema.$ref ? { type: "object" } : schema;
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2294
|
+
const PARAMETER_LOCATION_MAP = {
|
|
2295
|
+
path: "path",
|
|
2296
|
+
query: "query",
|
|
2297
|
+
header: "header",
|
|
2298
|
+
cookie: "cookie",
|
|
2299
|
+
body: "query"
|
|
2300
|
+
};
|
|
2301
|
+
const getParameterLocation = (location) => PARAMETER_LOCATION_MAP[location];
|
|
2302
|
+
const applyParameterConstraints = (baseSchema, constraints) => {
|
|
2303
|
+
const {
|
|
2304
|
+
isArray,
|
|
2305
|
+
type: typeOverride,
|
|
2306
|
+
enum: enumValues,
|
|
2307
|
+
...restConstraints
|
|
2308
|
+
} = constraints;
|
|
2309
|
+
const shouldApplyItemConstraints = (isArray === true || baseSchema.type === "array") && typeof typeOverride === "string" && typeOverride !== "array";
|
|
2310
|
+
if (shouldApplyItemConstraints || baseSchema.type === "array" && enumValues) {
|
|
2311
|
+
return {
|
|
2312
|
+
...baseSchema,
|
|
2313
|
+
...restConstraints,
|
|
2314
|
+
type: "array",
|
|
2315
|
+
items: {
|
|
2316
|
+
...shouldApplyItemConstraints ? {} : baseSchema.items,
|
|
2317
|
+
...shouldApplyItemConstraints ? { type: typeOverride } : {},
|
|
2318
|
+
...enumValues ? { enum: enumValues } : {}
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2165
2321
|
}
|
|
2322
|
+
return {
|
|
2323
|
+
...baseSchema,
|
|
2324
|
+
...restConstraints,
|
|
2325
|
+
...typeOverride === void 0 ? {} : { type: typeOverride },
|
|
2326
|
+
...enumValues === void 0 ? {} : { enum: enumValues }
|
|
2327
|
+
};
|
|
2166
2328
|
};
|
|
2167
2329
|
const transformParameter = (param) => {
|
|
2168
2330
|
const baseSchema = tsTypeToOpenApiSchema(param.tsType);
|
|
2169
|
-
const schema = param.constraints ?
|
|
2331
|
+
const schema = param.constraints ? applyParameterConstraints(baseSchema, param.constraints) : baseSchema;
|
|
2170
2332
|
return {
|
|
2171
2333
|
name: param.name,
|
|
2172
2334
|
in: getParameterLocation(param.location),
|
|
@@ -2179,21 +2341,7 @@ const isInlineOptionalBodyType = (tsType) => {
|
|
|
2179
2341
|
const parsed = parseInlineObjectType(tsType);
|
|
2180
2342
|
return parsed !== null && parsed.required.length === 0;
|
|
2181
2343
|
};
|
|
2182
|
-
const
|
|
2183
|
-
const shouldFallback = Option.isNone(returnType.container) && shouldFallbackExternalReturnRef(returnType);
|
|
2184
|
-
if (Option.isSome(returnType.container) && returnType.container.value === "array") {
|
|
2185
|
-
if (Option.isSome(returnType.inline)) {
|
|
2186
|
-
return {
|
|
2187
|
-
type: "array",
|
|
2188
|
-
items: tsTypeToOpenApiSchema(returnType.inline.value)
|
|
2189
|
-
};
|
|
2190
|
-
}
|
|
2191
|
-
const itemSchema = Option.isSome(returnType.type) ? tsTypeToOpenApiSchema(returnType.type.value) : { type: "object" };
|
|
2192
|
-
return {
|
|
2193
|
-
type: "array",
|
|
2194
|
-
items: shouldFallback ? responseSchemaOrObjectFallback(itemSchema) : itemSchema
|
|
2195
|
-
};
|
|
2196
|
-
}
|
|
2344
|
+
const buildScalarReturnSchema = (returnType, shouldFallback) => {
|
|
2197
2345
|
if (Option.isSome(returnType.type)) {
|
|
2198
2346
|
const schema = tsTypeToOpenApiSchema(returnType.type.value);
|
|
2199
2347
|
return shouldFallback ? responseSchemaOrObjectFallback(schema) : schema;
|
|
@@ -2203,81 +2351,77 @@ const buildResponseSchema = (returnType) => {
|
|
|
2203
2351
|
}
|
|
2204
2352
|
return { type: "string" };
|
|
2205
2353
|
};
|
|
2206
|
-
const
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
return
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
return methodInfo.httpMethod === "POST" ? 201 : 200;
|
|
2222
|
-
};
|
|
2223
|
-
const hasMeaningfulReturnType = (returnType) => {
|
|
2224
|
-
if (Option.isSome(returnType.type)) {
|
|
2225
|
-
const typeName = returnType.type.value.toLowerCase();
|
|
2226
|
-
if (["void", "undefined", "never"].includes(typeName)) {
|
|
2227
|
-
return false;
|
|
2228
|
-
}
|
|
2229
|
-
return true;
|
|
2230
|
-
}
|
|
2231
|
-
return Option.isSome(returnType.inline);
|
|
2354
|
+
const buildArrayReturnSchema = (returnType) => ({
|
|
2355
|
+
type: "array",
|
|
2356
|
+
items: Option.isSome(returnType.inline) ? tsTypeToOpenApiSchema(returnType.inline.value) : Option.match(returnType.type, {
|
|
2357
|
+
onNone: () => ({ type: "object" }),
|
|
2358
|
+
onSome: (typeName) => tsTypeToOpenApiSchema(typeName)
|
|
2359
|
+
})
|
|
2360
|
+
});
|
|
2361
|
+
const buildResponseSchema = (returnType) => {
|
|
2362
|
+
const shouldFallback = Option.isNone(returnType.container) && shouldFallbackExternalReturnRef(returnType);
|
|
2363
|
+
return returnType.container.pipe(
|
|
2364
|
+
Option.match({
|
|
2365
|
+
onNone: () => buildScalarReturnSchema(returnType, shouldFallback),
|
|
2366
|
+
onSome: () => buildArrayReturnSchema(returnType)
|
|
2367
|
+
})
|
|
2368
|
+
);
|
|
2232
2369
|
};
|
|
2370
|
+
const buildResponseSchemaFromMetadata = (response) => response.type.pipe(
|
|
2371
|
+
Option.map((typeName) => {
|
|
2372
|
+
const schema = tsTypeToOpenApiSchema(typeName);
|
|
2373
|
+
return response.isArray ? { type: "array", items: schema } : schema;
|
|
2374
|
+
}),
|
|
2375
|
+
Option.getOrUndefined
|
|
2376
|
+
);
|
|
2377
|
+
const getDefaultSuccessCode = (methodInfo) => Option.getOrElse(
|
|
2378
|
+
methodInfo.httpCode,
|
|
2379
|
+
() => methodInfo.httpMethod === "POST" ? 201 : 200
|
|
2380
|
+
);
|
|
2381
|
+
const hasMeaningfulReturnType = (returnType) => returnType.type.pipe(
|
|
2382
|
+
Option.match({
|
|
2383
|
+
onNone: () => Option.isSome(returnType.inline),
|
|
2384
|
+
onSome: (typeName) => !["void", "undefined", "never"].includes(typeName.toLowerCase())
|
|
2385
|
+
})
|
|
2386
|
+
);
|
|
2233
2387
|
const isSuccessWithContent = (statusCode) => statusCode >= 200 && statusCode < 300 && statusCode !== 204;
|
|
2234
2388
|
const buildResponseEntry = (response, returnType, hasReturnType, contentTypes) => {
|
|
2235
2389
|
const schema = buildResponseSchemaFromMetadata(response) ?? (hasReturnType && isSuccessWithContent(response.statusCode) ? buildResponseSchema(returnType) : void 0);
|
|
2236
2390
|
const description = Option.getOrElse(response.description, () => "");
|
|
2237
|
-
|
|
2238
|
-
return { description, content: buildContentObject(contentTypes, schema) };
|
|
2391
|
+
return schema ? { description, content: buildContentObject(contentTypes, schema) } : { description };
|
|
2239
2392
|
};
|
|
2393
|
+
const buildDefaultResponseEntry = (returnType, hasReturnType, contentTypes) => ({
|
|
2394
|
+
description: "",
|
|
2395
|
+
...hasReturnType ? {
|
|
2396
|
+
content: buildContentObject(
|
|
2397
|
+
contentTypes,
|
|
2398
|
+
buildResponseSchema(returnType)
|
|
2399
|
+
)
|
|
2400
|
+
} : {}
|
|
2401
|
+
});
|
|
2402
|
+
const hasDeclaredSuccessResponse = (responses) => responses.some(
|
|
2403
|
+
(response) => response.statusCode >= 200 && response.statusCode < 300
|
|
2404
|
+
);
|
|
2240
2405
|
const buildResponses = (methodInfo) => {
|
|
2241
2406
|
const contentTypes = getResponseContentTypes(methodInfo);
|
|
2242
2407
|
const returnType = methodInfo.returnType;
|
|
2243
2408
|
const hasReturnType = hasMeaningfulReturnType(returnType);
|
|
2244
2409
|
const statusCode = getDefaultSuccessCode(methodInfo);
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
return {
|
|
2250
|
-
[statusCode.toString()]: {
|
|
2251
|
-
description: "",
|
|
2252
|
-
content: buildContentObject(
|
|
2253
|
-
contentTypes,
|
|
2254
|
-
buildResponseSchema(returnType)
|
|
2255
|
-
)
|
|
2256
|
-
}
|
|
2257
|
-
};
|
|
2258
|
-
}
|
|
2259
|
-
const result = {};
|
|
2260
|
-
const hasSuccessResponse = methodInfo.responses.some(
|
|
2261
|
-
(r) => r.statusCode >= 200 && r.statusCode < 300
|
|
2410
|
+
const defaultSuccessEntry = buildDefaultResponseEntry(
|
|
2411
|
+
returnType,
|
|
2412
|
+
hasReturnType,
|
|
2413
|
+
contentTypes
|
|
2262
2414
|
);
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
result[response.statusCode.toString()] = buildResponseEntry(
|
|
2274
|
-
response,
|
|
2275
|
-
returnType,
|
|
2276
|
-
hasReturnType,
|
|
2277
|
-
contentTypes
|
|
2278
|
-
);
|
|
2279
|
-
}
|
|
2280
|
-
return result;
|
|
2415
|
+
const declaredResponses = Object.fromEntries(
|
|
2416
|
+
methodInfo.responses.map((response) => [
|
|
2417
|
+
response.statusCode.toString(),
|
|
2418
|
+
buildResponseEntry(response, returnType, hasReturnType, contentTypes)
|
|
2419
|
+
])
|
|
2420
|
+
);
|
|
2421
|
+
return {
|
|
2422
|
+
...methodInfo.responses.length === 0 || !hasDeclaredSuccessResponse(methodInfo.responses) && hasReturnType ? { [statusCode.toString()]: defaultSuccessEntry } : {},
|
|
2423
|
+
...declaredResponses
|
|
2424
|
+
};
|
|
2281
2425
|
};
|
|
2282
2426
|
const buildOpenApiPath = (path) => path.replace(/:([^/]+)/g, "{$1}") || "/";
|
|
2283
2427
|
const transformMethodInternal = (methodInfo) => {
|
|
@@ -2323,33 +2467,49 @@ const transformMethodInternal = (methodInfo) => {
|
|
|
2323
2467
|
};
|
|
2324
2468
|
};
|
|
2325
2469
|
const transformMethod = (methodInfo) => transformMethodInternal(methodInfo);
|
|
2326
|
-
const
|
|
2327
|
-
function* (methodInfo) {
|
|
2328
|
-
return yield* Effect.succeed(transformMethodInternal(methodInfo));
|
|
2329
|
-
}
|
|
2330
|
-
);
|
|
2331
|
-
const transformMethods = (methodInfos) => methodInfos.reduce((acc, methodInfo) => {
|
|
2332
|
-
const endpoint = transformMethodInternal(methodInfo);
|
|
2470
|
+
const mergeOpenApiPaths = (endpoints) => endpoints.reduce((acc, endpoint) => {
|
|
2333
2471
|
for (const path in endpoint) {
|
|
2334
|
-
|
|
2335
|
-
Object.assign(acc[path], endpoint[path]);
|
|
2472
|
+
acc[path] = { ...acc[path] ?? {}, ...endpoint[path] };
|
|
2336
2473
|
}
|
|
2337
2474
|
return acc;
|
|
2338
2475
|
}, {});
|
|
2339
|
-
const
|
|
2340
|
-
|
|
2476
|
+
const transformMethods = (methodInfos) => mergeOpenApiPaths(methodInfos.map(transformMethodInternal));
|
|
2477
|
+
const serviceTransformMethod = Effect.fn("TransformerService.transformMethod")(
|
|
2478
|
+
function* (methodInfo) {
|
|
2479
|
+
const paths = yield* Effect.sync(() => transformMethodInternal(methodInfo));
|
|
2480
|
+
yield* Effect.annotateCurrentSpan(
|
|
2481
|
+
"controllerName",
|
|
2482
|
+
methodInfo.controllerName
|
|
2483
|
+
);
|
|
2484
|
+
yield* Effect.annotateCurrentSpan("methodName", methodInfo.methodName);
|
|
2485
|
+
yield* Effect.annotateCurrentSpan("httpMethod", methodInfo.httpMethod);
|
|
2486
|
+
yield* Effect.annotateCurrentSpan("path", methodInfo.path);
|
|
2487
|
+
return paths;
|
|
2488
|
+
}
|
|
2489
|
+
);
|
|
2490
|
+
const serviceTransformMethods = Effect.fn(
|
|
2491
|
+
"TransformerService.transformMethods"
|
|
2341
2492
|
)(function* (methodInfos) {
|
|
2342
|
-
const endpoints = yield* Effect.forEach(methodInfos,
|
|
2493
|
+
const endpoints = yield* Effect.forEach(methodInfos, serviceTransformMethod, {
|
|
2343
2494
|
concurrency: "unbounded"
|
|
2344
2495
|
});
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
}
|
|
2350
|
-
return acc;
|
|
2351
|
-
}, {});
|
|
2496
|
+
const paths = mergeOpenApiPaths(endpoints);
|
|
2497
|
+
yield* Effect.annotateCurrentSpan("methodCount", methodInfos.length);
|
|
2498
|
+
yield* Effect.annotateCurrentSpan("pathCount", Object.keys(paths).length);
|
|
2499
|
+
return paths;
|
|
2352
2500
|
});
|
|
2501
|
+
class TransformerService extends Effect.Service()(
|
|
2502
|
+
"TransformerService",
|
|
2503
|
+
{
|
|
2504
|
+
effect: Effect.succeed({
|
|
2505
|
+
transformMethod: serviceTransformMethod,
|
|
2506
|
+
transformMethods: serviceTransformMethods
|
|
2507
|
+
})
|
|
2508
|
+
}
|
|
2509
|
+
) {
|
|
2510
|
+
}
|
|
2511
|
+
const transformMethodEffect = serviceTransformMethod;
|
|
2512
|
+
const transformMethodsEffect = serviceTransformMethods;
|
|
2353
2513
|
|
|
2354
2514
|
const UGLY_NAME_PATTERN = /^(.+?)<.*>$/;
|
|
2355
2515
|
const UGLY_NAME_PATTERN_ENCODED = /^(.+?)%3C.*%3E$/i;
|
|
@@ -4682,39 +4842,14 @@ class SchemaService extends Effect.Service()(
|
|
|
4682
4842
|
) {
|
|
4683
4843
|
}
|
|
4684
4844
|
|
|
4685
|
-
const serviceExtractClassValidationInfo = Effect.fn(
|
|
4686
|
-
"ValidationService.extractClassValidationInfo"
|
|
4687
|
-
)(function* (classDecl) {
|
|
4688
|
-
return yield* extractClassValidationInfoEffect(classDecl);
|
|
4689
|
-
});
|
|
4690
|
-
const serviceMergeValidationConstraints = Effect.fn(
|
|
4691
|
-
"ValidationService.mergeValidationConstraints"
|
|
4692
|
-
)(function* (schemas, classConstraints, classRequired) {
|
|
4693
|
-
return yield* mergeValidationConstraintsEffect(
|
|
4694
|
-
schemas,
|
|
4695
|
-
classConstraints,
|
|
4696
|
-
classRequired
|
|
4697
|
-
);
|
|
4698
|
-
});
|
|
4699
|
-
class ValidationService extends Effect.Service()(
|
|
4700
|
-
"ValidationService",
|
|
4701
|
-
{
|
|
4702
|
-
accessors: true,
|
|
4703
|
-
effect: Effect.succeed({
|
|
4704
|
-
extractClassValidationInfo: serviceExtractClassValidationInfo,
|
|
4705
|
-
mergeValidationConstraints: serviceMergeValidationConstraints
|
|
4706
|
-
})
|
|
4707
|
-
}
|
|
4708
|
-
) {
|
|
4709
|
-
}
|
|
4710
|
-
|
|
4711
4845
|
const generatorServicesLayer = Layer.mergeAll(
|
|
4712
4846
|
ConfigService.Default,
|
|
4713
4847
|
ProjectService.Default,
|
|
4714
4848
|
ModuleTraversalService.Default,
|
|
4715
4849
|
MethodExtractionService.Default,
|
|
4716
4850
|
SchemaService.Default,
|
|
4717
|
-
|
|
4851
|
+
TransformerService.Default,
|
|
4852
|
+
ValidationMapperService.Default,
|
|
4718
4853
|
OutputService.Default
|
|
4719
4854
|
);
|
|
4720
4855
|
|
|
@@ -5006,6 +5141,7 @@ const extractValidationConstraintsEffect = Effect.fn(
|
|
|
5006
5141
|
if (dtoFiles.length === 0) {
|
|
5007
5142
|
return schemas;
|
|
5008
5143
|
}
|
|
5144
|
+
const validation = yield* ValidationMapperService;
|
|
5009
5145
|
const project = new Project({
|
|
5010
5146
|
tsConfigFilePath: tsconfig,
|
|
5011
5147
|
skipAddingFilesFromTsConfig: true,
|
|
@@ -5026,7 +5162,7 @@ const extractValidationConstraintsEffect = Effect.fn(
|
|
|
5026
5162
|
for (const classDecl of sourceFile.getClasses()) {
|
|
5027
5163
|
const className = classDecl.getName();
|
|
5028
5164
|
if (!className) continue;
|
|
5029
|
-
const { constraints, required } = yield*
|
|
5165
|
+
const { constraints, required } = yield* validation.extractClassValidationInfo(classDecl);
|
|
5030
5166
|
if (Object.keys(constraints).length > 0) {
|
|
5031
5167
|
classConstraints.set(className, constraints);
|
|
5032
5168
|
}
|
|
@@ -5042,7 +5178,7 @@ const extractValidationConstraintsEffect = Effect.fn(
|
|
|
5042
5178
|
classesWithRequired: classRequired.size
|
|
5043
5179
|
})
|
|
5044
5180
|
);
|
|
5045
|
-
return yield*
|
|
5181
|
+
return yield* validation.mergeValidationConstraints(
|
|
5046
5182
|
schemas,
|
|
5047
5183
|
classConstraints,
|
|
5048
5184
|
classRequired
|
|
@@ -5212,7 +5348,8 @@ const generateEffect = Effect.fn("Generate.generateEffect")(function* (configPat
|
|
|
5212
5348
|
"filteredMethodCount",
|
|
5213
5349
|
filteredMethodInfos.length
|
|
5214
5350
|
);
|
|
5215
|
-
|
|
5351
|
+
const transformer = yield* TransformerService;
|
|
5352
|
+
let paths = yield* transformer.transformMethods(filteredMethodInfos);
|
|
5216
5353
|
yield* Effect.annotateCurrentSpan("initialPathCount", Object.keys(paths).length);
|
|
5217
5354
|
if (options.basePath) {
|
|
5218
5355
|
const prefix = options.basePath.startsWith("/") ? options.basePath : `/${options.basePath}`;
|
|
@@ -5500,4 +5637,4 @@ const generate = async (configPath, overrides) => {
|
|
|
5500
5637
|
return runGeneratorApiPromise(program);
|
|
5501
5638
|
};
|
|
5502
5639
|
|
|
5503
|
-
export {
|
|
5640
|
+
export { generateSchemas as $, getControllerMethodInfosEffect as A, MethodExtractionService as B, ConfigNotFoundError as C, DtoGlobResolutionError as D, EntryNotFoundError as E, transformMethod as F, transformMethodEffect as G, transformMethods as H, InvalidMethodError as I, transformMethodsEffect as J, mergeSchemas as K, mergeSchemasEffect as L, MissingGenericSchemaTempFileCleanupError as M, mergeGeneratedSchemas as N, mergeGeneratedSchemasEffect as O, ProjectInitError as P, filterSchemas as Q, filterSchemasEffect as R, SpecFileNotFoundError as S, TransformerService as T, normalizeSchemas as U, normalizeSchemasEffect as V, filterInternalSchemas as W, filterInternalSchemasEffect as X, normalizeStructureRefs as Y, normalizeStructureRefsEffect as Z, toPascalCase as _, SpecFileParseError as a, generateSchemasFromFiles as a0, SchemaGenerationError as a1, SchemaService as a2, ValidationMapperService as a3, extractPropertyConstraints as a4, isPropertyOptional as a5, extractPropertyValidationInfo as a6, extractClassValidationInfo as a7, extractClassValidationInfoEffect as a8, extractClassConstraints as a9, runtimeLayerFor as aA, runGeneratorApiPromise as aB, getRequiredProperties as aa, applyConstraintsToSchema as ab, mergeValidationConstraints as ac, mergeValidationConstraintsEffect as ad, OutputService as ae, ConfigService as af, findConfigFile as ag, loadConfigFromFile as ah, loadConfig as ai, resolveConfig as aj, loadAndResolveConfig as ak, generatorServicesLayer as al, resolveClassFromSymbol as am, getArrayInitializer as an, getStringLiteralValue as ao, getSymbolFromIdentifier as ap, isModuleClass as aq, getModuleDecoratorArg as ar, resolveClassFromExpression as as, resolveArrayOfClasses as at, getModuleMetadata as au, validateSpec as av, categorizeBrokenRefs as aw, formatValidationResult as ax, generateEffect as ay, runProjectApiPromise as az, SpecFileReadError as b, ConfigLoadError as c, defineConfig as d, ConfigValidationError as e, MissingGenericSchemaTempFileWriteError as f, generate as g, PublicApiError as h, ProjectService as i, ProjectServiceLive as j, getModules as k, getAllControllers as l, makeProjectContext as m, ModuleTraversalService as n, getControllerPrefix as o, getControllerName as p, isHttpMethod as q, getHttpMethods as r, getDecoratorName$1 as s, getControllerTags as t, getHttpDecorator as u, isHttpDecorator as v, normalizePath as w, getMethodInfo as x, getMethodInfoEffect as y, getControllerMethodInfos as z };
|