Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support API URL configuration in JS and Python SDKs #302

Merged
merged 6 commits into from
Feb 6, 2024
Merged
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
9 changes: 3 additions & 6 deletions config/clients/js/template/README_initializing.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ The documentation below refers to the `{{appShortName}}Client`, to read the docu
const { {{appShortName}}Client } = require('{{packageName}}'); // OR import { {{appShortName}}Client } from '{{packageName}}';

const fgaClient = new {{appShortName}}Client({
apiScheme: process.env.FGA_API_SCHEME, // optional, defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.{{sampleApiDomain}} instead of https://api.{{sampleApiDomain}})
apiUrl: process.env.FGA_API_URL, // required
storeId: process.env.FGA_STORE_ID, // not needed when calling `CreateStore` or `ListStores`
authorizationModelId: process.env.FGA_AUTHORIZATION_MODEL_ID, // Optional, can be overridden per request
});
Expand All @@ -21,8 +20,7 @@ const fgaClient = new {{appShortName}}Client({
const { {{appShortName}}Client } = require('{{packageName}}'); // OR import { {{appShortName}}Client } from '{{packageName}}';

const fgaClient = new {{appShortName}}Client({
apiScheme: process.env.FGA_API_SCHEME, // optional, defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.{{sampleApiDomain}} instead of https://api.{{sampleApiDomain}})
apiUrl: process.env.FGA_API_URL, // required
storeId: process.env.FGA_STORE_ID, // not needed when calling `CreateStore` or `ListStores`
authorizationModelId: process.env.FGA_AUTHORIZATION_MODEL_ID, // Optional, can be overridden per request
credentials: {
Expand All @@ -40,8 +38,7 @@ const fgaClient = new {{appShortName}}Client({
const { {{appShortName}}Client } = require('{{packageName}}'); // OR import { {{appShortName}}Client } from '{{packageName}}';

const fgaClient = new {{appShortName}}Client({
apiScheme: process.env.FGA_API_SCHEME, // optional, defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.{{sampleApiDomain}} instead of https://api.{{sampleApiDomain}})
apiUrl: process.env.FGA_API_URL, // required
storeId: process.env.FGA_STORE_ID, // not needed when calling `CreateStore` or `ListStores`
authorizationModelId: process.env.FGA_AUTHORIZATION_MODEL_ID, // Optional, can be overridden per request
credentials: {
Expand Down
39 changes: 34 additions & 5 deletions config/clients/js/template/configuration.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ export interface RetryParams {
}

export interface UserConfigurationParams {
apiUrl?: string;
/**
* @deprecated Replace usage of `apiScheme` + `apiHost` with `apiUrl`
*/
apiScheme?: string;
apiHost: string;
/**
* @deprecated Replace usage of `apiScheme` + `apiHost` with `apiUrl`
*/
apiHost?: string;
storeId?: string;
credentials?: CredentialsConfig;
baseOptions?: any;
Expand Down Expand Up @@ -58,18 +65,28 @@ export class Configuration {
*/
private static sdkVersion = "{{packageVersion}}";

/**
* provide the full api URL (e.g. `https://api.{{sampleApiDomain}}`)
*
* @type {string}
* @memberof Configuration
*/
apiUrl: string;

/**
* provide scheme (e.g. `https`)
*
* @type {string}
* @memberof Configuration
* @deprecated
*/
apiScheme = "https";
/**
* provide server host (e.g. `api.{{sampleApiDomain}}`)
*
* @type {string}
* @memberof Configuration
* @deprecated
*/
apiHost: string;
/**
Expand Down Expand Up @@ -104,6 +121,7 @@ export class Configuration {
constructor(params: UserConfigurationParams = {} as unknown as UserConfigurationParams) {
this.apiScheme = params.apiScheme || this.apiScheme;
this.apiHost = params.apiHost!;
this.apiUrl = params.apiUrl!;
this.storeId = params.storeId!;

const credentialParams = params.credentials;
Expand Down Expand Up @@ -156,12 +174,17 @@ export class Configuration {
* @throws {FgaValidationError}
*/
public isValid(): boolean {
assertParamExists("Configuration", "apiScheme", this.apiScheme);
assertParamExists("Configuration", "apiHost", this.apiHost);
if (!this.apiUrl) {
assertParamExists("Configuration", "apiScheme", this.apiScheme);
assertParamExists("Configuration", "apiHost", this.apiHost);
}

if (!isWellFormedUriString(this.getBasePath())) {
throw new FgaValidationError(
`Configuration.apiScheme (${this.apiScheme}) and Configuration.apiHost (${this.apiHost}) do not form a valid URI (${this.getBasePath()})`);
this.apiUrl ?
`Configuration.apiUrl (${this.apiUrl}) is not a valid URI (${this.getBasePath()})` :
`Configuration.apiScheme (${this.apiScheme}) and Configuration.apiHost (${this.apiHost}) do not form a valid URI (${this.getBasePath()})`
);
}

if (this.storeId && !isWellFormedUlidString(this.storeId)) {
Expand All @@ -178,5 +201,11 @@ export class Configuration {
/**
* Returns the API base path (apiScheme+apiHost)
*/
public getBasePath: () => string = () => `${this.apiScheme}://${this.apiHost}`;
public getBasePath: () => string = () => {
if (this.apiUrl) {
return this.apiUrl
} else {
return `${this.apiScheme}://${this.apiHost}`
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Configuration, UserConfigurationParams } from "../../configuration";
import { CredentialsMethod } from "../../credentials";

export const {{appUpperCaseName}}_STORE_ID = "01H0H015178Y2V4CX10C2KGHF4";
export const {{appUpperCaseName}}_API_HOST = "api.{{sampleApiDomain}}";
export const {{appUpperCaseName}}_API_URL = "http://api.{{sampleApiDomain}}";
export const {{appUpperCaseName}}_API_TOKEN_ISSUER = "tokenissuer.{{sampleApiDomain}}";
export const {{appUpperCaseName}}_API_AUDIENCE = "https://api.{{sampleApiDomain}}/";
export const {{appUpperCaseName}}_CLIENT_ID = "01H0H3D8TD07EWAQHXY9BWJG3V";
Expand All @@ -13,7 +13,7 @@ export const {{appUpperCaseName}}_API_TOKEN = "fga_abcdef";

export const baseConfig: UserConfigurationParams = {
storeId: {{appUpperCaseName}}_STORE_ID,
apiHost: {{appUpperCaseName}}_API_HOST,
apiUrl: {{appUpperCaseName}}_API_URL,
credentials: {
method: CredentialsMethod.ClientCredentials,
config: {
Expand Down
26 changes: 19 additions & 7 deletions config/clients/js/template/tests/index.test.ts.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { AuthCredentialsConfig } from "../credentials";
import {
baseConfig,
defaultConfiguration,
{{appUpperCaseName}}_API_HOST,
{{appUpperCaseName}}_API_URL,
{{appUpperCaseName}}_API_TOKEN_ISSUER,
{{appUpperCaseName}}_STORE_ID
} from "./helpers/default-config";
Expand Down Expand Up @@ -53,16 +53,28 @@ describe("{{appTitleCaseName}} SDK", function () {

it("should require host in configuration", () => {
expect(
() => new {{appShortName}}Api({ ...baseConfig, apiHost: undefined! })
() => new {{appShortName}}Api({ ...baseConfig, apiUrl: undefined! })
).toThrowError();
});

it("should validate host in configuration (adding scheme as part of the host)", () => {
expect(
() => new {{appShortName}}Api({ ...baseConfig, apiHost: "https://api.{{sampleApiDomain}}" })
() => new {{appShortName}}Api({ ...baseConfig, apiUrl: "//api.{{sampleApiDomain}}" })
).toThrowError();
});

it("should allow using apiHost if apiUrl is not provided", () => {
expect(
() => new OpenFgaApi({ ...baseConfig, apiHost: "api.fga.example" })
).not.toThrowError();
});

it("should still validate apiHost", () => {
expect(
() => new OpenFgaApi({ ...baseConfig, apiHost: "//api.{{sampleApiDomain}}" })
).not.toThrowError();
});

it("should validate apiTokenIssuer in configuration (should not allow scheme as part of the apiTokenIssuer)", () => {
expect(
() => new {{appShortName}}Api({
Expand All @@ -83,7 +95,7 @@ describe("{{appTitleCaseName}} SDK", function () {
() =>
new {{appShortName}}Api({
storeId: baseConfig.storeId!,
apiHost: baseConfig.apiHost,
apiUrl: baseConfig.apiUrl,
})
).not.toThrowError();
});
Expand All @@ -93,7 +105,7 @@ describe("{{appTitleCaseName}} SDK", function () {
() =>
new {{appShortName}}Api({
storeId: baseConfig.storeId!,
apiHost: baseConfig.apiHost,
apiUrl: baseConfig.apiUrl,
credentials: {
method: CredentialsMethod.ApiToken as any
}
Expand Down Expand Up @@ -229,7 +241,7 @@ describe("{{appTitleCaseName}} SDK", function () {

const fgaApi = new {{appShortName}}Api({
storeId: baseConfig.storeId!,
apiHost: baseConfig.apiHost,
apiUrl: baseConfig.apiUrl,
});
expect(scope.isDone()).toBe(false);

Expand All @@ -247,7 +259,7 @@ describe("{{appTitleCaseName}} SDK", function () {

it("should allow updating the storeId after initialization", async () => {
const fgaApi = new {{appShortName}}Api({
apiHost: {{appUpperCaseName}}_API_HOST
apiUrl: {{appUpperCaseName}}_API_URL
});
expect(fgaApi.storeId).toBe(undefined);
fgaApi.storeId = {{appUpperCaseName}}_STORE_ID;
Expand Down
Loading
Loading