nestjs-openapi 0.3.2 → 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.C1wj3WtU.d.mts → nestjs-openapi.-SW6E7E-.d.mts} +221 -130
- package/dist/shared/{nestjs-openapi.C1wj3WtU.d.ts → nestjs-openapi.-SW6E7E-.d.ts} +221 -130
- package/dist/shared/{nestjs-openapi.9yw4zAwS.mjs → nestjs-openapi.zDlSJGHz.mjs} +496 -399
- 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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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 };
|
|
861
|
+
}
|
|
862
|
+
const numericLiteral = initializer.asKind?.(ts.SyntaxKind.NumericLiteral);
|
|
863
|
+
if (numericLiteral) {
|
|
864
|
+
const value = numericLiteral.getLiteralValue();
|
|
865
|
+
return { value, nextValue: value + 1 };
|
|
901
866
|
}
|
|
902
|
-
|
|
903
|
-
(decl) => decl.getSymbol()?.getAliasedSymbol()?.getDeclarations() ?? []
|
|
904
|
-
).find((decl) => Node.isEnumDeclaration(decl));
|
|
905
|
-
return importedEnumDecl ? extractEnumValues(importedEnumDecl) : void 0;
|
|
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 = [
|
|
@@ -968,77 +969,80 @@ const buildConstraintsFromKeys = (objectLiteral, keys, read) => Object.fromEntri
|
|
|
968
969
|
return value === void 0 ? [] : [[key, value]];
|
|
969
970
|
})
|
|
970
971
|
);
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
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) => {
|
|
974
979
|
const arrayLiteral = initializer.asKind?.(
|
|
975
980
|
ts.SyntaxKind.ArrayLiteralExpression
|
|
976
981
|
);
|
|
977
|
-
if (arrayLiteral) {
|
|
978
|
-
|
|
979
|
-
if (Node.isStringLiteral(element)) {
|
|
980
|
-
return [...acc, element.getLiteralValue()];
|
|
981
|
-
}
|
|
982
|
-
if (Node.isNumericLiteral(element)) {
|
|
983
|
-
return [...acc, element.getLiteralValue()];
|
|
984
|
-
}
|
|
985
|
-
if (Node.isPrefixUnaryExpression(element)) {
|
|
986
|
-
const numericValue = parseNumber(element.getText());
|
|
987
|
-
return numericValue === void 0 ? acc : [...acc, numericValue];
|
|
988
|
-
}
|
|
989
|
-
return acc;
|
|
990
|
-
}, []);
|
|
991
|
-
return values.length > 0 ? values : void 0;
|
|
982
|
+
if (!arrayLiteral) {
|
|
983
|
+
return void 0;
|
|
992
984
|
}
|
|
993
|
-
|
|
985
|
+
const values = arrayLiteral.getElements().map(readEnumArrayElement).filter((value) => value !== void 0);
|
|
986
|
+
return values.length > 0 ? values : void 0;
|
|
994
987
|
};
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
if (
|
|
999
|
-
return
|
|
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;
|
|
1000
993
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
return
|
|
994
|
+
return readEnumArrayLiteral(initializer) ?? readEnumIdentifier(initializer);
|
|
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;
|
|
1008
1001
|
}
|
|
1009
|
-
|
|
1002
|
+
const itemType = API_TYPE_IDENTIFIERS[firstArrayElement.getText()];
|
|
1003
|
+
return itemType ? { ...itemType, isArray: true } : void 0;
|
|
1010
1004
|
};
|
|
1011
|
-
const
|
|
1012
|
-
const
|
|
1013
|
-
if (!
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
...enumValues ? { enum: enumValues } : {},
|
|
1018
|
-
...typeConstraints ?? {},
|
|
1019
|
-
...buildConstraintsFromKeys(
|
|
1020
|
-
objectLiteral,
|
|
1021
|
-
API_STRING_KEYS,
|
|
1022
|
-
(obj, key) => getStringProp(obj, key)
|
|
1023
|
-
),
|
|
1024
|
-
...buildConstraintsFromKeys(
|
|
1025
|
-
objectLiteral,
|
|
1026
|
-
API_NUMBER_KEYS,
|
|
1027
|
-
(obj, key) => getNumericProp(obj, key)
|
|
1028
|
-
),
|
|
1029
|
-
...buildConstraintsFromKeys(
|
|
1030
|
-
objectLiteral,
|
|
1031
|
-
API_BOOLEAN_KEYS,
|
|
1032
|
-
(obj, key) => getBooleanProp(obj, key)
|
|
1033
|
-
),
|
|
1034
|
-
...buildConstraintsFromKeys(
|
|
1035
|
-
objectLiteral,
|
|
1036
|
-
API_PRIMITIVE_KEYS,
|
|
1037
|
-
(obj, key) => getPrimitiveValue(obj, key)
|
|
1038
|
-
)
|
|
1039
|
-
};
|
|
1040
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
1005
|
+
const extractApiPropertyType = (objLit) => {
|
|
1006
|
+
const initializer = getPropertyInitializer(objLit, "type");
|
|
1007
|
+
if (!initializer) {
|
|
1008
|
+
return void 0;
|
|
1009
|
+
}
|
|
1010
|
+
return readApiTypeIdentifier(initializer) ?? readApiTypeArrayElement(initializer);
|
|
1041
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
|
+
);
|
|
1042
1046
|
const compactConstraints = (constraints) => Object.fromEntries(
|
|
1043
1047
|
Object.entries(constraints).filter(([, value]) => value !== void 0)
|
|
1044
1048
|
);
|
|
@@ -1125,35 +1129,93 @@ const collectHiddenProperties = (propertyConstraints) => new Set(
|
|
|
1125
1129
|
([propertyName, constraints]) => constraints.hidden ? [propertyName] : []
|
|
1126
1130
|
)
|
|
1127
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
|
+
});
|
|
1128
1182
|
const applyPropertyConstraintsToSchema = (propertySchema, constraints) => Option.fromNullable(constraints).pipe(
|
|
1129
1183
|
Option.map((propertyConstraints) => {
|
|
1130
|
-
const
|
|
1184
|
+
const isArray = propertyConstraints.isArray;
|
|
1131
1185
|
const {
|
|
1132
1186
|
type: typeOverride,
|
|
1133
1187
|
enum: enumValues,
|
|
1134
1188
|
...restConstraints
|
|
1135
|
-
} =
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
return
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
};
|
|
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
|
+
);
|
|
1150
1203
|
}
|
|
1151
|
-
return
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
+
);
|
|
1157
1219
|
}),
|
|
1158
1220
|
Option.getOrElse(() => propertySchema)
|
|
1159
1221
|
);
|
|
@@ -1219,8 +1281,8 @@ const mergeValidationConstraints = (schemas, classConstraints, classRequired) =>
|
|
|
1219
1281
|
);
|
|
1220
1282
|
return { definitions };
|
|
1221
1283
|
};
|
|
1222
|
-
const
|
|
1223
|
-
"
|
|
1284
|
+
const serviceExtractClassValidationInfo = Effect.fn(
|
|
1285
|
+
"ValidationMapperService.extractClassValidationInfo"
|
|
1224
1286
|
)(function* (classDecl) {
|
|
1225
1287
|
const className = classDecl.getName() ?? "<anonymous>";
|
|
1226
1288
|
const filePath = classDecl.getSourceFile().getFilePath();
|
|
@@ -1240,8 +1302,8 @@ const extractClassValidationInfoEffect = Effect.fn(
|
|
|
1240
1302
|
);
|
|
1241
1303
|
return info;
|
|
1242
1304
|
});
|
|
1243
|
-
const
|
|
1244
|
-
"
|
|
1305
|
+
const serviceMergeValidationConstraints = Effect.fn(
|
|
1306
|
+
"ValidationMapperService.mergeValidationConstraints"
|
|
1245
1307
|
)(function* (schemas, classConstraints, classRequired) {
|
|
1246
1308
|
const merged = mergeValidationConstraints(
|
|
1247
1309
|
schemas,
|
|
@@ -1263,6 +1325,18 @@ const mergeValidationConstraintsEffect = Effect.fn(
|
|
|
1263
1325
|
yield* Effect.annotateCurrentSpan("classRequiredCount", classRequired.size);
|
|
1264
1326
|
return merged;
|
|
1265
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;
|
|
1266
1340
|
|
|
1267
1341
|
const methodInfoCache = /* @__PURE__ */ new WeakMap();
|
|
1268
1342
|
const returnTypeInfoCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -2061,152 +2135,200 @@ class MethodExtractionService extends Effect.Service()(
|
|
|
2061
2135
|
) {
|
|
2062
2136
|
}
|
|
2063
2137
|
|
|
2064
|
-
const buildSecurityRequirements = (requirements) =>
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
}
|
|
2070
|
-
return [combined];
|
|
2071
|
-
};
|
|
2138
|
+
const buildSecurityRequirements = (requirements) => requirements.length === 0 ? void 0 : [
|
|
2139
|
+
Object.fromEntries(
|
|
2140
|
+
requirements.map((req) => [req.schemeName, [...req.scopes]])
|
|
2141
|
+
)
|
|
2142
|
+
];
|
|
2072
2143
|
const getRequestContentTypes = (methodInfo) => methodInfo.consumes.length > 0 ? methodInfo.consumes : ["application/json"];
|
|
2073
2144
|
const getResponseContentTypes = (methodInfo) => methodInfo.produces.length > 0 ? methodInfo.produces : ["application/json"];
|
|
2074
2145
|
const buildContentObject = (contentTypes, schema) => Object.fromEntries(contentTypes.map((type) => [type, { schema }]));
|
|
2075
|
-
const
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
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);
|
|
2083
2156
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2157
|
+
};
|
|
2158
|
+
const splitInlineObjectParts = (content) => {
|
|
2086
2159
|
const parts = [];
|
|
2087
2160
|
let current = "";
|
|
2088
2161
|
let braceDepth = 0;
|
|
2089
2162
|
for (const char of content) {
|
|
2090
|
-
if (char ===
|
|
2091
|
-
|
|
2092
|
-
current += char;
|
|
2093
|
-
} else if (char === "}") {
|
|
2094
|
-
braceDepth--;
|
|
2095
|
-
current += char;
|
|
2096
|
-
} else if ((char === ";" || char === ",") && braceDepth === 0) {
|
|
2097
|
-
if (current.trim()) {
|
|
2098
|
-
parts.push(current.trim());
|
|
2099
|
-
}
|
|
2163
|
+
if (INLINE_OBJECT_SEPARATORS.has(char) && braceDepth === 0) {
|
|
2164
|
+
pushInlinePart(parts, current);
|
|
2100
2165
|
current = "";
|
|
2101
|
-
|
|
2102
|
-
current += char;
|
|
2166
|
+
continue;
|
|
2103
2167
|
}
|
|
2168
|
+
current += char;
|
|
2169
|
+
braceDepth += BRACE_DEPTH_CHANGE[char] ?? 0;
|
|
2104
2170
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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;
|
|
2122
2236
|
}
|
|
2123
|
-
|
|
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]);
|
|
2251
|
+
}
|
|
2252
|
+
return { oneOf: types.map((type) => tsTypeToOpenApiSchema(type)) };
|
|
2124
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
|
+
];
|
|
2125
2281
|
const tsTypeToOpenApiSchema = (tsType) => {
|
|
2126
2282
|
const trimmed = tsType.trim();
|
|
2127
|
-
|
|
2128
|
-
const
|
|
2129
|
-
if (
|
|
2130
|
-
const schema = {
|
|
2131
|
-
type: "object",
|
|
2132
|
-
properties: parsed.properties
|
|
2133
|
-
};
|
|
2134
|
-
if (parsed.required.length > 0) {
|
|
2135
|
-
return { ...schema, required: parsed.required };
|
|
2136
|
-
}
|
|
2283
|
+
for (const resolver of TYPE_SCHEMA_RESOLVERS) {
|
|
2284
|
+
const schema = resolver(trimmed);
|
|
2285
|
+
if (schema !== void 0) {
|
|
2137
2286
|
return schema;
|
|
2138
2287
|
}
|
|
2139
2288
|
}
|
|
2140
|
-
if (trimmed.includes(" | ")) {
|
|
2141
|
-
const allMembers = trimmed.split(" | ").map((t) => t.trim());
|
|
2142
|
-
const hasNull = allMembers.includes("null");
|
|
2143
|
-
const types = allMembers.filter((t) => t !== "undefined" && t !== "null");
|
|
2144
|
-
if (types.length === 0) return { type: "object" };
|
|
2145
|
-
const schema = types.length === 1 ? tsTypeToOpenApiSchema(types[0]) : { oneOf: types.map((type) => tsTypeToOpenApiSchema(type)) };
|
|
2146
|
-
if (!hasNull) return schema;
|
|
2147
|
-
if (schema.$ref) {
|
|
2148
|
-
return { allOf: [{ $ref: schema.$ref }], nullable: true };
|
|
2149
|
-
}
|
|
2150
|
-
return { ...schema, nullable: true };
|
|
2151
|
-
}
|
|
2152
|
-
switch (trimmed.toLowerCase()) {
|
|
2153
|
-
case "string":
|
|
2154
|
-
return { type: "string" };
|
|
2155
|
-
case "number":
|
|
2156
|
-
return { type: "number" };
|
|
2157
|
-
case "boolean":
|
|
2158
|
-
return { type: "boolean" };
|
|
2159
|
-
case "date":
|
|
2160
|
-
return { type: "string", format: "date-time" };
|
|
2161
|
-
case "void":
|
|
2162
|
-
case "undefined":
|
|
2163
|
-
case "never":
|
|
2164
|
-
case "null":
|
|
2165
|
-
return { type: "object" };
|
|
2166
|
-
case "unknown":
|
|
2167
|
-
case "any":
|
|
2168
|
-
case "object":
|
|
2169
|
-
return { type: "object" };
|
|
2170
|
-
}
|
|
2171
|
-
if (trimmed === "StreamableFile" || trimmed === "Buffer" || trimmed === "Readable" || trimmed === "ReadableStream") {
|
|
2172
|
-
return { type: "string", format: "binary" };
|
|
2173
|
-
}
|
|
2174
|
-
if (trimmed.endsWith("[]")) {
|
|
2175
|
-
const itemType = trimmed.slice(0, -2);
|
|
2176
|
-
return {
|
|
2177
|
-
type: "array",
|
|
2178
|
-
items: tsTypeToOpenApiSchema(itemType)
|
|
2179
|
-
};
|
|
2180
|
-
}
|
|
2181
|
-
const recordMatch = trimmed.match(/^Record<string,\s*(.+)>$/);
|
|
2182
|
-
if (recordMatch) {
|
|
2183
|
-
return {
|
|
2184
|
-
type: "object"
|
|
2185
|
-
};
|
|
2186
|
-
}
|
|
2187
|
-
if (trimmed.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*(<[^>]+>)?$/)) {
|
|
2188
|
-
return { $ref: `#/components/schemas/${trimmed}` };
|
|
2189
|
-
}
|
|
2190
2289
|
return { type: "object" };
|
|
2191
2290
|
};
|
|
2192
2291
|
const isNodeModulesPath = (filePath) => filePath.includes("/node_modules/") || filePath.includes("\\node_modules\\");
|
|
2193
2292
|
const shouldFallbackExternalReturnRef = (returnType) => Option.isSome(returnType.filePath) && isNodeModulesPath(returnType.filePath.value);
|
|
2194
2293
|
const responseSchemaOrObjectFallback = (schema) => schema.$ref ? { type: "object" } : schema;
|
|
2195
|
-
const
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
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
|
+
};
|
|
2205
2321
|
}
|
|
2322
|
+
return {
|
|
2323
|
+
...baseSchema,
|
|
2324
|
+
...restConstraints,
|
|
2325
|
+
...typeOverride === void 0 ? {} : { type: typeOverride },
|
|
2326
|
+
...enumValues === void 0 ? {} : { enum: enumValues }
|
|
2327
|
+
};
|
|
2206
2328
|
};
|
|
2207
2329
|
const transformParameter = (param) => {
|
|
2208
2330
|
const baseSchema = tsTypeToOpenApiSchema(param.tsType);
|
|
2209
|
-
const schema = param.constraints ?
|
|
2331
|
+
const schema = param.constraints ? applyParameterConstraints(baseSchema, param.constraints) : baseSchema;
|
|
2210
2332
|
return {
|
|
2211
2333
|
name: param.name,
|
|
2212
2334
|
in: getParameterLocation(param.location),
|
|
@@ -2219,21 +2341,7 @@ const isInlineOptionalBodyType = (tsType) => {
|
|
|
2219
2341
|
const parsed = parseInlineObjectType(tsType);
|
|
2220
2342
|
return parsed !== null && parsed.required.length === 0;
|
|
2221
2343
|
};
|
|
2222
|
-
const
|
|
2223
|
-
const shouldFallback = Option.isNone(returnType.container) && shouldFallbackExternalReturnRef(returnType);
|
|
2224
|
-
if (Option.isSome(returnType.container) && returnType.container.value === "array") {
|
|
2225
|
-
if (Option.isSome(returnType.inline)) {
|
|
2226
|
-
return {
|
|
2227
|
-
type: "array",
|
|
2228
|
-
items: tsTypeToOpenApiSchema(returnType.inline.value)
|
|
2229
|
-
};
|
|
2230
|
-
}
|
|
2231
|
-
const itemSchema = Option.isSome(returnType.type) ? tsTypeToOpenApiSchema(returnType.type.value) : { type: "object" };
|
|
2232
|
-
return {
|
|
2233
|
-
type: "array",
|
|
2234
|
-
items: shouldFallback ? responseSchemaOrObjectFallback(itemSchema) : itemSchema
|
|
2235
|
-
};
|
|
2236
|
-
}
|
|
2344
|
+
const buildScalarReturnSchema = (returnType, shouldFallback) => {
|
|
2237
2345
|
if (Option.isSome(returnType.type)) {
|
|
2238
2346
|
const schema = tsTypeToOpenApiSchema(returnType.type.value);
|
|
2239
2347
|
return shouldFallback ? responseSchemaOrObjectFallback(schema) : schema;
|
|
@@ -2243,81 +2351,77 @@ const buildResponseSchema = (returnType) => {
|
|
|
2243
2351
|
}
|
|
2244
2352
|
return { type: "string" };
|
|
2245
2353
|
};
|
|
2246
|
-
const
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
return
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
return methodInfo.httpMethod === "POST" ? 201 : 200;
|
|
2262
|
-
};
|
|
2263
|
-
const hasMeaningfulReturnType = (returnType) => {
|
|
2264
|
-
if (Option.isSome(returnType.type)) {
|
|
2265
|
-
const typeName = returnType.type.value.toLowerCase();
|
|
2266
|
-
if (["void", "undefined", "never"].includes(typeName)) {
|
|
2267
|
-
return false;
|
|
2268
|
-
}
|
|
2269
|
-
return true;
|
|
2270
|
-
}
|
|
2271
|
-
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
|
+
);
|
|
2272
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
|
+
);
|
|
2273
2387
|
const isSuccessWithContent = (statusCode) => statusCode >= 200 && statusCode < 300 && statusCode !== 204;
|
|
2274
2388
|
const buildResponseEntry = (response, returnType, hasReturnType, contentTypes) => {
|
|
2275
2389
|
const schema = buildResponseSchemaFromMetadata(response) ?? (hasReturnType && isSuccessWithContent(response.statusCode) ? buildResponseSchema(returnType) : void 0);
|
|
2276
2390
|
const description = Option.getOrElse(response.description, () => "");
|
|
2277
|
-
|
|
2278
|
-
return { description, content: buildContentObject(contentTypes, schema) };
|
|
2391
|
+
return schema ? { description, content: buildContentObject(contentTypes, schema) } : { description };
|
|
2279
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
|
+
);
|
|
2280
2405
|
const buildResponses = (methodInfo) => {
|
|
2281
2406
|
const contentTypes = getResponseContentTypes(methodInfo);
|
|
2282
2407
|
const returnType = methodInfo.returnType;
|
|
2283
2408
|
const hasReturnType = hasMeaningfulReturnType(returnType);
|
|
2284
2409
|
const statusCode = getDefaultSuccessCode(methodInfo);
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
return {
|
|
2290
|
-
[statusCode.toString()]: {
|
|
2291
|
-
description: "",
|
|
2292
|
-
content: buildContentObject(
|
|
2293
|
-
contentTypes,
|
|
2294
|
-
buildResponseSchema(returnType)
|
|
2295
|
-
)
|
|
2296
|
-
}
|
|
2297
|
-
};
|
|
2298
|
-
}
|
|
2299
|
-
const result = {};
|
|
2300
|
-
const hasSuccessResponse = methodInfo.responses.some(
|
|
2301
|
-
(r) => r.statusCode >= 200 && r.statusCode < 300
|
|
2410
|
+
const defaultSuccessEntry = buildDefaultResponseEntry(
|
|
2411
|
+
returnType,
|
|
2412
|
+
hasReturnType,
|
|
2413
|
+
contentTypes
|
|
2302
2414
|
);
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
}
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
result[response.statusCode.toString()] = buildResponseEntry(
|
|
2314
|
-
response,
|
|
2315
|
-
returnType,
|
|
2316
|
-
hasReturnType,
|
|
2317
|
-
contentTypes
|
|
2318
|
-
);
|
|
2319
|
-
}
|
|
2320
|
-
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
|
+
};
|
|
2321
2425
|
};
|
|
2322
2426
|
const buildOpenApiPath = (path) => path.replace(/:([^/]+)/g, "{$1}") || "/";
|
|
2323
2427
|
const transformMethodInternal = (methodInfo) => {
|
|
@@ -2363,33 +2467,49 @@ const transformMethodInternal = (methodInfo) => {
|
|
|
2363
2467
|
};
|
|
2364
2468
|
};
|
|
2365
2469
|
const transformMethod = (methodInfo) => transformMethodInternal(methodInfo);
|
|
2366
|
-
const
|
|
2367
|
-
function* (methodInfo) {
|
|
2368
|
-
return yield* Effect.succeed(transformMethodInternal(methodInfo));
|
|
2369
|
-
}
|
|
2370
|
-
);
|
|
2371
|
-
const transformMethods = (methodInfos) => methodInfos.reduce((acc, methodInfo) => {
|
|
2372
|
-
const endpoint = transformMethodInternal(methodInfo);
|
|
2470
|
+
const mergeOpenApiPaths = (endpoints) => endpoints.reduce((acc, endpoint) => {
|
|
2373
2471
|
for (const path in endpoint) {
|
|
2374
|
-
|
|
2375
|
-
Object.assign(acc[path], endpoint[path]);
|
|
2472
|
+
acc[path] = { ...acc[path] ?? {}, ...endpoint[path] };
|
|
2376
2473
|
}
|
|
2377
2474
|
return acc;
|
|
2378
2475
|
}, {});
|
|
2379
|
-
const
|
|
2380
|
-
|
|
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"
|
|
2381
2492
|
)(function* (methodInfos) {
|
|
2382
|
-
const endpoints = yield* Effect.forEach(methodInfos,
|
|
2493
|
+
const endpoints = yield* Effect.forEach(methodInfos, serviceTransformMethod, {
|
|
2383
2494
|
concurrency: "unbounded"
|
|
2384
2495
|
});
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
}
|
|
2390
|
-
return acc;
|
|
2391
|
-
}, {});
|
|
2496
|
+
const paths = mergeOpenApiPaths(endpoints);
|
|
2497
|
+
yield* Effect.annotateCurrentSpan("methodCount", methodInfos.length);
|
|
2498
|
+
yield* Effect.annotateCurrentSpan("pathCount", Object.keys(paths).length);
|
|
2499
|
+
return paths;
|
|
2392
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;
|
|
2393
2513
|
|
|
2394
2514
|
const UGLY_NAME_PATTERN = /^(.+?)<.*>$/;
|
|
2395
2515
|
const UGLY_NAME_PATTERN_ENCODED = /^(.+?)%3C.*%3E$/i;
|
|
@@ -4722,39 +4842,14 @@ class SchemaService extends Effect.Service()(
|
|
|
4722
4842
|
) {
|
|
4723
4843
|
}
|
|
4724
4844
|
|
|
4725
|
-
const serviceExtractClassValidationInfo = Effect.fn(
|
|
4726
|
-
"ValidationService.extractClassValidationInfo"
|
|
4727
|
-
)(function* (classDecl) {
|
|
4728
|
-
return yield* extractClassValidationInfoEffect(classDecl);
|
|
4729
|
-
});
|
|
4730
|
-
const serviceMergeValidationConstraints = Effect.fn(
|
|
4731
|
-
"ValidationService.mergeValidationConstraints"
|
|
4732
|
-
)(function* (schemas, classConstraints, classRequired) {
|
|
4733
|
-
return yield* mergeValidationConstraintsEffect(
|
|
4734
|
-
schemas,
|
|
4735
|
-
classConstraints,
|
|
4736
|
-
classRequired
|
|
4737
|
-
);
|
|
4738
|
-
});
|
|
4739
|
-
class ValidationService extends Effect.Service()(
|
|
4740
|
-
"ValidationService",
|
|
4741
|
-
{
|
|
4742
|
-
accessors: true,
|
|
4743
|
-
effect: Effect.succeed({
|
|
4744
|
-
extractClassValidationInfo: serviceExtractClassValidationInfo,
|
|
4745
|
-
mergeValidationConstraints: serviceMergeValidationConstraints
|
|
4746
|
-
})
|
|
4747
|
-
}
|
|
4748
|
-
) {
|
|
4749
|
-
}
|
|
4750
|
-
|
|
4751
4845
|
const generatorServicesLayer = Layer.mergeAll(
|
|
4752
4846
|
ConfigService.Default,
|
|
4753
4847
|
ProjectService.Default,
|
|
4754
4848
|
ModuleTraversalService.Default,
|
|
4755
4849
|
MethodExtractionService.Default,
|
|
4756
4850
|
SchemaService.Default,
|
|
4757
|
-
|
|
4851
|
+
TransformerService.Default,
|
|
4852
|
+
ValidationMapperService.Default,
|
|
4758
4853
|
OutputService.Default
|
|
4759
4854
|
);
|
|
4760
4855
|
|
|
@@ -5046,6 +5141,7 @@ const extractValidationConstraintsEffect = Effect.fn(
|
|
|
5046
5141
|
if (dtoFiles.length === 0) {
|
|
5047
5142
|
return schemas;
|
|
5048
5143
|
}
|
|
5144
|
+
const validation = yield* ValidationMapperService;
|
|
5049
5145
|
const project = new Project({
|
|
5050
5146
|
tsConfigFilePath: tsconfig,
|
|
5051
5147
|
skipAddingFilesFromTsConfig: true,
|
|
@@ -5066,7 +5162,7 @@ const extractValidationConstraintsEffect = Effect.fn(
|
|
|
5066
5162
|
for (const classDecl of sourceFile.getClasses()) {
|
|
5067
5163
|
const className = classDecl.getName();
|
|
5068
5164
|
if (!className) continue;
|
|
5069
|
-
const { constraints, required } = yield*
|
|
5165
|
+
const { constraints, required } = yield* validation.extractClassValidationInfo(classDecl);
|
|
5070
5166
|
if (Object.keys(constraints).length > 0) {
|
|
5071
5167
|
classConstraints.set(className, constraints);
|
|
5072
5168
|
}
|
|
@@ -5082,7 +5178,7 @@ const extractValidationConstraintsEffect = Effect.fn(
|
|
|
5082
5178
|
classesWithRequired: classRequired.size
|
|
5083
5179
|
})
|
|
5084
5180
|
);
|
|
5085
|
-
return yield*
|
|
5181
|
+
return yield* validation.mergeValidationConstraints(
|
|
5086
5182
|
schemas,
|
|
5087
5183
|
classConstraints,
|
|
5088
5184
|
classRequired
|
|
@@ -5252,7 +5348,8 @@ const generateEffect = Effect.fn("Generate.generateEffect")(function* (configPat
|
|
|
5252
5348
|
"filteredMethodCount",
|
|
5253
5349
|
filteredMethodInfos.length
|
|
5254
5350
|
);
|
|
5255
|
-
|
|
5351
|
+
const transformer = yield* TransformerService;
|
|
5352
|
+
let paths = yield* transformer.transformMethods(filteredMethodInfos);
|
|
5256
5353
|
yield* Effect.annotateCurrentSpan("initialPathCount", Object.keys(paths).length);
|
|
5257
5354
|
if (options.basePath) {
|
|
5258
5355
|
const prefix = options.basePath.startsWith("/") ? options.basePath : `/${options.basePath}`;
|
|
@@ -5540,4 +5637,4 @@ const generate = async (configPath, overrides) => {
|
|
|
5540
5637
|
return runGeneratorApiPromise(program);
|
|
5541
5638
|
};
|
|
5542
5639
|
|
|
5543
|
-
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 };
|