Skip to main content

ScopeSet

Struct ScopeSet 

Source
pub struct ScopeSet(/* private fields */);
Expand description

An ordered, deduplicated set of scope tokens. The wire form (both directions) is the RFC’s space-delimited string; ordering here is lexicographic so serialization is deterministic.

§Why a sorted Vec and not a BTreeSet

This was a BTreeSet<Scope> through 0.9.1, and the INVARIANT is unchanged: the tokens are held sorted and deduplicated, so Display, Serialize, PartialEq and is_subset all answer exactly what the tree answered. What changed is the code the invariant costs.

The set this type actually holds is tiny. A scope set is the scope parameter of one request or the allowed_scopes of one client: single digits of tokens, each a handful of bytes. A B-tree is machinery for a set that is large enough for the log factor to pay for the node bookkeeping, and at these sizes it never does — it allocates a whole node to hold one token, and every operation on it links a distinct instantiation of BTreeMap’s insert, clone and comparison paths into the binary.

MEASURED 2026-08-13 by scripts/size-report.sh on the default row (aarch64-apple-darwin, rustc 1.97.0): 15,394 bytes, taking the row from 234,623 to 219,229. That is not the tree’s insert alone; it is every BTreeMap instantiation this type forced into a linked binary — insert with its node splitting, clone_subtree, PartialEq::eq, is_subset’s range descent, the iterators and the drop glue — none of which a host that only ever holds five scope tokens was getting anything for. It is the same trade, for the same reason, as the one crate::store’s barrier list records: an ordered container whose reads were a linear scan anyway does not need a tree to be one.

It is also a much smaller heap footprint per stored record, which is the part that scales with a deployment rather than with the binary. A BTreeSet allocates a whole leaf node the moment it holds anything, and that node is the same size for one token as for eleven. MEASURED with tests/support/alloc.rs, ScopeSet::parse:

tokensbeforeafter
12 allocs, 284 B2 allocs, 28 B
34 allocs, 294 B4 allocs, 86 B

Same allocation COUNT — the vector is sized once from the token count, so it does not trade bytes for calls — and 90% fewer bytes at the size a real scope parameter actually is. Every Client, IssuedToken, AuthorizationCodeRecord, RefreshTokenRecord, DeviceGrant and consent record in a store was carrying one of those 256-byte nodes to hold a word or two.

Implementations§

Source§

impl ScopeSet

Source

pub fn empty() -> Self

The empty set (serializes to the empty string; hosts normally omit the parameter instead).

Source

pub fn parse(s: &str) -> Result<Self, InvalidScopeToken>

Parse a space-delimited scope string. Repeated whitespace is tolerated; each token is charset-validated.

§There is NO cap on the token count, and that is a decision with a cost

MEASURED by benches/scaling.rs, both implementations on the same machine in the same session, 2026-08-13:

tokensBTreeSet (through 0.9.1)sorted Vec
132.0 ns39.0 ns
10330 ns340 ns
1006.05 us4.41 us
100081.09 us48.68 us

The seven nanoseconds at one token are the pre-pass that COUNTS the tokens, and they are what buys the single correctly-sized allocation; from a hundred tokens up the sort is the faster structure by a wide margin. The growth is n log n either way, so this is not the accidental quadratic that crate::server::MAX_RESOURCE_INDICATORS exists to bound; it is a straightforward “how big may the parameter be” question, and reaching the top of that range takes roughly ten kilobytes of scope, which a host’s own request-size limit is the right place to refuse.

A cap here was considered and NOT taken, because it cannot be expressed without a breaking change that is out of proportion to the problem: InvalidScopeToken is a tuple struct with a public field, so it cannot gain a “too many” variant, and this same function is the serde::Deserialize implementation for every persisted record that carries a scope, as well as the constructor a host uses for its own allowed_scopes. A limit applied here would therefore be a limit on what a deployment may REGISTER and on what it can read back out of its own store, which is a different and much larger decision than bounding a request.

If a bound is wanted, the place for it is the wire boundary, alongside the other request caps, and it needs an error type this one cannot currently express.

Source

pub fn from_tokens<I, T>(tokens: I) -> Result<Self, InvalidScopeToken>
where I: IntoIterator<Item = T>, T: Into<String>,

Build from tokens, validating each.

Source

pub fn is_subset(&self, other: &ScopeSet) -> bool

True when every token in self is also in other.

A merge walk over two sorted, deduplicated slices: linear in the two lengths, with no allocation and no tree descent. Same answer the BTreeSet gave.

Source

pub fn is_empty(&self) -> bool

True when the set holds no tokens.

Source

pub fn len(&self) -> usize

Number of tokens.

Source

pub fn contains(&self, token: &str) -> bool

Membership test.

Source

pub fn iter(&self) -> impl Iterator<Item = &Scope>

Iterate tokens in lexicographic order.

Trait Implementations§

Source§

impl Clone for ScopeSet

Source§

fn clone(&self) -> ScopeSet

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ScopeSet

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ScopeSet

Source§

fn default() -> ScopeSet

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for ScopeSet

Source§

fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Display for ScopeSet

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for ScopeSet

Source§

impl PartialEq for ScopeSet

Source§

fn eq(&self, other: &ScopeSet) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for ScopeSet

Source§

fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for ScopeSet

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.