Skip to content
Open
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
2 changes: 2 additions & 0 deletions docs/content/modeling/agents/agents-as-principals.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Check whether the agent can read a specific issue. The agent's project membershi
object={'issue:issue-123'}
allowed={true}
skipSetup={true}
pseudoCodeMode={true}
/>

The agent cannot delete the issue because `can_delete` requires `reporter` or project-level `can_delete` (which requires `owner` or `admin`):
Expand All @@ -110,6 +111,7 @@ The agent cannot delete the issue because `can_delete` requires `reporter` or pr
object={'issue:issue-123'}
allowed={false}
skipSetup={true}
pseudoCodeMode={true}
/>

## Direct assignment for fine-grained control
Expand Down
17 changes: 6 additions & 11 deletions src/components/Docs/SnippetViewer/BatchCheckRequestViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFilteredAllowedLangs, SupportedLanguage, DefaultAuthorizationModelId } from './SupportedLanguage';
import { defaultOperationsViewer } from './DefaultTabbedViewer';
import { defaultOperationsViewer, type DefaultTabbedViewerOpts } from './DefaultTabbedViewer';
import assertNever from 'assert-never/index';
import { TupleKey } from '@openfga/sdk';

Expand All @@ -13,10 +13,9 @@ interface Check {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context?: Record<string, any>;
}
interface BatchCheckRequestViewerOpts {
interface BatchCheckRequestViewerOpts extends DefaultTabbedViewerOpts {
authorizationModelId?: string;
checks: Check[];
skipSetup?: boolean;
allowedLanguages?: SupportedLanguage[];
}

Expand All @@ -32,8 +31,7 @@ function batchCheckRequestViewer(lang: SupportedLanguage, opts: BatchCheckReques
throw new Error('Batch check is not supported in the CLI');

case SupportedLanguage.JS_SDK:
return `// Requires >=v0.8.0 for the server side BatchCheck, earlier versions support a client-side BatchCheck with a slightly different interface
const body = {
return `const body = {
checks: [
${checks
.map(
Expand Down Expand Up @@ -108,8 +106,7 @@ const { result } = await fgaClient.batchCheck(body, options);
*/`;

case SupportedLanguage.GO_SDK:
return `// Requires >=v0.7.0 for the server side BatchCheck, earlier versions support a client-side BatchCheck with a slightly different interface
body := ClientBatchCheckRequest{
return `body := ClientBatchCheckRequest{
Checks: []ClientBatchCheckItem{${checks
.map(
(check) => `
Expand Down Expand Up @@ -249,9 +246,7 @@ response.Result = [${checks
`;

case SupportedLanguage.PYTHON_SDK:
return `# Requires >=v0.9.0 for the server side BatchCheck, earlier versions support a client-side BatchCheck with a slightly different interface

checks = [${checks
return `checks = [${checks
.map(
(check) => `
ClientBatchCheckItem(
Expand Down Expand Up @@ -298,7 +293,7 @@ response = await fga_client.batch_check(ClientBatchCheckRequest(checks=checks),
.join(', ')}]`;

case SupportedLanguage.JAVA_SDK:
return ` // Requires >=v0.8.0 for the server side BatchCheck, earlier versions support a client-side BatchCheck with a slightly different interface
return `
var request = new ClientBatchCheckRequest().checks(
List.of(
${checks
Expand Down
26 changes: 15 additions & 11 deletions src/components/Docs/SnippetViewer/CheckRequestViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface CheckRequestViewerOpts {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context?: Record<string, any>;
skipSetup?: boolean;
pseudoCodeMode?: boolean;
allowedLanguages?: SupportedLanguage[];

// Optional custom headers
Expand Down Expand Up @@ -256,12 +257,22 @@ response = await fga_client.check(body, options)
return `check(
user = "${user}", // check if the user \`${user}\`
relation = "${relation}", // has an \`${relation}\` relation
object = "${object}", // with the object \`${object}\`
${
object = "${object}", // with the object \`${object}\`${
headers && Object.keys(headers).length > 0
? `
headers = { ${Object.entries(headers)
.map(([key, value]) => `"${key}" = "${value}"`)
.join(', ')} },`
: ''
}${
contextualTuples
? `contextual_tuples = [ // Assuming the following is true
? `
contextual_tuples = [ // Assuming the following is true
${contextualTuples
.map((tuple) => `{user = "${tuple.user}", relation = "${tuple.relation}", object = "${tuple.object}"}`)
.map(
(tuple) =>
`{\n user = "${tuple.user}",\n relation = "${tuple.relation}",\n object = "${tuple.object}",\n }`,
)
.join(',\n ')}
],`
: ''
Expand All @@ -272,13 +283,6 @@ response = await fga_client.check(body, options)
.map(([k, v]) => `${k} = "${v}"`)
.join(', ')} },`
: ''
} authorization_id = "${modelId}"${
headers && Object.keys(headers).length > 0
? `
extra_headers = { ${Object.entries(headers)
.map(([k, v]) => `"${k}": "${v}"`)
.join(', ')} },`
: ''
}
);

Expand Down
88 changes: 84 additions & 4 deletions src/components/Docs/SnippetViewer/DefaultTabbedViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
Expand All @@ -8,6 +8,62 @@ import { GenerateSetupHeader, LanguageWrapper } from './SdkSetup';

export interface DefaultTabbedViewerOpts {
skipSetup?: boolean;
pseudoCodeMode?: boolean;
}

function SdkToggle<T extends DefaultTabbedViewerOpts>({
allowedLanguages,
opts,
tabViewFn,
langMappings,
}: {
allowedLanguages: SupportedLanguage[];
opts: T;
tabViewFn: (lang: SupportedLanguage, opts: T, langMappings: LanguageMappings) => string;
langMappings: LanguageMappings;
}): JSX.Element {
const [showSdk, setShowSdk] = useState(false);

const sdkLanguages = allowedLanguages.filter((language) => language !== SupportedLanguage.RPC);
const toggleLabel = showSdk ? 'View pseudocode' : 'View code';

Comment on lines +14 to +29
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SdkToggle, sdkLanguages, and showSdk are named as if the toggle only switches SDK languages, but the implementation includes all non-RPC languages (e.g., CLI/curl) by filtering only SupportedLanguage.RPC. Consider renaming to reflect the actual behavior (e.g., CodeToggle, codeLanguages, showCode) or tighten the filter to SDK languages if that’s the intent.

Copilot uses AI. Check for mistakes.
return (
<div className="snippet-mode-toggle-wrapper">
<div className="snippet-mode-toggle">
<button
type="button"
aria-pressed={showSdk}
aria-label={toggleLabel}
className="snippet-mode-toggle__button"
onClick={() => setShowSdk((prev) => !prev)}
>
{toggleLabel}
</button>
</div>
{showSdk ? (
sdkLanguages.length > 0 && (
<Tabs groupId="languages" values={getAllowedValuesLabels({ allowedLanguages: sdkLanguages })}>
{sdkLanguages.map((allowedLanguage) => (
<TabItem value={allowedLanguage} key={allowedLanguage}>
{GenerateSetupHeader(allowedLanguage, opts.skipSetup)}
{LanguageWrapper({
allowedLanguage: allowedLanguage,
content: tabViewFn(allowedLanguage, opts, langMappings),
})}
</TabItem>
))}
</Tabs>
)
) : (
<div style={{ paddingTop: '1.5rem' }}>
{LanguageWrapper({
allowedLanguage: SupportedLanguage.RPC,
content: tabViewFn(SupportedLanguage.RPC, opts, langMappings),
})}
</div>
)}
</div>
);
}

export function defaultOperationsViewer<T extends DefaultTabbedViewerOpts>(
Expand All @@ -16,9 +72,33 @@ export function defaultOperationsViewer<T extends DefaultTabbedViewerOpts>(
tabViewFn: (lang: SupportedLanguage, opts: T, langMappings: LanguageMappings) => string,
): JSX.Element {
const { siteConfig } = useDocusaurusContext();
const configuredLanguage = siteConfig.customFields.languageMapping as LanguageMappings;
const configuredLanguageMapping = siteConfig.customFields?.languageMapping;

if (!configuredLanguageMapping) {
throw new Error('Missing required siteConfig.customFields.languageMapping configuration.');
}

const configuredLanguage = configuredLanguageMapping as LanguageMappings;

const pseudoCodeMode = opts.pseudoCodeMode ?? false;
const hasRpc = allowedLanguages.includes(SupportedLanguage.RPC);
const hasSdkLanguages = allowedLanguages.some((language) => language !== SupportedLanguage.RPC);

if (pseudoCodeMode && hasRpc && hasSdkLanguages) {
return (
<div style={{ marginTop: -20 }}>
<SdkToggle
allowedLanguages={allowedLanguages}
opts={opts}
tabViewFn={tabViewFn}
langMappings={configuredLanguage}
/>
</div>
Comment thread
aaguiarz marked this conversation as resolved.
);
}
Comment thread
aaguiarz marked this conversation as resolved.

return (
<>
<div style={{ marginTop: -5 }}>
<Tabs groupId="languages" values={getAllowedValuesLabels({ allowedLanguages })}>
{allowedLanguages.map((allowedLanguage) => (
<TabItem value={allowedLanguage} key={allowedLanguage}>
Expand All @@ -30,6 +110,6 @@ export function defaultOperationsViewer<T extends DefaultTabbedViewerOpts>(
</TabItem>
))}
</Tabs>
</>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface ListObjectsRequestViewerOpts {
context?: Record<string, any>;
expectedResults: string[];
skipSetup?: boolean;
pseudoCodeMode?: boolean;
allowedLanguages?: SupportedLanguage[];
}

Expand Down Expand Up @@ -206,8 +207,7 @@ response = await fga_client.list_objects(body, options)
return `listObjects(
"${user}", // list the objects that the user \`${user}\`
"${relation}", // has an \`${relation}\` relation
"${objectType}", // and that are of type \`${objectType}\`
authorization_model_id = "${modelId}", // for this particular authorization model id ${
"${objectType}", // and that are of type \`${objectType}\` ${
contextualTuples
? `
contextual_tuples = [ // Assuming the following is true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface ListUsersRequestViewerOpts {
context?: Record<string, any>;
expectedResults: ListUsersResponse;
skipSetup?: boolean;
pseudoCodeMode?: boolean;
allowedLanguages?: SupportedLanguage[];
}

Expand Down Expand Up @@ -263,8 +264,7 @@ var response = await fgaClient.ListUsers(body, options);
return `listUsers(
user_filter=[ "${userFilterType}" ], // list users of type \`${userFilterType}\`
"${relation}", // that have the \`${relation}\` relation
"${objectType}:${objectId}", // for the object \`${objectType}:${objectId}\`
authorization_model_id = "${modelId}", // for this particular authorization model id ${
"${objectType}:${objectId}", // for the object \`${objectType}:${objectId}\` ${
contextualTuples
? `
contextual_tuples = [ // Assuming the following is true
Expand Down
5 changes: 3 additions & 2 deletions src/components/Docs/SnippetViewer/WriteRequestViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface WriteRequestViewerOpts {
deleteRelationshipTuples: RelationshipTupleWithoutCondition[];
isDelete?: boolean;
skipSetup?: boolean;
pseudoCodeMode?: boolean;
allowedLanguages?: SupportedLanguage[];
conflictOptions?: {
onDuplicateWrites?: 'error' | 'ignore';
Expand Down Expand Up @@ -439,8 +440,8 @@ response = await fga_client.write(body, options)
}`,
)
.join(',');
const writes = `write([${writeTuples}\n], authorization_model_id="${modelId}")`;
const deletes = `delete([${deleteTuples}\n], authorization_model_id="${modelId}")`;
const writes = `write([${writeTuples}\n])`;
const deletes = `delete([${deleteTuples}\n])`;
Comment thread
aaguiarz marked this conversation as resolved.
const separator = `${opts.deleteRelationshipTuples && opts.relationshipTuples ? ',' : ''}`;

return `${opts.relationshipTuples ? writes : ''}${separator}
Expand Down
53 changes: 53 additions & 0 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,59 @@ table td {
order: 2;
}

.snippet-mode-toggle-wrapper {
position: relative;
}

.snippet-mode-toggle {
position: absolute;
right: 0;
top: 0.85rem;
z-index: 2;
}

.snippet-mode-toggle__button {
border: 0;
border-bottom: 1px solid transparent;
border-radius: 0;
background: transparent;
color: var(--ifm-font-color-base);
cursor: pointer;
font: inherit;
font-size: 0.9rem;
font-weight: 700;
line-height: 1;
letter-spacing: 0.01em;
opacity: 0.78;
padding: 0.2rem 0;
transition:
border-color 0.2s ease,
color 0.2s ease,
opacity 0.2s ease;
}

.snippet-mode-toggle__button:focus-visible {
outline: 2px solid color-mix(in srgb, var(--ifm-font-color-base), transparent 35%);
outline-offset: 2px;
}

.snippet-mode-toggle__button:hover {
color: var(--ifm-font-color-base);
border-color: currentColor;
opacity: 1;
}

[data-theme='light'] .snippet-mode-toggle__button {
color: var(--ifm-font-color-base);
opacity: 0.72;
}

[data-theme='light'] .snippet-mode-toggle__button:hover {
color: var(--ifm-font-color-base);
border-color: currentColor;
opacity: 0.9;
}

/* Mobile Layout */

@media (max-width: 996px) {
Expand Down
Loading