Skip to content

Commit

Permalink
(feat): support default-url and url override on imports (#4116)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi authored Jul 24, 2024
1 parent d3cf7e4 commit 8595d8b
Show file tree
Hide file tree
Showing 20 changed files with 97 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ async function writeDefinitionForFernWorkspace({
workspace: FernWorkspace;
context: TaskContext;
}): Promise<void> {
for (const [relativePath, definition] of Object.entries(workspace.definition.importedDefinitions)) {
for (const [relativePath, importedDefinition] of Object.entries(workspace.definition.importedDefinitions)) {
const absolutePathToOutputDirectory = join(
workspace.absoluteFilepath,
RelativeFilePath.of(DEFINITION_DIRECTORY),
RelativeFilePath.of(relativePath),
RelativeFilePath.of(`.${DEFINITION_DIRECTORY}`)
);
await writeFernDefinition({
definition,
definition: importedDefinition.definition,
absolutePathToOutputDirectory
});
context.logger.info(
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/generation/ir-generator/src/FernFileContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { parseInlineType } from "./utils/parseInlineType";
* here is a description
*/
export interface FernFileContext {
defaultUrl: string | undefined;
relativeFilepath: RelativeFilePath;
fernFilepath: FernFilepath;
imports: Record<string, RelativeFilePath>;
Expand Down Expand Up @@ -39,17 +40,20 @@ export function constructRootApiFileContext({
}

export function constructFernFileContext({
defaultUrl,
relativeFilepath,
definitionFile,
casingsGenerator,
rootApiFile
}: {
defaultUrl?: string;
relativeFilepath: RelativeFilePath;
definitionFile: DefinitionFileSchema;
casingsGenerator: CasingsGenerator;
rootApiFile: RootApiFileSchema;
}): FernFileContext {
const file: FernFileContext = {
defaultUrl,
relativeFilepath,
fernFilepath: convertToFernFilepath({ relativeFilepath, casingsGenerator }),
imports: mapValues(definitionFile.imports ?? {}, RelativeFilePath.of),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { convertQueryParameter } from "./convertQueryParameter";
import { convertResponseErrors } from "./convertResponseErrors";

export async function convertHttpService({
rootDefaultUrl,
rootPathParameters,
serviceDefinition,
file,
Expand All @@ -42,6 +43,7 @@ export async function convertHttpService({
globalErrors,
workspace
}: {
rootDefaultUrl: string | undefined;
rootPathParameters: PathParameter[];
serviceDefinition: RawSchemas.HttpServiceSchema;
file: FernFileContext;
Expand Down Expand Up @@ -90,7 +92,7 @@ export async function convertHttpService({
displayName: endpoint["display-name"],
auth: endpoint.auth ?? serviceDefinition.auth,
idempotent: endpoint.idempotent ?? serviceDefinition.idempotent ?? false,
baseUrl: endpoint.url ?? serviceDefinition.url,
baseUrl: endpoint.url ?? serviceDefinition.url ?? rootDefaultUrl,
method: endpoint.method != null ? convertHttpMethod(endpoint.method) : HttpMethod.Post,
path: constructHttpPath(endpoint.path),
fullPath: constructHttpPath(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ export async function generateIntermediateRepresentation({
}

const convertedHttpService = await convertHttpService({
rootDefaultUrl: file.defaultUrl ?? workspace.definition.rootApiFile.contents["default-url"],
rootPathParameters: intermediateRepresentation.pathParameters,
serviceDefinition: service,
file,
Expand Down Expand Up @@ -335,13 +336,14 @@ export async function generateIntermediateRepresentation({
});
};

await visitAllDefinitionFiles(workspace, async (relativeFilepath, file) => {
await visitAllDefinitionFiles(workspace, async (relativeFilepath, file, metadata) => {
await visitDefinitionFile(
constructFernFileContext({
relativeFilepath,
definitionFile: file,
casingsGenerator,
rootApiFile: workspace.definition.rootApiFile.contents
rootApiFile: workspace.definition.rootApiFile.contents,
defaultUrl: metadata.defaultUrl
})
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ async function writeFernDefinition({
absolutePathToDefinitionDirectory,
RelativeFilePath.of(relativePath)
),
definition: importedDefinition
// TODO write with the defaulted url
definition: importedDefinition.definition
});
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/cli/workspace-loader/src/loadDependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ async function getAreRootApiFilesEquivalent(
version: noop,
name: noop,
imports: noop,
"default-url": noop,
"display-name": noop,
auth: (auth) => {
const isAuthEquals = isEqual(auth, workspaceOfDependency.definition.rootApiFile.contents.auth);
Expand Down
1 change: 1 addition & 0 deletions packages/cli/workspace-loader/src/parseYamlFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export async function parseYamlFiles(files: readonly FernFile[]): Promise<Parser
function parseFilePath(file: FernFile) {
try {
parsedFiles[file.relativeFilepath] = {
defaultUrl: undefined,
contents: yaml.load(file.fileContents, {
schema: yaml.CORE_SCHEMA
}),
Expand Down
22 changes: 18 additions & 4 deletions packages/cli/workspace-loader/src/processPackageMarkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@ export declare namespace processPackageMarkers {
export interface SuccessfulResult {
didSucceed: true;
packageMarkers: Record<RelativeFilePath, ParsedFernFile<PackageMarkerFileSchema>>;
importedDefinitions: Record<RelativeFilePath, FernDefinition>;
importedDefinitions: Record<RelativeFilePath, ImportedDefinition>;
}

export interface FailedResult {
didSucceed: false;
failures: Record<RelativeFilePath, WorkspaceLoader.DependencyFailure>;
}

export interface ImportedDefinition {
url: string | undefined;
definition: FernDefinition;
}
}

export async function processPackageMarkers({
Expand All @@ -37,7 +42,7 @@ export async function processPackageMarkers({
cliVersion: string;
}): Promise<processPackageMarkers.Return> {
const packageMarkers: Record<RelativeFilePath, ParsedFernFile<PackageMarkerFileSchema>> = {};
const importedDefinitions: Record<RelativeFilePath, FernDefinition> = {};
const importedDefinitions: Record<RelativeFilePath, processPackageMarkers.ImportedDefinition> = {};
const failures: Record<RelativeFilePath, WorkspaceLoader.DependencyFailure> = {};

await Promise.all(
Expand All @@ -63,14 +68,23 @@ export async function processPackageMarkers({
};
} else {
const loadDependencyResult = await loadDependency({
dependencyName: packageMarker.contents.export,
dependencyName:
typeof packageMarker.contents.export === "string"
? packageMarker.contents.export
: packageMarker.contents.export.dependency,
dependenciesConfiguration,
context,
rootApiFile: structuralValidationResult.rootApiFile.contents,
cliVersion
});
if (loadDependencyResult.didSucceed) {
importedDefinitions[dirname(pathOfPackageMarker)] = loadDependencyResult.definition;
importedDefinitions[dirname(pathOfPackageMarker)] = {
definition: loadDependencyResult.definition,
url:
typeof packageMarker.contents.export === "object"
? packageMarker.contents.export.url
: undefined
};
} else {
failures[pathOfPackageMarker] = loadDependencyResult.failure;
}
Expand Down
1 change: 1 addition & 0 deletions packages/cli/workspace-loader/src/types/FernFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export interface FernFile {
export interface ParsedFernFile<Schema> {
rawContents: string;
contents: Schema;
defaultUrl: string | undefined;
}
3 changes: 2 additions & 1 deletion packages/cli/workspace-loader/src/types/Workspace.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { docsYml, generatorsYml } from "@fern-api/configuration";
import { AbsoluteFilePath, RelativeFilePath } from "@fern-api/fs-utils";
import { DefinitionFileSchema, PackageMarkerFileSchema, RootApiFileSchema } from "@fern-api/yaml-schema";
import { processPackageMarkers } from "../processPackageMarkers";
import { FernWorkspace } from "../workspaces/FernWorkspace";
import { OSSWorkspace } from "../workspaces/OSSWorkspace";
import { ParsedFernFile } from "./FernFile";
Expand Down Expand Up @@ -50,7 +51,7 @@ export interface FernDefinition {
rootApiFile: ParsedFernFile<RootApiFileSchema>;
namedDefinitionFiles: Record<RelativeFilePath, OnDiskNamedDefinitionFile>;
packageMarkers: Record<RelativeFilePath, ParsedFernFile<PackageMarkerFileSchema>>;
importedDefinitions: Record<RelativeFilePath, FernDefinition>;
importedDefinitions: Record<RelativeFilePath, processPackageMarkers.ImportedDefinition>;
}

export interface OnDiskNamedDefinitionFile extends ParsedFernFile<DefinitionFileSchema> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,28 @@ import { mapKeys } from "lodash-es";
import { ParsedFernFile } from "../types/FernFile";
import { FernDefinition } from "../types/Workspace";

export declare namespace getAllNamedDefinitionFiles {
interface Opts {
defaultURL?: string;
}
}

export function getAllNamedDefinitionFiles(
definition: FernDefinition
definition: FernDefinition,
opts: getAllNamedDefinitionFiles.Opts = {}
): Record<RelativeFilePath, ParsedFernFile<DefinitionFileSchema>> {
return {
...definition.namedDefinitionFiles,
...Object.fromEntries(
entries(definition.namedDefinitionFiles).map(([path, file]) => {
return [path, { ...file, defaultUrl: opts.defaultURL }];
})
),
...entries(definition.importedDefinitions).reduce((acc, [pathToImportedDefinition, definition]) => {
return {
...acc,
...mapKeys(getAllNamedDefinitionFiles(definition), (_file, path) =>
join(pathToImportedDefinition, RelativeFilePath.of(path))
...mapKeys(
getAllNamedDefinitionFiles(definition.definition, { defaultURL: definition.url }),
(_file, path) => join(pathToImportedDefinition, RelativeFilePath.of(path))
)
};
}, {})
Expand Down
17 changes: 14 additions & 3 deletions packages/cli/workspace-loader/src/utils/getAllPackageMarkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,26 @@ import { mapKeys } from "lodash-es";
import { ParsedFernFile } from "../types/FernFile";
import { FernDefinition } from "../types/Workspace";

export declare namespace getAllPackageMarkers {
interface Opts {
defaultURL?: string;
}
}

export function getAllPackageMarkers(
definition: FernDefinition
definition: FernDefinition,
opts: getAllPackageMarkers.Opts = {}
): Record<RelativeFilePath, ParsedFernFile<PackageMarkerFileSchema>> {
return {
...definition.packageMarkers,
...Object.fromEntries(
entries(definition.packageMarkers).map(([path, file]) => {
return [path, { ...file, defaultUrl: opts.defaultURL }];
})
),
...entries(definition.importedDefinitions).reduce((acc, [pathToImportedDefinition, definition]) => {
return {
...acc,
...mapKeys(getAllPackageMarkers(definition), (_file, path) =>
...mapKeys(getAllPackageMarkers(definition.definition, { defaultURL: definition.url }), (_file, path) =>
join(pathToImportedDefinition, RelativeFilePath.of(path))
)
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ export async function visitAllDefinitionFiles(
visitor: (
filepath: RelativeFilePath,
definitionFile: DefinitionFileSchema,
metadata: { isPackageMarker: boolean }
metadata: { isPackageMarker: boolean; defaultUrl: string | undefined }
) => void | Promise<void>
): Promise<void> {
for (const [relativeFilepath, file] of entries(getAllDefinitionFiles(workspace.definition))) {
await visitor(relativeFilepath, file.contents, {
isPackageMarker: path.basename(relativeFilepath) === FERN_PACKAGE_MARKER_FILENAME
isPackageMarker: path.basename(relativeFilepath) === FERN_PACKAGE_MARKER_FILENAME,
defaultUrl: file.defaultUrl
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export function validateStructureOfYamlFiles({
const maybeValidFileContents = RootApiFileSchema.safeParse(parsedFileContents);
if (maybeValidFileContents.success) {
rootApiFile = {
defaultUrl: maybeValidFileContents.data["default-url"],
contents: maybeValidFileContents.data,
rawContents: file.rawContents
};
Expand All @@ -67,6 +68,10 @@ export function validateStructureOfYamlFiles({
const maybeValidFileContents = PackageMarkerFileSchema.safeParse(parsedFileContents);
if (maybeValidFileContents.success) {
packageMarkers[relativeFilepath] = {
defaultUrl:
typeof maybeValidFileContents.data.export === "object"
? maybeValidFileContents.data.export.url
: undefined,
contents: maybeValidFileContents.data,
rawContents: file.rawContents
};
Expand All @@ -77,6 +82,7 @@ export function validateStructureOfYamlFiles({
const maybeValidFileContents = DefinitionFileSchema.safeParse(parsedFileContents);
if (maybeValidFileContents.success) {
namesDefinitionFiles[relativeFilepath] = {
defaultUrl: undefined,
contents: maybeValidFileContents.data,
rawContents: file.rawContents,
absoluteFilepath: join(absolutePathToDefinition, relativeFilepath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export class OSSWorkspace extends AbstractAPIWorkspace<OSSWorkspace.Settings> {
// these files doesn't live on disk, so there's no absolute filepath
absoluteFilepath: AbsoluteFilePath.of("/DUMMY_PATH"),
rootApiFile: {
defaultUrl: definition.rootApiFile["default-url"],
contents: definition.rootApiFile,
rawContents: yaml.dump(definition.rootApiFile)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export const ValidServiceUrlsRule: Rule = {
return [];
}

if (urlIds.length === 0 && workspace.definition.rootApiFile.contents?.["default-url"] != null) {
return [];
}

if (urlIds.length === 0) {
return [
{
Expand Down Expand Up @@ -44,6 +48,10 @@ export const ValidServiceUrlsRule: Rule = {
return validateBaseUrl(url);
},
endpointBaseUrl: ({ baseUrl, service }) => {
if (workspace.definition.rootApiFile.contents?.["default-url"]) {
return [];
}

if (baseUrl == null) {
if (urlIds.length === 0 || service.url != null) {
return [];
Expand All @@ -59,15 +67,6 @@ export const ValidServiceUrlsRule: Rule = {
];
}

if (service.url != null) {
return [
{
severity: "error",
message: '"url" cannot be specified on both the service and endpoint'
}
];
}

return validateBaseUrl(baseUrl);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function visitPackageMarkerYamlAst(
errors: noop,
channel: noop,
export: async (export_) => {
await visitor.export?.(export_, ["export"]);
await visitor.export?.(typeof export_ === "string" ? export_ : export_?.dependency, ["export"]);
},
navigation: async (navigation) => {
await visitor.navigation?.(navigation, ["navigation"]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export async function visitRootApiFileYamlAst(
await visitObject(contents, {
version: noop,
name: noop,
"default-url": noop,
"display-name": noop,
imports: noop,
auth: noop,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { z } from "zod";
import { DefinitionFileSchema } from "./DefinitionFileSchema";

export const ExportSchema = z.object({
dependency: z.string(),
url: z.optional(z.string())
});

export type ExportSchema = z.infer<typeof ExportSchema>;

export const PackageMarkerFileSchema = DefinitionFileSchema.extend({
export: z.optional(z.string()),
export: z.optional(z.union([z.string(), ExportSchema])),
navigation: z.optional(z.union([z.string(), z.array(z.string())]))
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const RootApiFileSchema = z.strictObject({
auth: z.optional(ApiAuthSchema),
"auth-schemes": z.optional(z.record(AuthSchemeDeclarationSchema)),
headers: z.optional(z.record(z.string(), HttpHeaderSchema)),
"default-url": z.optional(z.string()),
"default-environment": z.optional(z.string().or(z.null())),
environments: z.optional(z.record(z.string(), EnvironmentSchema)),
"error-discrimination": z.optional(ErrorDiscriminationSchema),
Expand Down

0 comments on commit 8595d8b

Please sign in to comment.