Skip to content

Commit

Permalink
Merge pull request #13 from gentlementlegen/feat/schema-validation
Browse files Browse the repository at this point in the history
feat: schema validation
  • Loading branch information
gentlementlegen authored Oct 4, 2024
2 parents 66f5f60 + c5028d4 commit b0498d3
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 82 deletions.
58 changes: 58 additions & 0 deletions .github/workflows/update-configuration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: "Update Configuration"

on:
workflow_dispatch:
push:

jobs:
update:
name: "Update Configuration in manifest.json"
runs-on: ubuntu-latest
permissions: write-all

steps:
- uses: actions/checkout@v4

- name: Setup node
uses: actions/setup-node@v4
with:
node-version: "20.10.0"

- name: Install deps and run configuration update
run: |
yarn install --immutable --immutable-cache --check-cache
yarn tsc --noCheck --project tsconfig.json
- name: Update manifest configuration using GitHub Script
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const { pluginSettingsSchema } = require('./src/types/plugin-input');
const manifestPath = path.resolve("${{ github.workspace }}", './manifest.json');
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
const configuration = JSON.stringify(pluginSettingsSchema);
manifest["configuration"] = JSON.parse(configuration);
const updatedManifest = JSON.stringify(manifest, null, 2)
console.log('Updated manifest:', updatedManifest);
fs.writeFileSync(manifestPath, updatedManifest);
- name: Commit and Push generated types
run: |
git config --global user.name 'ubiquity-os[bot]'
git config --global user.email 'ubiquity-os[bot]@users.noreply.github.com'
git add ./manifest.json
if [ -n "$(git diff-index --cached --name-only HEAD)" ]; then
git commit -m "chore: updated generated configuration" || echo "Lint-staged check failed"
git push origin HEAD:${{ github.ref_name }}
else
echo "No changes to commit"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/worker-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
id: wrangler_deploy
uses: cloudflare/wrangler-action@v3
with:
wranglerVersion: "3.61.0"
wranglerVersion: "3.79.0"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
secrets: |
Expand Down
18 changes: 16 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
{
"name": "Query user",
"description": "Queries a user and retrieves its related information, such as the wallet, the label access control, or the current XP.",
"ubiquity:listeners": [ "issue_comment.created" ],
"ubiquity:listeners": [
"issue_comment.created"
],
"commands": {
"query": {
"ubiquity:example": "/query @ubiquibot",
"description": "Returns the user's wallet, access, and multiplier information."
}
},
"configuration": {
"type": "object",
"properties": {
"allowPublicQuery": {
"default": true,
"type": "boolean"
}
},
"required": [
"allowPublicQuery"
]
}
}
}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"@octokit/webhooks": "13.2.7",
"@sinclair/typebox": "0.32.30",
"@supabase/supabase-js": "2.43.2",
"commander": "12.1.0"
"commander": "12.1.0",
"typebox-validators": "0.3.5"
},
"devDependencies": {
"@commitlint/cli": "^18.6.1",
Expand Down Expand Up @@ -67,8 +68,8 @@
"prettier": "^3.2.5",
"supabase": "1.167.4",
"ts-jest": "29.1.2",
"typescript": "^5.3.3",
"wrangler": "3.57.0"
"typescript": "5.6.2",
"wrangler": "3.79.0"
},
"lint-staged": {
"*.ts": [
Expand Down
37 changes: 37 additions & 0 deletions src/handlers/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { TransformDecodeCheckError, TransformDecodeError, Value, ValueError } from "@sinclair/typebox/value";
import { Env, envConfigValidator, envSchema } from "../types/env";
import { CommandQuerySettings, pluginSettingsSchema, commandQueryUserSchemaValidator } from "../types/plugin-input";

export function validateAndDecodeSchemas(env: Env, rawSettings: object) {
const errors: ValueError[] = [];
const settings = Value.Default(pluginSettingsSchema, rawSettings) as CommandQuerySettings;

if (!commandQueryUserSchemaValidator.test(settings)) {
for (const error of commandQueryUserSchemaValidator.errors(settings)) {
console.error(error);
errors.push(error);
}
}

if (!envConfigValidator.test(env)) {
for (const error of envConfigValidator.errors(env)) {
console.error(error);
errors.push(error);
}
}

if (errors.length) {
throw { errors };
}

try {
const decodedEnv = Value.Decode(envSchema, env);
const decodedSettings = Value.Decode(pluginSettingsSchema, settings);
return { decodedEnv, decodedSettings };
} catch (e) {
if (e instanceof TransformDecodeCheckError || e instanceof TransformDecodeError) {
throw { errors: [e.error] };
}
throw e;
}
}
3 changes: 3 additions & 0 deletions src/types/env.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Type as T, StaticDecode } from "@sinclair/typebox";
import { StandardValidator } from "typebox-validators";

export const envSchema = T.Object({
SUPABASE_URL: T.String(),
SUPABASE_KEY: T.String(),
});

export const envConfigValidator = new StandardValidator(envSchema);

export type Env = StaticDecode<typeof envSchema>;
7 changes: 5 additions & 2 deletions src/types/plugin-input.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EmitterWebhookEvent as WebhookEvent, EmitterWebhookEventName as WebhookEventName } from "@octokit/webhooks";
import { StandardValidator } from "typebox-validators";
import { SupportedEvents } from "./context";
import { StaticDecode, Type as T } from "@sinclair/typebox";

Expand All @@ -11,11 +12,13 @@ export interface PluginInputs<T extends WebhookEventName = SupportedEvents> {
ref: string;
}

export const commandQueryUserScheme = T.Object({
export const pluginSettingsSchema = T.Object({
/**
* Allows any user to query anyone else. If false, only org members can query others.
*/
allowPublicQuery: T.Boolean({ default: true }),
});

export type CommandQuerySettings = StaticDecode<typeof commandQueryUserScheme>;
export const commandQueryUserSchemaValidator = new StandardValidator(pluginSettingsSchema);

export type CommandQuerySettings = StaticDecode<typeof pluginSettingsSchema>;
28 changes: 13 additions & 15 deletions src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { Value } from "@sinclair/typebox/value";
import manifest from "../manifest.json";
import { validateAndDecodeSchemas } from "./handlers/validator";
import { run } from "./run";
import { Env } from "./types/env";
import { commandQueryUserScheme } from "./types/plugin-input";
import manifest from "../manifest.json";

export default {
async fetch(request: Request, env: Env): Promise<Response> {
try {
if (request.method === "GET") {
const url = new URL(request.url);
if (url.pathname === "/manifest.json") {
return new Response(JSON.stringify(manifest), {
headers: { "content-type": "application/json" },
});
}
const url = new URL(request.url);
if (url.pathname === "/manifest" && request.method === "GET") {
return new Response(JSON.stringify(manifest), {
headers: { "content-type": "application/json" },
});
}
if (request.method !== "POST") {
return new Response(JSON.stringify({ error: `Only POST requests are supported.` }), {
Expand All @@ -29,17 +26,18 @@ export default {
});
}
const webhookPayload = await request.json();
webhookPayload.settings = Value.Decode(commandQueryUserScheme, Value.Default(commandQueryUserScheme, webhookPayload.settings));
await run(webhookPayload, env);
const result = validateAndDecodeSchemas(env, webhookPayload.settings);
webhookPayload.settings = result.decodedSettings;
await run(webhookPayload, result.decodedEnv);
return new Response(JSON.stringify("OK"), { status: 200, headers: { "content-type": "application/json" } });
} catch (error) {
return handleUncaughtError(error);
}
},
};

function handleUncaughtError(error: unknown) {
console.error(error);
function handleUncaughtError(errors: unknown) {
console.error(errors);
const status = 500;
return new Response(JSON.stringify({ error }), { status: status, headers: { "content-type": "application/json" } });
return new Response(JSON.stringify(errors), { status: status, headers: { "content-type": "application/json" } });
}
Loading

0 comments on commit b0498d3

Please sign in to comment.