Skip to content

Commit 32d1c49

Browse files
authored
Add warnings field to FGA check / query response (#442)
* Add warnings to FGA check / query response * Fix typing issues with Pydantic * Fix python 3.8 issue
1 parent 17e86c6 commit 32d1c49

File tree

5 files changed

+140
-3
lines changed

5 files changed

+140
-3
lines changed

tests/test_fga.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,103 @@ def test_get_resource_401(self, mock_http_client_with_response):
101101
self.fga.get_resource(resource_type="test", resource_id="test")
102102

103103

104+
class TestWarnings:
105+
@pytest.fixture(autouse=True)
106+
def setup(self, sync_http_client_for_test):
107+
self.http_client = sync_http_client_for_test
108+
self.fga = FGA(http_client=self.http_client)
109+
110+
def test_check_with_warning(self, mock_http_client_with_response):
111+
mock_response = {
112+
"result": "authorized",
113+
"is_implicit": True,
114+
"warnings": [
115+
{
116+
"code": "missing_context_keys",
117+
"message": "Missing context keys",
118+
"keys": ["key1", "key2"],
119+
}
120+
],
121+
}
122+
mock_http_client_with_response(self.http_client, mock_response, 200)
123+
124+
response = self.fga.check(
125+
op="any_of",
126+
checks=[
127+
WarrantCheckInput(
128+
resource_type="schedule",
129+
resource_id="schedule-A1",
130+
relation="viewer",
131+
subject=SubjectInput(resource_type="user", resource_id="user-A"),
132+
)
133+
],
134+
)
135+
assert response.dict(exclude_none=True) == mock_response
136+
137+
def test_query_with_warning(self, mock_http_client_with_response):
138+
mock_response = {
139+
"object": "list",
140+
"data": [
141+
{
142+
"resource_type": "user",
143+
"resource_id": "richard",
144+
"relation": "member",
145+
"warrant": {
146+
"resource_type": "role",
147+
"resource_id": "developer",
148+
"relation": "member",
149+
"subject": {"resource_type": "user", "resource_id": "richard"},
150+
},
151+
"is_implicit": True,
152+
}
153+
],
154+
"list_metadata": {},
155+
"warnings": [
156+
{
157+
"code": "missing_context_keys",
158+
"message": "Missing context keys",
159+
"keys": ["key1", "key2"],
160+
}
161+
],
162+
}
163+
164+
mock_http_client_with_response(self.http_client, mock_response, 200)
165+
166+
response = self.fga.query(
167+
q="select member of type user for permission:view-docs",
168+
order="asc",
169+
warrant_token="warrant_token",
170+
)
171+
assert response.dict(exclude_none=True) == mock_response
172+
173+
def test_check_with_generic_warning(self, mock_http_client_with_response):
174+
mock_response = {
175+
"result": "authorized",
176+
"is_implicit": True,
177+
"warnings": [
178+
{
179+
"code": "generic",
180+
"message": "Generic warning",
181+
}
182+
],
183+
}
184+
185+
mock_http_client_with_response(self.http_client, mock_response, 200)
186+
187+
response = self.fga.check(
188+
op="any_of",
189+
checks=[
190+
WarrantCheckInput(
191+
resource_type="schedule",
192+
resource_id="schedule-A1",
193+
relation="viewer",
194+
subject=SubjectInput(resource_type="user", resource_id="user-A"),
195+
)
196+
],
197+
)
198+
assert response.dict(exclude_none=True) == mock_response
199+
200+
104201
class TestFGA:
105202
@pytest.fixture(autouse=True)
106203
def setup(self, sync_http_client_for_test):

workos/fga.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
WarrantWriteOperation,
1212
WriteWarrantResponse,
1313
WarrantQueryResult,
14+
FGAWarning,
1415
)
1516
from workos.types.fga.list_filters import (
1617
AuthorizationResourceListFilters,
@@ -45,9 +46,11 @@
4546

4647
WarrantListResource = WorkOSListResource[Warrant, WarrantListFilters, ListMetadata]
4748

48-
WarrantQueryListResource = WorkOSListResource[
49-
WarrantQueryResult, WarrantQueryListFilters, ListMetadata
50-
]
49+
50+
class WarrantQueryListResource(
51+
WorkOSListResource[WarrantQueryResult, WarrantQueryListFilters, ListMetadata]
52+
):
53+
warnings: Optional[Sequence[FGAWarning]] = None
5154

5255

5356
class FGAModule(Protocol):
@@ -641,5 +644,6 @@ def query(
641644
return WarrantQueryListResource(
642645
list_method=self.query,
643646
list_args=list_params,
647+
warnings=response.get("warnings"),
644648
**ListPage[WarrantQueryResult](**response).model_dump(),
645649
)

workos/types/fga/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
from .authorization_resource_types import *
33
from .authorization_resources import *
44
from .warrant import *
5+
from .warnings import *

workos/types/fga/check.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from workos.types.workos_model import WorkOSModel
44
from workos.typing.literals import LiteralOrUntyped
55

6+
from .warnings import FGAWarning
67
from .warrant import Subject, SubjectInput
78

89
CheckOperation = Literal["any_of", "all_of", "batch"]
@@ -44,6 +45,7 @@ class CheckResponse(WorkOSModel):
4445
result: LiteralOrUntyped[CheckResult]
4546
is_implicit: bool
4647
debug_info: Optional[DebugInfo] = None
48+
warnings: Optional[Sequence[FGAWarning]] = None
4749

4850
def authorized(self) -> bool:
4951
return self.result == "authorized"

workos/types/fga/warnings.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import Sequence, Union, Any, Dict, Literal
2+
from typing_extensions import Annotated
3+
4+
from pydantic import BeforeValidator
5+
from pydantic_core.core_schema import ValidationInfo
6+
7+
from workos.types.workos_model import WorkOSModel
8+
9+
10+
class FGABaseWarning(WorkOSModel):
11+
code: str
12+
message: str
13+
14+
15+
class MissingContextKeysWarning(FGABaseWarning):
16+
code: Literal["missing_context_keys"]
17+
keys: Sequence[str]
18+
19+
20+
def fga_warning_dispatch_validator(
21+
value: Dict[str, Any], info: ValidationInfo
22+
) -> FGABaseWarning:
23+
if value.get("code") == "missing_context_keys":
24+
return MissingContextKeysWarning.model_validate(value)
25+
26+
# Fallback to the base warning model
27+
return FGABaseWarning.model_validate(value)
28+
29+
30+
FGAWarning = Annotated[
31+
Union[MissingContextKeysWarning, FGABaseWarning],
32+
BeforeValidator(fga_warning_dispatch_validator),
33+
]

0 commit comments

Comments
 (0)