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

[http/openapi] Use enum-driven visibility analysis APIs #5416

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1672b83
Additional Delete/Query visibilities and filter versions of returntyp…
willmtemple Dec 18, 2024
00aa92c
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Dec 19, 2024
3721fa3
Rework HTTP to use new visibility APIs
willmtemple Dec 19, 2024
a0b2361
[hsc] fix a null visibility constraint in effective type calculation
willmtemple Dec 19, 2024
e6720be
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Dec 19, 2024
42242b2
Make fallback HttpVisibilityProvider logic more accurate
willmtemple Dec 19, 2024
d851685
Chronus
willmtemple Dec 19, 2024
be4057f
Update .chronus/changes/witemple-msft-http-visibility-enum-2024-11-19…
witemple-msft Dec 19, 2024
f03067d
Docs
willmtemple Dec 19, 2024
8dc8808
Merge remote-tracking branch 'origin/witemple-msft/http-visibility-en…
willmtemple Dec 19, 2024
6158348
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Jan 7, 2025
dc6cb7b
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Jan 8, 2025
d81b712
Fix bug where hasVisibility was improperly initializing visibility st…
willmtemple Jan 8, 2025
b0714a4
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Jan 8, 2025
b6d2de0
Prevent hasVisibility from eagerly initializing visibility
willmtemple Jan 8, 2025
282b794
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Jan 13, 2025
0747732
Made visibility filters empty when returnType/parameter visibility ar…
willmtemple Jan 13, 2025
ebb3f5d
Updated documentation of returnType/parameter visibility decorators
willmtemple Jan 14, 2025
9189138
Fixed a subtle bug in invocation of .map
willmtemple Jan 14, 2025
1e53aeb
Changed body-type detection to use payload disposition instead of vis…
willmtemple Jan 14, 2025
c926043
Changed default canonicalVisibility to Visibility.All
willmtemple Jan 14, 2025
0a3ca5b
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Jan 15, 2025
48c88bb
Consolidated synthetic visibility metadata, added SkipEffectiveOption…
willmtemple Jan 15, 2025
e7c3e62
Added underbar protocol for detecting empty parameterVisibility for HTTP
willmtemple Jan 15, 2025
adee210
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Jan 15, 2025
942b0a1
Un-internal visibility flags
willmtemple Jan 15, 2025
451ead4
Add horrible workaround for empty parameterVisibility
willmtemple Jan 17, 2025
64572ef
Update packages/http/test/http-decorators.test.ts
witemple-msft Jan 21, 2025
a243416
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Jan 21, 2025
3211758
Improve documentation
willmtemple Jan 21, 2025
827e42f
Change shared symbol for IsParameterVisibilityEmpty
willmtemple Jan 21, 2025
9eb32a0
More thorough test
willmtemple Jan 21, 2025
e2c3c75
Merge remote-tracking branch 'upstream/main' into witemple-msft/http-…
willmtemple Jan 21, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
changeKind: internal
packages:
- "@typespec/http"
- "@typespec/openapi3"
witemple-msft marked this conversation as resolved.
Show resolved Hide resolved
---

Updated the OpenAPI3 and HTTP libraries to use the new visibility analysis APIs.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/compiler"
---

Added APIs for getting parameterVisibility and returnTypeVisibility as VisibilityFilter objects.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-server-csharp"
---

Fixed a null visibility constraint when calculating effective type.
103 changes: 101 additions & 2 deletions packages/compiler/lib/std/visibility.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,7 @@ extern dec defaultVisibility(target: Enum, ...visibilities: valueof EnumMember[]
/**
* A visibility class for resource lifecycle phases.
*
* These visibilities control whether a property is visible during the create, read, and update phases of a resource's
* lifecycle.
* These visibilities control whether a property is visible during the various phases of a resource's lifecycle.
*
* @example
* ```typespec
Expand All @@ -195,9 +194,32 @@ extern dec defaultVisibility(target: Enum, ...visibilities: valueof EnumMember[]
* therefore visible in all phases.
*/
enum Lifecycle {
/**
* The property is visible when a resource is being created.
*/
Create,

/**
* The property is visible when a resource is being read.
*/
Read,

/**
* The property is visible when a resource is being updated.
*/
Update,

/**
* The property is visible when a resource is being deleted.
*/
Delete,

/**
* The property is visible when a resource is being queried.
*
* In HTTP APIs, this visibility applies to parameters of GET or HEAD operations.
*/
Query,
}

/**
Expand Down Expand Up @@ -306,6 +328,9 @@ model Create<T extends Reflection.Model, NameTemplate extends valueof string = "
* A copy of the input model `T` with only the properties that are visible during the
* "Read" resource lifecycle phase.
*
* The "Read" lifecycle phase is used for properties returned by operations that read data, like
* HTTP GET operations.
*
* This transformation is recursive, and will include only properties that have the
* `Lifecycle.Read` visibility modifier.
*
Expand Down Expand Up @@ -337,6 +362,9 @@ model Read<T extends Reflection.Model, NameTemplate extends valueof string = "Re
* A copy of the input model `T` with only the properties that are visible during the
* "Update" resource lifecycle phase.
*
* The "Update" lifecycle phase is used for properties passed as parameters to operations
* that update data, like HTTP PATCH operations.
*
* This transformation will include only the properties that have the `Lifecycle.Update`
* visibility modifier, and the types of all properties will be replaced with the
* equivalent `CreateOrUpdate` transformation.
Expand Down Expand Up @@ -369,6 +397,9 @@ model Update<T extends Reflection.Model, NameTemplate extends valueof string = "
* A copy of the input model `T` with only the properties that are visible during the
* "Create" or "Update" resource lifecycle phases.
*
* The "CreateOrUpdate" lifecycle phase is used by default for properties passed as parameters to operations
* that can create _or_ update data, like HTTP PUT operations.
*
* This transformation is recursive, and will include only properties that have the
* `Lifecycle.Create` or `Lifecycle.Update` visibility modifier.
*
Expand Down Expand Up @@ -398,3 +429,71 @@ model CreateOrUpdate<
> {
...T;
}

/**
* A copy of the input model `T` with only the properties that are visible during the
* "Delete" resource lifecycle phase.
*
* The "Delete" lifecycle phase is used for properties passed as parameters to operations
* that delete data, like HTTP DELETE operations.
*
* This transformation is recursive, and will include only properties that have the
* `Lifecycle.Delete` visibility modifier.
*
* If a `NameTemplate` is provided, the new model will be named according to the template.
* The template uses the same syntax as the `@friendlyName` decorator.
*
* @template T The model to transform.
* @template NameTemplate The name template to use for the new model.
*
* * @example
* ```typespec
* model Dog {
* @visibility(Lifecycle.Read)
* id: int32;
*
* name: string;
* }
*
* model DeleteDog is Delete<Dog>;
* ```
*/
@friendlyName(NameTemplate, T)
@withVisibilityFilter(#{ all: #[Lifecycle.Delete] })
model Delete<T extends Reflection.Model, NameTemplate extends valueof string = "Delete{name}"> {
...T;
}

/**
* A copy of the input model `T` with only the properties that are visible during the
* "Query" resource lifecycle phase.
*
* The "Query" lifecycle phase is used for properties passed as parameters to operations
* that read data, like HTTP GET or HEAD operations.
*
* This transformation is recursive, and will include only properties that have the
* `Lifecycle.Query` visibility modifier.
*
* If a `NameTemplate` is provided, the new model will be named according to the template.
* The template uses the same syntax as the `@friendlyName` decorator.
*
* @template T The model to transform.
* @template NameTemplate The name template to use for the new model.
*
* * @example
* ```typespec
* model Dog {
* @visibility(Lifecycle.Read)
* id: int32;
*
* name: string;
* }
*
* model QueryDog is Query<Dog>;
* ```
*/
@friendlyName(NameTemplate, T)
@withVisibilityFilter(#{ all: #[Lifecycle.Query] })
model Query<T extends Reflection.Model, NameTemplate extends valueof string = "Query{name}"> {
...T;
}
41 changes: 27 additions & 14 deletions packages/compiler/src/core/visibility/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,18 +603,31 @@ export function hasVisibility(
* AND
*
* - NONE of the visibilities in the `none` set.
*
* Note: The constraints behave similarly to the `every` and `some` methods of the Array prototype in JavaScript. If the
* `any` constraint is set to an empty set, it will _NEVER_ be satisfied (similarly, `Array.prototype.some` will always
* return `false` for an empty array). If the `none` constraint is set to an empty set, it will _ALWAYS_ be satisfied.
* If the `all` constraint is set to an empty set, it will be satisfied (similarly, `Array.prototype.every` will always
* return `true` for an empty array).
*
*/
export interface VisibilityFilter {
/**
* If set, the filter considers a property visible if it has ALL of these visibility modifiers.
*
* If this set is empty, the filter will be satisfied if the other constraints are satisfied.
*/
all?: Set<EnumMember>;
/**
* If set, the filter considers a property visible if it has ANY of these visibility modifiers.
*
* If this set is empty, the filter will _NEVER_ be satisfied.
*/
any?: Set<EnumMember>;
/**
* If set, the filter considers a property visible if it has NONE of these visibility modifiers.
*
* If this set is empty, the filter will be satisfied if the other constraints are satisfied.
*/
none?: Set<EnumMember>;
}
Expand Down Expand Up @@ -690,30 +703,30 @@ export function isVisible(
return isVisibleLegacy(_filterOrLegacyVisibilities);
}

const filter = { ...(_filterOrLegacyVisibilities as VisibilityFilter) };
filter.all ??= new Set();
filter.any ??= new Set();
filter.none ??= new Set();
const filter = _filterOrLegacyVisibilities as VisibilityFilter;

// Validate that property has ALL of the required visibilities of filter.all
for (const modifier of filter.all) {
if (!hasVisibility(program, property, modifier)) return false;
if (filter.all) {
for (const modifier of filter.all) {
if (!hasVisibility(program, property, modifier)) return false;
}
}

// Validate that property has ANY of the required visibilities of filter.any
outer: while (filter.any.size > 0) {
// Validate that property has NONE of the excluded visibilities of filter.none
if (filter.none) {
for (const modifier of filter.none) {
if (hasVisibility(program, property, modifier)) return false;
}
}

if (filter.any) {
for (const modifier of filter.any) {
if (hasVisibility(program, property, modifier)) break outer;
if (hasVisibility(program, property, modifier)) return true;
}

return false;
}

// Validate that property has NONE of the excluded visibilities of filter.none
for (const modifier of filter.none) {
if (hasVisibility(program, property, modifier)) return false;
}

return true;

function isVisibleLegacy(visibilities: readonly string[]) {
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/core/visibility/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export { getLifecycleVisibilityEnum } from "./lifecycle.js";

export {
VisibilityFilter,
addVisibilityModifiers,
clearVisibilityModifiersForClass,
getVisibility,
Expand Down
8 changes: 8 additions & 0 deletions packages/compiler/src/core/visibility/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export function normalizeLegacyLifecycleVisibilityString(
return lifecycle.members.get("Read")!;
case "update":
return lifecycle.members.get("Update")!;
case "delete":
return lifecycle.members.get("Delete")!;
case "query":
return lifecycle.members.get("Query")!;
default:
return undefined;
}
Expand Down Expand Up @@ -83,6 +87,10 @@ export function normalizeVisibilityToLegacyLifecycleString(
return "read";
case "Update":
return "update";
case "Delete":
return "delete";
case "Query":
return "query";
default:
return undefined;
}
Expand Down
Loading
Loading