-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add OpenAPI v3 parser and loader (#1)
- Loading branch information
1 parent
96126a4
commit 9892029
Showing
17 changed files
with
329 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {SpecLoader} from "@/core/loader"; | ||
import {ParserFactory} from "@/core/parser"; | ||
import type {DocutopiaParserOutput} from "@/types/output"; | ||
|
||
export class DocutopiaParser { | ||
public static async parse(source: string): Promise<DocutopiaParserOutput> { | ||
const spec = await SpecLoader.load(source); | ||
const parser = ParserFactory.createParser(spec); | ||
return parser.parse(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as yaml from "js-yaml"; | ||
import type {OpenAPISpec} from "@/types/openapi"; | ||
|
||
export class SpecLoader { | ||
static async load(source: string): Promise<OpenAPISpec> { | ||
if (SpecLoader.isUrl(source)) { | ||
return SpecLoader.loadFromUrl(source); | ||
} | ||
throw new Error("Invalid source provided. Please provide a valid URL."); | ||
} | ||
|
||
private static isUrl(source: string): boolean { | ||
return /^https?:\/\//.test(source); | ||
} | ||
|
||
private static async loadFromUrl(url: string): Promise<OpenAPISpec> { | ||
const response = await fetch(url); | ||
if (!response.ok) { | ||
throw new Error(`Failed to fetch OpenAPI spec from URL: ${url}`); | ||
} | ||
const contentType = response.headers.get("content-type"); | ||
const text = await response.text(); | ||
|
||
if (contentType?.includes("application/json")) { | ||
return <OpenAPISpec>JSON.parse(text); | ||
} else if ( | ||
contentType?.includes("application/x-yaml") || | ||
contentType?.includes("text/yaml") | ||
) { | ||
return <OpenAPISpec>yaml.load(text); | ||
} else { | ||
throw new Error("Unsupported content type. Please provide JSON or YAML."); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type {OpenAPISpec} from "@/types/openapi"; | ||
import type {BaseParser} from "@/parsers/base"; | ||
import {OpenAPIParserV3} from "@/parsers/openapi-v3"; | ||
|
||
export class ParserFactory { | ||
static createParser(spec: OpenAPISpec): BaseParser { | ||
const version = spec.openapi; | ||
if (version.startsWith("3.0")) { | ||
return new OpenAPIParserV3(spec); | ||
} | ||
throw new Error(`Unsupported OpenAPI version: ${version}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from "./parser"; | ||
export * from "./core/docutopia"; | ||
export * from "./types/output"; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type {OpenAPISpec} from "@/types/openapi"; | ||
import type {BaseResolver} from "@/resolvers/base"; | ||
import {ResolverFactory} from "@/resolvers/factory"; | ||
import type {DocutopiaParserOutput} from "@/types/output"; | ||
|
||
export abstract class BaseParser { | ||
protected spec: OpenAPISpec; | ||
protected resolver: BaseResolver; | ||
|
||
constructor(spec: OpenAPISpec) { | ||
this.spec = spec; | ||
this.resolver = ResolverFactory.createResolver(spec); | ||
} | ||
|
||
public abstract parse(): DocutopiaParserOutput; | ||
|
||
protected replaceRefs(obj: any): any { | ||
return this.resolver.replaceRefs(obj); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import type {DocutopiaParserOutput} from "@/types/output"; | ||
import {BaseParser} from "@/parsers/base"; | ||
|
||
export class OpenAPIParserV3 extends BaseParser { | ||
private groups: Array<any> = []; | ||
|
||
public parse(): DocutopiaParserOutput { | ||
this.groups = this.parseGroups(); | ||
|
||
return { | ||
info: this.parseInfo(), | ||
servers: this.parseServers(), | ||
groups: this.groups, | ||
schemas: this.parseSchemas(), | ||
sidebar: this.generateSidebar(this.groups), | ||
}; | ||
} | ||
|
||
private parseInfo() { | ||
return this.spec.info; | ||
} | ||
|
||
private parseServers() { | ||
return this.spec.servers; | ||
} | ||
|
||
private parseGroups() { | ||
const groupsMap: { [key: string]: any } = {}; | ||
|
||
for (const path in this.spec.paths) { | ||
for (const method in this.spec.paths[path]) { | ||
const operation = this.spec.paths[path][method]; | ||
const group = operation.tags?.[0] || "Default Group"; | ||
const summary = operation.summary || ""; | ||
const description = operation.description || ""; | ||
|
||
if (!groupsMap[group]) { | ||
groupsMap[group] = { | ||
group, | ||
description: group, | ||
endpoints: [], | ||
}; | ||
} | ||
|
||
const endpoint = { | ||
path, | ||
method: method.toUpperCase(), | ||
summary, | ||
description, | ||
parameters: operation.parameters || [], | ||
requestBody: this.replaceRefs(operation.requestBody || null), | ||
responses: this.replaceRefs(operation.responses || {}), | ||
}; | ||
|
||
groupsMap[group].endpoints.push(endpoint); | ||
} | ||
} | ||
|
||
return Object.values(groupsMap); | ||
} | ||
|
||
private generateSidebar(groups: Array<any>) { | ||
return groups.map((group) => ({ | ||
group: group.group, | ||
description: group.description, | ||
endpoints: group.endpoints.map((endpoint: any) => ({ | ||
method: endpoint.method, | ||
description: endpoint.summary, | ||
path: `#${endpoint.method.toLowerCase()}-${endpoint.path.replace(/\//g, "-")}`, | ||
})), | ||
})); | ||
} | ||
|
||
private parseSchemas() { | ||
return this.spec.components?.schemas || {}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import type {OpenAPISpec} from "@/types/openapi"; | ||
|
||
export abstract class BaseResolver { | ||
protected spec: OpenAPISpec; | ||
|
||
constructor(spec: OpenAPISpec) { | ||
this.spec = spec; | ||
} | ||
|
||
public abstract resolveRef(ref: string): any; | ||
|
||
public replaceRefs(obj: any): any { | ||
if (typeof obj === "object" && obj !== null) { | ||
if (obj.$ref) { | ||
return this.resolveRef(obj.$ref); | ||
} | ||
for (const key in obj) { | ||
obj[key] = this.replaceRefs(obj[key]); | ||
} | ||
} | ||
return obj; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type {OpenAPISpec} from "@/types/openapi"; | ||
import type {BaseResolver} from "@/resolvers/base"; | ||
import {OpenAPIResolverV3} from "@/resolvers/openapi-v3"; | ||
|
||
export class ResolverFactory { | ||
public static createResolver(spec: OpenAPISpec): BaseResolver { | ||
const version = spec.openapi; | ||
if (version.startsWith("3.0")) { | ||
return new OpenAPIResolverV3(spec); | ||
} | ||
throw new Error(`Unsupported OpenAPI version: ${version}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import {BaseResolver} from "@/resolvers/base"; | ||
|
||
export class OpenAPIResolverV3 extends BaseResolver { | ||
public resolveRef(ref: string): any { | ||
const [_, type, name] = ref.split("/"); | ||
if ( | ||
type === "components" && | ||
this.spec.components && | ||
this.spec.components.schemas | ||
) { | ||
return this.spec.components.schemas[name]; | ||
} | ||
throw new Error(`Reference ${ref} not found`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
export interface OpenAPISpec { | ||
openapi: string; | ||
info: { | ||
title: string; | ||
description: string; | ||
version: string; | ||
}; | ||
servers: Array<{ | ||
url: string; | ||
description: string; | ||
}>; | ||
paths: { | ||
[path: string]: { | ||
[method: string]: { | ||
tags?: string[]; | ||
summary?: string; | ||
description?: string; | ||
parameters?: any[]; | ||
requestBody?: any; | ||
responses?: any; | ||
}; | ||
}; | ||
}; | ||
components?: { | ||
schemas?: { | ||
[key: string]: any; | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
export interface DocutopiaParserOutput { | ||
info: { | ||
title: string; | ||
description: string; | ||
version: string; | ||
}; | ||
servers: Array<{ | ||
url: string; | ||
description: string; | ||
}>; | ||
groups: Array<{ | ||
group: string; | ||
description: string; | ||
endpoints: Array<{ | ||
path: string; | ||
method: string; | ||
summary: string; | ||
description: string; | ||
parameters: any[]; | ||
requestBody: any; | ||
responses: any; | ||
}>; | ||
}>; | ||
schemas: { | ||
[key: string]: any; | ||
}; | ||
sidebar: Array<{ | ||
group: string; | ||
description: string; | ||
endpoints: Array<{ | ||
method: string; | ||
description: string; | ||
path: string; | ||
}>; | ||
}>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.