Generate TypeScript types for the Data Connector reference agent from the OpenAPI schema

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4855
GitOrigin-RevId: 4cc09d6706e67c69fbbedef72ff816365b9f7b4e
This commit is contained in:
Daniel Chambers 2022-06-29 17:42:51 +10:00 committed by hasura-bot
parent 4b2ca2ea63
commit 59ffce9ac1
70 changed files with 2476 additions and 864 deletions

View File

@ -32,8 +32,8 @@ constraints: any.Cabal ==3.2.1.0,
any.attoparsec-iso8601 ==1.0.2.1, any.attoparsec-iso8601 ==1.0.2.1,
any.authenticate-oauth ==1.7, any.authenticate-oauth ==1.7,
any.auto-update ==0.1.6, any.auto-update ==0.1.6,
any.autodocodec ==0.1.0.0, any.autodocodec ==0.1.0.1,
any.autodocodec-openapi3 ==0.2.0.0, any.autodocodec-openapi3 ==0.2.1.0,
any.barbies ==2.0.3.1, any.barbies ==2.0.3.1,
any.base ==4.14.3.0, any.base ==4.14.3.0,
any.base-compat ==0.11.2, any.base-compat ==0.11.2,
@ -352,4 +352,4 @@ constraints: any.Cabal ==3.2.1.0,
any.xml-types ==0.3.8, any.xml-types ==0.3.8,
any.yaml ==0.11.7.0, any.yaml ==0.11.7.0,
any.zlib ==0.6.2.3, any.zlib ==0.6.2.3,
index-state: hackage.haskell.org 2022-06-13T15:16:34Z index-state: hackage.haskell.org 2022-06-20T06:51:52Z

View File

@ -210,7 +210,7 @@ Notice that the names of tables and columns are used in the metadata document to
#### Type definitions #### Type definitions
The `SchemaResponse` TypeScript type from [the reference implementation](./reference/src/types/schema.ts) describes the valid response body for the `GET /schema` endpoint. The `SchemaResponse` TypeScript type from [the reference implementation](./reference/src/types/index.ts) describes the valid response body for the `GET /schema` endpoint.
### Responding to queries ### Responding to queries
@ -672,4 +672,4 @@ The key point of interest here is in the `where` field where we are comparing be
#### Type Definitions #### Type Definitions
The `QueryRequest` TypeScript type in the [reference implementation](./reference/src/types/query.ts) describes the valid request body payloads which may be passed to the `POST /query` endpoint. The response body structure is captured by the `QueryResponse` type. The `QueryRequest` TypeScript type in the [reference implementation](./reference/src/types/index.ts) describes the valid request body payloads which may be passed to the `POST /query` endpoint. The response body structure is captured by the `QueryResponse` type.

File diff suppressed because it is too large Load Diff

View File

@ -18,12 +18,12 @@
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"start": "ts-node ./src/index.ts", "start": "ts-node ./src/index.ts",
"start-no-typecheck": "ts-node --transpileOnly ./src/index.ts", "start-no-typecheck": "ts-node --transpileOnly ./src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1",
"generate-types": "./scripts/generate-types.sh"
}, },
"dependencies": { "dependencies": {
"@fastify/cors": "^7.0.0", "@fastify/cors": "^7.0.0",
"fastify": "^3.29.0", "fastify": "^3.29.0",
"openapi3-ts": "^2.0.2",
"pino-pretty": "^8.0.0", "pino-pretty": "^8.0.0",
"xml2js": "^0.4.23" "xml2js": "^0.4.23"
}, },
@ -31,6 +31,7 @@
"@tsconfig/node16": "^1.0.2", "@tsconfig/node16": "^1.0.2",
"@types/node": "^16.11.38", "@types/node": "^16.11.38",
"@types/xml2js": "^0.4.11", "@types/xml2js": "^0.4.11",
"openapi-typescript-codegen": "^0.23.0",
"ts-node": "^10.8.1", "ts-node": "^10.8.1",
"typescript": "^4.7.3" "typescript": "^4.7.3"
} }

View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
PROJECT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/dev/null 2>&1 && pwd )" # ... https://stackoverflow.com/a/246128/176841
cd "$PROJECT_ROOT"
TYPES_DIR="./src/types"
SCHEMA_FILE="$TYPES_DIR/agent.openapi.json"
mkdir -p $TYPES_DIR
if [ ! -f $SCHEMA_FILE ] ; then
echo "$SCHEMA_FILE does not exist, re-generating it using the agent test suite"
cabal run test:tests-dc-api -- export-openapi-spec | tail -n 1 | jq > $SCHEMA_FILE
fi
echo "Deleting existing generated model..."
rm -rf "$TYPES_DIR/models"
rm -f "$TYPES_DIR/index.ts"
echo "Generating model from $SCHEMA_FILE..."
openapi --useUnionTypes --input $SCHEMA_FILE --output $TYPES_DIR --exportServices false --exportCore false --indent 2

View File

@ -1,17 +1,7 @@
import { ConfigSchemaResponse, configSchema } from "./config" import { configSchema } from "./config"
import { CapabilitiesResponse } from "./types"
export type Relationships = {}
export type Capabilities = {
relationships: Relationships
}
export type CapabilitiesResponse = {
capabilities: Capabilities,
configSchemas: ConfigSchemaResponse,
}
export const capabilitiesResponse: CapabilitiesResponse = { export const capabilitiesResponse: CapabilitiesResponse = {
capabilities: { relationships: {}}, capabilities: { relationships: {} },
configSchemas: configSchema configSchemas: configSchema
} }

View File

@ -1,15 +1,10 @@
import { FastifyRequest } from "fastify" import { FastifyRequest } from "fastify"
import { SchemaObject } from "openapi3-ts" import { ConfigSchemaResponse } from "./types"
export type Config = { export type Config = {
tables: String[] | null tables: String[] | null
} }
export type ConfigSchemaResponse = {
configSchema: SchemaObject,
otherSchemas: { [schemaName: string]: SchemaObject },
}
export const getConfig = (request: FastifyRequest): Config => { export const getConfig = (request: FastifyRequest): Config => {
const configHeader = request.headers["x-hasura-dataconnector-config"]; const configHeader = request.headers["x-hasura-dataconnector-config"];
const rawConfigJson = Array.isArray(configHeader) ? configHeader[0] : configHeader ?? "{}"; const rawConfigJson = Array.isArray(configHeader) ? configHeader[0] : configHeader ?? "{}";

View File

@ -1,4 +1,4 @@
import { SchemaResponse, ScalarType } from "../types/schema" import { SchemaResponse } from "../types"
import { Config } from "../config"; import { Config } from "../config";
import xml2js from "xml2js" import xml2js from "xml2js"
import fs from "fs" import fs from "fs"
@ -43,13 +43,13 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "ArtistId", name: "ArtistId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Artist primary key identifier" description: "Artist primary key identifier"
}, },
{ {
name: "Name", name: "Name",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The name of the artist" description: "The name of the artist"
} }
@ -62,19 +62,19 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "AlbumId", name: "AlbumId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Album primary key identifier" description: "Album primary key identifier"
}, },
{ {
name: "Title", name: "Title",
type: ScalarType.String, type: "string",
nullable: false, nullable: false,
description: "The title of the album" description: "The title of the album"
}, },
{ {
name: "ArtistId", name: "ArtistId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "The ID of the artist that created this album" description: "The ID of the artist that created this album"
} }
@ -87,79 +87,79 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "CustomerId", name: "CustomerId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Customer primary key identifier" description: "Customer primary key identifier"
}, },
{ {
name: "FirstName", name: "FirstName",
type: ScalarType.String, type: "string",
nullable: false, nullable: false,
description: "The customer's first name" description: "The customer's first name"
}, },
{ {
name: "LastName", name: "LastName",
type: ScalarType.String, type: "string",
nullable: false, nullable: false,
description: "The customer's last name" description: "The customer's last name"
}, },
{ {
name: "Company", name: "Company",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The customer's company name" description: "The customer's company name"
}, },
{ {
name: "Address", name: "Address",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The customer's address line (street number, street)" description: "The customer's address line (street number, street)"
}, },
{ {
name: "City", name: "City",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The customer's address city" description: "The customer's address city"
}, },
{ {
name: "State", name: "State",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The customer's address state" description: "The customer's address state"
}, },
{ {
name: "Country", name: "Country",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The customer's address country" description: "The customer's address country"
}, },
{ {
name: "PostalCode", name: "PostalCode",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The customer's address postal code" description: "The customer's address postal code"
}, },
{ {
name: "Phone", name: "Phone",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The customer's phone number" description: "The customer's phone number"
}, },
{ {
name: "Fax", name: "Fax",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The customer's fax number" description: "The customer's fax number"
}, },
{ {
name: "Email", name: "Email",
type: ScalarType.String, type: "string",
nullable: false, nullable: false,
description: "The customer's email address" description: "The customer's email address"
}, },
{ {
name: "SupportRepId", name: "SupportRepId",
type: ScalarType.Number, type: "number",
nullable: true, nullable: true,
description: "The ID of the Employee who is this customer's support representative" description: "The ID of the Employee who is this customer's support representative"
} }
@ -172,85 +172,85 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "EmployeeId", name: "EmployeeId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Employee primary key identifier" description: "Employee primary key identifier"
}, },
{ {
name: "FirstName", name: "FirstName",
type: ScalarType.String, type: "string",
nullable: false, nullable: false,
description: "The employee's first name" description: "The employee's first name"
}, },
{ {
name: "LastName", name: "LastName",
type: ScalarType.String, type: "string",
nullable: false, nullable: false,
description: "The employee's last name" description: "The employee's last name"
}, },
{ {
name: "Title", name: "Title",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The employee's job title" description: "The employee's job title"
}, },
{ {
name: "BirthDate", name: "BirthDate",
type: ScalarType.String, // Ought to be DateTime but we don't have a type for this yet type: "string", // Ought to be DateTime but we don't have a type for this yet
nullable: true, nullable: true,
description: "The employee's birth date" description: "The employee's birth date"
}, },
{ {
name: "HireDate", name: "HireDate",
type: ScalarType.String, // Ought to be DateTime but we don't have a type for this yet type: "string", // Ought to be DateTime but we don't have a type for this yet
nullable: true, nullable: true,
description: "The employee's birth date" description: "The employee's birth date"
}, },
{ {
name: "Address", name: "Address",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The employee's address line (street number, street)" description: "The employee's address line (street number, street)"
}, },
{ {
name: "City", name: "City",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The employee's address city" description: "The employee's address city"
}, },
{ {
name: "State", name: "State",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The employee's address state" description: "The employee's address state"
}, },
{ {
name: "Country", name: "Country",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The employee's address country" description: "The employee's address country"
}, },
{ {
name: "PostalCode", name: "PostalCode",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The employee's address postal code" description: "The employee's address postal code"
}, },
{ {
name: "Phone", name: "Phone",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The employee's phone number" description: "The employee's phone number"
}, },
{ {
name: "Fax", name: "Fax",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The employee's fax number" description: "The employee's fax number"
}, },
{ {
name: "Email", name: "Email",
type: ScalarType.String, type: "string",
nullable: false, nullable: false,
description: "The employee's email address" description: "The employee's email address"
}, },
@ -263,13 +263,13 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "GenreId", name: "GenreId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Genre primary key identifier" description: "Genre primary key identifier"
}, },
{ {
name: "Name", name: "Name",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The name of the genre" description: "The name of the genre"
} }
@ -282,55 +282,55 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "InvoiceId", name: "InvoiceId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Invoice primary key identifier" description: "Invoice primary key identifier"
}, },
{ {
name: "CustomerId", name: "CustomerId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "ID of the customer who bought the music" description: "ID of the customer who bought the music"
}, },
{ {
name: "InvoiceDate", name: "InvoiceDate",
type: ScalarType.String, // Ought to be DateTime but we don't have a type for this yet type: "string", // Ought to be DateTime but we don't have a type for this yet
nullable: false, nullable: false,
description: "Date of the invoice" description: "Date of the invoice"
}, },
{ {
name: "BillingAddress", name: "BillingAddress",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The invoice's billing address line (street number, street)" description: "The invoice's billing address line (street number, street)"
}, },
{ {
name: "BillingCity", name: "BillingCity",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The invoice's billing address city" description: "The invoice's billing address city"
}, },
{ {
name: "BillingState", name: "BillingState",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The invoice's billing address state" description: "The invoice's billing address state"
}, },
{ {
name: "BillingCountry", name: "BillingCountry",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The invoice's billing address country" description: "The invoice's billing address country"
}, },
{ {
name: "BillingPostalCode", name: "BillingPostalCode",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The invoice's billing address postal code" description: "The invoice's billing address postal code"
}, },
{ {
name: "Total", name: "Total",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "The total amount due on the invoice" description: "The total amount due on the invoice"
}, },
@ -343,31 +343,31 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "InvoiceLineId", name: "InvoiceLineId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Invoice Line primary key identifier" description: "Invoice Line primary key identifier"
}, },
{ {
name: "InvoiceId", name: "InvoiceId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "ID of the invoice the line belongs to" description: "ID of the invoice the line belongs to"
}, },
{ {
name: "TrackId", name: "TrackId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "ID of the music track being purchased" description: "ID of the music track being purchased"
}, },
{ {
name: "UnitPrice", name: "UnitPrice",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Price of each individual track unit" description: "Price of each individual track unit"
}, },
{ {
name: "Quantity", name: "Quantity",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Quantity of the track purchased" description: "Quantity of the track purchased"
}, },
@ -380,13 +380,13 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "MediaTypeId", name: "MediaTypeId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Media Type primary key identifier" description: "Media Type primary key identifier"
}, },
{ {
name: "Name", name: "Name",
type: ScalarType.Number, type: "number",
nullable: true, nullable: true,
description: "The name of the media type format" description: "The name of the media type format"
}, },
@ -399,13 +399,13 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "PlaylistId", name: "PlaylistId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "Playlist primary key identifier" description: "Playlist primary key identifier"
}, },
{ {
name: "Name", name: "Name",
type: ScalarType.Number, type: "number",
nullable: true, nullable: true,
description: "The name of the playlist" description: "The name of the playlist"
}, },
@ -419,13 +419,13 @@ const schema: SchemaResponse = {
// columns: [ // columns: [
// { // {
// name: "PlaylistId", // name: "PlaylistId",
// type: ScalarType.Number, // type: "number",
// nullable: false, // nullable: false,
// description: "The ID of the playlist" // description: "The ID of the playlist"
// }, // },
// { // {
// name: "TrackId", // name: "TrackId",
// type: ScalarType.Number, // type: "number",
// nullable: true, // nullable: true,
// description: "The ID of the track" // description: "The ID of the track"
// }, // },
@ -438,55 +438,55 @@ const schema: SchemaResponse = {
columns: [ columns: [
{ {
name: "TrackId", name: "TrackId",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "The ID of the track" description: "The ID of the track"
}, },
{ {
name: "Name", name: "Name",
type: ScalarType.String, type: "string",
nullable: false, nullable: false,
description: "The name of the track" description: "The name of the track"
}, },
{ {
name: "AlbumId", name: "AlbumId",
type: ScalarType.Number, type: "number",
nullable: true, nullable: true,
description: "The ID of the album the track belongs to" description: "The ID of the album the track belongs to"
}, },
{ {
name: "MediaTypeId", name: "MediaTypeId",
type: ScalarType.Number, type: "number",
nullable: true, nullable: true,
description: "The ID of the media type the track is encoded with" description: "The ID of the media type the track is encoded with"
}, },
{ {
name: "GenreId", name: "GenreId",
type: ScalarType.Number, type: "number",
nullable: true, nullable: true,
description: "The ID of the genre of the track" description: "The ID of the genre of the track"
}, },
{ {
name: "Composer", name: "Composer",
type: ScalarType.String, type: "string",
nullable: true, nullable: true,
description: "The name of the composer of the track" description: "The name of the composer of the track"
}, },
{ {
name: "Milliseconds", name: "Milliseconds",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "The length of the track in milliseconds" description: "The length of the track in milliseconds"
}, },
{ {
name: "Bytes", name: "Bytes",
type: ScalarType.Number, type: "number",
nullable: true, nullable: true,
description: "The size of the track in bytes" description: "The size of the track in bytes"
}, },
{ {
name: "UnitPrice", name: "UnitPrice",
type: ScalarType.Number, type: "number",
nullable: false, nullable: false,
description: "The price of the track" description: "The price of the track"
}, },

View File

@ -1,11 +1,10 @@
import Fastify from 'fastify'; import Fastify from 'fastify';
import FastifyCors from '@fastify/cors'; import FastifyCors from '@fastify/cors';
import { SchemaResponse } from './types/schema';
import { ProjectedRow, QueryRequest } from './types/query';
import { filterAvailableTables, getSchema, loadStaticData } from './data'; import { filterAvailableTables, getSchema, loadStaticData } from './data';
import { queryData } from './query'; import { queryData } from './query';
import { getConfig } from './config'; import { getConfig } from './config';
import { CapabilitiesResponse, capabilitiesResponse} from './capabilities'; import { capabilitiesResponse } from './capabilities';
import { CapabilitiesResponse, SchemaResponse, QueryRequest, QueryResponse } from './types';
const port = Number(process.env.PORT) || 8100; const port = Number(process.env.PORT) || 8100;
const server = Fastify({ logger: { prettyPrint: true } }); const server = Fastify({ logger: { prettyPrint: true } });
@ -31,7 +30,7 @@ server.get<{ Reply: SchemaResponse }>("/schema", async (request, _response) => {
return getSchema(config); return getSchema(config);
}); });
server.post<{ Body: QueryRequest, Reply: ProjectedRow[] }>("/query", async (request, _response) => { server.post<{ Body: QueryRequest, Reply: QueryResponse }>("/query", async (request, _response) => {
server.log.info({ headers: request.headers, query: request.body, }, "query.request"); server.log.info({ headers: request.headers, query: request.body, }, "query.request");
const config = getConfig(request); const config = getConfig(request);
const data = filterAvailableTables(staticData, config); const data = filterAvailableTables(staticData, config);

View File

@ -1,49 +1,56 @@
import { Expression, Fields, BinaryComparisonOperator, OrderBy, OrderType, ProjectedRow, Query, QueryResponse, RelationshipType, ScalarValue, UnaryComparisonOperator, ComparisonValue, BinaryArrayComparisonOperator, QueryRequest, TableName, ComparisonColumn, TableRelationships, Relationship, RelationshipName } from "./types/query"; import { QueryRequest, TableRelationships, Relationship, Query, Field, OrderBy, Expression, BinaryComparisonOperator, UnaryComparisonOperator, BinaryArrayComparisonOperator, ComparisonColumn, ComparisonValue, ScalarValue, QueryResponse } from "./types";
import { coerceUndefinedToNull, crossProduct, unreachable, zip } from "./util"; import { coerceUndefinedToNull, crossProduct, unreachable, zip } from "./util";
type StaticData = { type StaticData = {
[tableName: string]: Record<string, ScalarValue>[] [tableName: string]: Record<string, ScalarValue>[]
} }
type TableName = string
type RelationshipName = string
type ProjectedRow = {
[fieldName: string]: ScalarValue | ProjectedRow[] | ProjectedRow
}
const prettyPrintBinaryComparisonOperator = (operator: BinaryComparisonOperator): string => { const prettyPrintBinaryComparisonOperator = (operator: BinaryComparisonOperator): string => {
switch (operator) { switch (operator) {
case BinaryComparisonOperator.GreaterThan: return ">"; case "greater_than": return ">";
case BinaryComparisonOperator.GreaterThanOrEqual: return ">="; case "greater_than_or_equal": return ">=";
case BinaryComparisonOperator.LessThan: return "<"; case "less_than": return "<";
case BinaryComparisonOperator.LessThanOrEqual: return "<="; case "less_than_or_equal": return "<=";
case BinaryComparisonOperator.Equal: return "=="; case "equal": return "==";
default: return unreachable(operator); default: return unreachable(operator);
}; };
}; };
const prettyPrintBinaryArrayComparisonOperator = (operator: BinaryArrayComparisonOperator): string => { const prettyPrintBinaryArrayComparisonOperator = (operator: BinaryArrayComparisonOperator): string => {
switch (operator) { switch (operator) {
case BinaryArrayComparisonOperator.In: return "IN"; case "in": return "IN";
default: return unreachable(operator); default: return unreachable(operator);
}; };
}; };
const prettyPrintUnaryComparisonOperator = (operator: UnaryComparisonOperator): string => { const prettyPrintUnaryComparisonOperator = (operator: UnaryComparisonOperator): string => {
switch (operator) { switch (operator) {
case UnaryComparisonOperator.IsNull: return "IS NULL"; case "is_null": return "IS NULL";
default: return unreachable(operator); default: return unreachable(operator);
}; };
}; };
const getBinaryComparisonOperatorEvaluator = (operator: BinaryComparisonOperator): ((left: ScalarValue, right: ScalarValue) => boolean) => { const getBinaryComparisonOperatorEvaluator = (operator: BinaryComparisonOperator): ((left: ScalarValue, right: ScalarValue) => boolean) => {
switch (operator) { switch (operator) {
case BinaryComparisonOperator.GreaterThan: return (a, b) => a !== null && b !== null && a > b; case "greater_than": return (a, b) => a !== null && b !== null && a > b;
case BinaryComparisonOperator.GreaterThanOrEqual: return (a, b) => a !== null && b !== null && a >= b; case "greater_than_or_equal": return (a, b) => a !== null && b !== null && a >= b;
case BinaryComparisonOperator.LessThan: return (a, b) => a !== null && b !== null && a < b; case "less_than": return (a, b) => a !== null && b !== null && a < b;
case BinaryComparisonOperator.LessThanOrEqual: return (a, b) => a !== null && b !== null && a <= b; case "less_than_or_equal": return (a, b) => a !== null && b !== null && a <= b;
case BinaryComparisonOperator.Equal: return (a, b) => a !== null && b !== null && a === b; case "equal": return (a, b) => a !== null && b !== null && a === b;
default: return unreachable(operator); default: return unreachable(operator);
}; };
}; };
const getBinaryArrayComparisonOperatorEvaluator = (operator: BinaryArrayComparisonOperator): ((left: ScalarValue, right: ScalarValue[]) => boolean) => { const getBinaryArrayComparisonOperatorEvaluator = (operator: BinaryArrayComparisonOperator): ((left: ScalarValue, right: ScalarValue[]) => boolean) => {
switch (operator) { switch (operator) {
case BinaryArrayComparisonOperator.In: return (a, bs) => a !== null && bs.includes(a); case "in": return (a, bs) => a !== null && bs.includes(a);
default: return unreachable(operator); default: return unreachable(operator);
}; };
}; };
@ -51,7 +58,7 @@ const getBinaryArrayComparisonOperatorEvaluator = (operator: BinaryArrayComparis
const getUnaryComparisonOperatorEvaluator = (operator: UnaryComparisonOperator): ((value: ScalarValue) => boolean) => { const getUnaryComparisonOperatorEvaluator = (operator: UnaryComparisonOperator): ((value: ScalarValue) => boolean) => {
switch (operator) { switch (operator) {
case UnaryComparisonOperator.IsNull: return (v) => v === null; case "is_null": return (v) => v === null;
default: return unreachable(operator); default: return unreachable(operator);
}; };
}; };
@ -162,7 +169,7 @@ const sortRows = (rows: Record<string, ScalarValue>[], orderBy: OrderBy[]): Reco
? -1 ? -1
: 1; : 1;
return ordering === OrderType.Descending ? -compared : compared; return ordering === "desc" ? -compared : compared;
}, 0) }, 0)
); );
@ -191,7 +198,7 @@ const createFilterExpressionForRelationshipJoin = (row: Record<string, ScalarVal
.map(([outerValue, innerColumnName]) => { .map(([outerValue, innerColumnName]) => {
return { return {
type: "binary_op", type: "binary_op",
operator: BinaryComparisonOperator.Equal, operator: "equal",
column: { column: {
path: [], path: [],
name: innerColumnName, name: innerColumnName,
@ -222,7 +229,7 @@ const addRelationshipFilterToQuery = (row: Record<string, ScalarValue>, relation
} }
}; };
const buildFieldsForPathedComparisonColumn = (comparisonColumn: ComparisonColumn): Fields => { const buildFieldsForPathedComparisonColumn = (comparisonColumn: ComparisonColumn): Record<string, Field> => {
const [relationshipName, ...remainingPath] = comparisonColumn.path; const [relationshipName, ...remainingPath] = comparisonColumn.path;
if (relationshipName === undefined) { if (relationshipName === undefined) {
return { return {
@ -276,7 +283,7 @@ const makeGetComparisonColumnValues = (findRelationship: (relationshipName: Rela
} }
}; };
const projectRow = (fields: Fields, findRelationship: (relationshipName: RelationshipName) => Relationship, performQuery: (tableName: TableName, query: Query) => ProjectedRow[]) => (row: Record<string, ScalarValue>): ProjectedRow => { const projectRow = (fields: Record<string, Field>, findRelationship: (relationshipName: RelationshipName) => Relationship, performQuery: (tableName: TableName, query: Query) => ProjectedRow[]) => (row: Record<string, ScalarValue>): ProjectedRow => {
const projectedRow: ProjectedRow = {}; const projectedRow: ProjectedRow = {};
for (const [fieldName, field] of Object.entries(fields)) { for (const [fieldName, field] of Object.entries(fields)) {
@ -289,11 +296,11 @@ const projectRow = (fields: Fields, findRelationship: (relationshipName: Relatio
const relationship = findRelationship(field.relationship); const relationship = findRelationship(field.relationship);
const subquery = addRelationshipFilterToQuery(row, relationship, field.query); const subquery = addRelationshipFilterToQuery(row, relationship, field.query);
switch (relationship.relationship_type) { switch (relationship.relationship_type) {
case RelationshipType.Object: case "object":
projectedRow[fieldName] = subquery ? coerceUndefinedToNull(performQuery(relationship.target_table, subquery)[0]) : null; projectedRow[fieldName] = subquery ? coerceUndefinedToNull(performQuery(relationship.target_table, subquery)[0]) : null;
break; break;
case RelationshipType.Array: case "array":
projectedRow[fieldName] = subquery ? performQuery(relationship.target_table, subquery) : []; projectedRow[fieldName] = subquery ? performQuery(relationship.target_table, subquery) : [];
break; break;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type { AndExpression } from './models/AndExpression';
export type { AnotherColumnComparison } from './models/AnotherColumnComparison';
export type { ApplyBinaryArrayComparisonExpression } from './models/ApplyBinaryArrayComparisonExpression';
export type { ApplyBinaryComparisonOperator } from './models/ApplyBinaryComparisonOperator';
export type { ApplyUnaryComparisonOperator } from './models/ApplyUnaryComparisonOperator';
export type { BinaryArrayComparisonOperator } from './models/BinaryArrayComparisonOperator';
export type { BinaryComparisonOperator } from './models/BinaryComparisonOperator';
export type { BooleanOperators } from './models/BooleanOperators';
export type { Capabilities } from './models/Capabilities';
export type { CapabilitiesResponse } from './models/CapabilitiesResponse';
export type { ColumnField } from './models/ColumnField';
export type { ColumnInfo } from './models/ColumnInfo';
export type { ComparisonColumn } from './models/ComparisonColumn';
export type { ComparisonOperators } from './models/ComparisonOperators';
export type { ComparisonValue } from './models/ComparisonValue';
export type { ConfigSchemaResponse } from './models/ConfigSchemaResponse';
export type { Expression } from './models/Expression';
export type { Field } from './models/Field';
export type { FilteringCapabilities } from './models/FilteringCapabilities';
export type { MutationCapabilities } from './models/MutationCapabilities';
export type { NotExpression } from './models/NotExpression';
export type { OpenApiDiscriminator } from './models/OpenApiDiscriminator';
export type { OpenApiExternalDocumentation } from './models/OpenApiExternalDocumentation';
export type { OpenApiReference } from './models/OpenApiReference';
export type { OpenApiSchema } from './models/OpenApiSchema';
export type { OpenApiXml } from './models/OpenApiXml';
export type { OrderBy } from './models/OrderBy';
export type { OrderType } from './models/OrderType';
export type { OrExpression } from './models/OrExpression';
export type { Query } from './models/Query';
export type { QueryCapabilities } from './models/QueryCapabilities';
export type { QueryRequest } from './models/QueryRequest';
export type { QueryResponse } from './models/QueryResponse';
export type { Relationship } from './models/Relationship';
export type { RelationshipCapabilities } from './models/RelationshipCapabilities';
export type { RelationshipField } from './models/RelationshipField';
export type { RelationshipType } from './models/RelationshipType';
export type { ScalarType } from './models/ScalarType';
export type { ScalarValue } from './models/ScalarValue';
export type { ScalarValueComparison } from './models/ScalarValueComparison';
export type { SchemaResponse } from './models/SchemaResponse';
export type { SubscriptionCapabilities } from './models/SubscriptionCapabilities';
export type { TableInfo } from './models/TableInfo';
export type { TableRelationships } from './models/TableRelationships';
export type { UnaryComparisonOperator } from './models/UnaryComparisonOperator';

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Expression } from './Expression';
export type AndExpression = {
expressions: Array<Expression>;
type: 'and';
};

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ComparisonColumn } from './ComparisonColumn';
export type AnotherColumnComparison = {
column: ComparisonColumn;
type: 'column';
};

View File

@ -0,0 +1,15 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { BinaryArrayComparisonOperator } from './BinaryArrayComparisonOperator';
import type { ComparisonColumn } from './ComparisonColumn';
import type { ScalarValue } from './ScalarValue';
export type ApplyBinaryArrayComparisonExpression = {
column: ComparisonColumn;
operator: BinaryArrayComparisonOperator;
type: 'binary_arr_op';
values: Array<ScalarValue>;
};

View File

@ -0,0 +1,15 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { BinaryComparisonOperator } from './BinaryComparisonOperator';
import type { ComparisonColumn } from './ComparisonColumn';
import type { ComparisonValue } from './ComparisonValue';
export type ApplyBinaryComparisonOperator = {
column: ComparisonColumn;
operator: BinaryComparisonOperator;
type: 'binary_op';
value: ComparisonValue;
};

View File

@ -0,0 +1,13 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ComparisonColumn } from './ComparisonColumn';
import type { UnaryComparisonOperator } from './UnaryComparisonOperator';
export type ApplyUnaryComparisonOperator = {
column: ComparisonColumn;
operator: UnaryComparisonOperator;
type: 'unary_op';
};

View File

@ -0,0 +1,5 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type BinaryArrayComparisonOperator = 'in';

View File

@ -0,0 +1,5 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type BinaryComparisonOperator = 'less_than' | 'less_than_or_equal' | 'greater_than' | 'greater_than_or_equal' | 'equal';

View File

@ -0,0 +1,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type BooleanOperators = {
};

View File

@ -0,0 +1,18 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { FilteringCapabilities } from './FilteringCapabilities';
import type { MutationCapabilities } from './MutationCapabilities';
import type { QueryCapabilities } from './QueryCapabilities';
import type { RelationshipCapabilities } from './RelationshipCapabilities';
import type { SubscriptionCapabilities } from './SubscriptionCapabilities';
export type Capabilities = {
filtering?: FilteringCapabilities;
mutations?: MutationCapabilities;
queries?: QueryCapabilities;
relationships?: RelationshipCapabilities;
subscriptions?: SubscriptionCapabilities;
};

View File

@ -0,0 +1,12 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Capabilities } from './Capabilities';
import type { ConfigSchemaResponse } from './ConfigSchemaResponse';
export type CapabilitiesResponse = {
capabilities: Capabilities;
configSchemas: ConfigSchemaResponse;
};

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ColumnField = {
column: string;
type: 'column';
};

View File

@ -0,0 +1,22 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ScalarType } from './ScalarType';
export type ColumnInfo = {
/**
* Column description
*/
description?: string | null;
/**
* Column name
*/
name: string;
/**
* Is column nullable
*/
nullable: boolean;
type: ScalarType;
};

View File

@ -0,0 +1,15 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ComparisonColumn = {
/**
* The name of the column
*/
name: string;
/**
* The relationship path from the current query table to the table that contains the specified column. Empty array means the current query table.
*/
path: Array<string>;
};

View File

@ -0,0 +1,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ComparisonOperators = {
};

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { AnotherColumnComparison } from './AnotherColumnComparison';
import type { ScalarValueComparison } from './ScalarValueComparison';
export type ComparisonValue = (AnotherColumnComparison | ScalarValueComparison);

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { OpenApiSchema } from './OpenApiSchema';
export type ConfigSchemaResponse = {
configSchema: OpenApiSchema;
otherSchemas: Record<string, OpenApiSchema>;
};

View File

@ -0,0 +1,13 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { AndExpression } from './AndExpression';
import type { ApplyBinaryArrayComparisonExpression } from './ApplyBinaryArrayComparisonExpression';
import type { ApplyBinaryComparisonOperator } from './ApplyBinaryComparisonOperator';
import type { ApplyUnaryComparisonOperator } from './ApplyUnaryComparisonOperator';
import type { NotExpression } from './NotExpression';
import type { OrExpression } from './OrExpression';
export type Expression = (AndExpression | OrExpression | NotExpression | ApplyBinaryComparisonOperator | ApplyBinaryArrayComparisonExpression | ApplyUnaryComparisonOperator);

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ColumnField } from './ColumnField';
import type { RelationshipField } from './RelationshipField';
export type Field = (ColumnField | RelationshipField);

View File

@ -0,0 +1,12 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { BooleanOperators } from './BooleanOperators';
import type { ComparisonOperators } from './ComparisonOperators';
export type FilteringCapabilities = {
booleanOperators: BooleanOperators;
comparisonOperators: ComparisonOperators;
};

View File

@ -0,0 +1,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type MutationCapabilities = {
};

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Expression } from './Expression';
export type NotExpression = {
expression: Expression;
type: 'not';
};

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type OpenApiDiscriminator = {
mapping?: Record<string, string>;
propertyName: string;
};

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type OpenApiExternalDocumentation = {
description?: string;
url: string;
};

View File

@ -0,0 +1,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type OpenApiReference = {
$ref: string;
};

View File

@ -0,0 +1,47 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { OpenApiDiscriminator } from './OpenApiDiscriminator';
import type { OpenApiExternalDocumentation } from './OpenApiExternalDocumentation';
import type { OpenApiReference } from './OpenApiReference';
import type { OpenApiXml } from './OpenApiXml';
export type OpenApiSchema = {
additionalProperties?: any;
allOf?: Array<(OpenApiSchema | OpenApiReference)>;
anyOf?: Array<(OpenApiSchema | OpenApiReference)>;
default?: any;
deprecated?: boolean;
description?: string;
discriminator?: OpenApiDiscriminator;
enum?: Array<any>;
example?: any;
exclusiveMaximum?: boolean;
exclusiveMinimum?: boolean;
externalDocs?: OpenApiExternalDocumentation;
format?: string;
items?: (OpenApiSchema | OpenApiReference);
maxItems?: number;
maxLength?: number;
maxProperties?: number;
maximum?: number;
minItems?: number;
minLength?: number;
minProperties?: number;
minimum?: number;
multipleOf?: number;
not?: (OpenApiSchema | OpenApiReference);
nullable?: boolean;
oneOf?: Array<(OpenApiSchema | OpenApiReference)>;
pattern?: string;
properties?: Record<string, (OpenApiSchema | OpenApiReference)>;
readOnly?: boolean;
required?: Array<string>;
title?: string;
type?: 'array' | 'boolean' | 'integer' | 'number' | 'object' | 'string';
uniqueItems?: boolean;
writeOnly?: boolean;
xml?: OpenApiXml;
};

View File

@ -0,0 +1,12 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type OpenApiXml = {
attribute?: boolean;
name?: string;
namespace?: string;
prefix?: string;
wrapped?: boolean;
};

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Expression } from './Expression';
export type OrExpression = {
expressions: Array<Expression>;
type: 'or';
};

View File

@ -0,0 +1,14 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { OrderType } from './OrderType';
export type OrderBy = {
/**
* Column to order by
*/
column: string;
ordering: OrderType;
};

View File

@ -0,0 +1,5 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type OrderType = 'asc' | 'desc';

View File

@ -0,0 +1,28 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Expression } from './Expression';
import type { Field } from './Field';
import type { OrderBy } from './OrderBy';
export type Query = {
/**
* Fields of the query
*/
fields: Record<string, Field>;
/**
* Optionally limit to N results
*/
limit?: number | null;
/**
* Optionally offset from the Nth result
*/
offset?: number | null;
/**
* Optionally order the results by the value of one or more fields
*/
order_by?: Array<OrderBy> | null;
where?: Expression;
};

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type QueryCapabilities = {
/**
* Does the agent support querying a table by primary key?
*/
supportsPrimaryKeys: boolean;
};

View File

@ -0,0 +1,19 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Query } from './Query';
import type { TableRelationships } from './TableRelationships';
export type QueryRequest = {
query: Query;
/**
* The name of the table to query
*/
table: string;
/**
* The relationships between tables involved in the entire query request
*/
table_relationships: Array<TableRelationships>;
};

View File

@ -0,0 +1,5 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type QueryResponse = Array<Record<string, any>>;

View File

@ -0,0 +1,18 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { RelationshipType } from './RelationshipType';
export type Relationship = {
/**
* A mapping between columns on the source table to columns on the target table
*/
column_mapping: Record<string, string>;
relationship_type: RelationshipType;
/**
* The name of the target table in the relationship
*/
target_table: string;
};

View File

@ -0,0 +1,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type RelationshipCapabilities = {
};

View File

@ -0,0 +1,15 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Query } from './Query';
export type RelationshipField = {
query: Query;
/**
* The name of the relationship to follow for the subquery
*/
relationship: string;
type: 'relationship';
};

View File

@ -0,0 +1,5 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type RelationshipType = 'object' | 'array';

View File

@ -0,0 +1,5 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ScalarType = 'string' | 'number' | 'bool';

View File

@ -0,0 +1,6 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ScalarValue = (string | number | boolean | null);

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ScalarValue } from './ScalarValue';
export type ScalarValueComparison = {
type: 'scalar';
value: ScalarValue;
};

View File

@ -0,0 +1,13 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { TableInfo } from './TableInfo';
export type SchemaResponse = {
/**
* Available tables
*/
tables: Array<TableInfo>;
};

View File

@ -0,0 +1,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type SubscriptionCapabilities = {
};

View File

@ -0,0 +1,25 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ColumnInfo } from './ColumnInfo';
export type TableInfo = {
/**
* The columns of the table
*/
columns: Array<ColumnInfo>;
/**
* Description of the table
*/
description?: string | null;
/**
* The name of the table
*/
name: string;
/**
* The primary key of the table
*/
primary_key?: string | null;
};

View File

@ -0,0 +1,17 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Relationship } from './Relationship';
export type TableRelationships = {
/**
* A map of relationships from the source table to target tables. The key of the map is the relationship name
*/
relationships: Record<string, Relationship>;
/**
* The name of the source table in the relationship
*/
source_table: string;
};

View File

@ -0,0 +1,5 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UnaryComparisonOperator = 'is_null';

View File

@ -1,148 +0,0 @@
export type QueryRequest = {
table: TableName,
table_relationships: TableRelationships[],
query: Query,
}
export type TableName = string
export type TableRelationships = {
source_table: TableName,
relationships: { [relationshipName: RelationshipName]: Relationship }
}
export type Relationship = {
target_table: TableName,
relationship_type: RelationshipType,
column_mapping: { [source: SourceColumnName]: TargetColumnName },
}
export type SourceColumnName = ColumnName
export type TargetColumnName = ColumnName
export type RelationshipName = string
export enum RelationshipType {
Object = "object",
Array = "array"
}
export type Query = {
fields: Fields,
limit?: number | null,
offset?: number | null,
where?: Expression | null,
order_by?: OrderBy[],
}
export type Fields = { [fieldName: string]: Field }
export type Field = ColumnField | RelationshipField
export type ColumnName = string
export type ColumnField = {
type: "column",
column: ColumnName,
}
export type RelationshipField = {
type: "relationship",
relationship: RelationshipName
query: Query,
}
export type ScalarValue = string | number | boolean | null
export type ComparisonColumn = {
path: RelationshipName[],
name: ColumnName,
}
export type ComparisonValue =
| AnotherColumnComparisonValue
| ScalarComparisonValue
export type AnotherColumnComparisonValue = {
type: "column",
column: ComparisonColumn,
}
export type ScalarComparisonValue = {
type: "scalar",
value: ScalarValue,
}
export type Expression =
| AndExpression
| OrExpression
| NotExpression
| ApplyBinaryComparisonOperatorExpression
| ApplyBinaryArrayComparisonOperatorExpression
| ApplyUnaryComparisonOperatorExpression
export type AndExpression = {
type: "and",
expressions: Expression[],
}
export type OrExpression = {
type: "or",
expressions: Expression[],
}
export type NotExpression = {
type: "not",
expression: Expression,
}
export type ApplyBinaryComparisonOperatorExpression = {
type: "binary_op",
operator: BinaryComparisonOperator,
column: ComparisonColumn,
value: ComparisonValue,
}
export type ApplyBinaryArrayComparisonOperatorExpression = {
type: "binary_arr_op",
operator: BinaryArrayComparisonOperator,
column: ComparisonColumn,
values: ScalarValue[],
}
export type ApplyUnaryComparisonOperatorExpression = {
type: "unary_op",
operator: UnaryComparisonOperator,
column: ComparisonColumn,
}
export enum BinaryComparisonOperator {
LessThan = "less_than",
LessThanOrEqual = "less_than_or_equal",
GreaterThan = "greater_than",
GreaterThanOrEqual = "greater_than_or_equal",
Equal = "equal",
}
export enum BinaryArrayComparisonOperator {
In = "in",
}
export enum UnaryComparisonOperator {
IsNull = "is_null",
}
export type OrderBy = {
column: ColumnName,
ordering: OrderType,
}
export enum OrderType {
Ascending = "asc",
Descending = "desc",
}
export type QueryResponse = ProjectedRow[]
export type ProjectedRow = {
[fieldName: string]: ScalarValue | ProjectedRow[] | ProjectedRow
}

View File

@ -1,23 +0,0 @@
export type SchemaResponse = {
tables: Table[],
}
export type Table = {
name: string,
columns: ColumnInfo[],
primary_key?: string | null,
description?: string | null
}
export type ColumnInfo = {
name: string,
type: ScalarType,
nullable: boolean,
description?: string | null
}
export enum ScalarType {
String = "string",
Number = "number",
Boolean = "bool"
}

View File

@ -1,9 +1,8 @@
export const coerceUndefinedToNull = <T>(v: T | undefined): T | null => v === undefined ? null : v; export const coerceUndefinedToNull = <T>(v: T | undefined): T | null => v === undefined ? null : v;
export const unreachable = (x: never): never => { throw new Error(`Unreachable code reached! The types lied! 😭 Unexpected value: ${x}`) }; export const unreachable = (x: never): never => { throw new Error(`Unreachable code reached! The types lied! 😭 Unexpected value: ${x}`) };
;
export const zip = <T, U>(arr1: T[], arr2: U[]): [T,U][] => { export const zip = <T, U>(arr1: T[], arr2: U[]): [T, U][] => {
const length = Math.min(arr1.length, arr2.length); const length = Math.min(arr1.length, arr2.length);
const newArray = Array(length); const newArray = Array(length);
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
@ -12,6 +11,6 @@ export const zip = <T, U>(arr1: T[], arr2: U[]): [T,U][] => {
return newArray; return newArray;
}; };
export const crossProduct = <T, U>(arr1: T[], arr2: U[]): [T,U][] => { export const crossProduct = <T, U>(arr1: T[], arr2: U[]): [T, U][] => {
return arr1.flatMap(a1 => arr2.map(a2 => [a1, a2]) as [T,U][]); return arr1.flatMap(a1 => arr2.map(a2 => [a1, a2]) as [T, U][]);
}; };

View File

@ -7,13 +7,12 @@ module Hasura.Backends.DataConnector.API
ConfigHeader, ConfigHeader,
SourceNameHeader, SourceNameHeader,
SourceName, SourceName,
openApiSchemaJson, openApiSchema,
Routes (..), Routes (..),
apiClient, apiClient,
) )
where where
import Data.Aeson qualified as J
import Data.Data (Proxy (..)) import Data.Data (Proxy (..))
import Data.OpenApi (OpenApi) import Data.OpenApi (OpenApi)
import Data.Text (Text) import Data.Text (Text)
@ -22,7 +21,6 @@ import Servant.API
import Servant.API.Generic import Servant.API.Generic
import Servant.Client (Client, ClientM, client) import Servant.Client (Client, ClientM, client)
import Servant.OpenApi import Servant.OpenApi
import Prelude
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Servant Routes -- Servant Routes
@ -68,13 +66,6 @@ type Api = CapabilitiesApi :<|> SchemaApi :<|> QueryApi
openApiSchema :: OpenApi openApiSchema :: OpenApi
openApiSchema = toOpenApi (Proxy @Api) openApiSchema = toOpenApi (Proxy @Api)
-- | The OpenAPI 3.0 schema for the API
--
-- This is not exposed as the 'OpenApi' type because we need to do some hackery in
-- the serialized JSON to work around some limitations in the openapi3 library
openApiSchemaJson :: J.Value
openApiSchemaJson = V0.fixExternalSchemaRefsInComponentSchemas $ J.toJSON openApiSchema
apiClient :: Client ClientM (NamedRoutes Routes) apiClient :: Client ClientM (NamedRoutes Routes)
apiClient = apiClient =
client (Proxy @(NamedRoutes Routes)) client (Proxy @(NamedRoutes Routes))

View File

@ -22,7 +22,7 @@ import Data.Aeson (FromJSON, ToJSON)
import Data.Data (Data, Proxy (..)) import Data.Data (Data, Proxy (..))
import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
import Data.Hashable (Hashable) import Data.Hashable (Hashable)
import Data.OpenApi (NamedSchema (..), OpenApiType (OpenApiObject), Referenced (..), Schema (..), ToSchema (..)) import Data.OpenApi (NamedSchema (..), OpenApiType (OpenApiObject), Schema (..), ToSchema (..), declareSchemaRef)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Hasura.Backends.DataConnector.API.V0.ConfigSchema (ConfigSchemaResponse) import Hasura.Backends.DataConnector.API.V0.ConfigSchema (ConfigSchemaResponse)
import Prelude import Prelude
@ -137,8 +137,8 @@ instance HasCodec CapabilitiesResponse where
instance ToSchema CapabilitiesResponse where instance ToSchema CapabilitiesResponse where
declareNamedSchema _ = do declareNamedSchema _ = do
capabilitiesSchema <- declareNamedSchema (Proxy @Capabilities) capabilitiesSchemaRef <- declareSchemaRef (Proxy @Capabilities)
configSchemasSchema <- declareNamedSchema (Proxy @ConfigSchemaResponse) configSchemasSchemaRef <- declareSchemaRef (Proxy @ConfigSchemaResponse)
let schema = let schema =
mempty mempty
{ _schemaType = Just OpenApiObject, { _schemaType = Just OpenApiObject,
@ -146,8 +146,8 @@ instance ToSchema CapabilitiesResponse where
_schemaRequired = ["capabilities", "configSchemas"], _schemaRequired = ["capabilities", "configSchemas"],
_schemaProperties = _schemaProperties =
InsOrdHashMap.fromList InsOrdHashMap.fromList
[ ("capabilities", Inline $ _namedSchemaSchema capabilitiesSchema), [ ("capabilities", capabilitiesSchemaRef),
("configSchemas", Inline $ _namedSchemaSchema configSchemasSchema) ("configSchemas", configSchemasSchemaRef)
] ]
} }

View File

@ -4,8 +4,6 @@ module Hasura.Backends.DataConnector.API.V0.ConfigSchema
( Config (..), ( Config (..),
ConfigSchemaResponse (..), ConfigSchemaResponse (..),
validateConfigAgainstConfigSchema, validateConfigAgainstConfigSchema,
fixExternalSchemaRefsInComponentSchemas,
fixExternalSchemaRefsInSchema,
) )
where where
@ -20,8 +18,9 @@ import Data.ByteString.Lazy qualified as BSL
import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
import Data.Hashable (Hashable) import Data.Hashable (Hashable)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.OpenApi (AdditionalProperties (..), Definitions, NamedSchema (..), OpenApiType (..), Reference (..), Referenced (..), Schema (..), ToParamSchema (..), ToSchema (..), ValidationError) import Data.OpenApi (AdditionalProperties (..), Definitions, NamedSchema (..), OpenApiItems (..), OpenApiType (..), Reference (..), Referenced (..), Schema (..), ToParamSchema (..), ToSchema (..), ValidationError)
import Data.OpenApi qualified as OpenApi import Data.OpenApi qualified as OpenApi
import Data.OpenApi.Declare (Declare, MonadDeclare (..))
import Data.Text (Text) import Data.Text (Text)
import Data.Text qualified as Text import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text import Data.Text.Encoding qualified as Text
@ -75,33 +74,184 @@ instance Autodocodec.HasCodec ConfigSchemaResponse where
codec = Autodocodec.codecViaAeson "Configuration schemas" codec = Autodocodec.codecViaAeson "Configuration schemas"
instance ToSchema ConfigSchemaResponse where instance ToSchema ConfigSchemaResponse where
declareNamedSchema _ = declareNamedSchema _ = do
openApiSchemaRef <- declareOpenApiSchema
let otherSchemasSchema =
mempty
{ _schemaType = Just OpenApiObject,
_schemaNullable = Just False,
_schemaAdditionalProperties = Just $ AdditionalPropertiesSchema openApiSchemaRef
}
let schema =
mempty
{ _schemaType = Just OpenApiObject,
_schemaNullable = Just False,
_schemaRequired = ["configSchema", "otherSchemas"],
_schemaProperties =
InsOrdHashMap.fromList
[ ("configSchema", openApiSchemaRef),
("otherSchemas", Inline otherSchemasSchema)
]
}
pure $ NamedSchema (Just "ConfigSchemaResponse") schema pure $ NamedSchema (Just "ConfigSchemaResponse") schema
where
schema :: Schema -- | Declares the schema for the OpenAPI Schema type (and its dependent types) and
schema = -- returns a reference that can be used to refer to it from other schemas.
--
-- This is a transcription of the schemas defined here:
-- https://raw.githubusercontent.com/OAI/OpenAPI-Specification/80c781e479f85ac67001ceb3e7e410e25d2a561b/schemas/v3.0/schema.json#/definitions/Schema
--
-- Unfortunately using external references to the above schema tends to make many
-- OpenAPI type generators choke, so importing the relevant schemas into our spec
-- is a pragmatic workaround.
declareOpenApiSchema :: Declare (Definitions Schema) (Referenced Schema)
declareOpenApiSchema = do
declare $
InsOrdHashMap.fromList
[ openApiSchema,
openApiReference,
openApiDiscriminator,
openApiExternalDocumentation,
openApiXml
]
pure . Ref $ Reference "OpenApiSchema"
where
openApiSchema :: (Text, Schema)
openApiSchema =
("OpenApiSchema",)
mempty mempty
{ _schemaType = Just OpenApiObject, { _schemaType = Just OpenApiObject,
_schemaNullable = Just False,
_schemaRequired = ["configSchema", "otherSchemas"],
_schemaProperties = _schemaProperties =
InsOrdHashMap.fromList InsOrdHashMap.fromList
[ ("configSchema", openApiSchemaSchema), [ ("title", Inline mempty {_schemaType = Just OpenApiString}),
("otherSchemas", Inline otherSchemasSchema) ("multipleOf", Inline mempty {_schemaType = Just OpenApiNumber, _schemaMinimum = Just 0, _schemaExclusiveMinimum = Just True}),
("maximum", Inline mempty {_schemaType = Just OpenApiNumber}),
("exclusiveMaximum", Inline mempty {_schemaType = Just OpenApiBoolean, _schemaDefault = Just $ Bool False}),
("minimum", Inline mempty {_schemaType = Just OpenApiNumber}),
("exclusiveMinimum", Inline mempty {_schemaType = Just OpenApiBoolean, _schemaDefault = Just $ Bool False}),
("maxLength", Inline mempty {_schemaType = Just OpenApiInteger, _schemaMinimum = Just 0}),
("minLength", Inline mempty {_schemaType = Just OpenApiInteger, _schemaMinimum = Just 0, _schemaDefault = Just $ Number 0}),
("pattern", Inline mempty {_schemaType = Just OpenApiString, _schemaFormat = Just "regex"}),
("maxItems", Inline mempty {_schemaType = Just OpenApiInteger, _schemaMinimum = Just 0}),
("minItems", Inline mempty {_schemaType = Just OpenApiInteger, _schemaMinimum = Just 0, _schemaDefault = Just $ Number 0}),
("uniqueItems", Inline mempty {_schemaType = Just OpenApiBoolean, _schemaDefault = Just $ Bool False}),
("maxProperties", Inline mempty {_schemaType = Just OpenApiInteger, _schemaMinimum = Just 0}),
("minProperties", Inline mempty {_schemaType = Just OpenApiInteger, _schemaMinimum = Just 0, _schemaDefault = Just $ Number 0}),
( "required",
Inline
mempty
{ _schemaType = Just OpenApiArray,
_schemaItems = Just . OpenApiItemsObject $ Inline mempty {_schemaType = Just OpenApiString},
_schemaMinItems = Just 1,
_schemaUniqueItems = Just True
}
),
( "enum",
Inline
mempty
{ _schemaType = Just OpenApiArray,
_schemaItems = Just . OpenApiItemsObject $ Inline mempty,
_schemaMinItems = Just 1,
_schemaUniqueItems = Just False
}
),
("type", Inline mempty {_schemaType = Just OpenApiString, _schemaEnum = Just ["array", "boolean", "integer", "number", "object", "string"]}),
("not", Inline mempty {_schemaOneOf = Just schemaOrReference}),
("allOf", Inline mempty {_schemaType = Just OpenApiArray, _schemaItems = Just . OpenApiItemsObject $ Inline mempty {_schemaOneOf = Just schemaOrReference}}),
("oneOf", Inline mempty {_schemaType = Just OpenApiArray, _schemaItems = Just . OpenApiItemsObject $ Inline mempty {_schemaOneOf = Just schemaOrReference}}),
("anyOf", Inline mempty {_schemaType = Just OpenApiArray, _schemaItems = Just . OpenApiItemsObject $ Inline mempty {_schemaOneOf = Just schemaOrReference}}),
("items", Inline mempty {_schemaOneOf = Just schemaOrReference}),
("properties", Inline mempty {_schemaType = Just OpenApiObject, _schemaAdditionalProperties = Just . AdditionalPropertiesSchema $ Inline mempty {_schemaOneOf = Just schemaOrReference}}),
( "additionalProperties",
Inline
mempty
{ _schemaAdditionalProperties = Just . AdditionalPropertiesSchema $ Inline mempty {_schemaOneOf = Just $ schemaOrReference <> [Inline mempty {_schemaType = Just OpenApiBoolean}]},
_schemaDefault = Just $ Bool True
}
),
("description", Inline mempty {_schemaType = Just OpenApiString}),
("format", Inline mempty {_schemaType = Just OpenApiString}),
("default", Inline mempty),
("nullable", Inline mempty {_schemaType = Just OpenApiBoolean, _schemaDefault = Just $ Bool False}),
("discriminator", Ref . Reference $ fst openApiDiscriminator),
("readOnly", Inline mempty {_schemaType = Just OpenApiBoolean, _schemaDefault = Just $ Bool False}),
("writeOnly", Inline mempty {_schemaType = Just OpenApiBoolean, _schemaDefault = Just $ Bool False}),
("example", Inline mempty),
("externalDocs", Ref . Reference $ fst openApiExternalDocumentation),
("deprecated", Inline mempty {_schemaType = Just OpenApiBoolean, _schemaDefault = Just $ Bool False}),
("xml", Ref . Reference $ fst openApiXml)
],
-- Note: Technically OpenAPI schemas should be able to define extension properties but since OpenAPI itself doesn't
-- support defining patternProperties, I can't define them here. 😢
-- "patternProperties": { "^x-": {} }
-- _schemaPatternProperties =
_schemaAdditionalProperties = Just $ AdditionalPropertiesAllowed False
}
openApiReference :: (Text, Schema)
openApiReference =
("OpenApiReference",)
mempty
{ _schemaType = Just OpenApiObject,
_schemaRequired = ["$ref"],
-- Note: This is technically defined using "patternProperties" with the property name regex ^\$ref$
-- but OpenAPI doesn't support patternProperties ironically, so this is close enough
_schemaProperties = InsOrdHashMap.fromList [("$ref", Inline mempty {_schemaType = Just OpenApiString, _schemaFormat = Just "uri-reference"})]
}
schemaOrReference :: [Referenced Schema]
schemaOrReference = [Ref . Reference $ fst openApiSchema, Ref . Reference $ fst openApiReference]
openApiDiscriminator :: (Text, Schema)
openApiDiscriminator =
("OpenApiDiscriminator",)
mempty
{ _schemaType = Just OpenApiObject,
_schemaRequired = ["propertyName"],
_schemaProperties =
InsOrdHashMap.fromList
[ ("propertyName", Inline mempty {_schemaType = Just OpenApiString}),
("mapping", Inline mempty {_schemaType = Just OpenApiObject, _schemaAdditionalProperties = Just . AdditionalPropertiesSchema $ Inline mempty {_schemaType = Just OpenApiString}})
] ]
} }
otherSchemasSchema :: Schema openApiExternalDocumentation :: (Text, Schema)
otherSchemasSchema = openApiExternalDocumentation =
("OpenApiExternalDocumentation",)
mempty mempty
{ _schemaType = Just OpenApiObject, { _schemaType = Just OpenApiObject,
_schemaNullable = Just False, _schemaRequired = ["url"],
_schemaAdditionalProperties = Just $ AdditionalPropertiesSchema openApiSchemaSchema _schemaProperties =
InsOrdHashMap.fromList
[ ("description", Inline mempty {_schemaType = Just OpenApiString}),
("url", Inline mempty {_schemaType = Just OpenApiString, _schemaFormat = Just "uri-reference"})
],
-- Note: Technically external docs should be able to define extension properties but since OpenAPI itself doesn't
-- support defining patternProperties, I can't define them here. 😢
-- "patternProperties": { "^x-": {} }
-- _schemaPatternProperties =
_schemaAdditionalProperties = Just $ AdditionalPropertiesAllowed False
} }
openApiSchemaSchema :: Referenced Schema openApiXml :: (Text, Schema)
openApiSchemaSchema = openApiXml =
Ref (Reference "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/80c781e479f85ac67001ceb3e7e410e25d2a561b/schemas/v3.0/schema.json#/definitions/Schema") ("OpenApiXml",)
mempty
{ _schemaType = Just OpenApiObject,
_schemaProperties =
InsOrdHashMap.fromList
[ ("name", Inline mempty {_schemaType = Just OpenApiString}),
("namespace", Inline mempty {_schemaType = Just OpenApiString, _schemaFormat = Just "uri"}),
("prefix", Inline mempty {_schemaType = Just OpenApiString}),
("attribute", Inline mempty {_schemaType = Just OpenApiBoolean, _schemaDefault = Just $ Bool False}),
("wrapped", Inline mempty {_schemaType = Just OpenApiBoolean, _schemaDefault = Just $ Bool False})
],
-- Note: Technically XML should be able to define extension properties but since OpenAPI itself doesn't
-- support defining patternProperties, I can't define them here. 😢
-- "patternProperties": { "^x-": {} }
-- _schemaPatternProperties =
_schemaAdditionalProperties = Just $ AdditionalPropertiesAllowed False
}
-- | Rewrites the config schema internal refs to the form that openapi3 expects when it deserialized them -- | Rewrites the config schema internal refs to the form that openapi3 expects when it deserialized them
-- --
@ -157,26 +307,3 @@ rewriteSchemaRefs rewriteRefText schemaObj =
validateConfigAgainstConfigSchema :: ConfigSchemaResponse -> Config -> [ValidationError] validateConfigAgainstConfigSchema :: ConfigSchemaResponse -> Config -> [ValidationError]
validateConfigAgainstConfigSchema ConfigSchemaResponse {..} (Config config) = validateConfigAgainstConfigSchema ConfigSchemaResponse {..} (Config config) =
OpenApi.validateJSON _csrOtherSchemas _csrConfigSchema (Object config) OpenApi.validateJSON _csrOtherSchemas _csrConfigSchema (Object config)
-- | Fixes any refs in schemas that are external refs to an http-based URL.
-- Note that this is limited to schemas in the components/schemas section.
-- This is used to specifically address the external refs defined by the
-- OpenAPI schema spec of the 'ConfigSchemaResponse' type.
--
-- This works around a limitation in the openapi3 library where it does not
-- understand the concept of external refs and will always assume any defined
-- ref refers to a schema inside the top level OpenApi document itself.
-- Practically, this means that #/components/schemas/ gets mashed onto the
-- front of any external ref :(
fixExternalSchemaRefsInComponentSchemas :: Value -> Value
fixExternalSchemaRefsInComponentSchemas openApiObj =
openApiObj
& key "components" . key "schemas" . members %~ fixExternalSchemaRefsInSchema
fixExternalSchemaRefsInSchema :: Value -> Value
fixExternalSchemaRefsInSchema = rewriteSchemaRefs fixExternalHttpSchemaRef
fixExternalHttpSchemaRef :: Text -> Text
fixExternalHttpSchemaRef = \case
(Text.stripPrefix "#/components/schemas/http" -> Just suffix) -> "http" <> suffix
other -> other

View File

@ -113,7 +113,10 @@ deriving via Autodocodec Field instance ToSchema Field
-- endpoint encoded as a list of JSON objects. -- endpoint encoded as a list of JSON objects.
newtype QueryResponse = QueryResponse {getQueryResponse :: [Object]} newtype QueryResponse = QueryResponse {getQueryResponse :: [Object]}
deriving newtype (Eq, Ord, Show, NFData) deriving newtype (Eq, Ord, Show, NFData)
deriving (ToJSON, FromJSON, ToSchema) via Autodocodec [Object] deriving (ToJSON, FromJSON, ToSchema) via Autodocodec QueryResponse
instance HasCodec QueryResponse where
codec = named "QueryResponse" $ dimapCodec QueryResponse getQueryResponse codec
$(makeLenses ''QueryRequest) $(makeLenses ''QueryRequest)
$(makeLenses ''Query) $(makeLenses ''Query)

View File

@ -31,5 +31,5 @@ data Type
instance HasCodec Type where instance HasCodec Type where
codec = codec =
named "Type" $ named "ScalarType" $
disjointStringConstCodec [(StringTy, "string"), (NumberTy, "number"), (BoolTy, "bool")] disjointStringConstCodec [(StringTy, "string"), (NumberTy, "number"), (BoolTy, "bool")]

View File

@ -42,7 +42,7 @@ import Data.Text.Extended
import Data.Text.Lazy qualified as LT import Data.Text.Lazy qualified as LT
import Data.Text.Lazy.Encoding qualified as TL import Data.Text.Lazy.Encoding qualified as TL
import GHC.Stats.Extended qualified as RTS import GHC.Stats.Extended qualified as RTS
import Hasura.Backends.DataConnector.API (openApiSchemaJson) import Hasura.Backends.DataConnector.API (openApiSchema)
import Hasura.Backends.Postgres.Execute.Types import Hasura.Backends.Postgres.Execute.Types
import Hasura.Base.Error import Hasura.Base.Error
import Hasura.EncJSON import Hasura.EncJSON
@ -1078,7 +1078,7 @@ httpApp setupHook corsCfg serverCtx enableConsole consoleAssetsDir enableTelemet
spockAction encodeQErr id $ spockAction encodeQErr id $
mkGetHandler $ do mkGetHandler $ do
onlyAdmin onlyAdmin
return (emptyHttpLogMetadata @m, JSONResp $ HttpResponse (encJFromJValue openApiSchemaJson) []) return (emptyHttpLogMetadata @m, JSONResp $ HttpResponse (encJFromJValue openApiSchema) [])
Spock.get "api/swagger/json" $ Spock.get "api/swagger/json" $
spockAction encodeQErr id $ spockAction encodeQErr id $
mkGetHandler $ do mkGetHandler $ do

View File

@ -65,8 +65,8 @@ spec = do
|] |]
testToFromJSON val jsonVal testToFromJSON val jsonVal
it "produces the correct OpenAPI Spec once external schema refs are fixed up" $ it "OpenAPI spec is as expected" $
fixExternalSchemaRefsInSchema (toJSON $ toSchema (Proxy @ConfigSchemaResponse)) toJSON (toSchema (Proxy @ConfigSchemaResponse))
`shouldBe` [aesonQQ| `shouldBe` [aesonQQ|
{ {
"required": [ "required": [
@ -77,11 +77,11 @@ spec = do
"nullable": false, "nullable": false,
"properties": { "properties": {
"configSchema": { "configSchema": {
"$ref": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/80c781e479f85ac67001ceb3e7e410e25d2a561b/schemas/v3.0/schema.json#/definitions/Schema" "$ref": "#/components/schemas/OpenApiSchema"
}, },
"otherSchemas": { "otherSchemas": {
"additionalProperties": { "additionalProperties": {
"$ref": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/80c781e479f85ac67001ceb3e7e410e25d2a561b/schemas/v3.0/schema.json#/definitions/Schema" "$ref": "#/components/schemas/OpenApiSchema"
}, },
"type": "object", "type": "object",
"nullable": false "nullable": false

View File

@ -6,7 +6,7 @@ import Control.Monad ((>=>))
import Data.Aeson.Text (encodeToLazyText) import Data.Aeson.Text (encodeToLazyText)
import Data.Proxy (Proxy (..)) import Data.Proxy (Proxy (..))
import Data.Text.Lazy.IO qualified as Text import Data.Text.Lazy.IO qualified as Text
import Hasura.Backends.DataConnector.API (Routes (..), apiClient, openApiSchemaJson) import Hasura.Backends.DataConnector.API (Routes (..), apiClient, openApiSchema)
import Hasura.Backends.DataConnector.API qualified as API import Hasura.Backends.DataConnector.API qualified as API
import Network.HTTP.Client (defaultManagerSettings, newManager) import Network.HTTP.Client (defaultManagerSettings, newManager)
import Servant.API (NamedRoutes) import Servant.API (NamedRoutes)
@ -38,7 +38,7 @@ main = do
agentCapabilities <- getAgentCapabilities api _toAgentCapabilities agentCapabilities <- getAgentCapabilities api _toAgentCapabilities
runSpec (tests api testSourceName _toAgentConfig agentCapabilities) (applyTestConfig defaultConfig testOptions) >>= evaluateSummary runSpec (tests api testSourceName _toAgentConfig agentCapabilities) (applyTestConfig defaultConfig testOptions) >>= evaluateSummary
ExportOpenAPISpec -> ExportOpenAPISpec ->
Text.putStrLn $ encodeToLazyText openApiSchemaJson Text.putStrLn $ encodeToLazyText openApiSchema
pure () pure ()

View File

@ -27,6 +27,7 @@ import Data.Aeson qualified as Aeson
import Data.IORef qualified as I import Data.IORef qualified as I
import Harness.Backend.DataConnector.MockAgent import Harness.Backend.DataConnector.MockAgent
import Harness.GraphqlEngine qualified as GraphqlEngine import Harness.GraphqlEngine qualified as GraphqlEngine
import Harness.Http (healthCheck)
import Harness.Quoter.Yaml (shouldReturnYaml, yaml) import Harness.Quoter.Yaml (shouldReturnYaml, yaml)
import Harness.Test.Context (BackendType (DataConnector), Options, defaultBackendTypeString) import Harness.Test.Context (BackendType (DataConnector), Options, defaultBackendTypeString)
import Harness.TestEnvironment (TestEnvironment) import Harness.TestEnvironment (TestEnvironment)
@ -48,10 +49,11 @@ dataconnector:
mockBackendConfig :: Aeson.Value mockBackendConfig :: Aeson.Value
mockBackendConfig = mockBackendConfig =
let backendType = defaultBackendTypeString $ DataConnector let backendType = defaultBackendTypeString $ DataConnector
agentUri = "http://127.0.0.1:" <> show mockAgentPort <> "/"
in [yaml| in [yaml|
dataconnector: dataconnector:
*backendType: *backendType:
uri: "http://127.0.0.1:65006/" uri: *agentUri
|] |]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -114,6 +116,7 @@ mkLocalTestEnvironmentMock _ = do
maeConfig <- I.newIORef chinookMock maeConfig <- I.newIORef chinookMock
maeQuery <- I.newIORef Nothing maeQuery <- I.newIORef Nothing
maeThreadId <- forkIO $ runMockServer maeConfig maeQuery maeThreadId <- forkIO $ runMockServer maeConfig maeQuery
healthCheck $ "http://127.0.0.1:" <> show mockAgentPort <> "/healthz"
pure $ MockAgentEnvironment {..} pure $ MockAgentEnvironment {..}
-- | Load the agent schema into HGE. -- | Load the agent schema into HGE.

View File

@ -1,6 +1,7 @@
module Harness.Backend.DataConnector.MockAgent module Harness.Backend.DataConnector.MockAgent
( MockConfig (..), ( MockConfig (..),
chinookMock, chinookMock,
mockAgentPort,
runMockServer, runMockServer,
) )
where where
@ -220,12 +221,22 @@ mockQueryHandler :: I.IORef MockConfig -> I.IORef (Maybe API.QueryRequest) -> AP
mockQueryHandler mcfg mquery _sourceName _cfg query = liftIO $ do mockQueryHandler mcfg mquery _sourceName _cfg query = liftIO $ do
handler <- fmap _queryResponse $ I.readIORef mcfg handler <- fmap _queryResponse $ I.readIORef mcfg
I.writeIORef mquery (Just query) I.writeIORef mquery (Just query)
pure $ handler (error "WTF DUDE") pure $ handler query
dcMockableServer :: I.IORef MockConfig -> I.IORef (Maybe API.QueryRequest) -> Server API.Api type HealthcheckApi =
dcMockableServer mcfg mquery = mockCapabilitiesHandler mcfg :<|> mockSchemaHandler mcfg :<|> mockQueryHandler mcfg mquery "healthz"
:> Get '[JSON] ()
healthcheckHandler :: Handler ()
healthcheckHandler = pure ()
dcMockableServer :: I.IORef MockConfig -> I.IORef (Maybe API.QueryRequest) -> Server (API.Api :<|> HealthcheckApi)
dcMockableServer mcfg mquery = (mockCapabilitiesHandler mcfg :<|> mockSchemaHandler mcfg :<|> mockQueryHandler mcfg mquery) :<|> healthcheckHandler
mockAgentPort :: Warp.Port
mockAgentPort = 65006
runMockServer :: I.IORef MockConfig -> I.IORef (Maybe API.QueryRequest) -> IO () runMockServer :: I.IORef MockConfig -> I.IORef (Maybe API.QueryRequest) -> IO ()
runMockServer mcfg mquery = do runMockServer mcfg mquery = do
let app = serve (Proxy :: Proxy API.Api) $ dcMockableServer mcfg mquery let app = serve (Proxy :: Proxy (API.Api :<|> HealthcheckApi)) $ dcMockableServer mcfg mquery
Warp.run 65006 app Warp.run mockAgentPort app