Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,11 @@ jobs:
- name: Build project
run: npm run build

- name: Generate schema
run: SKIP_VALIDATION=true npm run generate

- name: Validate generated schema
run: npm run validate

- name: Run tests
run: npm test
14 changes: 13 additions & 1 deletion dist/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7813,7 +7813,19 @@
"securitySchemes": {
"OAuth2": {
"type": "oauth2",
"description": "OAuth 2.0 authentication"
"description": "OAuth 2.0 authentication",
"flows": {
"authorizationCode": {
"authorizationUrl": "https://mastodon.example/oauth/authorize",
"tokenUrl": "https://mastodon.example/oauth/token",
"scopes": {
"read": "Read access",
"write": "Write access",
"follow": "Follow access",
"push": "Push access"
}
}
}
},
"BearerAuth": {
"type": "http",
Expand Down
119 changes: 119 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
"generate": "ts-node src/generate.ts",
"validate": "ts-node src/validate.ts",
"test": "jest",
"test:watch": "jest --watch",
"clean": "rm -rf dist",
Expand All @@ -34,6 +35,7 @@
"typescript": "^5.0.0"
},
"dependencies": {
"@seriousme/openapi-schema-validator": "^2.4.1",
"gray-matter": "^4.0.3"
}
}
10 changes: 8 additions & 2 deletions src/__tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ describe('main', () => {
}
});

it('should generate and write schema.json to dist directory', () => {
it('should generate and write schema.json to dist directory', async () => {
// Set skip validation to avoid ES module import issues in tests
process.env.SKIP_VALIDATION = 'true';

// Run the main function
main();
await main();

// Check that the file was created
expect(fs.existsSync(schemaPath)).toBe(true);
Expand All @@ -30,5 +33,8 @@ describe('main', () => {
expect(schema.paths).toBeDefined();
expect(schema.components).toBeDefined();
expect(schema.components.schemas).toBeDefined();

// Clean up env var
delete process.env.SKIP_VALIDATION;
});
});
12 changes: 12 additions & 0 deletions src/generators/OpenAPIGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ class OpenAPIGenerator {
OAuth2: {
type: 'oauth2',
description: 'OAuth 2.0 authentication',
flows: {
authorizationCode: {
authorizationUrl: 'https://mastodon.example/oauth/authorize',
tokenUrl: 'https://mastodon.example/oauth/token',
scopes: {
read: 'Read access',
write: 'Write access',
follow: 'Follow access',
push: 'Push access',
},
},
},
},
BearerAuth: {
type: 'http',
Expand Down
19 changes: 19 additions & 0 deletions src/interfaces/OpenAPISchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@ interface OpenAPISecurityScheme {
scheme?: string;
bearerFormat?: string;
description?: string;
flows?: {
authorizationCode?: {
authorizationUrl: string;
tokenUrl: string;
scopes: Record<string, string>;
};
implicit?: {
authorizationUrl: string;
scopes: Record<string, string>;
};
password?: {
tokenUrl: string;
scopes: Record<string, string>;
};
clientCredentials?: {
tokenUrl: string;
scopes: Record<string, string>;
};
};
}

interface OpenAPIProperty {
Expand Down
29 changes: 27 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,26 @@ import { OpenAPIGenerator } from './generators/OpenAPIGenerator';
import * as fs from 'fs';
import * as path from 'path';

function main() {
async function validateSchemaIfEnabled(schemaPath: string): Promise<void> {
const shouldSkipValidation = process.env.SKIP_VALIDATION === 'true';
if (shouldSkipValidation) {
console.log('⏭️ Schema validation skipped (SKIP_VALIDATION=true)');
return;
}

try {
// Dynamic import to avoid Jest ES module issues
const { validateSchema } = await import('./validator');
await validateSchema(schemaPath, true);
} catch (error) {
if (error instanceof Error) {
throw error;
}
throw new Error(`Schema validation failed: ${String(error)}`);
}
}

async function main() {
console.log('Parsing Mastodon entity files...');

const parser = new EntityParser();
Expand Down Expand Up @@ -44,10 +63,16 @@ function main() {
// Write schema to file
fs.writeFileSync(schemaPath, generator.toJSON());
console.log(`Schema written to ${schemaPath}`);

// Validate the generated schema
await validateSchemaIfEnabled(schemaPath);
}

if (require.main === module) {
main();
main().catch(error => {
console.error('Error:', error.message);
process.exit(1);
});
}

export { main };
14 changes: 14 additions & 0 deletions src/validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { validateSchema } from './validator';
import * as path from 'path';

async function main() {
const schemaPath = path.join(__dirname, '..', 'dist', 'schema.json');
await validateSchema(schemaPath, true);
}

if (require.main === module) {
main().catch(error => {
console.error('Error:', error.message);
process.exit(1);
});
}
46 changes: 46 additions & 0 deletions src/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Validator } from '@seriousme/openapi-schema-validator';
import * as fs from 'fs';

export async function validateSchema(schemaPath: string, exitOnError: boolean = true): Promise<boolean> {
console.log('Validating OpenAPI schema...');

try {
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
const schema = JSON.parse(schemaContent);

const validator = new Validator();
const result = await validator.validate(schema);

if (result.valid) {
console.log('✅ Schema validation passed');
return true;
} else {
console.error('❌ Schema validation failed with the following errors:');
const errors = result.errors;
if (Array.isArray(errors)) {
errors.forEach((error: any, index: number) => {
console.error(` ${index + 1}. ${error.instancePath}: ${error.message}`);
});
if (exitOnError) {
throw new Error(`Schema validation failed with ${errors.length} error(s)`);
}
} else if (typeof errors === 'string') {
console.error(` 1. ${errors}`);
if (exitOnError) {
throw new Error(`Schema validation failed: ${errors}`);
}
} else {
console.error('Unknown error format:', errors);
if (exitOnError) {
throw new Error('Schema validation failed with unknown errors');
}
}
return false;
}
} catch (error) {
if (error instanceof Error) {
throw error;
}
throw new Error(`Schema validation failed: ${String(error)}`);
}
}
Loading