lexicon fixes, bunch of building out the actual edit flow, incl remote crdt storage

Orual 29177f7f 157cb538

+6501 -296
+1
Cargo.lock
··· 9955 9955 "mini-moka 0.11.0", 9956 9956 "n0-future", 9957 9957 "regex", 9958 + "regex-lite", 9958 9959 "reqwest", 9959 9960 "serde", 9960 9961 "serde_html_form",
+60
crates/weaver-api/lexicons/app_bsky_ageassurance_begin.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.ageassurance.begin", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Initiate Age Assurance for an account.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": [ 13 + "email", 14 + "language", 15 + "countryCode" 16 + ], 17 + "properties": { 18 + "countryCode": { 19 + "type": "string", 20 + "description": "An ISO 3166-1 alpha-2 code of the user's location." 21 + }, 22 + "email": { 23 + "type": "string", 24 + "description": "The user's email address to receive Age Assurance instructions." 25 + }, 26 + "language": { 27 + "type": "string", 28 + "description": "The user's preferred language for communication during the Age Assurance process." 29 + }, 30 + "regionCode": { 31 + "type": "string", 32 + "description": "An optional ISO 3166-2 code of the user's region or state within the country." 33 + } 34 + } 35 + } 36 + }, 37 + "output": { 38 + "encoding": "application/json", 39 + "schema": { 40 + "type": "ref", 41 + "ref": "app.bsky.ageassurance.defs#state" 42 + } 43 + }, 44 + "errors": [ 45 + { 46 + "name": "InvalidEmail" 47 + }, 48 + { 49 + "name": "DidTooLong" 50 + }, 51 + { 52 + "name": "InvalidInitiation" 53 + }, 54 + { 55 + "name": "RegionNotSupported" 56 + } 57 + ] 58 + } 59 + } 60 + }
+305
crates/weaver-api/lexicons/app_bsky_ageassurance_defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.ageassurance.defs", 4 + "defs": { 5 + "access": { 6 + "type": "string", 7 + "description": "The access level granted based on Age Assurance data we've processed.", 8 + "knownValues": [ 9 + "unknown", 10 + "none", 11 + "safe", 12 + "full" 13 + ] 14 + }, 15 + "config": { 16 + "type": "object", 17 + "description": "", 18 + "required": [ 19 + "regions" 20 + ], 21 + "properties": { 22 + "regions": { 23 + "type": "array", 24 + "description": "The per-region Age Assurance configuration.", 25 + "items": { 26 + "type": "ref", 27 + "ref": "app.bsky.ageassurance.defs#configRegion" 28 + } 29 + } 30 + } 31 + }, 32 + "configRegion": { 33 + "type": "object", 34 + "description": "The Age Assurance configuration for a specific region.", 35 + "required": [ 36 + "countryCode", 37 + "rules" 38 + ], 39 + "properties": { 40 + "countryCode": { 41 + "type": "string", 42 + "description": "The ISO 3166-1 alpha-2 country code this configuration applies to." 43 + }, 44 + "regionCode": { 45 + "type": "string", 46 + "description": "The ISO 3166-2 region code this configuration applies to. If omitted, the configuration applies to the entire country." 47 + }, 48 + "rules": { 49 + "type": "array", 50 + "description": "The ordered list of Age Assurance rules that apply to this region. Rules should be applied in order, and the first matching rule determines the access level granted. The rules array should always include a default rule as the last item.", 51 + "items": { 52 + "type": "union", 53 + "refs": [ 54 + "#configRegionRuleDefault", 55 + "#configRegionRuleIfDeclaredOverAge", 56 + "#configRegionRuleIfDeclaredUnderAge", 57 + "#configRegionRuleIfAssuredOverAge", 58 + "#configRegionRuleIfAssuredUnderAge", 59 + "#configRegionRuleIfAccountNewerThan", 60 + "#configRegionRuleIfAccountOlderThan" 61 + ] 62 + } 63 + } 64 + } 65 + }, 66 + "configRegionRuleDefault": { 67 + "type": "object", 68 + "description": "Age Assurance rule that applies by default.", 69 + "required": [ 70 + "access" 71 + ], 72 + "properties": { 73 + "access": { 74 + "type": "ref", 75 + "ref": "app.bsky.ageassurance.defs#access" 76 + } 77 + } 78 + }, 79 + "configRegionRuleIfAccountNewerThan": { 80 + "type": "object", 81 + "description": "Age Assurance rule that applies if the account is equal-to or newer than a certain date.", 82 + "required": [ 83 + "date", 84 + "access" 85 + ], 86 + "properties": { 87 + "access": { 88 + "type": "ref", 89 + "ref": "app.bsky.ageassurance.defs#access" 90 + }, 91 + "date": { 92 + "type": "string", 93 + "description": "The date threshold as a datetime string.", 94 + "format": "datetime" 95 + } 96 + } 97 + }, 98 + "configRegionRuleIfAccountOlderThan": { 99 + "type": "object", 100 + "description": "Age Assurance rule that applies if the account is older than a certain date.", 101 + "required": [ 102 + "date", 103 + "access" 104 + ], 105 + "properties": { 106 + "access": { 107 + "type": "ref", 108 + "ref": "app.bsky.ageassurance.defs#access" 109 + }, 110 + "date": { 111 + "type": "string", 112 + "description": "The date threshold as a datetime string.", 113 + "format": "datetime" 114 + } 115 + } 116 + }, 117 + "configRegionRuleIfAssuredOverAge": { 118 + "type": "object", 119 + "description": "Age Assurance rule that applies if the user has been assured to be equal-to or over a certain age.", 120 + "required": [ 121 + "age", 122 + "access" 123 + ], 124 + "properties": { 125 + "access": { 126 + "type": "ref", 127 + "ref": "app.bsky.ageassurance.defs#access" 128 + }, 129 + "age": { 130 + "type": "integer", 131 + "description": "The age threshold as a whole integer." 132 + } 133 + } 134 + }, 135 + "configRegionRuleIfAssuredUnderAge": { 136 + "type": "object", 137 + "description": "Age Assurance rule that applies if the user has been assured to be under a certain age.", 138 + "required": [ 139 + "age", 140 + "access" 141 + ], 142 + "properties": { 143 + "access": { 144 + "type": "ref", 145 + "ref": "app.bsky.ageassurance.defs#access" 146 + }, 147 + "age": { 148 + "type": "integer", 149 + "description": "The age threshold as a whole integer." 150 + } 151 + } 152 + }, 153 + "configRegionRuleIfDeclaredOverAge": { 154 + "type": "object", 155 + "description": "Age Assurance rule that applies if the user has declared themselves equal-to or over a certain age.", 156 + "required": [ 157 + "age", 158 + "access" 159 + ], 160 + "properties": { 161 + "access": { 162 + "type": "ref", 163 + "ref": "app.bsky.ageassurance.defs#access" 164 + }, 165 + "age": { 166 + "type": "integer", 167 + "description": "The age threshold as a whole integer." 168 + } 169 + } 170 + }, 171 + "configRegionRuleIfDeclaredUnderAge": { 172 + "type": "object", 173 + "description": "Age Assurance rule that applies if the user has declared themselves under a certain age.", 174 + "required": [ 175 + "age", 176 + "access" 177 + ], 178 + "properties": { 179 + "access": { 180 + "type": "ref", 181 + "ref": "app.bsky.ageassurance.defs#access" 182 + }, 183 + "age": { 184 + "type": "integer", 185 + "description": "The age threshold as a whole integer." 186 + } 187 + } 188 + }, 189 + "event": { 190 + "type": "object", 191 + "description": "Object used to store Age Assurance data in stash.", 192 + "required": [ 193 + "createdAt", 194 + "status", 195 + "access", 196 + "attemptId", 197 + "countryCode" 198 + ], 199 + "properties": { 200 + "access": { 201 + "type": "string", 202 + "description": "The access level granted based on Age Assurance data we've processed.", 203 + "knownValues": [ 204 + "unknown", 205 + "none", 206 + "safe", 207 + "full" 208 + ] 209 + }, 210 + "attemptId": { 211 + "type": "string", 212 + "description": "The unique identifier for this instance of the Age Assurance flow, in UUID format." 213 + }, 214 + "completeIp": { 215 + "type": "string", 216 + "description": "The IP address used when completing the Age Assurance flow." 217 + }, 218 + "completeUa": { 219 + "type": "string", 220 + "description": "The user agent used when completing the Age Assurance flow." 221 + }, 222 + "countryCode": { 223 + "type": "string", 224 + "description": "The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow." 225 + }, 226 + "createdAt": { 227 + "type": "string", 228 + "description": "The date and time of this write operation.", 229 + "format": "datetime" 230 + }, 231 + "email": { 232 + "type": "string", 233 + "description": "The email used for Age Assurance." 234 + }, 235 + "initIp": { 236 + "type": "string", 237 + "description": "The IP address used when initiating the Age Assurance flow." 238 + }, 239 + "initUa": { 240 + "type": "string", 241 + "description": "The user agent used when initiating the Age Assurance flow." 242 + }, 243 + "regionCode": { 244 + "type": "string", 245 + "description": "The ISO 3166-2 region code provided when beginning the Age Assurance flow." 246 + }, 247 + "status": { 248 + "type": "string", 249 + "description": "The status of the Age Assurance process.", 250 + "knownValues": [ 251 + "unknown", 252 + "pending", 253 + "assured", 254 + "blocked" 255 + ] 256 + } 257 + } 258 + }, 259 + "state": { 260 + "type": "object", 261 + "description": "The user's computed Age Assurance state.", 262 + "required": [ 263 + "status", 264 + "access" 265 + ], 266 + "properties": { 267 + "access": { 268 + "type": "ref", 269 + "ref": "app.bsky.ageassurance.defs#access" 270 + }, 271 + "lastInitiatedAt": { 272 + "type": "string", 273 + "description": "The timestamp when this state was last updated.", 274 + "format": "datetime" 275 + }, 276 + "status": { 277 + "type": "ref", 278 + "ref": "app.bsky.ageassurance.defs#status" 279 + } 280 + } 281 + }, 282 + "stateMetadata": { 283 + "type": "object", 284 + "description": "Additional metadata needed to compute Age Assurance state client-side.", 285 + "required": [], 286 + "properties": { 287 + "accountCreatedAt": { 288 + "type": "string", 289 + "description": "The account creation timestamp.", 290 + "format": "datetime" 291 + } 292 + } 293 + }, 294 + "status": { 295 + "type": "string", 296 + "description": "The status of the Age Assurance process.", 297 + "knownValues": [ 298 + "unknown", 299 + "pending", 300 + "assured", 301 + "blocked" 302 + ] 303 + } 304 + } 305 + }
+17
crates/weaver-api/lexicons/app_bsky_ageassurance_getConfig.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.ageassurance.getConfig", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Returns Age Assurance configuration for use on the client.", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "ref", 12 + "ref": "app.bsky.ageassurance.defs#config" 13 + } 14 + } 15 + } 16 + } 17 + }
+44
crates/weaver-api/lexicons/app_bsky_ageassurance_getState.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.ageassurance.getState", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Returns server-computed Age Assurance state, if available, and any additional metadata needed to compute Age Assurance state client-side.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "countryCode" 12 + ], 13 + "properties": { 14 + "countryCode": { 15 + "type": "string" 16 + }, 17 + "regionCode": { 18 + "type": "string" 19 + } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "required": [ 27 + "state", 28 + "metadata" 29 + ], 30 + "properties": { 31 + "metadata": { 32 + "type": "ref", 33 + "ref": "app.bsky.ageassurance.defs#stateMetadata" 34 + }, 35 + "state": { 36 + "type": "ref", 37 + "ref": "app.bsky.ageassurance.defs#state" 38 + } 39 + } 40 + } 41 + } 42 + } 43 + } 44 + }
+16 -3
crates/weaver-api/lexicons/sh_weaver_edit_defs.json
··· 12 12 "type": "union", 13 13 "refs": [ 14 14 "#notebookRef", 15 - "#entryRef" 15 + "#entryRef", 16 + "#draftRef" 16 17 ] 17 18 } 18 19 } 19 20 }, 21 + "draftRef": { 22 + "type": "object", 23 + "required": [ 24 + "draft_key" 25 + ], 26 + "properties": { 27 + "draft_key": { 28 + "type": "string", 29 + "maxLength": 200 30 + } 31 + } 32 + }, 20 33 "entryRef": { 21 34 "type": "object", 22 35 "required": [ 23 - "notebook" 36 + "entry" 24 37 ], 25 38 "properties": { 26 - "notebook": { 39 + "entry": { 27 40 "type": "ref", 28 41 "ref": "com.atproto.repo.strongRef" 29 42 }
+4
crates/weaver-api/lexicons/sh_weaver_edit_diff.json
··· 18 18 "type": "ref", 19 19 "ref": "sh.weaver.edit.defs#docRef" 20 20 }, 21 + "prev": { 22 + "type": "ref", 23 + "ref": "com.atproto.repo.strongRef" 24 + }, 21 25 "root": { 22 26 "type": "ref", 23 27 "ref": "com.atproto.repo.strongRef"
+21 -5
crates/weaver-api/lexicons/tools_ozone_moderation_defs.json
··· 131 131 "attemptId" 132 132 ], 133 133 "properties": { 134 + "access": { 135 + "type": "ref", 136 + "ref": "app.bsky.ageassurance.defs#access" 137 + }, 134 138 "attemptId": { 135 139 "type": "string", 136 140 "description": "The unique identifier for this instance of the age assurance flow, in UUID format." ··· 143 147 "type": "string", 144 148 "description": "The user agent used when completing the AA flow." 145 149 }, 150 + "countryCode": { 151 + "type": "string", 152 + "description": "The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow." 153 + }, 146 154 "createdAt": { 147 155 "type": "string", 148 156 "description": "The date and time of this write operation.", ··· 156 164 "type": "string", 157 165 "description": "The user agent used when initiating the AA flow." 158 166 }, 167 + "regionCode": { 168 + "type": "string", 169 + "description": "The ISO 3166-2 region code provided when beginning the Age Assurance flow." 170 + }, 159 171 "status": { 160 172 "type": "string", 161 - "description": "The status of the age assurance process.", 173 + "description": "The status of the Age Assurance process.", 162 174 "knownValues": [ 163 175 "unknown", 164 176 "pending", ··· 175 187 "status" 176 188 ], 177 189 "properties": { 190 + "access": { 191 + "type": "ref", 192 + "ref": "app.bsky.ageassurance.defs#access" 193 + }, 178 194 "comment": { 179 195 "type": "string", 180 196 "description": "Comment describing the reason for the override." ··· 1321 1337 "subjectReviewState": { 1322 1338 "type": "string", 1323 1339 "knownValues": [ 1324 - "#reviewOpen", 1325 - "#reviewEscalated", 1326 - "#reviewClosed", 1327 - "#reviewNone" 1340 + "tools.ozone.moderation.defs#reviewOpen", 1341 + "tools.ozone.moderation.defs#reviewEscalated", 1342 + "tools.ozone.moderation.defs#reviewClosed", 1343 + "tools.ozone.moderation.defs#reviewNone" 1328 1344 ] 1329 1345 }, 1330 1346 "subjectStatusView": {
+4 -4
crates/weaver-api/lexicons/tools_ozone_team_defs.json
··· 30 30 "role": { 31 31 "type": "string", 32 32 "knownValues": [ 33 - "#roleAdmin", 34 - "#roleModerator", 35 - "#roleTriage", 36 - "#roleVerifier" 33 + "tools.ozone.team.defs#roleAdmin", 34 + "tools.ozone.team.defs#roleModerator", 35 + "tools.ozone.team.defs#roleTriage", 36 + "tools.ozone.team.defs#roleVerifier" 37 37 ] 38 38 }, 39 39 "updatedAt": {
+1
crates/weaver-api/src/app_bsky.rs
··· 4 4 // Any manual changes will be overwritten on the next regeneration. 5 5 6 6 pub mod actor; 7 + pub mod ageassurance; 7 8 pub mod bookmark; 8 9 pub mod embed; 9 10 pub mod feed;
+3424
crates/weaver-api/src/app_bsky/ageassurance.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.ageassurance.defs 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + pub mod begin; 9 + pub mod get_config; 10 + pub mod get_state; 11 + 12 + /// The access level granted based on Age Assurance data we've processed. 13 + #[derive(Debug, Clone, PartialEq, Eq, Hash)] 14 + pub enum Access<'a> { 15 + Unknown, 16 + None, 17 + Safe, 18 + Full, 19 + Other(jacquard_common::CowStr<'a>), 20 + } 21 + 22 + impl<'a> Access<'a> { 23 + pub fn as_str(&self) -> &str { 24 + match self { 25 + Self::Unknown => "unknown", 26 + Self::None => "none", 27 + Self::Safe => "safe", 28 + Self::Full => "full", 29 + Self::Other(s) => s.as_ref(), 30 + } 31 + } 32 + } 33 + 34 + impl<'a> From<&'a str> for Access<'a> { 35 + fn from(s: &'a str) -> Self { 36 + match s { 37 + "unknown" => Self::Unknown, 38 + "none" => Self::None, 39 + "safe" => Self::Safe, 40 + "full" => Self::Full, 41 + _ => Self::Other(jacquard_common::CowStr::from(s)), 42 + } 43 + } 44 + } 45 + 46 + impl<'a> From<String> for Access<'a> { 47 + fn from(s: String) -> Self { 48 + match s.as_str() { 49 + "unknown" => Self::Unknown, 50 + "none" => Self::None, 51 + "safe" => Self::Safe, 52 + "full" => Self::Full, 53 + _ => Self::Other(jacquard_common::CowStr::from(s)), 54 + } 55 + } 56 + } 57 + 58 + impl<'a> AsRef<str> for Access<'a> { 59 + fn as_ref(&self) -> &str { 60 + self.as_str() 61 + } 62 + } 63 + 64 + impl<'a> serde::Serialize for Access<'a> { 65 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 66 + where 67 + S: serde::Serializer, 68 + { 69 + serializer.serialize_str(self.as_str()) 70 + } 71 + } 72 + 73 + impl<'de, 'a> serde::Deserialize<'de> for Access<'a> 74 + where 75 + 'de: 'a, 76 + { 77 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 78 + where 79 + D: serde::Deserializer<'de>, 80 + { 81 + let s = <&'de str>::deserialize(deserializer)?; 82 + Ok(Self::from(s)) 83 + } 84 + } 85 + 86 + impl jacquard_common::IntoStatic for Access<'_> { 87 + type Output = Access<'static>; 88 + fn into_static(self) -> Self::Output { 89 + match self { 90 + Access::Unknown => Access::Unknown, 91 + Access::None => Access::None, 92 + Access::Safe => Access::Safe, 93 + Access::Full => Access::Full, 94 + Access::Other(v) => Access::Other(v.into_static()), 95 + } 96 + } 97 + } 98 + 99 + /// 100 + #[jacquard_derive::lexicon] 101 + #[derive( 102 + serde::Serialize, 103 + serde::Deserialize, 104 + Debug, 105 + Clone, 106 + PartialEq, 107 + Eq, 108 + jacquard_derive::IntoStatic 109 + )] 110 + #[serde(rename_all = "camelCase")] 111 + pub struct Config<'a> { 112 + /// The per-region Age Assurance configuration. 113 + #[serde(borrow)] 114 + pub regions: Vec<crate::app_bsky::ageassurance::ConfigRegion<'a>>, 115 + } 116 + 117 + pub mod config_state { 118 + 119 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 120 + #[allow(unused)] 121 + use ::core::marker::PhantomData; 122 + mod sealed { 123 + pub trait Sealed {} 124 + } 125 + /// State trait tracking which required fields have been set 126 + pub trait State: sealed::Sealed { 127 + type Regions; 128 + } 129 + /// Empty state - all required fields are unset 130 + pub struct Empty(()); 131 + impl sealed::Sealed for Empty {} 132 + impl State for Empty { 133 + type Regions = Unset; 134 + } 135 + ///State transition - sets the `regions` field to Set 136 + pub struct SetRegions<S: State = Empty>(PhantomData<fn() -> S>); 137 + impl<S: State> sealed::Sealed for SetRegions<S> {} 138 + impl<S: State> State for SetRegions<S> { 139 + type Regions = Set<members::regions>; 140 + } 141 + /// Marker types for field names 142 + #[allow(non_camel_case_types)] 143 + pub mod members { 144 + ///Marker type for the `regions` field 145 + pub struct regions(()); 146 + } 147 + } 148 + 149 + /// Builder for constructing an instance of this type 150 + pub struct ConfigBuilder<'a, S: config_state::State> { 151 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 152 + __unsafe_private_named: ( 153 + ::core::option::Option<Vec<crate::app_bsky::ageassurance::ConfigRegion<'a>>>, 154 + ), 155 + _phantom: ::core::marker::PhantomData<&'a ()>, 156 + } 157 + 158 + impl<'a> Config<'a> { 159 + /// Create a new builder for this type 160 + pub fn new() -> ConfigBuilder<'a, config_state::Empty> { 161 + ConfigBuilder::new() 162 + } 163 + } 164 + 165 + impl<'a> ConfigBuilder<'a, config_state::Empty> { 166 + /// Create a new builder with all fields unset 167 + pub fn new() -> Self { 168 + ConfigBuilder { 169 + _phantom_state: ::core::marker::PhantomData, 170 + __unsafe_private_named: (None,), 171 + _phantom: ::core::marker::PhantomData, 172 + } 173 + } 174 + } 175 + 176 + impl<'a, S> ConfigBuilder<'a, S> 177 + where 178 + S: config_state::State, 179 + S::Regions: config_state::IsUnset, 180 + { 181 + /// Set the `regions` field (required) 182 + pub fn regions( 183 + mut self, 184 + value: impl Into<Vec<crate::app_bsky::ageassurance::ConfigRegion<'a>>>, 185 + ) -> ConfigBuilder<'a, config_state::SetRegions<S>> { 186 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 187 + ConfigBuilder { 188 + _phantom_state: ::core::marker::PhantomData, 189 + __unsafe_private_named: self.__unsafe_private_named, 190 + _phantom: ::core::marker::PhantomData, 191 + } 192 + } 193 + } 194 + 195 + impl<'a, S> ConfigBuilder<'a, S> 196 + where 197 + S: config_state::State, 198 + S::Regions: config_state::IsSet, 199 + { 200 + /// Build the final struct 201 + pub fn build(self) -> Config<'a> { 202 + Config { 203 + regions: self.__unsafe_private_named.0.unwrap(), 204 + extra_data: Default::default(), 205 + } 206 + } 207 + /// Build the final struct with custom extra_data 208 + pub fn build_with_data( 209 + self, 210 + extra_data: std::collections::BTreeMap< 211 + jacquard_common::smol_str::SmolStr, 212 + jacquard_common::types::value::Data<'a>, 213 + >, 214 + ) -> Config<'a> { 215 + Config { 216 + regions: self.__unsafe_private_named.0.unwrap(), 217 + extra_data: Some(extra_data), 218 + } 219 + } 220 + } 221 + 222 + fn lexicon_doc_app_bsky_ageassurance_defs() -> ::jacquard_lexicon::lexicon::LexiconDoc< 223 + 'static, 224 + > { 225 + ::jacquard_lexicon::lexicon::LexiconDoc { 226 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 227 + id: ::jacquard_common::CowStr::new_static("app.bsky.ageassurance.defs"), 228 + revision: None, 229 + description: None, 230 + defs: { 231 + let mut map = ::std::collections::BTreeMap::new(); 232 + map.insert( 233 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 234 + ::jacquard_lexicon::lexicon::LexUserType::String(::jacquard_lexicon::lexicon::LexString { 235 + description: Some( 236 + ::jacquard_common::CowStr::new_static( 237 + "The access level granted based on Age Assurance data we've processed.", 238 + ), 239 + ), 240 + format: None, 241 + default: None, 242 + min_length: None, 243 + max_length: None, 244 + min_graphemes: None, 245 + max_graphemes: None, 246 + r#enum: None, 247 + r#const: None, 248 + known_values: None, 249 + }), 250 + ); 251 + map.insert( 252 + ::jacquard_common::smol_str::SmolStr::new_static("config"), 253 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 254 + description: Some(::jacquard_common::CowStr::new_static("")), 255 + required: Some( 256 + vec![::jacquard_common::smol_str::SmolStr::new_static("regions")], 257 + ), 258 + nullable: None, 259 + properties: { 260 + #[allow(unused_mut)] 261 + let mut map = ::std::collections::BTreeMap::new(); 262 + map.insert( 263 + ::jacquard_common::smol_str::SmolStr::new_static("regions"), 264 + ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 265 + description: Some( 266 + ::jacquard_common::CowStr::new_static( 267 + "The per-region Age Assurance configuration.", 268 + ), 269 + ), 270 + items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef { 271 + description: None, 272 + r#ref: ::jacquard_common::CowStr::new_static( 273 + "app.bsky.ageassurance.defs#configRegion", 274 + ), 275 + }), 276 + min_length: None, 277 + max_length: None, 278 + }), 279 + ); 280 + map 281 + }, 282 + }), 283 + ); 284 + map.insert( 285 + ::jacquard_common::smol_str::SmolStr::new_static("configRegion"), 286 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 287 + description: Some( 288 + ::jacquard_common::CowStr::new_static( 289 + "The Age Assurance configuration for a specific region.", 290 + ), 291 + ), 292 + required: Some( 293 + vec![ 294 + ::jacquard_common::smol_str::SmolStr::new_static("countryCode"), 295 + ::jacquard_common::smol_str::SmolStr::new_static("rules") 296 + ], 297 + ), 298 + nullable: None, 299 + properties: { 300 + #[allow(unused_mut)] 301 + let mut map = ::std::collections::BTreeMap::new(); 302 + map.insert( 303 + ::jacquard_common::smol_str::SmolStr::new_static( 304 + "countryCode", 305 + ), 306 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 307 + description: Some( 308 + ::jacquard_common::CowStr::new_static( 309 + "The ISO 3166-1 alpha-2 country code this configuration applies to.", 310 + ), 311 + ), 312 + format: None, 313 + default: None, 314 + min_length: None, 315 + max_length: None, 316 + min_graphemes: None, 317 + max_graphemes: None, 318 + r#enum: None, 319 + r#const: None, 320 + known_values: None, 321 + }), 322 + ); 323 + map.insert( 324 + ::jacquard_common::smol_str::SmolStr::new_static( 325 + "regionCode", 326 + ), 327 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 328 + description: Some( 329 + ::jacquard_common::CowStr::new_static( 330 + "The ISO 3166-2 region code this configuration applies to. If omitted, the configuration applies to the entire country.", 331 + ), 332 + ), 333 + format: None, 334 + default: None, 335 + min_length: None, 336 + max_length: None, 337 + min_graphemes: None, 338 + max_graphemes: None, 339 + r#enum: None, 340 + r#const: None, 341 + known_values: None, 342 + }), 343 + ); 344 + map.insert( 345 + ::jacquard_common::smol_str::SmolStr::new_static("rules"), 346 + ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 347 + description: Some( 348 + ::jacquard_common::CowStr::new_static( 349 + "The ordered list of Age Assurance rules that apply to this region. Rules should be applied in order, and the first matching rule determines the access level granted. The rules array should always include a default rule as the last item.", 350 + ), 351 + ), 352 + items: ::jacquard_lexicon::lexicon::LexArrayItem::Union(::jacquard_lexicon::lexicon::LexRefUnion { 353 + description: None, 354 + refs: vec![ 355 + ::jacquard_common::CowStr::new_static("#configRegionRuleDefault"), 356 + ::jacquard_common::CowStr::new_static("#configRegionRuleIfDeclaredOverAge"), 357 + ::jacquard_common::CowStr::new_static("#configRegionRuleIfDeclaredUnderAge"), 358 + ::jacquard_common::CowStr::new_static("#configRegionRuleIfAssuredOverAge"), 359 + ::jacquard_common::CowStr::new_static("#configRegionRuleIfAssuredUnderAge"), 360 + ::jacquard_common::CowStr::new_static("#configRegionRuleIfAccountNewerThan"), 361 + ::jacquard_common::CowStr::new_static("#configRegionRuleIfAccountOlderThan") 362 + ], 363 + closed: None, 364 + }), 365 + min_length: None, 366 + max_length: None, 367 + }), 368 + ); 369 + map 370 + }, 371 + }), 372 + ); 373 + map.insert( 374 + ::jacquard_common::smol_str::SmolStr::new_static( 375 + "configRegionRuleDefault", 376 + ), 377 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 378 + description: Some( 379 + ::jacquard_common::CowStr::new_static( 380 + "Age Assurance rule that applies by default.", 381 + ), 382 + ), 383 + required: Some( 384 + vec![::jacquard_common::smol_str::SmolStr::new_static("access")], 385 + ), 386 + nullable: None, 387 + properties: { 388 + #[allow(unused_mut)] 389 + let mut map = ::std::collections::BTreeMap::new(); 390 + map.insert( 391 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 392 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 393 + description: None, 394 + r#ref: ::jacquard_common::CowStr::new_static( 395 + "app.bsky.ageassurance.defs#access", 396 + ), 397 + }), 398 + ); 399 + map 400 + }, 401 + }), 402 + ); 403 + map.insert( 404 + ::jacquard_common::smol_str::SmolStr::new_static( 405 + "configRegionRuleIfAccountNewerThan", 406 + ), 407 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 408 + description: Some( 409 + ::jacquard_common::CowStr::new_static( 410 + "Age Assurance rule that applies if the account is equal-to or newer than a certain date.", 411 + ), 412 + ), 413 + required: Some( 414 + vec![ 415 + ::jacquard_common::smol_str::SmolStr::new_static("date"), 416 + ::jacquard_common::smol_str::SmolStr::new_static("access") 417 + ], 418 + ), 419 + nullable: None, 420 + properties: { 421 + #[allow(unused_mut)] 422 + let mut map = ::std::collections::BTreeMap::new(); 423 + map.insert( 424 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 425 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 426 + description: None, 427 + r#ref: ::jacquard_common::CowStr::new_static( 428 + "app.bsky.ageassurance.defs#access", 429 + ), 430 + }), 431 + ); 432 + map.insert( 433 + ::jacquard_common::smol_str::SmolStr::new_static("date"), 434 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 435 + description: Some( 436 + ::jacquard_common::CowStr::new_static( 437 + "The date threshold as a datetime string.", 438 + ), 439 + ), 440 + format: Some( 441 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 442 + ), 443 + default: None, 444 + min_length: None, 445 + max_length: None, 446 + min_graphemes: None, 447 + max_graphemes: None, 448 + r#enum: None, 449 + r#const: None, 450 + known_values: None, 451 + }), 452 + ); 453 + map 454 + }, 455 + }), 456 + ); 457 + map.insert( 458 + ::jacquard_common::smol_str::SmolStr::new_static( 459 + "configRegionRuleIfAccountOlderThan", 460 + ), 461 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 462 + description: Some( 463 + ::jacquard_common::CowStr::new_static( 464 + "Age Assurance rule that applies if the account is older than a certain date.", 465 + ), 466 + ), 467 + required: Some( 468 + vec![ 469 + ::jacquard_common::smol_str::SmolStr::new_static("date"), 470 + ::jacquard_common::smol_str::SmolStr::new_static("access") 471 + ], 472 + ), 473 + nullable: None, 474 + properties: { 475 + #[allow(unused_mut)] 476 + let mut map = ::std::collections::BTreeMap::new(); 477 + map.insert( 478 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 479 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 480 + description: None, 481 + r#ref: ::jacquard_common::CowStr::new_static( 482 + "app.bsky.ageassurance.defs#access", 483 + ), 484 + }), 485 + ); 486 + map.insert( 487 + ::jacquard_common::smol_str::SmolStr::new_static("date"), 488 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 489 + description: Some( 490 + ::jacquard_common::CowStr::new_static( 491 + "The date threshold as a datetime string.", 492 + ), 493 + ), 494 + format: Some( 495 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 496 + ), 497 + default: None, 498 + min_length: None, 499 + max_length: None, 500 + min_graphemes: None, 501 + max_graphemes: None, 502 + r#enum: None, 503 + r#const: None, 504 + known_values: None, 505 + }), 506 + ); 507 + map 508 + }, 509 + }), 510 + ); 511 + map.insert( 512 + ::jacquard_common::smol_str::SmolStr::new_static( 513 + "configRegionRuleIfAssuredOverAge", 514 + ), 515 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 516 + description: Some( 517 + ::jacquard_common::CowStr::new_static( 518 + "Age Assurance rule that applies if the user has been assured to be equal-to or over a certain age.", 519 + ), 520 + ), 521 + required: Some( 522 + vec![ 523 + ::jacquard_common::smol_str::SmolStr::new_static("age"), 524 + ::jacquard_common::smol_str::SmolStr::new_static("access") 525 + ], 526 + ), 527 + nullable: None, 528 + properties: { 529 + #[allow(unused_mut)] 530 + let mut map = ::std::collections::BTreeMap::new(); 531 + map.insert( 532 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 533 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 534 + description: None, 535 + r#ref: ::jacquard_common::CowStr::new_static( 536 + "app.bsky.ageassurance.defs#access", 537 + ), 538 + }), 539 + ); 540 + map.insert( 541 + ::jacquard_common::smol_str::SmolStr::new_static("age"), 542 + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger { 543 + description: None, 544 + default: None, 545 + minimum: None, 546 + maximum: None, 547 + r#enum: None, 548 + r#const: None, 549 + }), 550 + ); 551 + map 552 + }, 553 + }), 554 + ); 555 + map.insert( 556 + ::jacquard_common::smol_str::SmolStr::new_static( 557 + "configRegionRuleIfAssuredUnderAge", 558 + ), 559 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 560 + description: Some( 561 + ::jacquard_common::CowStr::new_static( 562 + "Age Assurance rule that applies if the user has been assured to be under a certain age.", 563 + ), 564 + ), 565 + required: Some( 566 + vec![ 567 + ::jacquard_common::smol_str::SmolStr::new_static("age"), 568 + ::jacquard_common::smol_str::SmolStr::new_static("access") 569 + ], 570 + ), 571 + nullable: None, 572 + properties: { 573 + #[allow(unused_mut)] 574 + let mut map = ::std::collections::BTreeMap::new(); 575 + map.insert( 576 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 577 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 578 + description: None, 579 + r#ref: ::jacquard_common::CowStr::new_static( 580 + "app.bsky.ageassurance.defs#access", 581 + ), 582 + }), 583 + ); 584 + map.insert( 585 + ::jacquard_common::smol_str::SmolStr::new_static("age"), 586 + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger { 587 + description: None, 588 + default: None, 589 + minimum: None, 590 + maximum: None, 591 + r#enum: None, 592 + r#const: None, 593 + }), 594 + ); 595 + map 596 + }, 597 + }), 598 + ); 599 + map.insert( 600 + ::jacquard_common::smol_str::SmolStr::new_static( 601 + "configRegionRuleIfDeclaredOverAge", 602 + ), 603 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 604 + description: Some( 605 + ::jacquard_common::CowStr::new_static( 606 + "Age Assurance rule that applies if the user has declared themselves equal-to or over a certain age.", 607 + ), 608 + ), 609 + required: Some( 610 + vec![ 611 + ::jacquard_common::smol_str::SmolStr::new_static("age"), 612 + ::jacquard_common::smol_str::SmolStr::new_static("access") 613 + ], 614 + ), 615 + nullable: None, 616 + properties: { 617 + #[allow(unused_mut)] 618 + let mut map = ::std::collections::BTreeMap::new(); 619 + map.insert( 620 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 621 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 622 + description: None, 623 + r#ref: ::jacquard_common::CowStr::new_static( 624 + "app.bsky.ageassurance.defs#access", 625 + ), 626 + }), 627 + ); 628 + map.insert( 629 + ::jacquard_common::smol_str::SmolStr::new_static("age"), 630 + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger { 631 + description: None, 632 + default: None, 633 + minimum: None, 634 + maximum: None, 635 + r#enum: None, 636 + r#const: None, 637 + }), 638 + ); 639 + map 640 + }, 641 + }), 642 + ); 643 + map.insert( 644 + ::jacquard_common::smol_str::SmolStr::new_static( 645 + "configRegionRuleIfDeclaredUnderAge", 646 + ), 647 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 648 + description: Some( 649 + ::jacquard_common::CowStr::new_static( 650 + "Age Assurance rule that applies if the user has declared themselves under a certain age.", 651 + ), 652 + ), 653 + required: Some( 654 + vec![ 655 + ::jacquard_common::smol_str::SmolStr::new_static("age"), 656 + ::jacquard_common::smol_str::SmolStr::new_static("access") 657 + ], 658 + ), 659 + nullable: None, 660 + properties: { 661 + #[allow(unused_mut)] 662 + let mut map = ::std::collections::BTreeMap::new(); 663 + map.insert( 664 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 665 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 666 + description: None, 667 + r#ref: ::jacquard_common::CowStr::new_static( 668 + "app.bsky.ageassurance.defs#access", 669 + ), 670 + }), 671 + ); 672 + map.insert( 673 + ::jacquard_common::smol_str::SmolStr::new_static("age"), 674 + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger { 675 + description: None, 676 + default: None, 677 + minimum: None, 678 + maximum: None, 679 + r#enum: None, 680 + r#const: None, 681 + }), 682 + ); 683 + map 684 + }, 685 + }), 686 + ); 687 + map.insert( 688 + ::jacquard_common::smol_str::SmolStr::new_static("event"), 689 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 690 + description: Some( 691 + ::jacquard_common::CowStr::new_static( 692 + "Object used to store Age Assurance data in stash.", 693 + ), 694 + ), 695 + required: Some( 696 + vec![ 697 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt"), 698 + ::jacquard_common::smol_str::SmolStr::new_static("status"), 699 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 700 + ::jacquard_common::smol_str::SmolStr::new_static("attemptId"), 701 + ::jacquard_common::smol_str::SmolStr::new_static("countryCode") 702 + ], 703 + ), 704 + nullable: None, 705 + properties: { 706 + #[allow(unused_mut)] 707 + let mut map = ::std::collections::BTreeMap::new(); 708 + map.insert( 709 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 710 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 711 + description: Some( 712 + ::jacquard_common::CowStr::new_static( 713 + "The access level granted based on Age Assurance data we've processed.", 714 + ), 715 + ), 716 + format: None, 717 + default: None, 718 + min_length: None, 719 + max_length: None, 720 + min_graphemes: None, 721 + max_graphemes: None, 722 + r#enum: None, 723 + r#const: None, 724 + known_values: None, 725 + }), 726 + ); 727 + map.insert( 728 + ::jacquard_common::smol_str::SmolStr::new_static( 729 + "attemptId", 730 + ), 731 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 732 + description: Some( 733 + ::jacquard_common::CowStr::new_static( 734 + "The unique identifier for this instance of the Age Assurance flow, in UUID format.", 735 + ), 736 + ), 737 + format: None, 738 + default: None, 739 + min_length: None, 740 + max_length: None, 741 + min_graphemes: None, 742 + max_graphemes: None, 743 + r#enum: None, 744 + r#const: None, 745 + known_values: None, 746 + }), 747 + ); 748 + map.insert( 749 + ::jacquard_common::smol_str::SmolStr::new_static( 750 + "completeIp", 751 + ), 752 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 753 + description: Some( 754 + ::jacquard_common::CowStr::new_static( 755 + "The IP address used when completing the Age Assurance flow.", 756 + ), 757 + ), 758 + format: None, 759 + default: None, 760 + min_length: None, 761 + max_length: None, 762 + min_graphemes: None, 763 + max_graphemes: None, 764 + r#enum: None, 765 + r#const: None, 766 + known_values: None, 767 + }), 768 + ); 769 + map.insert( 770 + ::jacquard_common::smol_str::SmolStr::new_static( 771 + "completeUa", 772 + ), 773 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 774 + description: Some( 775 + ::jacquard_common::CowStr::new_static( 776 + "The user agent used when completing the Age Assurance flow.", 777 + ), 778 + ), 779 + format: None, 780 + default: None, 781 + min_length: None, 782 + max_length: None, 783 + min_graphemes: None, 784 + max_graphemes: None, 785 + r#enum: None, 786 + r#const: None, 787 + known_values: None, 788 + }), 789 + ); 790 + map.insert( 791 + ::jacquard_common::smol_str::SmolStr::new_static( 792 + "countryCode", 793 + ), 794 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 795 + description: Some( 796 + ::jacquard_common::CowStr::new_static( 797 + "The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow.", 798 + ), 799 + ), 800 + format: None, 801 + default: None, 802 + min_length: None, 803 + max_length: None, 804 + min_graphemes: None, 805 + max_graphemes: None, 806 + r#enum: None, 807 + r#const: None, 808 + known_values: None, 809 + }), 810 + ); 811 + map.insert( 812 + ::jacquard_common::smol_str::SmolStr::new_static( 813 + "createdAt", 814 + ), 815 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 816 + description: Some( 817 + ::jacquard_common::CowStr::new_static( 818 + "The date and time of this write operation.", 819 + ), 820 + ), 821 + format: Some( 822 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 823 + ), 824 + default: None, 825 + min_length: None, 826 + max_length: None, 827 + min_graphemes: None, 828 + max_graphemes: None, 829 + r#enum: None, 830 + r#const: None, 831 + known_values: None, 832 + }), 833 + ); 834 + map.insert( 835 + ::jacquard_common::smol_str::SmolStr::new_static("email"), 836 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 837 + description: Some( 838 + ::jacquard_common::CowStr::new_static( 839 + "The email used for Age Assurance.", 840 + ), 841 + ), 842 + format: None, 843 + default: None, 844 + min_length: None, 845 + max_length: None, 846 + min_graphemes: None, 847 + max_graphemes: None, 848 + r#enum: None, 849 + r#const: None, 850 + known_values: None, 851 + }), 852 + ); 853 + map.insert( 854 + ::jacquard_common::smol_str::SmolStr::new_static("initIp"), 855 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 856 + description: Some( 857 + ::jacquard_common::CowStr::new_static( 858 + "The IP address used when initiating the Age Assurance flow.", 859 + ), 860 + ), 861 + format: None, 862 + default: None, 863 + min_length: None, 864 + max_length: None, 865 + min_graphemes: None, 866 + max_graphemes: None, 867 + r#enum: None, 868 + r#const: None, 869 + known_values: None, 870 + }), 871 + ); 872 + map.insert( 873 + ::jacquard_common::smol_str::SmolStr::new_static("initUa"), 874 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 875 + description: Some( 876 + ::jacquard_common::CowStr::new_static( 877 + "The user agent used when initiating the Age Assurance flow.", 878 + ), 879 + ), 880 + format: None, 881 + default: None, 882 + min_length: None, 883 + max_length: None, 884 + min_graphemes: None, 885 + max_graphemes: None, 886 + r#enum: None, 887 + r#const: None, 888 + known_values: None, 889 + }), 890 + ); 891 + map.insert( 892 + ::jacquard_common::smol_str::SmolStr::new_static( 893 + "regionCode", 894 + ), 895 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 896 + description: Some( 897 + ::jacquard_common::CowStr::new_static( 898 + "The ISO 3166-2 region code provided when beginning the Age Assurance flow.", 899 + ), 900 + ), 901 + format: None, 902 + default: None, 903 + min_length: None, 904 + max_length: None, 905 + min_graphemes: None, 906 + max_graphemes: None, 907 + r#enum: None, 908 + r#const: None, 909 + known_values: None, 910 + }), 911 + ); 912 + map.insert( 913 + ::jacquard_common::smol_str::SmolStr::new_static("status"), 914 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 915 + description: Some( 916 + ::jacquard_common::CowStr::new_static( 917 + "The status of the Age Assurance process.", 918 + ), 919 + ), 920 + format: None, 921 + default: None, 922 + min_length: None, 923 + max_length: None, 924 + min_graphemes: None, 925 + max_graphemes: None, 926 + r#enum: None, 927 + r#const: None, 928 + known_values: None, 929 + }), 930 + ); 931 + map 932 + }, 933 + }), 934 + ); 935 + map.insert( 936 + ::jacquard_common::smol_str::SmolStr::new_static("state"), 937 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 938 + description: Some( 939 + ::jacquard_common::CowStr::new_static( 940 + "The user's computed Age Assurance state.", 941 + ), 942 + ), 943 + required: Some( 944 + vec![ 945 + ::jacquard_common::smol_str::SmolStr::new_static("status"), 946 + ::jacquard_common::smol_str::SmolStr::new_static("access") 947 + ], 948 + ), 949 + nullable: None, 950 + properties: { 951 + #[allow(unused_mut)] 952 + let mut map = ::std::collections::BTreeMap::new(); 953 + map.insert( 954 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 955 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 956 + description: None, 957 + r#ref: ::jacquard_common::CowStr::new_static( 958 + "app.bsky.ageassurance.defs#access", 959 + ), 960 + }), 961 + ); 962 + map.insert( 963 + ::jacquard_common::smol_str::SmolStr::new_static( 964 + "lastInitiatedAt", 965 + ), 966 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 967 + description: Some( 968 + ::jacquard_common::CowStr::new_static( 969 + "The timestamp when this state was last updated.", 970 + ), 971 + ), 972 + format: Some( 973 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 974 + ), 975 + default: None, 976 + min_length: None, 977 + max_length: None, 978 + min_graphemes: None, 979 + max_graphemes: None, 980 + r#enum: None, 981 + r#const: None, 982 + known_values: None, 983 + }), 984 + ); 985 + map.insert( 986 + ::jacquard_common::smol_str::SmolStr::new_static("status"), 987 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 988 + description: None, 989 + r#ref: ::jacquard_common::CowStr::new_static( 990 + "app.bsky.ageassurance.defs#status", 991 + ), 992 + }), 993 + ); 994 + map 995 + }, 996 + }), 997 + ); 998 + map.insert( 999 + ::jacquard_common::smol_str::SmolStr::new_static("stateMetadata"), 1000 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 1001 + description: Some( 1002 + ::jacquard_common::CowStr::new_static( 1003 + "Additional metadata needed to compute Age Assurance state client-side.", 1004 + ), 1005 + ), 1006 + required: Some(vec![]), 1007 + nullable: None, 1008 + properties: { 1009 + #[allow(unused_mut)] 1010 + let mut map = ::std::collections::BTreeMap::new(); 1011 + map.insert( 1012 + ::jacquard_common::smol_str::SmolStr::new_static( 1013 + "accountCreatedAt", 1014 + ), 1015 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 1016 + description: Some( 1017 + ::jacquard_common::CowStr::new_static( 1018 + "The account creation timestamp.", 1019 + ), 1020 + ), 1021 + format: Some( 1022 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 1023 + ), 1024 + default: None, 1025 + min_length: None, 1026 + max_length: None, 1027 + min_graphemes: None, 1028 + max_graphemes: None, 1029 + r#enum: None, 1030 + r#const: None, 1031 + known_values: None, 1032 + }), 1033 + ); 1034 + map 1035 + }, 1036 + }), 1037 + ); 1038 + map.insert( 1039 + ::jacquard_common::smol_str::SmolStr::new_static("status"), 1040 + ::jacquard_lexicon::lexicon::LexUserType::String(::jacquard_lexicon::lexicon::LexString { 1041 + description: Some( 1042 + ::jacquard_common::CowStr::new_static( 1043 + "The status of the Age Assurance process.", 1044 + ), 1045 + ), 1046 + format: None, 1047 + default: None, 1048 + min_length: None, 1049 + max_length: None, 1050 + min_graphemes: None, 1051 + max_graphemes: None, 1052 + r#enum: None, 1053 + r#const: None, 1054 + known_values: None, 1055 + }), 1056 + ); 1057 + map 1058 + }, 1059 + } 1060 + } 1061 + 1062 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Config<'a> { 1063 + fn nsid() -> &'static str { 1064 + "app.bsky.ageassurance.defs" 1065 + } 1066 + fn def_name() -> &'static str { 1067 + "config" 1068 + } 1069 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1070 + lexicon_doc_app_bsky_ageassurance_defs() 1071 + } 1072 + fn validate( 1073 + &self, 1074 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1075 + Ok(()) 1076 + } 1077 + } 1078 + 1079 + /// The Age Assurance configuration for a specific region. 1080 + #[jacquard_derive::lexicon] 1081 + #[derive( 1082 + serde::Serialize, 1083 + serde::Deserialize, 1084 + Debug, 1085 + Clone, 1086 + PartialEq, 1087 + Eq, 1088 + jacquard_derive::IntoStatic 1089 + )] 1090 + #[serde(rename_all = "camelCase")] 1091 + pub struct ConfigRegion<'a> { 1092 + /// The ISO 3166-1 alpha-2 country code this configuration applies to. 1093 + #[serde(borrow)] 1094 + pub country_code: jacquard_common::CowStr<'a>, 1095 + /// The ISO 3166-2 region code this configuration applies to. If omitted, the configuration applies to the entire country. 1096 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 1097 + #[serde(borrow)] 1098 + pub region_code: std::option::Option<jacquard_common::CowStr<'a>>, 1099 + /// The ordered list of Age Assurance rules that apply to this region. Rules should be applied in order, and the first matching rule determines the access level granted. The rules array should always include a default rule as the last item. 1100 + #[serde(borrow)] 1101 + pub rules: Vec<ConfigRegionRulesItem<'a>>, 1102 + } 1103 + 1104 + pub mod config_region_state { 1105 + 1106 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1107 + #[allow(unused)] 1108 + use ::core::marker::PhantomData; 1109 + mod sealed { 1110 + pub trait Sealed {} 1111 + } 1112 + /// State trait tracking which required fields have been set 1113 + pub trait State: sealed::Sealed { 1114 + type CountryCode; 1115 + type Rules; 1116 + } 1117 + /// Empty state - all required fields are unset 1118 + pub struct Empty(()); 1119 + impl sealed::Sealed for Empty {} 1120 + impl State for Empty { 1121 + type CountryCode = Unset; 1122 + type Rules = Unset; 1123 + } 1124 + ///State transition - sets the `country_code` field to Set 1125 + pub struct SetCountryCode<S: State = Empty>(PhantomData<fn() -> S>); 1126 + impl<S: State> sealed::Sealed for SetCountryCode<S> {} 1127 + impl<S: State> State for SetCountryCode<S> { 1128 + type CountryCode = Set<members::country_code>; 1129 + type Rules = S::Rules; 1130 + } 1131 + ///State transition - sets the `rules` field to Set 1132 + pub struct SetRules<S: State = Empty>(PhantomData<fn() -> S>); 1133 + impl<S: State> sealed::Sealed for SetRules<S> {} 1134 + impl<S: State> State for SetRules<S> { 1135 + type CountryCode = S::CountryCode; 1136 + type Rules = Set<members::rules>; 1137 + } 1138 + /// Marker types for field names 1139 + #[allow(non_camel_case_types)] 1140 + pub mod members { 1141 + ///Marker type for the `country_code` field 1142 + pub struct country_code(()); 1143 + ///Marker type for the `rules` field 1144 + pub struct rules(()); 1145 + } 1146 + } 1147 + 1148 + /// Builder for constructing an instance of this type 1149 + pub struct ConfigRegionBuilder<'a, S: config_region_state::State> { 1150 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1151 + __unsafe_private_named: ( 1152 + ::core::option::Option<jacquard_common::CowStr<'a>>, 1153 + ::core::option::Option<jacquard_common::CowStr<'a>>, 1154 + ::core::option::Option<Vec<ConfigRegionRulesItem<'a>>>, 1155 + ), 1156 + _phantom: ::core::marker::PhantomData<&'a ()>, 1157 + } 1158 + 1159 + impl<'a> ConfigRegion<'a> { 1160 + /// Create a new builder for this type 1161 + pub fn new() -> ConfigRegionBuilder<'a, config_region_state::Empty> { 1162 + ConfigRegionBuilder::new() 1163 + } 1164 + } 1165 + 1166 + impl<'a> ConfigRegionBuilder<'a, config_region_state::Empty> { 1167 + /// Create a new builder with all fields unset 1168 + pub fn new() -> Self { 1169 + ConfigRegionBuilder { 1170 + _phantom_state: ::core::marker::PhantomData, 1171 + __unsafe_private_named: (None, None, None), 1172 + _phantom: ::core::marker::PhantomData, 1173 + } 1174 + } 1175 + } 1176 + 1177 + impl<'a, S> ConfigRegionBuilder<'a, S> 1178 + where 1179 + S: config_region_state::State, 1180 + S::CountryCode: config_region_state::IsUnset, 1181 + { 1182 + /// Set the `countryCode` field (required) 1183 + pub fn country_code( 1184 + mut self, 1185 + value: impl Into<jacquard_common::CowStr<'a>>, 1186 + ) -> ConfigRegionBuilder<'a, config_region_state::SetCountryCode<S>> { 1187 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 1188 + ConfigRegionBuilder { 1189 + _phantom_state: ::core::marker::PhantomData, 1190 + __unsafe_private_named: self.__unsafe_private_named, 1191 + _phantom: ::core::marker::PhantomData, 1192 + } 1193 + } 1194 + } 1195 + 1196 + impl<'a, S: config_region_state::State> ConfigRegionBuilder<'a, S> { 1197 + /// Set the `regionCode` field (optional) 1198 + pub fn region_code( 1199 + mut self, 1200 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 1201 + ) -> Self { 1202 + self.__unsafe_private_named.1 = value.into(); 1203 + self 1204 + } 1205 + /// Set the `regionCode` field to an Option value (optional) 1206 + pub fn maybe_region_code( 1207 + mut self, 1208 + value: Option<jacquard_common::CowStr<'a>>, 1209 + ) -> Self { 1210 + self.__unsafe_private_named.1 = value; 1211 + self 1212 + } 1213 + } 1214 + 1215 + impl<'a, S> ConfigRegionBuilder<'a, S> 1216 + where 1217 + S: config_region_state::State, 1218 + S::Rules: config_region_state::IsUnset, 1219 + { 1220 + /// Set the `rules` field (required) 1221 + pub fn rules( 1222 + mut self, 1223 + value: impl Into<Vec<ConfigRegionRulesItem<'a>>>, 1224 + ) -> ConfigRegionBuilder<'a, config_region_state::SetRules<S>> { 1225 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 1226 + ConfigRegionBuilder { 1227 + _phantom_state: ::core::marker::PhantomData, 1228 + __unsafe_private_named: self.__unsafe_private_named, 1229 + _phantom: ::core::marker::PhantomData, 1230 + } 1231 + } 1232 + } 1233 + 1234 + impl<'a, S> ConfigRegionBuilder<'a, S> 1235 + where 1236 + S: config_region_state::State, 1237 + S::CountryCode: config_region_state::IsSet, 1238 + S::Rules: config_region_state::IsSet, 1239 + { 1240 + /// Build the final struct 1241 + pub fn build(self) -> ConfigRegion<'a> { 1242 + ConfigRegion { 1243 + country_code: self.__unsafe_private_named.0.unwrap(), 1244 + region_code: self.__unsafe_private_named.1, 1245 + rules: self.__unsafe_private_named.2.unwrap(), 1246 + extra_data: Default::default(), 1247 + } 1248 + } 1249 + /// Build the final struct with custom extra_data 1250 + pub fn build_with_data( 1251 + self, 1252 + extra_data: std::collections::BTreeMap< 1253 + jacquard_common::smol_str::SmolStr, 1254 + jacquard_common::types::value::Data<'a>, 1255 + >, 1256 + ) -> ConfigRegion<'a> { 1257 + ConfigRegion { 1258 + country_code: self.__unsafe_private_named.0.unwrap(), 1259 + region_code: self.__unsafe_private_named.1, 1260 + rules: self.__unsafe_private_named.2.unwrap(), 1261 + extra_data: Some(extra_data), 1262 + } 1263 + } 1264 + } 1265 + 1266 + #[jacquard_derive::open_union] 1267 + #[derive( 1268 + serde::Serialize, 1269 + serde::Deserialize, 1270 + Debug, 1271 + Clone, 1272 + PartialEq, 1273 + Eq, 1274 + jacquard_derive::IntoStatic 1275 + )] 1276 + #[serde(tag = "$type")] 1277 + #[serde(bound(deserialize = "'de: 'a"))] 1278 + pub enum ConfigRegionRulesItem<'a> { 1279 + #[serde(rename = "app.bsky.ageassurance.defs#configRegionRuleDefault")] 1280 + ConfigRegionRuleDefault( 1281 + Box<crate::app_bsky::ageassurance::ConfigRegionRuleDefault<'a>>, 1282 + ), 1283 + #[serde(rename = "app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge")] 1284 + ConfigRegionRuleIfDeclaredOverAge( 1285 + Box<crate::app_bsky::ageassurance::ConfigRegionRuleIfDeclaredOverAge<'a>>, 1286 + ), 1287 + #[serde(rename = "app.bsky.ageassurance.defs#configRegionRuleIfDeclaredUnderAge")] 1288 + ConfigRegionRuleIfDeclaredUnderAge( 1289 + Box<crate::app_bsky::ageassurance::ConfigRegionRuleIfDeclaredUnderAge<'a>>, 1290 + ), 1291 + #[serde(rename = "app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge")] 1292 + ConfigRegionRuleIfAssuredOverAge( 1293 + Box<crate::app_bsky::ageassurance::ConfigRegionRuleIfAssuredOverAge<'a>>, 1294 + ), 1295 + #[serde(rename = "app.bsky.ageassurance.defs#configRegionRuleIfAssuredUnderAge")] 1296 + ConfigRegionRuleIfAssuredUnderAge( 1297 + Box<crate::app_bsky::ageassurance::ConfigRegionRuleIfAssuredUnderAge<'a>>, 1298 + ), 1299 + #[serde(rename = "app.bsky.ageassurance.defs#configRegionRuleIfAccountNewerThan")] 1300 + ConfigRegionRuleIfAccountNewerThan( 1301 + Box<crate::app_bsky::ageassurance::ConfigRegionRuleIfAccountNewerThan<'a>>, 1302 + ), 1303 + #[serde(rename = "app.bsky.ageassurance.defs#configRegionRuleIfAccountOlderThan")] 1304 + ConfigRegionRuleIfAccountOlderThan( 1305 + Box<crate::app_bsky::ageassurance::ConfigRegionRuleIfAccountOlderThan<'a>>, 1306 + ), 1307 + } 1308 + 1309 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ConfigRegion<'a> { 1310 + fn nsid() -> &'static str { 1311 + "app.bsky.ageassurance.defs" 1312 + } 1313 + fn def_name() -> &'static str { 1314 + "configRegion" 1315 + } 1316 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1317 + lexicon_doc_app_bsky_ageassurance_defs() 1318 + } 1319 + fn validate( 1320 + &self, 1321 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1322 + Ok(()) 1323 + } 1324 + } 1325 + 1326 + /// Age Assurance rule that applies by default. 1327 + #[jacquard_derive::lexicon] 1328 + #[derive( 1329 + serde::Serialize, 1330 + serde::Deserialize, 1331 + Debug, 1332 + Clone, 1333 + PartialEq, 1334 + Eq, 1335 + jacquard_derive::IntoStatic 1336 + )] 1337 + #[serde(rename_all = "camelCase")] 1338 + pub struct ConfigRegionRuleDefault<'a> { 1339 + #[serde(borrow)] 1340 + pub access: crate::app_bsky::ageassurance::Access<'a>, 1341 + } 1342 + 1343 + pub mod config_region_rule_default_state { 1344 + 1345 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1346 + #[allow(unused)] 1347 + use ::core::marker::PhantomData; 1348 + mod sealed { 1349 + pub trait Sealed {} 1350 + } 1351 + /// State trait tracking which required fields have been set 1352 + pub trait State: sealed::Sealed { 1353 + type Access; 1354 + } 1355 + /// Empty state - all required fields are unset 1356 + pub struct Empty(()); 1357 + impl sealed::Sealed for Empty {} 1358 + impl State for Empty { 1359 + type Access = Unset; 1360 + } 1361 + ///State transition - sets the `access` field to Set 1362 + pub struct SetAccess<S: State = Empty>(PhantomData<fn() -> S>); 1363 + impl<S: State> sealed::Sealed for SetAccess<S> {} 1364 + impl<S: State> State for SetAccess<S> { 1365 + type Access = Set<members::access>; 1366 + } 1367 + /// Marker types for field names 1368 + #[allow(non_camel_case_types)] 1369 + pub mod members { 1370 + ///Marker type for the `access` field 1371 + pub struct access(()); 1372 + } 1373 + } 1374 + 1375 + /// Builder for constructing an instance of this type 1376 + pub struct ConfigRegionRuleDefaultBuilder< 1377 + 'a, 1378 + S: config_region_rule_default_state::State, 1379 + > { 1380 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1381 + __unsafe_private_named: ( 1382 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 1383 + ), 1384 + _phantom: ::core::marker::PhantomData<&'a ()>, 1385 + } 1386 + 1387 + impl<'a> ConfigRegionRuleDefault<'a> { 1388 + /// Create a new builder for this type 1389 + pub fn new() -> ConfigRegionRuleDefaultBuilder< 1390 + 'a, 1391 + config_region_rule_default_state::Empty, 1392 + > { 1393 + ConfigRegionRuleDefaultBuilder::new() 1394 + } 1395 + } 1396 + 1397 + impl<'a> ConfigRegionRuleDefaultBuilder<'a, config_region_rule_default_state::Empty> { 1398 + /// Create a new builder with all fields unset 1399 + pub fn new() -> Self { 1400 + ConfigRegionRuleDefaultBuilder { 1401 + _phantom_state: ::core::marker::PhantomData, 1402 + __unsafe_private_named: (None,), 1403 + _phantom: ::core::marker::PhantomData, 1404 + } 1405 + } 1406 + } 1407 + 1408 + impl<'a, S> ConfigRegionRuleDefaultBuilder<'a, S> 1409 + where 1410 + S: config_region_rule_default_state::State, 1411 + S::Access: config_region_rule_default_state::IsUnset, 1412 + { 1413 + /// Set the `access` field (required) 1414 + pub fn access( 1415 + mut self, 1416 + value: impl Into<crate::app_bsky::ageassurance::Access<'a>>, 1417 + ) -> ConfigRegionRuleDefaultBuilder< 1418 + 'a, 1419 + config_region_rule_default_state::SetAccess<S>, 1420 + > { 1421 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 1422 + ConfigRegionRuleDefaultBuilder { 1423 + _phantom_state: ::core::marker::PhantomData, 1424 + __unsafe_private_named: self.__unsafe_private_named, 1425 + _phantom: ::core::marker::PhantomData, 1426 + } 1427 + } 1428 + } 1429 + 1430 + impl<'a, S> ConfigRegionRuleDefaultBuilder<'a, S> 1431 + where 1432 + S: config_region_rule_default_state::State, 1433 + S::Access: config_region_rule_default_state::IsSet, 1434 + { 1435 + /// Build the final struct 1436 + pub fn build(self) -> ConfigRegionRuleDefault<'a> { 1437 + ConfigRegionRuleDefault { 1438 + access: self.__unsafe_private_named.0.unwrap(), 1439 + extra_data: Default::default(), 1440 + } 1441 + } 1442 + /// Build the final struct with custom extra_data 1443 + pub fn build_with_data( 1444 + self, 1445 + extra_data: std::collections::BTreeMap< 1446 + jacquard_common::smol_str::SmolStr, 1447 + jacquard_common::types::value::Data<'a>, 1448 + >, 1449 + ) -> ConfigRegionRuleDefault<'a> { 1450 + ConfigRegionRuleDefault { 1451 + access: self.__unsafe_private_named.0.unwrap(), 1452 + extra_data: Some(extra_data), 1453 + } 1454 + } 1455 + } 1456 + 1457 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ConfigRegionRuleDefault<'a> { 1458 + fn nsid() -> &'static str { 1459 + "app.bsky.ageassurance.defs" 1460 + } 1461 + fn def_name() -> &'static str { 1462 + "configRegionRuleDefault" 1463 + } 1464 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1465 + lexicon_doc_app_bsky_ageassurance_defs() 1466 + } 1467 + fn validate( 1468 + &self, 1469 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1470 + Ok(()) 1471 + } 1472 + } 1473 + 1474 + /// Age Assurance rule that applies if the account is equal-to or newer than a certain date. 1475 + #[jacquard_derive::lexicon] 1476 + #[derive( 1477 + serde::Serialize, 1478 + serde::Deserialize, 1479 + Debug, 1480 + Clone, 1481 + PartialEq, 1482 + Eq, 1483 + jacquard_derive::IntoStatic 1484 + )] 1485 + #[serde(rename_all = "camelCase")] 1486 + pub struct ConfigRegionRuleIfAccountNewerThan<'a> { 1487 + #[serde(borrow)] 1488 + pub access: crate::app_bsky::ageassurance::Access<'a>, 1489 + /// The date threshold as a datetime string. 1490 + pub date: jacquard_common::types::string::Datetime, 1491 + } 1492 + 1493 + pub mod config_region_rule_if_account_newer_than_state { 1494 + 1495 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1496 + #[allow(unused)] 1497 + use ::core::marker::PhantomData; 1498 + mod sealed { 1499 + pub trait Sealed {} 1500 + } 1501 + /// State trait tracking which required fields have been set 1502 + pub trait State: sealed::Sealed { 1503 + type Date; 1504 + type Access; 1505 + } 1506 + /// Empty state - all required fields are unset 1507 + pub struct Empty(()); 1508 + impl sealed::Sealed for Empty {} 1509 + impl State for Empty { 1510 + type Date = Unset; 1511 + type Access = Unset; 1512 + } 1513 + ///State transition - sets the `date` field to Set 1514 + pub struct SetDate<S: State = Empty>(PhantomData<fn() -> S>); 1515 + impl<S: State> sealed::Sealed for SetDate<S> {} 1516 + impl<S: State> State for SetDate<S> { 1517 + type Date = Set<members::date>; 1518 + type Access = S::Access; 1519 + } 1520 + ///State transition - sets the `access` field to Set 1521 + pub struct SetAccess<S: State = Empty>(PhantomData<fn() -> S>); 1522 + impl<S: State> sealed::Sealed for SetAccess<S> {} 1523 + impl<S: State> State for SetAccess<S> { 1524 + type Date = S::Date; 1525 + type Access = Set<members::access>; 1526 + } 1527 + /// Marker types for field names 1528 + #[allow(non_camel_case_types)] 1529 + pub mod members { 1530 + ///Marker type for the `date` field 1531 + pub struct date(()); 1532 + ///Marker type for the `access` field 1533 + pub struct access(()); 1534 + } 1535 + } 1536 + 1537 + /// Builder for constructing an instance of this type 1538 + pub struct ConfigRegionRuleIfAccountNewerThanBuilder< 1539 + 'a, 1540 + S: config_region_rule_if_account_newer_than_state::State, 1541 + > { 1542 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1543 + __unsafe_private_named: ( 1544 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 1545 + ::core::option::Option<jacquard_common::types::string::Datetime>, 1546 + ), 1547 + _phantom: ::core::marker::PhantomData<&'a ()>, 1548 + } 1549 + 1550 + impl<'a> ConfigRegionRuleIfAccountNewerThan<'a> { 1551 + /// Create a new builder for this type 1552 + pub fn new() -> ConfigRegionRuleIfAccountNewerThanBuilder< 1553 + 'a, 1554 + config_region_rule_if_account_newer_than_state::Empty, 1555 + > { 1556 + ConfigRegionRuleIfAccountNewerThanBuilder::new() 1557 + } 1558 + } 1559 + 1560 + impl< 1561 + 'a, 1562 + > ConfigRegionRuleIfAccountNewerThanBuilder< 1563 + 'a, 1564 + config_region_rule_if_account_newer_than_state::Empty, 1565 + > { 1566 + /// Create a new builder with all fields unset 1567 + pub fn new() -> Self { 1568 + ConfigRegionRuleIfAccountNewerThanBuilder { 1569 + _phantom_state: ::core::marker::PhantomData, 1570 + __unsafe_private_named: (None, None), 1571 + _phantom: ::core::marker::PhantomData, 1572 + } 1573 + } 1574 + } 1575 + 1576 + impl<'a, S> ConfigRegionRuleIfAccountNewerThanBuilder<'a, S> 1577 + where 1578 + S: config_region_rule_if_account_newer_than_state::State, 1579 + S::Access: config_region_rule_if_account_newer_than_state::IsUnset, 1580 + { 1581 + /// Set the `access` field (required) 1582 + pub fn access( 1583 + mut self, 1584 + value: impl Into<crate::app_bsky::ageassurance::Access<'a>>, 1585 + ) -> ConfigRegionRuleIfAccountNewerThanBuilder< 1586 + 'a, 1587 + config_region_rule_if_account_newer_than_state::SetAccess<S>, 1588 + > { 1589 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 1590 + ConfigRegionRuleIfAccountNewerThanBuilder { 1591 + _phantom_state: ::core::marker::PhantomData, 1592 + __unsafe_private_named: self.__unsafe_private_named, 1593 + _phantom: ::core::marker::PhantomData, 1594 + } 1595 + } 1596 + } 1597 + 1598 + impl<'a, S> ConfigRegionRuleIfAccountNewerThanBuilder<'a, S> 1599 + where 1600 + S: config_region_rule_if_account_newer_than_state::State, 1601 + S::Date: config_region_rule_if_account_newer_than_state::IsUnset, 1602 + { 1603 + /// Set the `date` field (required) 1604 + pub fn date( 1605 + mut self, 1606 + value: impl Into<jacquard_common::types::string::Datetime>, 1607 + ) -> ConfigRegionRuleIfAccountNewerThanBuilder< 1608 + 'a, 1609 + config_region_rule_if_account_newer_than_state::SetDate<S>, 1610 + > { 1611 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 1612 + ConfigRegionRuleIfAccountNewerThanBuilder { 1613 + _phantom_state: ::core::marker::PhantomData, 1614 + __unsafe_private_named: self.__unsafe_private_named, 1615 + _phantom: ::core::marker::PhantomData, 1616 + } 1617 + } 1618 + } 1619 + 1620 + impl<'a, S> ConfigRegionRuleIfAccountNewerThanBuilder<'a, S> 1621 + where 1622 + S: config_region_rule_if_account_newer_than_state::State, 1623 + S::Date: config_region_rule_if_account_newer_than_state::IsSet, 1624 + S::Access: config_region_rule_if_account_newer_than_state::IsSet, 1625 + { 1626 + /// Build the final struct 1627 + pub fn build(self) -> ConfigRegionRuleIfAccountNewerThan<'a> { 1628 + ConfigRegionRuleIfAccountNewerThan { 1629 + access: self.__unsafe_private_named.0.unwrap(), 1630 + date: self.__unsafe_private_named.1.unwrap(), 1631 + extra_data: Default::default(), 1632 + } 1633 + } 1634 + /// Build the final struct with custom extra_data 1635 + pub fn build_with_data( 1636 + self, 1637 + extra_data: std::collections::BTreeMap< 1638 + jacquard_common::smol_str::SmolStr, 1639 + jacquard_common::types::value::Data<'a>, 1640 + >, 1641 + ) -> ConfigRegionRuleIfAccountNewerThan<'a> { 1642 + ConfigRegionRuleIfAccountNewerThan { 1643 + access: self.__unsafe_private_named.0.unwrap(), 1644 + date: self.__unsafe_private_named.1.unwrap(), 1645 + extra_data: Some(extra_data), 1646 + } 1647 + } 1648 + } 1649 + 1650 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema 1651 + for ConfigRegionRuleIfAccountNewerThan<'a> { 1652 + fn nsid() -> &'static str { 1653 + "app.bsky.ageassurance.defs" 1654 + } 1655 + fn def_name() -> &'static str { 1656 + "configRegionRuleIfAccountNewerThan" 1657 + } 1658 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1659 + lexicon_doc_app_bsky_ageassurance_defs() 1660 + } 1661 + fn validate( 1662 + &self, 1663 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1664 + Ok(()) 1665 + } 1666 + } 1667 + 1668 + /// Age Assurance rule that applies if the account is older than a certain date. 1669 + #[jacquard_derive::lexicon] 1670 + #[derive( 1671 + serde::Serialize, 1672 + serde::Deserialize, 1673 + Debug, 1674 + Clone, 1675 + PartialEq, 1676 + Eq, 1677 + jacquard_derive::IntoStatic 1678 + )] 1679 + #[serde(rename_all = "camelCase")] 1680 + pub struct ConfigRegionRuleIfAccountOlderThan<'a> { 1681 + #[serde(borrow)] 1682 + pub access: crate::app_bsky::ageassurance::Access<'a>, 1683 + /// The date threshold as a datetime string. 1684 + pub date: jacquard_common::types::string::Datetime, 1685 + } 1686 + 1687 + pub mod config_region_rule_if_account_older_than_state { 1688 + 1689 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1690 + #[allow(unused)] 1691 + use ::core::marker::PhantomData; 1692 + mod sealed { 1693 + pub trait Sealed {} 1694 + } 1695 + /// State trait tracking which required fields have been set 1696 + pub trait State: sealed::Sealed { 1697 + type Date; 1698 + type Access; 1699 + } 1700 + /// Empty state - all required fields are unset 1701 + pub struct Empty(()); 1702 + impl sealed::Sealed for Empty {} 1703 + impl State for Empty { 1704 + type Date = Unset; 1705 + type Access = Unset; 1706 + } 1707 + ///State transition - sets the `date` field to Set 1708 + pub struct SetDate<S: State = Empty>(PhantomData<fn() -> S>); 1709 + impl<S: State> sealed::Sealed for SetDate<S> {} 1710 + impl<S: State> State for SetDate<S> { 1711 + type Date = Set<members::date>; 1712 + type Access = S::Access; 1713 + } 1714 + ///State transition - sets the `access` field to Set 1715 + pub struct SetAccess<S: State = Empty>(PhantomData<fn() -> S>); 1716 + impl<S: State> sealed::Sealed for SetAccess<S> {} 1717 + impl<S: State> State for SetAccess<S> { 1718 + type Date = S::Date; 1719 + type Access = Set<members::access>; 1720 + } 1721 + /// Marker types for field names 1722 + #[allow(non_camel_case_types)] 1723 + pub mod members { 1724 + ///Marker type for the `date` field 1725 + pub struct date(()); 1726 + ///Marker type for the `access` field 1727 + pub struct access(()); 1728 + } 1729 + } 1730 + 1731 + /// Builder for constructing an instance of this type 1732 + pub struct ConfigRegionRuleIfAccountOlderThanBuilder< 1733 + 'a, 1734 + S: config_region_rule_if_account_older_than_state::State, 1735 + > { 1736 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1737 + __unsafe_private_named: ( 1738 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 1739 + ::core::option::Option<jacquard_common::types::string::Datetime>, 1740 + ), 1741 + _phantom: ::core::marker::PhantomData<&'a ()>, 1742 + } 1743 + 1744 + impl<'a> ConfigRegionRuleIfAccountOlderThan<'a> { 1745 + /// Create a new builder for this type 1746 + pub fn new() -> ConfigRegionRuleIfAccountOlderThanBuilder< 1747 + 'a, 1748 + config_region_rule_if_account_older_than_state::Empty, 1749 + > { 1750 + ConfigRegionRuleIfAccountOlderThanBuilder::new() 1751 + } 1752 + } 1753 + 1754 + impl< 1755 + 'a, 1756 + > ConfigRegionRuleIfAccountOlderThanBuilder< 1757 + 'a, 1758 + config_region_rule_if_account_older_than_state::Empty, 1759 + > { 1760 + /// Create a new builder with all fields unset 1761 + pub fn new() -> Self { 1762 + ConfigRegionRuleIfAccountOlderThanBuilder { 1763 + _phantom_state: ::core::marker::PhantomData, 1764 + __unsafe_private_named: (None, None), 1765 + _phantom: ::core::marker::PhantomData, 1766 + } 1767 + } 1768 + } 1769 + 1770 + impl<'a, S> ConfigRegionRuleIfAccountOlderThanBuilder<'a, S> 1771 + where 1772 + S: config_region_rule_if_account_older_than_state::State, 1773 + S::Access: config_region_rule_if_account_older_than_state::IsUnset, 1774 + { 1775 + /// Set the `access` field (required) 1776 + pub fn access( 1777 + mut self, 1778 + value: impl Into<crate::app_bsky::ageassurance::Access<'a>>, 1779 + ) -> ConfigRegionRuleIfAccountOlderThanBuilder< 1780 + 'a, 1781 + config_region_rule_if_account_older_than_state::SetAccess<S>, 1782 + > { 1783 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 1784 + ConfigRegionRuleIfAccountOlderThanBuilder { 1785 + _phantom_state: ::core::marker::PhantomData, 1786 + __unsafe_private_named: self.__unsafe_private_named, 1787 + _phantom: ::core::marker::PhantomData, 1788 + } 1789 + } 1790 + } 1791 + 1792 + impl<'a, S> ConfigRegionRuleIfAccountOlderThanBuilder<'a, S> 1793 + where 1794 + S: config_region_rule_if_account_older_than_state::State, 1795 + S::Date: config_region_rule_if_account_older_than_state::IsUnset, 1796 + { 1797 + /// Set the `date` field (required) 1798 + pub fn date( 1799 + mut self, 1800 + value: impl Into<jacquard_common::types::string::Datetime>, 1801 + ) -> ConfigRegionRuleIfAccountOlderThanBuilder< 1802 + 'a, 1803 + config_region_rule_if_account_older_than_state::SetDate<S>, 1804 + > { 1805 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 1806 + ConfigRegionRuleIfAccountOlderThanBuilder { 1807 + _phantom_state: ::core::marker::PhantomData, 1808 + __unsafe_private_named: self.__unsafe_private_named, 1809 + _phantom: ::core::marker::PhantomData, 1810 + } 1811 + } 1812 + } 1813 + 1814 + impl<'a, S> ConfigRegionRuleIfAccountOlderThanBuilder<'a, S> 1815 + where 1816 + S: config_region_rule_if_account_older_than_state::State, 1817 + S::Date: config_region_rule_if_account_older_than_state::IsSet, 1818 + S::Access: config_region_rule_if_account_older_than_state::IsSet, 1819 + { 1820 + /// Build the final struct 1821 + pub fn build(self) -> ConfigRegionRuleIfAccountOlderThan<'a> { 1822 + ConfigRegionRuleIfAccountOlderThan { 1823 + access: self.__unsafe_private_named.0.unwrap(), 1824 + date: self.__unsafe_private_named.1.unwrap(), 1825 + extra_data: Default::default(), 1826 + } 1827 + } 1828 + /// Build the final struct with custom extra_data 1829 + pub fn build_with_data( 1830 + self, 1831 + extra_data: std::collections::BTreeMap< 1832 + jacquard_common::smol_str::SmolStr, 1833 + jacquard_common::types::value::Data<'a>, 1834 + >, 1835 + ) -> ConfigRegionRuleIfAccountOlderThan<'a> { 1836 + ConfigRegionRuleIfAccountOlderThan { 1837 + access: self.__unsafe_private_named.0.unwrap(), 1838 + date: self.__unsafe_private_named.1.unwrap(), 1839 + extra_data: Some(extra_data), 1840 + } 1841 + } 1842 + } 1843 + 1844 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema 1845 + for ConfigRegionRuleIfAccountOlderThan<'a> { 1846 + fn nsid() -> &'static str { 1847 + "app.bsky.ageassurance.defs" 1848 + } 1849 + fn def_name() -> &'static str { 1850 + "configRegionRuleIfAccountOlderThan" 1851 + } 1852 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1853 + lexicon_doc_app_bsky_ageassurance_defs() 1854 + } 1855 + fn validate( 1856 + &self, 1857 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1858 + Ok(()) 1859 + } 1860 + } 1861 + 1862 + /// Age Assurance rule that applies if the user has been assured to be equal-to or over a certain age. 1863 + #[jacquard_derive::lexicon] 1864 + #[derive( 1865 + serde::Serialize, 1866 + serde::Deserialize, 1867 + Debug, 1868 + Clone, 1869 + PartialEq, 1870 + Eq, 1871 + jacquard_derive::IntoStatic 1872 + )] 1873 + #[serde(rename_all = "camelCase")] 1874 + pub struct ConfigRegionRuleIfAssuredOverAge<'a> { 1875 + #[serde(borrow)] 1876 + pub access: crate::app_bsky::ageassurance::Access<'a>, 1877 + /// The age threshold as a whole integer. 1878 + pub age: i64, 1879 + } 1880 + 1881 + pub mod config_region_rule_if_assured_over_age_state { 1882 + 1883 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1884 + #[allow(unused)] 1885 + use ::core::marker::PhantomData; 1886 + mod sealed { 1887 + pub trait Sealed {} 1888 + } 1889 + /// State trait tracking which required fields have been set 1890 + pub trait State: sealed::Sealed { 1891 + type Age; 1892 + type Access; 1893 + } 1894 + /// Empty state - all required fields are unset 1895 + pub struct Empty(()); 1896 + impl sealed::Sealed for Empty {} 1897 + impl State for Empty { 1898 + type Age = Unset; 1899 + type Access = Unset; 1900 + } 1901 + ///State transition - sets the `age` field to Set 1902 + pub struct SetAge<S: State = Empty>(PhantomData<fn() -> S>); 1903 + impl<S: State> sealed::Sealed for SetAge<S> {} 1904 + impl<S: State> State for SetAge<S> { 1905 + type Age = Set<members::age>; 1906 + type Access = S::Access; 1907 + } 1908 + ///State transition - sets the `access` field to Set 1909 + pub struct SetAccess<S: State = Empty>(PhantomData<fn() -> S>); 1910 + impl<S: State> sealed::Sealed for SetAccess<S> {} 1911 + impl<S: State> State for SetAccess<S> { 1912 + type Age = S::Age; 1913 + type Access = Set<members::access>; 1914 + } 1915 + /// Marker types for field names 1916 + #[allow(non_camel_case_types)] 1917 + pub mod members { 1918 + ///Marker type for the `age` field 1919 + pub struct age(()); 1920 + ///Marker type for the `access` field 1921 + pub struct access(()); 1922 + } 1923 + } 1924 + 1925 + /// Builder for constructing an instance of this type 1926 + pub struct ConfigRegionRuleIfAssuredOverAgeBuilder< 1927 + 'a, 1928 + S: config_region_rule_if_assured_over_age_state::State, 1929 + > { 1930 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1931 + __unsafe_private_named: ( 1932 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 1933 + ::core::option::Option<i64>, 1934 + ), 1935 + _phantom: ::core::marker::PhantomData<&'a ()>, 1936 + } 1937 + 1938 + impl<'a> ConfigRegionRuleIfAssuredOverAge<'a> { 1939 + /// Create a new builder for this type 1940 + pub fn new() -> ConfigRegionRuleIfAssuredOverAgeBuilder< 1941 + 'a, 1942 + config_region_rule_if_assured_over_age_state::Empty, 1943 + > { 1944 + ConfigRegionRuleIfAssuredOverAgeBuilder::new() 1945 + } 1946 + } 1947 + 1948 + impl< 1949 + 'a, 1950 + > ConfigRegionRuleIfAssuredOverAgeBuilder< 1951 + 'a, 1952 + config_region_rule_if_assured_over_age_state::Empty, 1953 + > { 1954 + /// Create a new builder with all fields unset 1955 + pub fn new() -> Self { 1956 + ConfigRegionRuleIfAssuredOverAgeBuilder { 1957 + _phantom_state: ::core::marker::PhantomData, 1958 + __unsafe_private_named: (None, None), 1959 + _phantom: ::core::marker::PhantomData, 1960 + } 1961 + } 1962 + } 1963 + 1964 + impl<'a, S> ConfigRegionRuleIfAssuredOverAgeBuilder<'a, S> 1965 + where 1966 + S: config_region_rule_if_assured_over_age_state::State, 1967 + S::Access: config_region_rule_if_assured_over_age_state::IsUnset, 1968 + { 1969 + /// Set the `access` field (required) 1970 + pub fn access( 1971 + mut self, 1972 + value: impl Into<crate::app_bsky::ageassurance::Access<'a>>, 1973 + ) -> ConfigRegionRuleIfAssuredOverAgeBuilder< 1974 + 'a, 1975 + config_region_rule_if_assured_over_age_state::SetAccess<S>, 1976 + > { 1977 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 1978 + ConfigRegionRuleIfAssuredOverAgeBuilder { 1979 + _phantom_state: ::core::marker::PhantomData, 1980 + __unsafe_private_named: self.__unsafe_private_named, 1981 + _phantom: ::core::marker::PhantomData, 1982 + } 1983 + } 1984 + } 1985 + 1986 + impl<'a, S> ConfigRegionRuleIfAssuredOverAgeBuilder<'a, S> 1987 + where 1988 + S: config_region_rule_if_assured_over_age_state::State, 1989 + S::Age: config_region_rule_if_assured_over_age_state::IsUnset, 1990 + { 1991 + /// Set the `age` field (required) 1992 + pub fn age( 1993 + mut self, 1994 + value: impl Into<i64>, 1995 + ) -> ConfigRegionRuleIfAssuredOverAgeBuilder< 1996 + 'a, 1997 + config_region_rule_if_assured_over_age_state::SetAge<S>, 1998 + > { 1999 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 2000 + ConfigRegionRuleIfAssuredOverAgeBuilder { 2001 + _phantom_state: ::core::marker::PhantomData, 2002 + __unsafe_private_named: self.__unsafe_private_named, 2003 + _phantom: ::core::marker::PhantomData, 2004 + } 2005 + } 2006 + } 2007 + 2008 + impl<'a, S> ConfigRegionRuleIfAssuredOverAgeBuilder<'a, S> 2009 + where 2010 + S: config_region_rule_if_assured_over_age_state::State, 2011 + S::Age: config_region_rule_if_assured_over_age_state::IsSet, 2012 + S::Access: config_region_rule_if_assured_over_age_state::IsSet, 2013 + { 2014 + /// Build the final struct 2015 + pub fn build(self) -> ConfigRegionRuleIfAssuredOverAge<'a> { 2016 + ConfigRegionRuleIfAssuredOverAge { 2017 + access: self.__unsafe_private_named.0.unwrap(), 2018 + age: self.__unsafe_private_named.1.unwrap(), 2019 + extra_data: Default::default(), 2020 + } 2021 + } 2022 + /// Build the final struct with custom extra_data 2023 + pub fn build_with_data( 2024 + self, 2025 + extra_data: std::collections::BTreeMap< 2026 + jacquard_common::smol_str::SmolStr, 2027 + jacquard_common::types::value::Data<'a>, 2028 + >, 2029 + ) -> ConfigRegionRuleIfAssuredOverAge<'a> { 2030 + ConfigRegionRuleIfAssuredOverAge { 2031 + access: self.__unsafe_private_named.0.unwrap(), 2032 + age: self.__unsafe_private_named.1.unwrap(), 2033 + extra_data: Some(extra_data), 2034 + } 2035 + } 2036 + } 2037 + 2038 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema 2039 + for ConfigRegionRuleIfAssuredOverAge<'a> { 2040 + fn nsid() -> &'static str { 2041 + "app.bsky.ageassurance.defs" 2042 + } 2043 + fn def_name() -> &'static str { 2044 + "configRegionRuleIfAssuredOverAge" 2045 + } 2046 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 2047 + lexicon_doc_app_bsky_ageassurance_defs() 2048 + } 2049 + fn validate( 2050 + &self, 2051 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 2052 + Ok(()) 2053 + } 2054 + } 2055 + 2056 + /// Age Assurance rule that applies if the user has been assured to be under a certain age. 2057 + #[jacquard_derive::lexicon] 2058 + #[derive( 2059 + serde::Serialize, 2060 + serde::Deserialize, 2061 + Debug, 2062 + Clone, 2063 + PartialEq, 2064 + Eq, 2065 + jacquard_derive::IntoStatic 2066 + )] 2067 + #[serde(rename_all = "camelCase")] 2068 + pub struct ConfigRegionRuleIfAssuredUnderAge<'a> { 2069 + #[serde(borrow)] 2070 + pub access: crate::app_bsky::ageassurance::Access<'a>, 2071 + /// The age threshold as a whole integer. 2072 + pub age: i64, 2073 + } 2074 + 2075 + pub mod config_region_rule_if_assured_under_age_state { 2076 + 2077 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 2078 + #[allow(unused)] 2079 + use ::core::marker::PhantomData; 2080 + mod sealed { 2081 + pub trait Sealed {} 2082 + } 2083 + /// State trait tracking which required fields have been set 2084 + pub trait State: sealed::Sealed { 2085 + type Age; 2086 + type Access; 2087 + } 2088 + /// Empty state - all required fields are unset 2089 + pub struct Empty(()); 2090 + impl sealed::Sealed for Empty {} 2091 + impl State for Empty { 2092 + type Age = Unset; 2093 + type Access = Unset; 2094 + } 2095 + ///State transition - sets the `age` field to Set 2096 + pub struct SetAge<S: State = Empty>(PhantomData<fn() -> S>); 2097 + impl<S: State> sealed::Sealed for SetAge<S> {} 2098 + impl<S: State> State for SetAge<S> { 2099 + type Age = Set<members::age>; 2100 + type Access = S::Access; 2101 + } 2102 + ///State transition - sets the `access` field to Set 2103 + pub struct SetAccess<S: State = Empty>(PhantomData<fn() -> S>); 2104 + impl<S: State> sealed::Sealed for SetAccess<S> {} 2105 + impl<S: State> State for SetAccess<S> { 2106 + type Age = S::Age; 2107 + type Access = Set<members::access>; 2108 + } 2109 + /// Marker types for field names 2110 + #[allow(non_camel_case_types)] 2111 + pub mod members { 2112 + ///Marker type for the `age` field 2113 + pub struct age(()); 2114 + ///Marker type for the `access` field 2115 + pub struct access(()); 2116 + } 2117 + } 2118 + 2119 + /// Builder for constructing an instance of this type 2120 + pub struct ConfigRegionRuleIfAssuredUnderAgeBuilder< 2121 + 'a, 2122 + S: config_region_rule_if_assured_under_age_state::State, 2123 + > { 2124 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 2125 + __unsafe_private_named: ( 2126 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 2127 + ::core::option::Option<i64>, 2128 + ), 2129 + _phantom: ::core::marker::PhantomData<&'a ()>, 2130 + } 2131 + 2132 + impl<'a> ConfigRegionRuleIfAssuredUnderAge<'a> { 2133 + /// Create a new builder for this type 2134 + pub fn new() -> ConfigRegionRuleIfAssuredUnderAgeBuilder< 2135 + 'a, 2136 + config_region_rule_if_assured_under_age_state::Empty, 2137 + > { 2138 + ConfigRegionRuleIfAssuredUnderAgeBuilder::new() 2139 + } 2140 + } 2141 + 2142 + impl< 2143 + 'a, 2144 + > ConfigRegionRuleIfAssuredUnderAgeBuilder< 2145 + 'a, 2146 + config_region_rule_if_assured_under_age_state::Empty, 2147 + > { 2148 + /// Create a new builder with all fields unset 2149 + pub fn new() -> Self { 2150 + ConfigRegionRuleIfAssuredUnderAgeBuilder { 2151 + _phantom_state: ::core::marker::PhantomData, 2152 + __unsafe_private_named: (None, None), 2153 + _phantom: ::core::marker::PhantomData, 2154 + } 2155 + } 2156 + } 2157 + 2158 + impl<'a, S> ConfigRegionRuleIfAssuredUnderAgeBuilder<'a, S> 2159 + where 2160 + S: config_region_rule_if_assured_under_age_state::State, 2161 + S::Access: config_region_rule_if_assured_under_age_state::IsUnset, 2162 + { 2163 + /// Set the `access` field (required) 2164 + pub fn access( 2165 + mut self, 2166 + value: impl Into<crate::app_bsky::ageassurance::Access<'a>>, 2167 + ) -> ConfigRegionRuleIfAssuredUnderAgeBuilder< 2168 + 'a, 2169 + config_region_rule_if_assured_under_age_state::SetAccess<S>, 2170 + > { 2171 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 2172 + ConfigRegionRuleIfAssuredUnderAgeBuilder { 2173 + _phantom_state: ::core::marker::PhantomData, 2174 + __unsafe_private_named: self.__unsafe_private_named, 2175 + _phantom: ::core::marker::PhantomData, 2176 + } 2177 + } 2178 + } 2179 + 2180 + impl<'a, S> ConfigRegionRuleIfAssuredUnderAgeBuilder<'a, S> 2181 + where 2182 + S: config_region_rule_if_assured_under_age_state::State, 2183 + S::Age: config_region_rule_if_assured_under_age_state::IsUnset, 2184 + { 2185 + /// Set the `age` field (required) 2186 + pub fn age( 2187 + mut self, 2188 + value: impl Into<i64>, 2189 + ) -> ConfigRegionRuleIfAssuredUnderAgeBuilder< 2190 + 'a, 2191 + config_region_rule_if_assured_under_age_state::SetAge<S>, 2192 + > { 2193 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 2194 + ConfigRegionRuleIfAssuredUnderAgeBuilder { 2195 + _phantom_state: ::core::marker::PhantomData, 2196 + __unsafe_private_named: self.__unsafe_private_named, 2197 + _phantom: ::core::marker::PhantomData, 2198 + } 2199 + } 2200 + } 2201 + 2202 + impl<'a, S> ConfigRegionRuleIfAssuredUnderAgeBuilder<'a, S> 2203 + where 2204 + S: config_region_rule_if_assured_under_age_state::State, 2205 + S::Age: config_region_rule_if_assured_under_age_state::IsSet, 2206 + S::Access: config_region_rule_if_assured_under_age_state::IsSet, 2207 + { 2208 + /// Build the final struct 2209 + pub fn build(self) -> ConfigRegionRuleIfAssuredUnderAge<'a> { 2210 + ConfigRegionRuleIfAssuredUnderAge { 2211 + access: self.__unsafe_private_named.0.unwrap(), 2212 + age: self.__unsafe_private_named.1.unwrap(), 2213 + extra_data: Default::default(), 2214 + } 2215 + } 2216 + /// Build the final struct with custom extra_data 2217 + pub fn build_with_data( 2218 + self, 2219 + extra_data: std::collections::BTreeMap< 2220 + jacquard_common::smol_str::SmolStr, 2221 + jacquard_common::types::value::Data<'a>, 2222 + >, 2223 + ) -> ConfigRegionRuleIfAssuredUnderAge<'a> { 2224 + ConfigRegionRuleIfAssuredUnderAge { 2225 + access: self.__unsafe_private_named.0.unwrap(), 2226 + age: self.__unsafe_private_named.1.unwrap(), 2227 + extra_data: Some(extra_data), 2228 + } 2229 + } 2230 + } 2231 + 2232 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema 2233 + for ConfigRegionRuleIfAssuredUnderAge<'a> { 2234 + fn nsid() -> &'static str { 2235 + "app.bsky.ageassurance.defs" 2236 + } 2237 + fn def_name() -> &'static str { 2238 + "configRegionRuleIfAssuredUnderAge" 2239 + } 2240 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 2241 + lexicon_doc_app_bsky_ageassurance_defs() 2242 + } 2243 + fn validate( 2244 + &self, 2245 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 2246 + Ok(()) 2247 + } 2248 + } 2249 + 2250 + /// Age Assurance rule that applies if the user has declared themselves equal-to or over a certain age. 2251 + #[jacquard_derive::lexicon] 2252 + #[derive( 2253 + serde::Serialize, 2254 + serde::Deserialize, 2255 + Debug, 2256 + Clone, 2257 + PartialEq, 2258 + Eq, 2259 + jacquard_derive::IntoStatic 2260 + )] 2261 + #[serde(rename_all = "camelCase")] 2262 + pub struct ConfigRegionRuleIfDeclaredOverAge<'a> { 2263 + #[serde(borrow)] 2264 + pub access: crate::app_bsky::ageassurance::Access<'a>, 2265 + /// The age threshold as a whole integer. 2266 + pub age: i64, 2267 + } 2268 + 2269 + pub mod config_region_rule_if_declared_over_age_state { 2270 + 2271 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 2272 + #[allow(unused)] 2273 + use ::core::marker::PhantomData; 2274 + mod sealed { 2275 + pub trait Sealed {} 2276 + } 2277 + /// State trait tracking which required fields have been set 2278 + pub trait State: sealed::Sealed { 2279 + type Age; 2280 + type Access; 2281 + } 2282 + /// Empty state - all required fields are unset 2283 + pub struct Empty(()); 2284 + impl sealed::Sealed for Empty {} 2285 + impl State for Empty { 2286 + type Age = Unset; 2287 + type Access = Unset; 2288 + } 2289 + ///State transition - sets the `age` field to Set 2290 + pub struct SetAge<S: State = Empty>(PhantomData<fn() -> S>); 2291 + impl<S: State> sealed::Sealed for SetAge<S> {} 2292 + impl<S: State> State for SetAge<S> { 2293 + type Age = Set<members::age>; 2294 + type Access = S::Access; 2295 + } 2296 + ///State transition - sets the `access` field to Set 2297 + pub struct SetAccess<S: State = Empty>(PhantomData<fn() -> S>); 2298 + impl<S: State> sealed::Sealed for SetAccess<S> {} 2299 + impl<S: State> State for SetAccess<S> { 2300 + type Age = S::Age; 2301 + type Access = Set<members::access>; 2302 + } 2303 + /// Marker types for field names 2304 + #[allow(non_camel_case_types)] 2305 + pub mod members { 2306 + ///Marker type for the `age` field 2307 + pub struct age(()); 2308 + ///Marker type for the `access` field 2309 + pub struct access(()); 2310 + } 2311 + } 2312 + 2313 + /// Builder for constructing an instance of this type 2314 + pub struct ConfigRegionRuleIfDeclaredOverAgeBuilder< 2315 + 'a, 2316 + S: config_region_rule_if_declared_over_age_state::State, 2317 + > { 2318 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 2319 + __unsafe_private_named: ( 2320 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 2321 + ::core::option::Option<i64>, 2322 + ), 2323 + _phantom: ::core::marker::PhantomData<&'a ()>, 2324 + } 2325 + 2326 + impl<'a> ConfigRegionRuleIfDeclaredOverAge<'a> { 2327 + /// Create a new builder for this type 2328 + pub fn new() -> ConfigRegionRuleIfDeclaredOverAgeBuilder< 2329 + 'a, 2330 + config_region_rule_if_declared_over_age_state::Empty, 2331 + > { 2332 + ConfigRegionRuleIfDeclaredOverAgeBuilder::new() 2333 + } 2334 + } 2335 + 2336 + impl< 2337 + 'a, 2338 + > ConfigRegionRuleIfDeclaredOverAgeBuilder< 2339 + 'a, 2340 + config_region_rule_if_declared_over_age_state::Empty, 2341 + > { 2342 + /// Create a new builder with all fields unset 2343 + pub fn new() -> Self { 2344 + ConfigRegionRuleIfDeclaredOverAgeBuilder { 2345 + _phantom_state: ::core::marker::PhantomData, 2346 + __unsafe_private_named: (None, None), 2347 + _phantom: ::core::marker::PhantomData, 2348 + } 2349 + } 2350 + } 2351 + 2352 + impl<'a, S> ConfigRegionRuleIfDeclaredOverAgeBuilder<'a, S> 2353 + where 2354 + S: config_region_rule_if_declared_over_age_state::State, 2355 + S::Access: config_region_rule_if_declared_over_age_state::IsUnset, 2356 + { 2357 + /// Set the `access` field (required) 2358 + pub fn access( 2359 + mut self, 2360 + value: impl Into<crate::app_bsky::ageassurance::Access<'a>>, 2361 + ) -> ConfigRegionRuleIfDeclaredOverAgeBuilder< 2362 + 'a, 2363 + config_region_rule_if_declared_over_age_state::SetAccess<S>, 2364 + > { 2365 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 2366 + ConfigRegionRuleIfDeclaredOverAgeBuilder { 2367 + _phantom_state: ::core::marker::PhantomData, 2368 + __unsafe_private_named: self.__unsafe_private_named, 2369 + _phantom: ::core::marker::PhantomData, 2370 + } 2371 + } 2372 + } 2373 + 2374 + impl<'a, S> ConfigRegionRuleIfDeclaredOverAgeBuilder<'a, S> 2375 + where 2376 + S: config_region_rule_if_declared_over_age_state::State, 2377 + S::Age: config_region_rule_if_declared_over_age_state::IsUnset, 2378 + { 2379 + /// Set the `age` field (required) 2380 + pub fn age( 2381 + mut self, 2382 + value: impl Into<i64>, 2383 + ) -> ConfigRegionRuleIfDeclaredOverAgeBuilder< 2384 + 'a, 2385 + config_region_rule_if_declared_over_age_state::SetAge<S>, 2386 + > { 2387 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 2388 + ConfigRegionRuleIfDeclaredOverAgeBuilder { 2389 + _phantom_state: ::core::marker::PhantomData, 2390 + __unsafe_private_named: self.__unsafe_private_named, 2391 + _phantom: ::core::marker::PhantomData, 2392 + } 2393 + } 2394 + } 2395 + 2396 + impl<'a, S> ConfigRegionRuleIfDeclaredOverAgeBuilder<'a, S> 2397 + where 2398 + S: config_region_rule_if_declared_over_age_state::State, 2399 + S::Age: config_region_rule_if_declared_over_age_state::IsSet, 2400 + S::Access: config_region_rule_if_declared_over_age_state::IsSet, 2401 + { 2402 + /// Build the final struct 2403 + pub fn build(self) -> ConfigRegionRuleIfDeclaredOverAge<'a> { 2404 + ConfigRegionRuleIfDeclaredOverAge { 2405 + access: self.__unsafe_private_named.0.unwrap(), 2406 + age: self.__unsafe_private_named.1.unwrap(), 2407 + extra_data: Default::default(), 2408 + } 2409 + } 2410 + /// Build the final struct with custom extra_data 2411 + pub fn build_with_data( 2412 + self, 2413 + extra_data: std::collections::BTreeMap< 2414 + jacquard_common::smol_str::SmolStr, 2415 + jacquard_common::types::value::Data<'a>, 2416 + >, 2417 + ) -> ConfigRegionRuleIfDeclaredOverAge<'a> { 2418 + ConfigRegionRuleIfDeclaredOverAge { 2419 + access: self.__unsafe_private_named.0.unwrap(), 2420 + age: self.__unsafe_private_named.1.unwrap(), 2421 + extra_data: Some(extra_data), 2422 + } 2423 + } 2424 + } 2425 + 2426 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema 2427 + for ConfigRegionRuleIfDeclaredOverAge<'a> { 2428 + fn nsid() -> &'static str { 2429 + "app.bsky.ageassurance.defs" 2430 + } 2431 + fn def_name() -> &'static str { 2432 + "configRegionRuleIfDeclaredOverAge" 2433 + } 2434 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 2435 + lexicon_doc_app_bsky_ageassurance_defs() 2436 + } 2437 + fn validate( 2438 + &self, 2439 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 2440 + Ok(()) 2441 + } 2442 + } 2443 + 2444 + /// Age Assurance rule that applies if the user has declared themselves under a certain age. 2445 + #[jacquard_derive::lexicon] 2446 + #[derive( 2447 + serde::Serialize, 2448 + serde::Deserialize, 2449 + Debug, 2450 + Clone, 2451 + PartialEq, 2452 + Eq, 2453 + jacquard_derive::IntoStatic 2454 + )] 2455 + #[serde(rename_all = "camelCase")] 2456 + pub struct ConfigRegionRuleIfDeclaredUnderAge<'a> { 2457 + #[serde(borrow)] 2458 + pub access: crate::app_bsky::ageassurance::Access<'a>, 2459 + /// The age threshold as a whole integer. 2460 + pub age: i64, 2461 + } 2462 + 2463 + pub mod config_region_rule_if_declared_under_age_state { 2464 + 2465 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 2466 + #[allow(unused)] 2467 + use ::core::marker::PhantomData; 2468 + mod sealed { 2469 + pub trait Sealed {} 2470 + } 2471 + /// State trait tracking which required fields have been set 2472 + pub trait State: sealed::Sealed { 2473 + type Age; 2474 + type Access; 2475 + } 2476 + /// Empty state - all required fields are unset 2477 + pub struct Empty(()); 2478 + impl sealed::Sealed for Empty {} 2479 + impl State for Empty { 2480 + type Age = Unset; 2481 + type Access = Unset; 2482 + } 2483 + ///State transition - sets the `age` field to Set 2484 + pub struct SetAge<S: State = Empty>(PhantomData<fn() -> S>); 2485 + impl<S: State> sealed::Sealed for SetAge<S> {} 2486 + impl<S: State> State for SetAge<S> { 2487 + type Age = Set<members::age>; 2488 + type Access = S::Access; 2489 + } 2490 + ///State transition - sets the `access` field to Set 2491 + pub struct SetAccess<S: State = Empty>(PhantomData<fn() -> S>); 2492 + impl<S: State> sealed::Sealed for SetAccess<S> {} 2493 + impl<S: State> State for SetAccess<S> { 2494 + type Age = S::Age; 2495 + type Access = Set<members::access>; 2496 + } 2497 + /// Marker types for field names 2498 + #[allow(non_camel_case_types)] 2499 + pub mod members { 2500 + ///Marker type for the `age` field 2501 + pub struct age(()); 2502 + ///Marker type for the `access` field 2503 + pub struct access(()); 2504 + } 2505 + } 2506 + 2507 + /// Builder for constructing an instance of this type 2508 + pub struct ConfigRegionRuleIfDeclaredUnderAgeBuilder< 2509 + 'a, 2510 + S: config_region_rule_if_declared_under_age_state::State, 2511 + > { 2512 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 2513 + __unsafe_private_named: ( 2514 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 2515 + ::core::option::Option<i64>, 2516 + ), 2517 + _phantom: ::core::marker::PhantomData<&'a ()>, 2518 + } 2519 + 2520 + impl<'a> ConfigRegionRuleIfDeclaredUnderAge<'a> { 2521 + /// Create a new builder for this type 2522 + pub fn new() -> ConfigRegionRuleIfDeclaredUnderAgeBuilder< 2523 + 'a, 2524 + config_region_rule_if_declared_under_age_state::Empty, 2525 + > { 2526 + ConfigRegionRuleIfDeclaredUnderAgeBuilder::new() 2527 + } 2528 + } 2529 + 2530 + impl< 2531 + 'a, 2532 + > ConfigRegionRuleIfDeclaredUnderAgeBuilder< 2533 + 'a, 2534 + config_region_rule_if_declared_under_age_state::Empty, 2535 + > { 2536 + /// Create a new builder with all fields unset 2537 + pub fn new() -> Self { 2538 + ConfigRegionRuleIfDeclaredUnderAgeBuilder { 2539 + _phantom_state: ::core::marker::PhantomData, 2540 + __unsafe_private_named: (None, None), 2541 + _phantom: ::core::marker::PhantomData, 2542 + } 2543 + } 2544 + } 2545 + 2546 + impl<'a, S> ConfigRegionRuleIfDeclaredUnderAgeBuilder<'a, S> 2547 + where 2548 + S: config_region_rule_if_declared_under_age_state::State, 2549 + S::Access: config_region_rule_if_declared_under_age_state::IsUnset, 2550 + { 2551 + /// Set the `access` field (required) 2552 + pub fn access( 2553 + mut self, 2554 + value: impl Into<crate::app_bsky::ageassurance::Access<'a>>, 2555 + ) -> ConfigRegionRuleIfDeclaredUnderAgeBuilder< 2556 + 'a, 2557 + config_region_rule_if_declared_under_age_state::SetAccess<S>, 2558 + > { 2559 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 2560 + ConfigRegionRuleIfDeclaredUnderAgeBuilder { 2561 + _phantom_state: ::core::marker::PhantomData, 2562 + __unsafe_private_named: self.__unsafe_private_named, 2563 + _phantom: ::core::marker::PhantomData, 2564 + } 2565 + } 2566 + } 2567 + 2568 + impl<'a, S> ConfigRegionRuleIfDeclaredUnderAgeBuilder<'a, S> 2569 + where 2570 + S: config_region_rule_if_declared_under_age_state::State, 2571 + S::Age: config_region_rule_if_declared_under_age_state::IsUnset, 2572 + { 2573 + /// Set the `age` field (required) 2574 + pub fn age( 2575 + mut self, 2576 + value: impl Into<i64>, 2577 + ) -> ConfigRegionRuleIfDeclaredUnderAgeBuilder< 2578 + 'a, 2579 + config_region_rule_if_declared_under_age_state::SetAge<S>, 2580 + > { 2581 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 2582 + ConfigRegionRuleIfDeclaredUnderAgeBuilder { 2583 + _phantom_state: ::core::marker::PhantomData, 2584 + __unsafe_private_named: self.__unsafe_private_named, 2585 + _phantom: ::core::marker::PhantomData, 2586 + } 2587 + } 2588 + } 2589 + 2590 + impl<'a, S> ConfigRegionRuleIfDeclaredUnderAgeBuilder<'a, S> 2591 + where 2592 + S: config_region_rule_if_declared_under_age_state::State, 2593 + S::Age: config_region_rule_if_declared_under_age_state::IsSet, 2594 + S::Access: config_region_rule_if_declared_under_age_state::IsSet, 2595 + { 2596 + /// Build the final struct 2597 + pub fn build(self) -> ConfigRegionRuleIfDeclaredUnderAge<'a> { 2598 + ConfigRegionRuleIfDeclaredUnderAge { 2599 + access: self.__unsafe_private_named.0.unwrap(), 2600 + age: self.__unsafe_private_named.1.unwrap(), 2601 + extra_data: Default::default(), 2602 + } 2603 + } 2604 + /// Build the final struct with custom extra_data 2605 + pub fn build_with_data( 2606 + self, 2607 + extra_data: std::collections::BTreeMap< 2608 + jacquard_common::smol_str::SmolStr, 2609 + jacquard_common::types::value::Data<'a>, 2610 + >, 2611 + ) -> ConfigRegionRuleIfDeclaredUnderAge<'a> { 2612 + ConfigRegionRuleIfDeclaredUnderAge { 2613 + access: self.__unsafe_private_named.0.unwrap(), 2614 + age: self.__unsafe_private_named.1.unwrap(), 2615 + extra_data: Some(extra_data), 2616 + } 2617 + } 2618 + } 2619 + 2620 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema 2621 + for ConfigRegionRuleIfDeclaredUnderAge<'a> { 2622 + fn nsid() -> &'static str { 2623 + "app.bsky.ageassurance.defs" 2624 + } 2625 + fn def_name() -> &'static str { 2626 + "configRegionRuleIfDeclaredUnderAge" 2627 + } 2628 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 2629 + lexicon_doc_app_bsky_ageassurance_defs() 2630 + } 2631 + fn validate( 2632 + &self, 2633 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 2634 + Ok(()) 2635 + } 2636 + } 2637 + 2638 + /// Object used to store Age Assurance data in stash. 2639 + #[jacquard_derive::lexicon] 2640 + #[derive( 2641 + serde::Serialize, 2642 + serde::Deserialize, 2643 + Debug, 2644 + Clone, 2645 + PartialEq, 2646 + Eq, 2647 + jacquard_derive::IntoStatic 2648 + )] 2649 + #[serde(rename_all = "camelCase")] 2650 + pub struct Event<'a> { 2651 + /// The access level granted based on Age Assurance data we've processed. 2652 + #[serde(borrow)] 2653 + pub access: jacquard_common::CowStr<'a>, 2654 + /// The unique identifier for this instance of the Age Assurance flow, in UUID format. 2655 + #[serde(borrow)] 2656 + pub attempt_id: jacquard_common::CowStr<'a>, 2657 + /// The IP address used when completing the Age Assurance flow. 2658 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 2659 + #[serde(borrow)] 2660 + pub complete_ip: std::option::Option<jacquard_common::CowStr<'a>>, 2661 + /// The user agent used when completing the Age Assurance flow. 2662 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 2663 + #[serde(borrow)] 2664 + pub complete_ua: std::option::Option<jacquard_common::CowStr<'a>>, 2665 + /// The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow. 2666 + #[serde(borrow)] 2667 + pub country_code: jacquard_common::CowStr<'a>, 2668 + /// The date and time of this write operation. 2669 + pub created_at: jacquard_common::types::string::Datetime, 2670 + /// The email used for Age Assurance. 2671 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 2672 + #[serde(borrow)] 2673 + pub email: std::option::Option<jacquard_common::CowStr<'a>>, 2674 + /// The IP address used when initiating the Age Assurance flow. 2675 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 2676 + #[serde(borrow)] 2677 + pub init_ip: std::option::Option<jacquard_common::CowStr<'a>>, 2678 + /// The user agent used when initiating the Age Assurance flow. 2679 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 2680 + #[serde(borrow)] 2681 + pub init_ua: std::option::Option<jacquard_common::CowStr<'a>>, 2682 + /// The ISO 3166-2 region code provided when beginning the Age Assurance flow. 2683 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 2684 + #[serde(borrow)] 2685 + pub region_code: std::option::Option<jacquard_common::CowStr<'a>>, 2686 + /// The status of the Age Assurance process. 2687 + #[serde(borrow)] 2688 + pub status: jacquard_common::CowStr<'a>, 2689 + } 2690 + 2691 + pub mod event_state { 2692 + 2693 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 2694 + #[allow(unused)] 2695 + use ::core::marker::PhantomData; 2696 + mod sealed { 2697 + pub trait Sealed {} 2698 + } 2699 + /// State trait tracking which required fields have been set 2700 + pub trait State: sealed::Sealed { 2701 + type CreatedAt; 2702 + type Status; 2703 + type Access; 2704 + type AttemptId; 2705 + type CountryCode; 2706 + } 2707 + /// Empty state - all required fields are unset 2708 + pub struct Empty(()); 2709 + impl sealed::Sealed for Empty {} 2710 + impl State for Empty { 2711 + type CreatedAt = Unset; 2712 + type Status = Unset; 2713 + type Access = Unset; 2714 + type AttemptId = Unset; 2715 + type CountryCode = Unset; 2716 + } 2717 + ///State transition - sets the `created_at` field to Set 2718 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 2719 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 2720 + impl<S: State> State for SetCreatedAt<S> { 2721 + type CreatedAt = Set<members::created_at>; 2722 + type Status = S::Status; 2723 + type Access = S::Access; 2724 + type AttemptId = S::AttemptId; 2725 + type CountryCode = S::CountryCode; 2726 + } 2727 + ///State transition - sets the `status` field to Set 2728 + pub struct SetStatus<S: State = Empty>(PhantomData<fn() -> S>); 2729 + impl<S: State> sealed::Sealed for SetStatus<S> {} 2730 + impl<S: State> State for SetStatus<S> { 2731 + type CreatedAt = S::CreatedAt; 2732 + type Status = Set<members::status>; 2733 + type Access = S::Access; 2734 + type AttemptId = S::AttemptId; 2735 + type CountryCode = S::CountryCode; 2736 + } 2737 + ///State transition - sets the `access` field to Set 2738 + pub struct SetAccess<S: State = Empty>(PhantomData<fn() -> S>); 2739 + impl<S: State> sealed::Sealed for SetAccess<S> {} 2740 + impl<S: State> State for SetAccess<S> { 2741 + type CreatedAt = S::CreatedAt; 2742 + type Status = S::Status; 2743 + type Access = Set<members::access>; 2744 + type AttemptId = S::AttemptId; 2745 + type CountryCode = S::CountryCode; 2746 + } 2747 + ///State transition - sets the `attempt_id` field to Set 2748 + pub struct SetAttemptId<S: State = Empty>(PhantomData<fn() -> S>); 2749 + impl<S: State> sealed::Sealed for SetAttemptId<S> {} 2750 + impl<S: State> State for SetAttemptId<S> { 2751 + type CreatedAt = S::CreatedAt; 2752 + type Status = S::Status; 2753 + type Access = S::Access; 2754 + type AttemptId = Set<members::attempt_id>; 2755 + type CountryCode = S::CountryCode; 2756 + } 2757 + ///State transition - sets the `country_code` field to Set 2758 + pub struct SetCountryCode<S: State = Empty>(PhantomData<fn() -> S>); 2759 + impl<S: State> sealed::Sealed for SetCountryCode<S> {} 2760 + impl<S: State> State for SetCountryCode<S> { 2761 + type CreatedAt = S::CreatedAt; 2762 + type Status = S::Status; 2763 + type Access = S::Access; 2764 + type AttemptId = S::AttemptId; 2765 + type CountryCode = Set<members::country_code>; 2766 + } 2767 + /// Marker types for field names 2768 + #[allow(non_camel_case_types)] 2769 + pub mod members { 2770 + ///Marker type for the `created_at` field 2771 + pub struct created_at(()); 2772 + ///Marker type for the `status` field 2773 + pub struct status(()); 2774 + ///Marker type for the `access` field 2775 + pub struct access(()); 2776 + ///Marker type for the `attempt_id` field 2777 + pub struct attempt_id(()); 2778 + ///Marker type for the `country_code` field 2779 + pub struct country_code(()); 2780 + } 2781 + } 2782 + 2783 + /// Builder for constructing an instance of this type 2784 + pub struct EventBuilder<'a, S: event_state::State> { 2785 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 2786 + __unsafe_private_named: ( 2787 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2788 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2789 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2790 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2791 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2792 + ::core::option::Option<jacquard_common::types::string::Datetime>, 2793 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2794 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2795 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2796 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2797 + ::core::option::Option<jacquard_common::CowStr<'a>>, 2798 + ), 2799 + _phantom: ::core::marker::PhantomData<&'a ()>, 2800 + } 2801 + 2802 + impl<'a> Event<'a> { 2803 + /// Create a new builder for this type 2804 + pub fn new() -> EventBuilder<'a, event_state::Empty> { 2805 + EventBuilder::new() 2806 + } 2807 + } 2808 + 2809 + impl<'a> EventBuilder<'a, event_state::Empty> { 2810 + /// Create a new builder with all fields unset 2811 + pub fn new() -> Self { 2812 + EventBuilder { 2813 + _phantom_state: ::core::marker::PhantomData, 2814 + __unsafe_private_named: ( 2815 + None, 2816 + None, 2817 + None, 2818 + None, 2819 + None, 2820 + None, 2821 + None, 2822 + None, 2823 + None, 2824 + None, 2825 + None, 2826 + ), 2827 + _phantom: ::core::marker::PhantomData, 2828 + } 2829 + } 2830 + } 2831 + 2832 + impl<'a, S> EventBuilder<'a, S> 2833 + where 2834 + S: event_state::State, 2835 + S::Access: event_state::IsUnset, 2836 + { 2837 + /// Set the `access` field (required) 2838 + pub fn access( 2839 + mut self, 2840 + value: impl Into<jacquard_common::CowStr<'a>>, 2841 + ) -> EventBuilder<'a, event_state::SetAccess<S>> { 2842 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 2843 + EventBuilder { 2844 + _phantom_state: ::core::marker::PhantomData, 2845 + __unsafe_private_named: self.__unsafe_private_named, 2846 + _phantom: ::core::marker::PhantomData, 2847 + } 2848 + } 2849 + } 2850 + 2851 + impl<'a, S> EventBuilder<'a, S> 2852 + where 2853 + S: event_state::State, 2854 + S::AttemptId: event_state::IsUnset, 2855 + { 2856 + /// Set the `attemptId` field (required) 2857 + pub fn attempt_id( 2858 + mut self, 2859 + value: impl Into<jacquard_common::CowStr<'a>>, 2860 + ) -> EventBuilder<'a, event_state::SetAttemptId<S>> { 2861 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 2862 + EventBuilder { 2863 + _phantom_state: ::core::marker::PhantomData, 2864 + __unsafe_private_named: self.__unsafe_private_named, 2865 + _phantom: ::core::marker::PhantomData, 2866 + } 2867 + } 2868 + } 2869 + 2870 + impl<'a, S: event_state::State> EventBuilder<'a, S> { 2871 + /// Set the `completeIp` field (optional) 2872 + pub fn complete_ip( 2873 + mut self, 2874 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 2875 + ) -> Self { 2876 + self.__unsafe_private_named.2 = value.into(); 2877 + self 2878 + } 2879 + /// Set the `completeIp` field to an Option value (optional) 2880 + pub fn maybe_complete_ip( 2881 + mut self, 2882 + value: Option<jacquard_common::CowStr<'a>>, 2883 + ) -> Self { 2884 + self.__unsafe_private_named.2 = value; 2885 + self 2886 + } 2887 + } 2888 + 2889 + impl<'a, S: event_state::State> EventBuilder<'a, S> { 2890 + /// Set the `completeUa` field (optional) 2891 + pub fn complete_ua( 2892 + mut self, 2893 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 2894 + ) -> Self { 2895 + self.__unsafe_private_named.3 = value.into(); 2896 + self 2897 + } 2898 + /// Set the `completeUa` field to an Option value (optional) 2899 + pub fn maybe_complete_ua( 2900 + mut self, 2901 + value: Option<jacquard_common::CowStr<'a>>, 2902 + ) -> Self { 2903 + self.__unsafe_private_named.3 = value; 2904 + self 2905 + } 2906 + } 2907 + 2908 + impl<'a, S> EventBuilder<'a, S> 2909 + where 2910 + S: event_state::State, 2911 + S::CountryCode: event_state::IsUnset, 2912 + { 2913 + /// Set the `countryCode` field (required) 2914 + pub fn country_code( 2915 + mut self, 2916 + value: impl Into<jacquard_common::CowStr<'a>>, 2917 + ) -> EventBuilder<'a, event_state::SetCountryCode<S>> { 2918 + self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); 2919 + EventBuilder { 2920 + _phantom_state: ::core::marker::PhantomData, 2921 + __unsafe_private_named: self.__unsafe_private_named, 2922 + _phantom: ::core::marker::PhantomData, 2923 + } 2924 + } 2925 + } 2926 + 2927 + impl<'a, S> EventBuilder<'a, S> 2928 + where 2929 + S: event_state::State, 2930 + S::CreatedAt: event_state::IsUnset, 2931 + { 2932 + /// Set the `createdAt` field (required) 2933 + pub fn created_at( 2934 + mut self, 2935 + value: impl Into<jacquard_common::types::string::Datetime>, 2936 + ) -> EventBuilder<'a, event_state::SetCreatedAt<S>> { 2937 + self.__unsafe_private_named.5 = ::core::option::Option::Some(value.into()); 2938 + EventBuilder { 2939 + _phantom_state: ::core::marker::PhantomData, 2940 + __unsafe_private_named: self.__unsafe_private_named, 2941 + _phantom: ::core::marker::PhantomData, 2942 + } 2943 + } 2944 + } 2945 + 2946 + impl<'a, S: event_state::State> EventBuilder<'a, S> { 2947 + /// Set the `email` field (optional) 2948 + pub fn email( 2949 + mut self, 2950 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 2951 + ) -> Self { 2952 + self.__unsafe_private_named.6 = value.into(); 2953 + self 2954 + } 2955 + /// Set the `email` field to an Option value (optional) 2956 + pub fn maybe_email(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 2957 + self.__unsafe_private_named.6 = value; 2958 + self 2959 + } 2960 + } 2961 + 2962 + impl<'a, S: event_state::State> EventBuilder<'a, S> { 2963 + /// Set the `initIp` field (optional) 2964 + pub fn init_ip( 2965 + mut self, 2966 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 2967 + ) -> Self { 2968 + self.__unsafe_private_named.7 = value.into(); 2969 + self 2970 + } 2971 + /// Set the `initIp` field to an Option value (optional) 2972 + pub fn maybe_init_ip(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 2973 + self.__unsafe_private_named.7 = value; 2974 + self 2975 + } 2976 + } 2977 + 2978 + impl<'a, S: event_state::State> EventBuilder<'a, S> { 2979 + /// Set the `initUa` field (optional) 2980 + pub fn init_ua( 2981 + mut self, 2982 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 2983 + ) -> Self { 2984 + self.__unsafe_private_named.8 = value.into(); 2985 + self 2986 + } 2987 + /// Set the `initUa` field to an Option value (optional) 2988 + pub fn maybe_init_ua(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 2989 + self.__unsafe_private_named.8 = value; 2990 + self 2991 + } 2992 + } 2993 + 2994 + impl<'a, S: event_state::State> EventBuilder<'a, S> { 2995 + /// Set the `regionCode` field (optional) 2996 + pub fn region_code( 2997 + mut self, 2998 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 2999 + ) -> Self { 3000 + self.__unsafe_private_named.9 = value.into(); 3001 + self 3002 + } 3003 + /// Set the `regionCode` field to an Option value (optional) 3004 + pub fn maybe_region_code( 3005 + mut self, 3006 + value: Option<jacquard_common::CowStr<'a>>, 3007 + ) -> Self { 3008 + self.__unsafe_private_named.9 = value; 3009 + self 3010 + } 3011 + } 3012 + 3013 + impl<'a, S> EventBuilder<'a, S> 3014 + where 3015 + S: event_state::State, 3016 + S::Status: event_state::IsUnset, 3017 + { 3018 + /// Set the `status` field (required) 3019 + pub fn status( 3020 + mut self, 3021 + value: impl Into<jacquard_common::CowStr<'a>>, 3022 + ) -> EventBuilder<'a, event_state::SetStatus<S>> { 3023 + self.__unsafe_private_named.10 = ::core::option::Option::Some(value.into()); 3024 + EventBuilder { 3025 + _phantom_state: ::core::marker::PhantomData, 3026 + __unsafe_private_named: self.__unsafe_private_named, 3027 + _phantom: ::core::marker::PhantomData, 3028 + } 3029 + } 3030 + } 3031 + 3032 + impl<'a, S> EventBuilder<'a, S> 3033 + where 3034 + S: event_state::State, 3035 + S::CreatedAt: event_state::IsSet, 3036 + S::Status: event_state::IsSet, 3037 + S::Access: event_state::IsSet, 3038 + S::AttemptId: event_state::IsSet, 3039 + S::CountryCode: event_state::IsSet, 3040 + { 3041 + /// Build the final struct 3042 + pub fn build(self) -> Event<'a> { 3043 + Event { 3044 + access: self.__unsafe_private_named.0.unwrap(), 3045 + attempt_id: self.__unsafe_private_named.1.unwrap(), 3046 + complete_ip: self.__unsafe_private_named.2, 3047 + complete_ua: self.__unsafe_private_named.3, 3048 + country_code: self.__unsafe_private_named.4.unwrap(), 3049 + created_at: self.__unsafe_private_named.5.unwrap(), 3050 + email: self.__unsafe_private_named.6, 3051 + init_ip: self.__unsafe_private_named.7, 3052 + init_ua: self.__unsafe_private_named.8, 3053 + region_code: self.__unsafe_private_named.9, 3054 + status: self.__unsafe_private_named.10.unwrap(), 3055 + extra_data: Default::default(), 3056 + } 3057 + } 3058 + /// Build the final struct with custom extra_data 3059 + pub fn build_with_data( 3060 + self, 3061 + extra_data: std::collections::BTreeMap< 3062 + jacquard_common::smol_str::SmolStr, 3063 + jacquard_common::types::value::Data<'a>, 3064 + >, 3065 + ) -> Event<'a> { 3066 + Event { 3067 + access: self.__unsafe_private_named.0.unwrap(), 3068 + attempt_id: self.__unsafe_private_named.1.unwrap(), 3069 + complete_ip: self.__unsafe_private_named.2, 3070 + complete_ua: self.__unsafe_private_named.3, 3071 + country_code: self.__unsafe_private_named.4.unwrap(), 3072 + created_at: self.__unsafe_private_named.5.unwrap(), 3073 + email: self.__unsafe_private_named.6, 3074 + init_ip: self.__unsafe_private_named.7, 3075 + init_ua: self.__unsafe_private_named.8, 3076 + region_code: self.__unsafe_private_named.9, 3077 + status: self.__unsafe_private_named.10.unwrap(), 3078 + extra_data: Some(extra_data), 3079 + } 3080 + } 3081 + } 3082 + 3083 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Event<'a> { 3084 + fn nsid() -> &'static str { 3085 + "app.bsky.ageassurance.defs" 3086 + } 3087 + fn def_name() -> &'static str { 3088 + "event" 3089 + } 3090 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 3091 + lexicon_doc_app_bsky_ageassurance_defs() 3092 + } 3093 + fn validate( 3094 + &self, 3095 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 3096 + Ok(()) 3097 + } 3098 + } 3099 + 3100 + /// The user's computed Age Assurance state. 3101 + #[jacquard_derive::lexicon] 3102 + #[derive( 3103 + serde::Serialize, 3104 + serde::Deserialize, 3105 + Debug, 3106 + Clone, 3107 + PartialEq, 3108 + Eq, 3109 + jacquard_derive::IntoStatic 3110 + )] 3111 + #[serde(rename_all = "camelCase")] 3112 + pub struct State<'a> { 3113 + #[serde(borrow)] 3114 + pub access: crate::app_bsky::ageassurance::Access<'a>, 3115 + /// The timestamp when this state was last updated. 3116 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 3117 + pub last_initiated_at: std::option::Option<jacquard_common::types::string::Datetime>, 3118 + #[serde(borrow)] 3119 + pub status: crate::app_bsky::ageassurance::Status<'a>, 3120 + } 3121 + 3122 + pub mod state_state { 3123 + 3124 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 3125 + #[allow(unused)] 3126 + use ::core::marker::PhantomData; 3127 + mod sealed { 3128 + pub trait Sealed {} 3129 + } 3130 + /// State trait tracking which required fields have been set 3131 + pub trait State: sealed::Sealed { 3132 + type Status; 3133 + type Access; 3134 + } 3135 + /// Empty state - all required fields are unset 3136 + pub struct Empty(()); 3137 + impl sealed::Sealed for Empty {} 3138 + impl State for Empty { 3139 + type Status = Unset; 3140 + type Access = Unset; 3141 + } 3142 + ///State transition - sets the `status` field to Set 3143 + pub struct SetStatus<S: State = Empty>(PhantomData<fn() -> S>); 3144 + impl<S: State> sealed::Sealed for SetStatus<S> {} 3145 + impl<S: State> State for SetStatus<S> { 3146 + type Status = Set<members::status>; 3147 + type Access = S::Access; 3148 + } 3149 + ///State transition - sets the `access` field to Set 3150 + pub struct SetAccess<S: State = Empty>(PhantomData<fn() -> S>); 3151 + impl<S: State> sealed::Sealed for SetAccess<S> {} 3152 + impl<S: State> State for SetAccess<S> { 3153 + type Status = S::Status; 3154 + type Access = Set<members::access>; 3155 + } 3156 + /// Marker types for field names 3157 + #[allow(non_camel_case_types)] 3158 + pub mod members { 3159 + ///Marker type for the `status` field 3160 + pub struct status(()); 3161 + ///Marker type for the `access` field 3162 + pub struct access(()); 3163 + } 3164 + } 3165 + 3166 + /// Builder for constructing an instance of this type 3167 + pub struct StateBuilder<'a, S: state_state::State> { 3168 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 3169 + __unsafe_private_named: ( 3170 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 3171 + ::core::option::Option<jacquard_common::types::string::Datetime>, 3172 + ::core::option::Option<crate::app_bsky::ageassurance::Status<'a>>, 3173 + ), 3174 + _phantom: ::core::marker::PhantomData<&'a ()>, 3175 + } 3176 + 3177 + impl<'a> State<'a> { 3178 + /// Create a new builder for this type 3179 + pub fn new() -> StateBuilder<'a, state_state::Empty> { 3180 + StateBuilder::new() 3181 + } 3182 + } 3183 + 3184 + impl<'a> StateBuilder<'a, state_state::Empty> { 3185 + /// Create a new builder with all fields unset 3186 + pub fn new() -> Self { 3187 + StateBuilder { 3188 + _phantom_state: ::core::marker::PhantomData, 3189 + __unsafe_private_named: (None, None, None), 3190 + _phantom: ::core::marker::PhantomData, 3191 + } 3192 + } 3193 + } 3194 + 3195 + impl<'a, S> StateBuilder<'a, S> 3196 + where 3197 + S: state_state::State, 3198 + S::Access: state_state::IsUnset, 3199 + { 3200 + /// Set the `access` field (required) 3201 + pub fn access( 3202 + mut self, 3203 + value: impl Into<crate::app_bsky::ageassurance::Access<'a>>, 3204 + ) -> StateBuilder<'a, state_state::SetAccess<S>> { 3205 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 3206 + StateBuilder { 3207 + _phantom_state: ::core::marker::PhantomData, 3208 + __unsafe_private_named: self.__unsafe_private_named, 3209 + _phantom: ::core::marker::PhantomData, 3210 + } 3211 + } 3212 + } 3213 + 3214 + impl<'a, S: state_state::State> StateBuilder<'a, S> { 3215 + /// Set the `lastInitiatedAt` field (optional) 3216 + pub fn last_initiated_at( 3217 + mut self, 3218 + value: impl Into<Option<jacquard_common::types::string::Datetime>>, 3219 + ) -> Self { 3220 + self.__unsafe_private_named.1 = value.into(); 3221 + self 3222 + } 3223 + /// Set the `lastInitiatedAt` field to an Option value (optional) 3224 + pub fn maybe_last_initiated_at( 3225 + mut self, 3226 + value: Option<jacquard_common::types::string::Datetime>, 3227 + ) -> Self { 3228 + self.__unsafe_private_named.1 = value; 3229 + self 3230 + } 3231 + } 3232 + 3233 + impl<'a, S> StateBuilder<'a, S> 3234 + where 3235 + S: state_state::State, 3236 + S::Status: state_state::IsUnset, 3237 + { 3238 + /// Set the `status` field (required) 3239 + pub fn status( 3240 + mut self, 3241 + value: impl Into<crate::app_bsky::ageassurance::Status<'a>>, 3242 + ) -> StateBuilder<'a, state_state::SetStatus<S>> { 3243 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 3244 + StateBuilder { 3245 + _phantom_state: ::core::marker::PhantomData, 3246 + __unsafe_private_named: self.__unsafe_private_named, 3247 + _phantom: ::core::marker::PhantomData, 3248 + } 3249 + } 3250 + } 3251 + 3252 + impl<'a, S> StateBuilder<'a, S> 3253 + where 3254 + S: state_state::State, 3255 + S::Status: state_state::IsSet, 3256 + S::Access: state_state::IsSet, 3257 + { 3258 + /// Build the final struct 3259 + pub fn build(self) -> State<'a> { 3260 + State { 3261 + access: self.__unsafe_private_named.0.unwrap(), 3262 + last_initiated_at: self.__unsafe_private_named.1, 3263 + status: self.__unsafe_private_named.2.unwrap(), 3264 + extra_data: Default::default(), 3265 + } 3266 + } 3267 + /// Build the final struct with custom extra_data 3268 + pub fn build_with_data( 3269 + self, 3270 + extra_data: std::collections::BTreeMap< 3271 + jacquard_common::smol_str::SmolStr, 3272 + jacquard_common::types::value::Data<'a>, 3273 + >, 3274 + ) -> State<'a> { 3275 + State { 3276 + access: self.__unsafe_private_named.0.unwrap(), 3277 + last_initiated_at: self.__unsafe_private_named.1, 3278 + status: self.__unsafe_private_named.2.unwrap(), 3279 + extra_data: Some(extra_data), 3280 + } 3281 + } 3282 + } 3283 + 3284 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for State<'a> { 3285 + fn nsid() -> &'static str { 3286 + "app.bsky.ageassurance.defs" 3287 + } 3288 + fn def_name() -> &'static str { 3289 + "state" 3290 + } 3291 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 3292 + lexicon_doc_app_bsky_ageassurance_defs() 3293 + } 3294 + fn validate( 3295 + &self, 3296 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 3297 + Ok(()) 3298 + } 3299 + } 3300 + 3301 + /// Additional metadata needed to compute Age Assurance state client-side. 3302 + #[jacquard_derive::lexicon] 3303 + #[derive( 3304 + serde::Serialize, 3305 + serde::Deserialize, 3306 + Debug, 3307 + Clone, 3308 + PartialEq, 3309 + Eq, 3310 + jacquard_derive::IntoStatic, 3311 + Default 3312 + )] 3313 + #[serde(rename_all = "camelCase")] 3314 + pub struct StateMetadata<'a> { 3315 + /// The account creation timestamp. 3316 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 3317 + pub account_created_at: std::option::Option< 3318 + jacquard_common::types::string::Datetime, 3319 + >, 3320 + } 3321 + 3322 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for StateMetadata<'a> { 3323 + fn nsid() -> &'static str { 3324 + "app.bsky.ageassurance.defs" 3325 + } 3326 + fn def_name() -> &'static str { 3327 + "stateMetadata" 3328 + } 3329 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 3330 + lexicon_doc_app_bsky_ageassurance_defs() 3331 + } 3332 + fn validate( 3333 + &self, 3334 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 3335 + Ok(()) 3336 + } 3337 + } 3338 + 3339 + /// The status of the Age Assurance process. 3340 + #[derive(Debug, Clone, PartialEq, Eq, Hash)] 3341 + pub enum Status<'a> { 3342 + Unknown, 3343 + Pending, 3344 + Assured, 3345 + Blocked, 3346 + Other(jacquard_common::CowStr<'a>), 3347 + } 3348 + 3349 + impl<'a> Status<'a> { 3350 + pub fn as_str(&self) -> &str { 3351 + match self { 3352 + Self::Unknown => "unknown", 3353 + Self::Pending => "pending", 3354 + Self::Assured => "assured", 3355 + Self::Blocked => "blocked", 3356 + Self::Other(s) => s.as_ref(), 3357 + } 3358 + } 3359 + } 3360 + 3361 + impl<'a> From<&'a str> for Status<'a> { 3362 + fn from(s: &'a str) -> Self { 3363 + match s { 3364 + "unknown" => Self::Unknown, 3365 + "pending" => Self::Pending, 3366 + "assured" => Self::Assured, 3367 + "blocked" => Self::Blocked, 3368 + _ => Self::Other(jacquard_common::CowStr::from(s)), 3369 + } 3370 + } 3371 + } 3372 + 3373 + impl<'a> From<String> for Status<'a> { 3374 + fn from(s: String) -> Self { 3375 + match s.as_str() { 3376 + "unknown" => Self::Unknown, 3377 + "pending" => Self::Pending, 3378 + "assured" => Self::Assured, 3379 + "blocked" => Self::Blocked, 3380 + _ => Self::Other(jacquard_common::CowStr::from(s)), 3381 + } 3382 + } 3383 + } 3384 + 3385 + impl<'a> AsRef<str> for Status<'a> { 3386 + fn as_ref(&self) -> &str { 3387 + self.as_str() 3388 + } 3389 + } 3390 + 3391 + impl<'a> serde::Serialize for Status<'a> { 3392 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 3393 + where 3394 + S: serde::Serializer, 3395 + { 3396 + serializer.serialize_str(self.as_str()) 3397 + } 3398 + } 3399 + 3400 + impl<'de, 'a> serde::Deserialize<'de> for Status<'a> 3401 + where 3402 + 'de: 'a, 3403 + { 3404 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 3405 + where 3406 + D: serde::Deserializer<'de>, 3407 + { 3408 + let s = <&'de str>::deserialize(deserializer)?; 3409 + Ok(Self::from(s)) 3410 + } 3411 + } 3412 + 3413 + impl jacquard_common::IntoStatic for Status<'_> { 3414 + type Output = Status<'static>; 3415 + fn into_static(self) -> Self::Output { 3416 + match self { 3417 + Status::Unknown => Status::Unknown, 3418 + Status::Pending => Status::Pending, 3419 + Status::Assured => Status::Assured, 3420 + Status::Blocked => Status::Blocked, 3421 + Status::Other(v) => Status::Other(v.into_static()), 3422 + } 3423 + } 3424 + }
+142
crates/weaver-api/src/app_bsky/ageassurance/begin.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.ageassurance.begin 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[jacquard_derive::lexicon] 9 + #[derive( 10 + serde::Serialize, 11 + serde::Deserialize, 12 + Debug, 13 + Clone, 14 + PartialEq, 15 + Eq, 16 + jacquard_derive::IntoStatic, 17 + Default 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct Begin<'a> { 21 + /// An ISO 3166-1 alpha-2 code of the user's location. 22 + #[serde(borrow)] 23 + pub country_code: jacquard_common::CowStr<'a>, 24 + /// The user's email address to receive Age Assurance instructions. 25 + #[serde(borrow)] 26 + pub email: jacquard_common::CowStr<'a>, 27 + /// The user's preferred language for communication during the Age Assurance process. 28 + #[serde(borrow)] 29 + pub language: jacquard_common::CowStr<'a>, 30 + /// An optional ISO 3166-2 code of the user's region or state within the country. 31 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 + #[serde(borrow)] 33 + pub region_code: std::option::Option<jacquard_common::CowStr<'a>>, 34 + } 35 + 36 + #[jacquard_derive::lexicon] 37 + #[derive( 38 + serde::Serialize, 39 + serde::Deserialize, 40 + Debug, 41 + Clone, 42 + PartialEq, 43 + Eq, 44 + jacquard_derive::IntoStatic 45 + )] 46 + #[serde(rename_all = "camelCase")] 47 + pub struct BeginOutput<'a> { 48 + #[serde(flatten)] 49 + #[serde(borrow)] 50 + pub value: crate::app_bsky::ageassurance::State<'a>, 51 + } 52 + 53 + #[jacquard_derive::open_union] 54 + #[derive( 55 + serde::Serialize, 56 + serde::Deserialize, 57 + Debug, 58 + Clone, 59 + PartialEq, 60 + Eq, 61 + thiserror::Error, 62 + miette::Diagnostic, 63 + jacquard_derive::IntoStatic 64 + )] 65 + #[serde(tag = "error", content = "message")] 66 + #[serde(bound(deserialize = "'de: 'a"))] 67 + pub enum BeginError<'a> { 68 + #[serde(rename = "InvalidEmail")] 69 + InvalidEmail(std::option::Option<String>), 70 + #[serde(rename = "DidTooLong")] 71 + DidTooLong(std::option::Option<String>), 72 + #[serde(rename = "InvalidInitiation")] 73 + InvalidInitiation(std::option::Option<String>), 74 + #[serde(rename = "RegionNotSupported")] 75 + RegionNotSupported(std::option::Option<String>), 76 + } 77 + 78 + impl std::fmt::Display for BeginError<'_> { 79 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 80 + match self { 81 + Self::InvalidEmail(msg) => { 82 + write!(f, "InvalidEmail")?; 83 + if let Some(msg) = msg { 84 + write!(f, ": {}", msg)?; 85 + } 86 + Ok(()) 87 + } 88 + Self::DidTooLong(msg) => { 89 + write!(f, "DidTooLong")?; 90 + if let Some(msg) = msg { 91 + write!(f, ": {}", msg)?; 92 + } 93 + Ok(()) 94 + } 95 + Self::InvalidInitiation(msg) => { 96 + write!(f, "InvalidInitiation")?; 97 + if let Some(msg) = msg { 98 + write!(f, ": {}", msg)?; 99 + } 100 + Ok(()) 101 + } 102 + Self::RegionNotSupported(msg) => { 103 + write!(f, "RegionNotSupported")?; 104 + if let Some(msg) = msg { 105 + write!(f, ": {}", msg)?; 106 + } 107 + Ok(()) 108 + } 109 + Self::Unknown(err) => write!(f, "Unknown error: {:?}", err), 110 + } 111 + } 112 + } 113 + 114 + /// Response type for 115 + ///app.bsky.ageassurance.begin 116 + pub struct BeginResponse; 117 + impl jacquard_common::xrpc::XrpcResp for BeginResponse { 118 + const NSID: &'static str = "app.bsky.ageassurance.begin"; 119 + const ENCODING: &'static str = "application/json"; 120 + type Output<'de> = BeginOutput<'de>; 121 + type Err<'de> = BeginError<'de>; 122 + } 123 + 124 + impl<'a> jacquard_common::xrpc::XrpcRequest for Begin<'a> { 125 + const NSID: &'static str = "app.bsky.ageassurance.begin"; 126 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Procedure( 127 + "application/json", 128 + ); 129 + type Response = BeginResponse; 130 + } 131 + 132 + /// Endpoint type for 133 + ///app.bsky.ageassurance.begin 134 + pub struct BeginRequest; 135 + impl jacquard_common::xrpc::XrpcEndpoint for BeginRequest { 136 + const PATH: &'static str = "/xrpc/app.bsky.ageassurance.begin"; 137 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Procedure( 138 + "application/json", 139 + ); 140 + type Request<'de> = Begin<'de>; 141 + type Response = BeginResponse; 142 + }
+61
crates/weaver-api/src/app_bsky/ageassurance/get_config.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.ageassurance.getConfig 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[jacquard_derive::lexicon] 9 + #[derive( 10 + serde::Serialize, 11 + serde::Deserialize, 12 + Debug, 13 + Clone, 14 + PartialEq, 15 + Eq, 16 + jacquard_derive::IntoStatic 17 + )] 18 + #[serde(rename_all = "camelCase")] 19 + pub struct GetConfigOutput<'a> { 20 + #[serde(flatten)] 21 + #[serde(borrow)] 22 + pub value: crate::app_bsky::ageassurance::Config<'a>, 23 + } 24 + 25 + /// XRPC request marker type 26 + #[derive( 27 + Debug, 28 + Clone, 29 + Copy, 30 + PartialEq, 31 + Eq, 32 + serde::Serialize, 33 + serde::Deserialize, 34 + jacquard_derive::IntoStatic 35 + )] 36 + pub struct GetConfig; 37 + /// Response type for 38 + ///app.bsky.ageassurance.getConfig 39 + pub struct GetConfigResponse; 40 + impl jacquard_common::xrpc::XrpcResp for GetConfigResponse { 41 + const NSID: &'static str = "app.bsky.ageassurance.getConfig"; 42 + const ENCODING: &'static str = "application/json"; 43 + type Output<'de> = GetConfigOutput<'de>; 44 + type Err<'de> = jacquard_common::xrpc::GenericError<'de>; 45 + } 46 + 47 + impl jacquard_common::xrpc::XrpcRequest for GetConfig { 48 + const NSID: &'static str = "app.bsky.ageassurance.getConfig"; 49 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 50 + type Response = GetConfigResponse; 51 + } 52 + 53 + /// Endpoint type for 54 + ///app.bsky.ageassurance.getConfig 55 + pub struct GetConfigRequest; 56 + impl jacquard_common::xrpc::XrpcEndpoint for GetConfigRequest { 57 + const PATH: &'static str = "/xrpc/app.bsky.ageassurance.getConfig"; 58 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 59 + type Request<'de> = GetConfig; 60 + type Response = GetConfigResponse; 61 + }
+180
crates/weaver-api/src/app_bsky/ageassurance/get_state.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.ageassurance.getState 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[derive( 9 + serde::Serialize, 10 + serde::Deserialize, 11 + Debug, 12 + Clone, 13 + PartialEq, 14 + Eq, 15 + jacquard_derive::IntoStatic 16 + )] 17 + #[serde(rename_all = "camelCase")] 18 + pub struct GetState<'a> { 19 + #[serde(borrow)] 20 + pub country_code: jacquard_common::CowStr<'a>, 21 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 + #[serde(borrow)] 23 + pub region_code: std::option::Option<jacquard_common::CowStr<'a>>, 24 + } 25 + 26 + pub mod get_state_state { 27 + 28 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 29 + #[allow(unused)] 30 + use ::core::marker::PhantomData; 31 + mod sealed { 32 + pub trait Sealed {} 33 + } 34 + /// State trait tracking which required fields have been set 35 + pub trait State: sealed::Sealed { 36 + type CountryCode; 37 + } 38 + /// Empty state - all required fields are unset 39 + pub struct Empty(()); 40 + impl sealed::Sealed for Empty {} 41 + impl State for Empty { 42 + type CountryCode = Unset; 43 + } 44 + ///State transition - sets the `country_code` field to Set 45 + pub struct SetCountryCode<S: State = Empty>(PhantomData<fn() -> S>); 46 + impl<S: State> sealed::Sealed for SetCountryCode<S> {} 47 + impl<S: State> State for SetCountryCode<S> { 48 + type CountryCode = Set<members::country_code>; 49 + } 50 + /// Marker types for field names 51 + #[allow(non_camel_case_types)] 52 + pub mod members { 53 + ///Marker type for the `country_code` field 54 + pub struct country_code(()); 55 + } 56 + } 57 + 58 + /// Builder for constructing an instance of this type 59 + pub struct GetStateBuilder<'a, S: get_state_state::State> { 60 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 61 + __unsafe_private_named: ( 62 + ::core::option::Option<jacquard_common::CowStr<'a>>, 63 + ::core::option::Option<jacquard_common::CowStr<'a>>, 64 + ), 65 + _phantom: ::core::marker::PhantomData<&'a ()>, 66 + } 67 + 68 + impl<'a> GetState<'a> { 69 + /// Create a new builder for this type 70 + pub fn new() -> GetStateBuilder<'a, get_state_state::Empty> { 71 + GetStateBuilder::new() 72 + } 73 + } 74 + 75 + impl<'a> GetStateBuilder<'a, get_state_state::Empty> { 76 + /// Create a new builder with all fields unset 77 + pub fn new() -> Self { 78 + GetStateBuilder { 79 + _phantom_state: ::core::marker::PhantomData, 80 + __unsafe_private_named: (None, None), 81 + _phantom: ::core::marker::PhantomData, 82 + } 83 + } 84 + } 85 + 86 + impl<'a, S> GetStateBuilder<'a, S> 87 + where 88 + S: get_state_state::State, 89 + S::CountryCode: get_state_state::IsUnset, 90 + { 91 + /// Set the `countryCode` field (required) 92 + pub fn country_code( 93 + mut self, 94 + value: impl Into<jacquard_common::CowStr<'a>>, 95 + ) -> GetStateBuilder<'a, get_state_state::SetCountryCode<S>> { 96 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 97 + GetStateBuilder { 98 + _phantom_state: ::core::marker::PhantomData, 99 + __unsafe_private_named: self.__unsafe_private_named, 100 + _phantom: ::core::marker::PhantomData, 101 + } 102 + } 103 + } 104 + 105 + impl<'a, S: get_state_state::State> GetStateBuilder<'a, S> { 106 + /// Set the `regionCode` field (optional) 107 + pub fn region_code( 108 + mut self, 109 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 110 + ) -> Self { 111 + self.__unsafe_private_named.1 = value.into(); 112 + self 113 + } 114 + /// Set the `regionCode` field to an Option value (optional) 115 + pub fn maybe_region_code( 116 + mut self, 117 + value: Option<jacquard_common::CowStr<'a>>, 118 + ) -> Self { 119 + self.__unsafe_private_named.1 = value; 120 + self 121 + } 122 + } 123 + 124 + impl<'a, S> GetStateBuilder<'a, S> 125 + where 126 + S: get_state_state::State, 127 + S::CountryCode: get_state_state::IsSet, 128 + { 129 + /// Build the final struct 130 + pub fn build(self) -> GetState<'a> { 131 + GetState { 132 + country_code: self.__unsafe_private_named.0.unwrap(), 133 + region_code: self.__unsafe_private_named.1, 134 + } 135 + } 136 + } 137 + 138 + #[jacquard_derive::lexicon] 139 + #[derive( 140 + serde::Serialize, 141 + serde::Deserialize, 142 + Debug, 143 + Clone, 144 + PartialEq, 145 + Eq, 146 + jacquard_derive::IntoStatic 147 + )] 148 + #[serde(rename_all = "camelCase")] 149 + pub struct GetStateOutput<'a> { 150 + #[serde(borrow)] 151 + pub metadata: crate::app_bsky::ageassurance::StateMetadata<'a>, 152 + #[serde(borrow)] 153 + pub state: crate::app_bsky::ageassurance::State<'a>, 154 + } 155 + 156 + /// Response type for 157 + ///app.bsky.ageassurance.getState 158 + pub struct GetStateResponse; 159 + impl jacquard_common::xrpc::XrpcResp for GetStateResponse { 160 + const NSID: &'static str = "app.bsky.ageassurance.getState"; 161 + const ENCODING: &'static str = "application/json"; 162 + type Output<'de> = GetStateOutput<'de>; 163 + type Err<'de> = jacquard_common::xrpc::GenericError<'de>; 164 + } 165 + 166 + impl<'a> jacquard_common::xrpc::XrpcRequest for GetState<'a> { 167 + const NSID: &'static str = "app.bsky.ageassurance.getState"; 168 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 169 + type Response = GetStateResponse; 170 + } 171 + 172 + /// Endpoint type for 173 + ///app.bsky.ageassurance.getState 174 + pub struct GetStateRequest; 175 + impl jacquard_common::xrpc::XrpcEndpoint for GetStateRequest { 176 + const PATH: &'static str = "/xrpc/app.bsky.ageassurance.getState"; 177 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 178 + type Request<'de> = GetState<'de>; 179 + type Response = GetStateResponse; 180 + }
+103 -21
crates/weaver-api/src/sh_weaver/edit.rs
··· 145 145 NotebookRef(Box<crate::sh_weaver::edit::NotebookRef<'a>>), 146 146 #[serde(rename = "sh.weaver.edit.defs#entryRef")] 147 147 EntryRef(Box<crate::sh_weaver::edit::EntryRef<'a>>), 148 + #[serde(rename = "sh.weaver.edit.defs#draftRef")] 149 + DraftRef(Box<crate::sh_weaver::edit::DraftRef<'a>>), 148 150 } 149 151 150 152 fn lexicon_doc_sh_weaver_edit_defs() -> ::jacquard_lexicon::lexicon::LexiconDoc< ··· 174 176 description: None, 175 177 refs: vec![ 176 178 ::jacquard_common::CowStr::new_static("#notebookRef"), 177 - ::jacquard_common::CowStr::new_static("#entryRef") 179 + ::jacquard_common::CowStr::new_static("#entryRef"), 180 + ::jacquard_common::CowStr::new_static("#draftRef") 178 181 ], 179 182 closed: None, 180 183 }), ··· 184 187 }), 185 188 ); 186 189 map.insert( 187 - ::jacquard_common::smol_str::SmolStr::new_static("entryRef"), 190 + ::jacquard_common::smol_str::SmolStr::new_static("draftRef"), 188 191 ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 189 192 description: None, 190 193 required: Some( 191 194 vec![ 192 - ::jacquard_common::smol_str::SmolStr::new_static("notebook") 195 + ::jacquard_common::smol_str::SmolStr::new_static("draft_key") 193 196 ], 194 197 ), 195 198 nullable: None, ··· 197 200 #[allow(unused_mut)] 198 201 let mut map = ::std::collections::BTreeMap::new(); 199 202 map.insert( 200 - ::jacquard_common::smol_str::SmolStr::new_static("notebook"), 203 + ::jacquard_common::smol_str::SmolStr::new_static( 204 + "draft_key", 205 + ), 206 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 207 + description: None, 208 + format: None, 209 + default: None, 210 + min_length: None, 211 + max_length: Some(200usize), 212 + min_graphemes: None, 213 + max_graphemes: None, 214 + r#enum: None, 215 + r#const: None, 216 + known_values: None, 217 + }), 218 + ); 219 + map 220 + }, 221 + }), 222 + ); 223 + map.insert( 224 + ::jacquard_common::smol_str::SmolStr::new_static("entryRef"), 225 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 226 + description: None, 227 + required: Some( 228 + vec![::jacquard_common::smol_str::SmolStr::new_static("entry")], 229 + ), 230 + nullable: None, 231 + properties: { 232 + #[allow(unused_mut)] 233 + let mut map = ::std::collections::BTreeMap::new(); 234 + map.insert( 235 + ::jacquard_common::smol_str::SmolStr::new_static("entry"), 201 236 ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 202 237 description: None, 203 238 r#ref: ::jacquard_common::CowStr::new_static( ··· 265 300 Clone, 266 301 PartialEq, 267 302 Eq, 303 + jacquard_derive::IntoStatic, 304 + Default 305 + )] 306 + #[serde(rename_all = "camelCase")] 307 + pub struct DraftRef<'a> { 308 + #[serde(borrow)] 309 + pub draft_key: jacquard_common::CowStr<'a>, 310 + } 311 + 312 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for DraftRef<'a> { 313 + fn nsid() -> &'static str { 314 + "sh.weaver.edit.defs" 315 + } 316 + fn def_name() -> &'static str { 317 + "draftRef" 318 + } 319 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 320 + lexicon_doc_sh_weaver_edit_defs() 321 + } 322 + fn validate( 323 + &self, 324 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 325 + { 326 + let value = &self.draft_key; 327 + #[allow(unused_comparisons)] 328 + if <str>::len(value.as_ref()) > 200usize { 329 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 330 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 331 + "draft_key", 332 + ), 333 + max: 200usize, 334 + actual: <str>::len(value.as_ref()), 335 + }); 336 + } 337 + } 338 + Ok(()) 339 + } 340 + } 341 + 342 + #[jacquard_derive::lexicon] 343 + #[derive( 344 + serde::Serialize, 345 + serde::Deserialize, 346 + Debug, 347 + Clone, 348 + PartialEq, 349 + Eq, 268 350 jacquard_derive::IntoStatic 269 351 )] 270 352 #[serde(rename_all = "camelCase")] 271 353 pub struct EntryRef<'a> { 272 354 #[serde(borrow)] 273 - pub notebook: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 355 + pub entry: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 274 356 } 275 357 276 358 pub mod entry_ref_state { ··· 283 365 } 284 366 /// State trait tracking which required fields have been set 285 367 pub trait State: sealed::Sealed { 286 - type Notebook; 368 + type Entry; 287 369 } 288 370 /// Empty state - all required fields are unset 289 371 pub struct Empty(()); 290 372 impl sealed::Sealed for Empty {} 291 373 impl State for Empty { 292 - type Notebook = Unset; 374 + type Entry = Unset; 293 375 } 294 - ///State transition - sets the `notebook` field to Set 295 - pub struct SetNotebook<S: State = Empty>(PhantomData<fn() -> S>); 296 - impl<S: State> sealed::Sealed for SetNotebook<S> {} 297 - impl<S: State> State for SetNotebook<S> { 298 - type Notebook = Set<members::notebook>; 376 + ///State transition - sets the `entry` field to Set 377 + pub struct SetEntry<S: State = Empty>(PhantomData<fn() -> S>); 378 + impl<S: State> sealed::Sealed for SetEntry<S> {} 379 + impl<S: State> State for SetEntry<S> { 380 + type Entry = Set<members::entry>; 299 381 } 300 382 /// Marker types for field names 301 383 #[allow(non_camel_case_types)] 302 384 pub mod members { 303 - ///Marker type for the `notebook` field 304 - pub struct notebook(()); 385 + ///Marker type for the `entry` field 386 + pub struct entry(()); 305 387 } 306 388 } 307 389 ··· 335 417 impl<'a, S> EntryRefBuilder<'a, S> 336 418 where 337 419 S: entry_ref_state::State, 338 - S::Notebook: entry_ref_state::IsUnset, 420 + S::Entry: entry_ref_state::IsUnset, 339 421 { 340 - /// Set the `notebook` field (required) 341 - pub fn notebook( 422 + /// Set the `entry` field (required) 423 + pub fn entry( 342 424 mut self, 343 425 value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 344 - ) -> EntryRefBuilder<'a, entry_ref_state::SetNotebook<S>> { 426 + ) -> EntryRefBuilder<'a, entry_ref_state::SetEntry<S>> { 345 427 self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 346 428 EntryRefBuilder { 347 429 _phantom_state: ::core::marker::PhantomData, ··· 354 436 impl<'a, S> EntryRefBuilder<'a, S> 355 437 where 356 438 S: entry_ref_state::State, 357 - S::Notebook: entry_ref_state::IsSet, 439 + S::Entry: entry_ref_state::IsSet, 358 440 { 359 441 /// Build the final struct 360 442 pub fn build(self) -> EntryRef<'a> { 361 443 EntryRef { 362 - notebook: self.__unsafe_private_named.0.unwrap(), 444 + entry: self.__unsafe_private_named.0.unwrap(), 363 445 extra_data: Default::default(), 364 446 } 365 447 } ··· 372 454 >, 373 455 ) -> EntryRef<'a> { 374 456 EntryRef { 375 - notebook: self.__unsafe_private_named.0.unwrap(), 457 + entry: self.__unsafe_private_named.0.unwrap(), 376 458 extra_data: Some(extra_data), 377 459 } 378 460 }
+41 -7
crates/weaver-api/src/sh_weaver/edit/diff.rs
··· 20 20 pub struct Diff<'a> { 21 21 #[serde(borrow)] 22 22 pub doc: crate::sh_weaver::edit::DocRef<'a>, 23 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 + #[serde(borrow)] 25 + pub prev: std::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 23 26 #[serde(borrow)] 24 27 pub root: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 25 28 #[serde(borrow)] ··· 90 93 __unsafe_private_named: ( 91 94 ::core::option::Option<crate::sh_weaver::edit::DocRef<'a>>, 92 95 ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 96 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 93 97 ::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 94 98 ), 95 99 _phantom: ::core::marker::PhantomData<&'a ()>, ··· 107 111 pub fn new() -> Self { 108 112 DiffBuilder { 109 113 _phantom_state: ::core::marker::PhantomData, 110 - __unsafe_private_named: (None, None, None), 114 + __unsafe_private_named: (None, None, None, None), 111 115 _phantom: ::core::marker::PhantomData, 112 116 } 113 117 } ··· 132 136 } 133 137 } 134 138 139 + impl<'a, S: diff_state::State> DiffBuilder<'a, S> { 140 + /// Set the `prev` field (optional) 141 + pub fn prev( 142 + mut self, 143 + value: impl Into<Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>>, 144 + ) -> Self { 145 + self.__unsafe_private_named.1 = value.into(); 146 + self 147 + } 148 + /// Set the `prev` field to an Option value (optional) 149 + pub fn maybe_prev( 150 + mut self, 151 + value: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 152 + ) -> Self { 153 + self.__unsafe_private_named.1 = value; 154 + self 155 + } 156 + } 157 + 135 158 impl<'a, S> DiffBuilder<'a, S> 136 159 where 137 160 S: diff_state::State, ··· 142 165 mut self, 143 166 value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 144 167 ) -> DiffBuilder<'a, diff_state::SetRoot<S>> { 145 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 168 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 146 169 DiffBuilder { 147 170 _phantom_state: ::core::marker::PhantomData, 148 171 __unsafe_private_named: self.__unsafe_private_named, ··· 161 184 mut self, 162 185 value: impl Into<jacquard_common::types::blob::BlobRef<'a>>, 163 186 ) -> DiffBuilder<'a, diff_state::SetSnapshot<S>> { 164 - self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 187 + self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 165 188 DiffBuilder { 166 189 _phantom_state: ::core::marker::PhantomData, 167 190 __unsafe_private_named: self.__unsafe_private_named, ··· 181 204 pub fn build(self) -> Diff<'a> { 182 205 Diff { 183 206 doc: self.__unsafe_private_named.0.unwrap(), 184 - root: self.__unsafe_private_named.1.unwrap(), 185 - snapshot: self.__unsafe_private_named.2.unwrap(), 207 + prev: self.__unsafe_private_named.1, 208 + root: self.__unsafe_private_named.2.unwrap(), 209 + snapshot: self.__unsafe_private_named.3.unwrap(), 186 210 extra_data: Default::default(), 187 211 } 188 212 } ··· 196 220 ) -> Diff<'a> { 197 221 Diff { 198 222 doc: self.__unsafe_private_named.0.unwrap(), 199 - root: self.__unsafe_private_named.1.unwrap(), 200 - snapshot: self.__unsafe_private_named.2.unwrap(), 223 + prev: self.__unsafe_private_named.1, 224 + root: self.__unsafe_private_named.2.unwrap(), 225 + snapshot: self.__unsafe_private_named.3.unwrap(), 201 226 extra_data: Some(extra_data), 202 227 } 203 228 } ··· 319 344 description: None, 320 345 r#ref: ::jacquard_common::CowStr::new_static( 321 346 "sh.weaver.edit.defs#docRef", 347 + ), 348 + }), 349 + ); 350 + map.insert( 351 + ::jacquard_common::smol_str::SmolStr::new_static("prev"), 352 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 353 + description: None, 354 + r#ref: ::jacquard_common::CowStr::new_static( 355 + "com.atproto.repo.strongRef", 322 356 ), 323 357 }), 324 358 );
+231 -48
crates/weaver-api/src/tools_ozone/moderation.rs
··· 628 628 #[allow(unused_mut)] 629 629 let mut map = ::std::collections::BTreeMap::new(); 630 630 map.insert( 631 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 632 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 633 + description: None, 634 + r#ref: ::jacquard_common::CowStr::new_static( 635 + "app.bsky.ageassurance.defs#access", 636 + ), 637 + }), 638 + ); 639 + map.insert( 631 640 ::jacquard_common::smol_str::SmolStr::new_static( 632 641 "attemptId", 633 642 ), ··· 692 701 ); 693 702 map.insert( 694 703 ::jacquard_common::smol_str::SmolStr::new_static( 704 + "countryCode", 705 + ), 706 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 707 + description: Some( 708 + ::jacquard_common::CowStr::new_static( 709 + "The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow.", 710 + ), 711 + ), 712 + format: None, 713 + default: None, 714 + min_length: None, 715 + max_length: None, 716 + min_graphemes: None, 717 + max_graphemes: None, 718 + r#enum: None, 719 + r#const: None, 720 + known_values: None, 721 + }), 722 + ); 723 + map.insert( 724 + ::jacquard_common::smol_str::SmolStr::new_static( 695 725 "createdAt", 696 726 ), 697 727 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { ··· 752 782 }), 753 783 ); 754 784 map.insert( 785 + ::jacquard_common::smol_str::SmolStr::new_static( 786 + "regionCode", 787 + ), 788 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 789 + description: Some( 790 + ::jacquard_common::CowStr::new_static( 791 + "The ISO 3166-2 region code provided when beginning the Age Assurance flow.", 792 + ), 793 + ), 794 + format: None, 795 + default: None, 796 + min_length: None, 797 + max_length: None, 798 + min_graphemes: None, 799 + max_graphemes: None, 800 + r#enum: None, 801 + r#const: None, 802 + known_values: None, 803 + }), 804 + ); 805 + map.insert( 755 806 ::jacquard_common::smol_str::SmolStr::new_static("status"), 756 807 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 757 808 description: Some( 758 809 ::jacquard_common::CowStr::new_static( 759 - "The status of the age assurance process.", 810 + "The status of the Age Assurance process.", 760 811 ), 761 812 ), 762 813 format: None, ··· 794 845 properties: { 795 846 #[allow(unused_mut)] 796 847 let mut map = ::std::collections::BTreeMap::new(); 848 + map.insert( 849 + ::jacquard_common::smol_str::SmolStr::new_static("access"), 850 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 851 + description: None, 852 + r#ref: ::jacquard_common::CowStr::new_static( 853 + "app.bsky.ageassurance.defs#access", 854 + ), 855 + }), 856 + ); 797 857 map.insert( 798 858 ::jacquard_common::smol_str::SmolStr::new_static("comment"), 799 859 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { ··· 4990 5050 )] 4991 5051 #[serde(rename_all = "camelCase")] 4992 5052 pub struct AgeAssuranceEvent<'a> { 5053 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 5054 + #[serde(borrow)] 5055 + pub access: std::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 4993 5056 /// The unique identifier for this instance of the age assurance flow, in UUID format. 4994 5057 #[serde(borrow)] 4995 5058 pub attempt_id: jacquard_common::CowStr<'a>, ··· 5001 5064 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5002 5065 #[serde(borrow)] 5003 5066 pub complete_ua: std::option::Option<jacquard_common::CowStr<'a>>, 5067 + /// The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow. 5068 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 5069 + #[serde(borrow)] 5070 + pub country_code: std::option::Option<jacquard_common::CowStr<'a>>, 5004 5071 /// The date and time of this write operation. 5005 5072 pub created_at: jacquard_common::types::string::Datetime, 5006 5073 /// The IP address used when initiating the AA flow. ··· 5011 5078 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5012 5079 #[serde(borrow)] 5013 5080 pub init_ua: std::option::Option<jacquard_common::CowStr<'a>>, 5014 - /// The status of the age assurance process. 5081 + /// The ISO 3166-2 region code provided when beginning the Age Assurance flow. 5082 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 5083 + #[serde(borrow)] 5084 + pub region_code: std::option::Option<jacquard_common::CowStr<'a>>, 5085 + /// The status of the Age Assurance process. 5015 5086 #[serde(borrow)] 5016 5087 pub status: jacquard_common::CowStr<'a>, 5017 5088 } ··· 5078 5149 pub struct AgeAssuranceEventBuilder<'a, S: age_assurance_event_state::State> { 5079 5150 _phantom_state: ::core::marker::PhantomData<fn() -> S>, 5080 5151 __unsafe_private_named: ( 5152 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 5153 + ::core::option::Option<jacquard_common::CowStr<'a>>, 5081 5154 ::core::option::Option<jacquard_common::CowStr<'a>>, 5082 5155 ::core::option::Option<jacquard_common::CowStr<'a>>, 5083 5156 ::core::option::Option<jacquard_common::CowStr<'a>>, 5084 5157 ::core::option::Option<jacquard_common::types::string::Datetime>, 5158 + ::core::option::Option<jacquard_common::CowStr<'a>>, 5085 5159 ::core::option::Option<jacquard_common::CowStr<'a>>, 5086 5160 ::core::option::Option<jacquard_common::CowStr<'a>>, 5087 5161 ::core::option::Option<jacquard_common::CowStr<'a>>, ··· 5101 5175 pub fn new() -> Self { 5102 5176 AgeAssuranceEventBuilder { 5103 5177 _phantom_state: ::core::marker::PhantomData, 5104 - __unsafe_private_named: (None, None, None, None, None, None, None), 5178 + __unsafe_private_named: ( 5179 + None, 5180 + None, 5181 + None, 5182 + None, 5183 + None, 5184 + None, 5185 + None, 5186 + None, 5187 + None, 5188 + None, 5189 + ), 5105 5190 _phantom: ::core::marker::PhantomData, 5106 5191 } 5107 5192 } 5108 5193 } 5109 5194 5195 + impl<'a, S: age_assurance_event_state::State> AgeAssuranceEventBuilder<'a, S> { 5196 + /// Set the `access` field (optional) 5197 + pub fn access( 5198 + mut self, 5199 + value: impl Into<Option<crate::app_bsky::ageassurance::Access<'a>>>, 5200 + ) -> Self { 5201 + self.__unsafe_private_named.0 = value.into(); 5202 + self 5203 + } 5204 + /// Set the `access` field to an Option value (optional) 5205 + pub fn maybe_access( 5206 + mut self, 5207 + value: Option<crate::app_bsky::ageassurance::Access<'a>>, 5208 + ) -> Self { 5209 + self.__unsafe_private_named.0 = value; 5210 + self 5211 + } 5212 + } 5213 + 5110 5214 impl<'a, S> AgeAssuranceEventBuilder<'a, S> 5111 5215 where 5112 5216 S: age_assurance_event_state::State, ··· 5117 5221 mut self, 5118 5222 value: impl Into<jacquard_common::CowStr<'a>>, 5119 5223 ) -> AgeAssuranceEventBuilder<'a, age_assurance_event_state::SetAttemptId<S>> { 5120 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 5224 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 5121 5225 AgeAssuranceEventBuilder { 5122 5226 _phantom_state: ::core::marker::PhantomData, 5123 5227 __unsafe_private_named: self.__unsafe_private_named, ··· 5132 5236 mut self, 5133 5237 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5134 5238 ) -> Self { 5135 - self.__unsafe_private_named.1 = value.into(); 5239 + self.__unsafe_private_named.2 = value.into(); 5136 5240 self 5137 5241 } 5138 5242 /// Set the `completeIp` field to an Option value (optional) ··· 5140 5244 mut self, 5141 5245 value: Option<jacquard_common::CowStr<'a>>, 5142 5246 ) -> Self { 5143 - self.__unsafe_private_named.1 = value; 5247 + self.__unsafe_private_named.2 = value; 5144 5248 self 5145 5249 } 5146 5250 } ··· 5151 5255 mut self, 5152 5256 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5153 5257 ) -> Self { 5154 - self.__unsafe_private_named.2 = value.into(); 5258 + self.__unsafe_private_named.3 = value.into(); 5155 5259 self 5156 5260 } 5157 5261 /// Set the `completeUa` field to an Option value (optional) ··· 5159 5263 mut self, 5160 5264 value: Option<jacquard_common::CowStr<'a>>, 5161 5265 ) -> Self { 5162 - self.__unsafe_private_named.2 = value; 5266 + self.__unsafe_private_named.3 = value; 5267 + self 5268 + } 5269 + } 5270 + 5271 + impl<'a, S: age_assurance_event_state::State> AgeAssuranceEventBuilder<'a, S> { 5272 + /// Set the `countryCode` field (optional) 5273 + pub fn country_code( 5274 + mut self, 5275 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5276 + ) -> Self { 5277 + self.__unsafe_private_named.4 = value.into(); 5278 + self 5279 + } 5280 + /// Set the `countryCode` field to an Option value (optional) 5281 + pub fn maybe_country_code( 5282 + mut self, 5283 + value: Option<jacquard_common::CowStr<'a>>, 5284 + ) -> Self { 5285 + self.__unsafe_private_named.4 = value; 5163 5286 self 5164 5287 } 5165 5288 } ··· 5174 5297 mut self, 5175 5298 value: impl Into<jacquard_common::types::string::Datetime>, 5176 5299 ) -> AgeAssuranceEventBuilder<'a, age_assurance_event_state::SetCreatedAt<S>> { 5177 - self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 5300 + self.__unsafe_private_named.5 = ::core::option::Option::Some(value.into()); 5178 5301 AgeAssuranceEventBuilder { 5179 5302 _phantom_state: ::core::marker::PhantomData, 5180 5303 __unsafe_private_named: self.__unsafe_private_named, ··· 5189 5312 mut self, 5190 5313 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5191 5314 ) -> Self { 5192 - self.__unsafe_private_named.4 = value.into(); 5315 + self.__unsafe_private_named.6 = value.into(); 5193 5316 self 5194 5317 } 5195 5318 /// Set the `initIp` field to an Option value (optional) 5196 5319 pub fn maybe_init_ip(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 5197 - self.__unsafe_private_named.4 = value; 5320 + self.__unsafe_private_named.6 = value; 5198 5321 self 5199 5322 } 5200 5323 } ··· 5205 5328 mut self, 5206 5329 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5207 5330 ) -> Self { 5208 - self.__unsafe_private_named.5 = value.into(); 5331 + self.__unsafe_private_named.7 = value.into(); 5209 5332 self 5210 5333 } 5211 5334 /// Set the `initUa` field to an Option value (optional) 5212 5335 pub fn maybe_init_ua(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 5213 - self.__unsafe_private_named.5 = value; 5336 + self.__unsafe_private_named.7 = value; 5337 + self 5338 + } 5339 + } 5340 + 5341 + impl<'a, S: age_assurance_event_state::State> AgeAssuranceEventBuilder<'a, S> { 5342 + /// Set the `regionCode` field (optional) 5343 + pub fn region_code( 5344 + mut self, 5345 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5346 + ) -> Self { 5347 + self.__unsafe_private_named.8 = value.into(); 5348 + self 5349 + } 5350 + /// Set the `regionCode` field to an Option value (optional) 5351 + pub fn maybe_region_code( 5352 + mut self, 5353 + value: Option<jacquard_common::CowStr<'a>>, 5354 + ) -> Self { 5355 + self.__unsafe_private_named.8 = value; 5214 5356 self 5215 5357 } 5216 5358 } ··· 5225 5367 mut self, 5226 5368 value: impl Into<jacquard_common::CowStr<'a>>, 5227 5369 ) -> AgeAssuranceEventBuilder<'a, age_assurance_event_state::SetStatus<S>> { 5228 - self.__unsafe_private_named.6 = ::core::option::Option::Some(value.into()); 5370 + self.__unsafe_private_named.9 = ::core::option::Option::Some(value.into()); 5229 5371 AgeAssuranceEventBuilder { 5230 5372 _phantom_state: ::core::marker::PhantomData, 5231 5373 __unsafe_private_named: self.__unsafe_private_named, ··· 5244 5386 /// Build the final struct 5245 5387 pub fn build(self) -> AgeAssuranceEvent<'a> { 5246 5388 AgeAssuranceEvent { 5247 - attempt_id: self.__unsafe_private_named.0.unwrap(), 5248 - complete_ip: self.__unsafe_private_named.1, 5249 - complete_ua: self.__unsafe_private_named.2, 5250 - created_at: self.__unsafe_private_named.3.unwrap(), 5251 - init_ip: self.__unsafe_private_named.4, 5252 - init_ua: self.__unsafe_private_named.5, 5253 - status: self.__unsafe_private_named.6.unwrap(), 5389 + access: self.__unsafe_private_named.0, 5390 + attempt_id: self.__unsafe_private_named.1.unwrap(), 5391 + complete_ip: self.__unsafe_private_named.2, 5392 + complete_ua: self.__unsafe_private_named.3, 5393 + country_code: self.__unsafe_private_named.4, 5394 + created_at: self.__unsafe_private_named.5.unwrap(), 5395 + init_ip: self.__unsafe_private_named.6, 5396 + init_ua: self.__unsafe_private_named.7, 5397 + region_code: self.__unsafe_private_named.8, 5398 + status: self.__unsafe_private_named.9.unwrap(), 5254 5399 extra_data: Default::default(), 5255 5400 } 5256 5401 } ··· 5263 5408 >, 5264 5409 ) -> AgeAssuranceEvent<'a> { 5265 5410 AgeAssuranceEvent { 5266 - attempt_id: self.__unsafe_private_named.0.unwrap(), 5267 - complete_ip: self.__unsafe_private_named.1, 5268 - complete_ua: self.__unsafe_private_named.2, 5269 - created_at: self.__unsafe_private_named.3.unwrap(), 5270 - init_ip: self.__unsafe_private_named.4, 5271 - init_ua: self.__unsafe_private_named.5, 5272 - status: self.__unsafe_private_named.6.unwrap(), 5411 + access: self.__unsafe_private_named.0, 5412 + attempt_id: self.__unsafe_private_named.1.unwrap(), 5413 + complete_ip: self.__unsafe_private_named.2, 5414 + complete_ua: self.__unsafe_private_named.3, 5415 + country_code: self.__unsafe_private_named.4, 5416 + created_at: self.__unsafe_private_named.5.unwrap(), 5417 + init_ip: self.__unsafe_private_named.6, 5418 + init_ua: self.__unsafe_private_named.7, 5419 + region_code: self.__unsafe_private_named.8, 5420 + status: self.__unsafe_private_named.9.unwrap(), 5273 5421 extra_data: Some(extra_data), 5274 5422 } 5275 5423 } ··· 5306 5454 )] 5307 5455 #[serde(rename_all = "camelCase")] 5308 5456 pub struct AgeAssuranceOverrideEvent<'a> { 5457 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 5458 + #[serde(borrow)] 5459 + pub access: std::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 5309 5460 /// Comment describing the reason for the override. 5310 5461 #[serde(borrow)] 5311 5462 pub comment: jacquard_common::CowStr<'a>, ··· 12247 12398 12248 12399 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 12249 12400 pub enum SubjectReviewState<'a> { 12250 - ReviewOpen, 12251 - ReviewEscalated, 12252 - ReviewClosed, 12253 - ReviewNone, 12401 + ToolsOzoneModerationDefsReviewOpen, 12402 + ToolsOzoneModerationDefsReviewEscalated, 12403 + ToolsOzoneModerationDefsReviewClosed, 12404 + ToolsOzoneModerationDefsReviewNone, 12254 12405 Other(jacquard_common::CowStr<'a>), 12255 12406 } 12256 12407 12257 12408 impl<'a> SubjectReviewState<'a> { 12258 12409 pub fn as_str(&self) -> &str { 12259 12410 match self { 12260 - Self::ReviewOpen => "#reviewOpen", 12261 - Self::ReviewEscalated => "#reviewEscalated", 12262 - Self::ReviewClosed => "#reviewClosed", 12263 - Self::ReviewNone => "#reviewNone", 12411 + Self::ToolsOzoneModerationDefsReviewOpen => { 12412 + "tools.ozone.moderation.defs#reviewOpen" 12413 + } 12414 + Self::ToolsOzoneModerationDefsReviewEscalated => { 12415 + "tools.ozone.moderation.defs#reviewEscalated" 12416 + } 12417 + Self::ToolsOzoneModerationDefsReviewClosed => { 12418 + "tools.ozone.moderation.defs#reviewClosed" 12419 + } 12420 + Self::ToolsOzoneModerationDefsReviewNone => { 12421 + "tools.ozone.moderation.defs#reviewNone" 12422 + } 12264 12423 Self::Other(s) => s.as_ref(), 12265 12424 } 12266 12425 } ··· 12269 12428 impl<'a> From<&'a str> for SubjectReviewState<'a> { 12270 12429 fn from(s: &'a str) -> Self { 12271 12430 match s { 12272 - "#reviewOpen" => Self::ReviewOpen, 12273 - "#reviewEscalated" => Self::ReviewEscalated, 12274 - "#reviewClosed" => Self::ReviewClosed, 12275 - "#reviewNone" => Self::ReviewNone, 12431 + "tools.ozone.moderation.defs#reviewOpen" => { 12432 + Self::ToolsOzoneModerationDefsReviewOpen 12433 + } 12434 + "tools.ozone.moderation.defs#reviewEscalated" => { 12435 + Self::ToolsOzoneModerationDefsReviewEscalated 12436 + } 12437 + "tools.ozone.moderation.defs#reviewClosed" => { 12438 + Self::ToolsOzoneModerationDefsReviewClosed 12439 + } 12440 + "tools.ozone.moderation.defs#reviewNone" => { 12441 + Self::ToolsOzoneModerationDefsReviewNone 12442 + } 12276 12443 _ => Self::Other(jacquard_common::CowStr::from(s)), 12277 12444 } 12278 12445 } ··· 12281 12448 impl<'a> From<String> for SubjectReviewState<'a> { 12282 12449 fn from(s: String) -> Self { 12283 12450 match s.as_str() { 12284 - "#reviewOpen" => Self::ReviewOpen, 12285 - "#reviewEscalated" => Self::ReviewEscalated, 12286 - "#reviewClosed" => Self::ReviewClosed, 12287 - "#reviewNone" => Self::ReviewNone, 12451 + "tools.ozone.moderation.defs#reviewOpen" => { 12452 + Self::ToolsOzoneModerationDefsReviewOpen 12453 + } 12454 + "tools.ozone.moderation.defs#reviewEscalated" => { 12455 + Self::ToolsOzoneModerationDefsReviewEscalated 12456 + } 12457 + "tools.ozone.moderation.defs#reviewClosed" => { 12458 + Self::ToolsOzoneModerationDefsReviewClosed 12459 + } 12460 + "tools.ozone.moderation.defs#reviewNone" => { 12461 + Self::ToolsOzoneModerationDefsReviewNone 12462 + } 12288 12463 _ => Self::Other(jacquard_common::CowStr::from(s)), 12289 12464 } 12290 12465 } ··· 12322 12497 type Output = SubjectReviewState<'static>; 12323 12498 fn into_static(self) -> Self::Output { 12324 12499 match self { 12325 - SubjectReviewState::ReviewOpen => SubjectReviewState::ReviewOpen, 12326 - SubjectReviewState::ReviewEscalated => SubjectReviewState::ReviewEscalated, 12327 - SubjectReviewState::ReviewClosed => SubjectReviewState::ReviewClosed, 12328 - SubjectReviewState::ReviewNone => SubjectReviewState::ReviewNone, 12500 + SubjectReviewState::ToolsOzoneModerationDefsReviewOpen => { 12501 + SubjectReviewState::ToolsOzoneModerationDefsReviewOpen 12502 + } 12503 + SubjectReviewState::ToolsOzoneModerationDefsReviewEscalated => { 12504 + SubjectReviewState::ToolsOzoneModerationDefsReviewEscalated 12505 + } 12506 + SubjectReviewState::ToolsOzoneModerationDefsReviewClosed => { 12507 + SubjectReviewState::ToolsOzoneModerationDefsReviewClosed 12508 + } 12509 + SubjectReviewState::ToolsOzoneModerationDefsReviewNone => { 12510 + SubjectReviewState::ToolsOzoneModerationDefsReviewNone 12511 + } 12329 12512 SubjectReviewState::Other(v) => SubjectReviewState::Other(v.into_static()), 12330 12513 } 12331 12514 }
+1
crates/weaver-app/Cargo.toml
··· 47 47 # diesel_migrations = { version = "2.3", features = ["sqlite"] } 48 48 tokio = { version = "1.28", features = ["sync"] } 49 49 serde_html_form = "0.2.8" 50 + regex-lite = "0.1" 50 51 tracing.workspace = true 51 52 serde_ipld_dagcbor = { version = "0.6" } 52 53 loro = "1.9.1"
+94 -18
crates/weaver-app/public/sw.js
··· 1 1 // Weaver Service Worker 2 - // Handles blob/image requests by intercepting fetch and serving from PDS 2 + // Handles blob/image requests by caching immutable images 3 + // 4 + // URL patterns handled: 5 + // - /image/{ident}/draft/{blob_rkey}/{name} - draft images (unpublished) 6 + // - /image/{ident}/{entry_rkey}/{name} - published entry images 7 + // - /image/{notebook}/{name} - notebook images (legacy) 8 + // - /{notebook}/image/{name} - notebook images (legacy path) 3 9 4 - const CACHE_NAME = "weaver-blobs-v1"; 10 + const CACHE_NAME = "weaver-blobs-v2"; 5 11 6 - // Map of notebook/path -> real URL 12 + // Map of notebook/path -> real URL for legacy blob mappings 7 13 // e.g., "notebook/image/foo.jpg" -> "https://pds.example.com/xrpc/com.atproto.sync.getBlob?..." 8 14 const urlMappings = new Map(); 9 15 ··· 13 19 }); 14 20 15 21 self.addEventListener("activate", (event) => { 16 - event.waitUntil(clients.claim()); 22 + event.waitUntil( 23 + Promise.all([ 24 + clients.claim(), 25 + // Clean up old cache versions 26 + caches.keys().then((names) => 27 + Promise.all( 28 + names 29 + .filter((name) => name.startsWith("weaver-blobs-") && name !== CACHE_NAME) 30 + .map((name) => caches.delete(name)) 31 + ) 32 + ), 33 + ]) 34 + ); 17 35 }); 18 36 19 - // Receive mappings from main thread 37 + // Receive mappings from main thread (for legacy notebook images) 20 38 self.addEventListener("message", (event) => { 21 39 if (event.data.type === "register_mappings") { 22 40 const notebook = event.data.notebook; ··· 28 46 } 29 47 }); 30 48 49 + // Check if a path is an image request we should cache 50 + function isImagePath(pathname) { 51 + // New format: /image/{ident}/{...}/{name} 52 + if (pathname.startsWith("/image/")) { 53 + return true; 54 + } 55 + // Legacy format: /{notebook}/image/{name} 56 + const parts = pathname.split("/").filter((p) => p); 57 + if (parts.length >= 3 && parts[parts.length - 2] === "image") { 58 + return true; 59 + } 60 + return false; 61 + } 62 + 31 63 // Intercept fetch requests 32 64 self.addEventListener("fetch", (event) => { 33 65 const url = new URL(event.request.url); 34 66 35 - // Extract key from path (e.g., "/notebook/image/foo.jpg" -> "notebook/image/foo.jpg") 67 + // Only handle same-origin requests 68 + if (url.origin !== self.location.origin) { 69 + return; 70 + } 71 + 72 + // Check if this is an image request 73 + if (!isImagePath(url.pathname)) { 74 + return; 75 + } 76 + 77 + // Use pathname as cache key 78 + const cacheKey = url.pathname; 79 + 80 + // Extract path parts for legacy mapping lookup 36 81 const pathParts = url.pathname.split("/").filter((p) => p); 37 82 38 - // Check if this looks like an image request (format: /:notebook/image/:name) 83 + // Check legacy mappings (for /{notebook}/image/{name} format) 39 84 if (pathParts.length >= 3 && pathParts[pathParts.length - 2] === "image") { 40 - // Reconstruct the key 41 - const key = pathParts.join("/"); 42 - 43 - const mapping = urlMappings.get(key); 85 + const legacyKey = pathParts.join("/"); 86 + const mapping = urlMappings.get(legacyKey); 44 87 if (mapping) { 45 - event.respondWith(handleBlobRequest(mapping, key)); 88 + event.respondWith(handleBlobRequest(mapping, cacheKey)); 46 89 return; 47 90 } 48 91 } 49 92 50 - // Let other requests pass through 93 + // For new /image/... routes, cache the response from our server 94 + event.respondWith(handleImageRequest(event.request, cacheKey)); 51 95 }); 52 96 53 - async function handleBlobRequest(url, key) { 97 + // Handle requests that have a direct URL mapping (legacy) 98 + async function handleBlobRequest(url, cacheKey) { 54 99 try { 55 - // Check cache first 56 100 const cache = await caches.open(CACHE_NAME); 57 - let response = await cache.match(key); 101 + let response = await cache.match(cacheKey); 58 102 59 103 if (response) { 60 104 return response; ··· 68 112 return new Response("Blob not found", { status: 404 }); 69 113 } 70 114 71 - // Cache the response 72 - await cache.put(key, response.clone()); 115 + // Cache the response (blobs are immutable by CID) 116 + await cache.put(cacheKey, response.clone()); 73 117 74 118 return response; 75 119 } catch (error) { ··· 77 121 return new Response("Error fetching blob", { status: 500 }); 78 122 } 79 123 } 124 + 125 + // Handle image requests via our server (new /image/... routes) 126 + async function handleImageRequest(request, cacheKey) { 127 + try { 128 + const cache = await caches.open(CACHE_NAME); 129 + let response = await cache.match(cacheKey); 130 + 131 + if (response) { 132 + return response; 133 + } 134 + 135 + // Fetch from our server 136 + response = await fetch(request); 137 + 138 + if (!response.ok) { 139 + // Don't cache error responses 140 + return response; 141 + } 142 + 143 + // Check if response is cacheable (has immutable cache-control) 144 + const cacheControl = response.headers.get("cache-control") || ""; 145 + if (cacheControl.includes("immutable") || cacheControl.includes("max-age=31536000")) { 146 + // Cache the response 147 + await cache.put(cacheKey, response.clone()); 148 + } 149 + 150 + return response; 151 + } catch (error) { 152 + console.error("[SW] Error handling image request:", error); 153 + return new Response("Error fetching image", { status: 500 }); 154 + } 155 + }
+178 -22
crates/weaver-app/src/blobcache.rs
··· 6 6 identity::JacquardResolver, 7 7 prelude::*, 8 8 smol_str::SmolStr, 9 - types::{cid::Cid, ident::AtIdentifier}, 9 + types::{cid::Cid, collection::Collection, ident::AtIdentifier, nsid::Nsid, string::Rkey}, 10 + xrpc::XrpcExt, 11 + IntoStatic, 10 12 }; 11 13 use std::{ 12 - sync::{Arc, Mutex}, 14 + sync::Arc, 13 15 time::Duration, 14 16 }; 17 + use weaver_api::com_atproto::repo::get_record::GetRecord; 15 18 use weaver_api::com_atproto::sync::get_blob::GetBlob; 19 + use weaver_api::sh_weaver::notebook::entry::Entry; 20 + use weaver_api::sh_weaver::publish::blob::Blob as PublishedBlob; 16 21 17 22 #[derive(Clone)] 18 23 pub struct BlobCache { ··· 29 34 Self { client, cache, map } 30 35 } 31 36 32 - pub async fn cache( 37 + /// Resolve DID and PDS URL from an identifier 38 + async fn resolve_ident( 33 39 &self, 34 - ident: AtIdentifier<'static>, 35 - cid: Cid<'static>, 36 - name: Option<SmolStr>, 37 - ) -> Result<()> { 38 - let (repo_did, pds_url) = match ident { 40 + ident: &AtIdentifier<'_>, 41 + ) -> Result<(jacquard::types::string::Did<'static>, jacquard::url::Url)> { 42 + match ident { 39 43 AtIdentifier::Did(did) => { 40 - let pds = self.client.pds_for_did(&did).await?; 41 - (did.clone(), pds) 44 + let pds = self.client.pds_for_did(did).await?; 45 + Ok((did.clone().into_static(), pds)) 42 46 } 43 - AtIdentifier::Handle(handle) => self.client.pds_for_handle(&handle).await?, 44 - }; 45 - if self.get_cid(&cid).is_some() { 46 - return Ok(()); 47 + AtIdentifier::Handle(handle) => { 48 + let (did, pds) = self.client.pds_for_handle(handle).await?; 49 + Ok((did, pds)) 50 + } 47 51 } 48 - let blob = if let Ok(blob_stream) = self 52 + } 53 + 54 + /// Fetch a blob by CID from a specific DID's PDS 55 + async fn fetch_blob( 56 + &self, 57 + did: &jacquard::types::string::Did<'_>, 58 + pds_url: jacquard::url::Url, 59 + cid: &Cid<'_>, 60 + ) -> Result<Bytes> { 61 + if let Ok(blob_stream) = self 49 62 .client 50 63 .xrpc(pds_url) 51 64 .send( 52 65 &GetBlob::new() 53 66 .cid(cid.clone()) 54 - .did(repo_did.clone()) 67 + .did(did.clone()) 55 68 .build(), 56 69 ) 57 70 .await 58 71 { 59 - blob_stream.buffer().clone() 72 + Ok(blob_stream.buffer().clone()) 60 73 } else { 61 - reqwest::get(format!( 74 + // Fallback to Bluesky CDN (works for blobs stored on bsky PDSes) 75 + let bytes = reqwest::get(format!( 62 76 "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@jpeg", 63 - repo_did, cid 77 + did, cid 64 78 )) 65 79 .await? 66 80 .bytes() 67 - .await? 68 - .clone() 69 - }; 81 + .await?; 82 + Ok(bytes) 83 + } 84 + } 85 + 86 + pub async fn cache( 87 + &self, 88 + ident: AtIdentifier<'static>, 89 + cid: Cid<'static>, 90 + name: Option<SmolStr>, 91 + ) -> Result<()> { 92 + let (repo_did, pds_url) = self.resolve_ident(&ident).await?; 93 + 94 + if self.get_cid(&cid).is_some() { 95 + return Ok(()); 96 + } 97 + 98 + let blob = self.fetch_blob(&repo_did, pds_url, &cid).await?; 70 99 71 100 self.cache.insert(cid.clone(), blob); 72 101 if let Some(name) = name { ··· 74 103 } 75 104 76 105 Ok(()) 106 + } 107 + 108 + /// Resolve an image from a published entry by name. 109 + /// 110 + /// Looks up the entry record at `{ident}/sh.weaver.notebook.entry/{rkey}`, 111 + /// finds the image by name in the embeds, and returns the blob bytes. 112 + pub async fn resolve_from_entry( 113 + &self, 114 + ident: &AtIdentifier<'_>, 115 + rkey: &str, 116 + name: &str, 117 + ) -> Result<Bytes> { 118 + let (repo_did, pds_url) = self.resolve_ident(ident).await?; 119 + 120 + // Fetch the entry record 121 + let resp = self 122 + .client 123 + .xrpc(pds_url.clone()) 124 + .send( 125 + &GetRecord::new() 126 + .repo(AtIdentifier::Did(repo_did.clone())) 127 + .collection(Nsid::raw(<Entry as Collection>::NSID)) 128 + .rkey(Rkey::new(rkey).map_err(|e| CapturedError::from_display(e))?) 129 + .build(), 130 + ) 131 + .await 132 + .map_err(|e| CapturedError::from_display(format!("Failed to fetch entry: {}", e)))?; 133 + 134 + let record = resp 135 + .into_output() 136 + .map_err(|e| CapturedError::from_display(format!("Failed to parse entry: {}", e)))?; 137 + 138 + // Parse the entry 139 + let entry: Entry = jacquard::from_data(&record.value) 140 + .map_err(|e| CapturedError::from_display(format!("Failed to deserialize entry: {}", e)))?; 141 + 142 + // Find the image by name 143 + let cid = entry 144 + .embeds 145 + .as_ref() 146 + .and_then(|e| e.images.as_ref()) 147 + .and_then(|imgs| { 148 + imgs.images.iter().find(|img| { 149 + img.name.as_ref().map(|n| n.as_ref()) == Some(name) 150 + }) 151 + }) 152 + .map(|img| img.image.blob().cid().clone().into_static()) 153 + .ok_or_else(|| CapturedError::from_display(format!("Image '{}' not found in entry", name)))?; 154 + 155 + // Check cache first 156 + if let Some(bytes) = self.get_cid(&cid) { 157 + return Ok(bytes); 158 + } 159 + 160 + // Fetch and cache the blob 161 + let blob = self.fetch_blob(&repo_did, pds_url, &cid).await?; 162 + self.cache.insert(cid.clone(), blob.clone()); 163 + self.map.insert(name.into(), cid); 164 + 165 + Ok(blob) 166 + } 167 + 168 + /// Resolve an image from a draft (unpublished) entry via PublishedBlob record. 169 + /// 170 + /// Looks up the PublishedBlob record at `{ident}/sh.weaver.publish.blob/{blob_rkey}`, 171 + /// gets the CID from it, and returns the blob bytes. 172 + pub async fn resolve_from_draft( 173 + &self, 174 + ident: &AtIdentifier<'_>, 175 + blob_rkey: &str, 176 + ) -> Result<Bytes> { 177 + let (repo_did, pds_url) = self.resolve_ident(ident).await?; 178 + 179 + // Fetch the PublishedBlob record 180 + let resp = self 181 + .client 182 + .xrpc(pds_url.clone()) 183 + .send( 184 + &GetRecord::new() 185 + .repo(AtIdentifier::Did(repo_did.clone())) 186 + .collection(Nsid::raw(<PublishedBlob as Collection>::NSID)) 187 + .rkey(Rkey::new(blob_rkey).map_err(|e| CapturedError::from_display(e))?) 188 + .build(), 189 + ) 190 + .await 191 + .map_err(|e| CapturedError::from_display(format!("Failed to fetch PublishedBlob: {}", e)))?; 192 + 193 + let record = resp 194 + .into_output() 195 + .map_err(|e| CapturedError::from_display(format!("Failed to parse PublishedBlob: {}", e)))?; 196 + 197 + // Parse the PublishedBlob 198 + let published: PublishedBlob = jacquard::from_data(&record.value) 199 + .map_err(|e| CapturedError::from_display(format!("Failed to deserialize PublishedBlob: {}", e)))?; 200 + 201 + // Get CID from the upload blob ref 202 + let cid = published.upload.blob().cid().clone().into_static(); 203 + 204 + // Check cache first 205 + if let Some(bytes) = self.get_cid(&cid) { 206 + return Ok(bytes); 207 + } 208 + 209 + // Fetch and cache the blob 210 + let blob = self.fetch_blob(&repo_did, pds_url, &cid).await?; 211 + self.cache.insert(cid, blob.clone()); 212 + 213 + Ok(blob) 214 + } 215 + 216 + /// Resolve an image from a notebook entry by name. 217 + /// 218 + /// This is a convenience method that looks up the notebook, finds the entry, 219 + /// and resolves the image. Used for `/image/{notebook}/{name}` paths. 220 + pub async fn resolve_from_notebook( 221 + &self, 222 + notebook_title: &str, 223 + image_name: &str, 224 + ) -> Result<Bytes> { 225 + // For now, just try to get from the name cache 226 + // Full notebook resolution would require fetching the notebook record 227 + // and iterating through entries to find the image 228 + self.get_named(&image_name.into()) 229 + .ok_or_else(|| CapturedError::from_display(format!( 230 + "Image '{}' not found in notebook '{}'", 231 + image_name, notebook_title 232 + ))) 77 233 } 78 234 79 235 pub fn get_cid(&self, cid: &Cid<'static>) -> Option<Bytes> {
+133 -39
crates/weaver-app/src/components/editor/component.rs
··· 1 1 //! The main MarkdownEditor component. 2 2 3 3 use dioxus::prelude::*; 4 + use jacquard::IntoStatic; 4 5 use jacquard::cowstr::ToCowStr; 5 6 use jacquard::types::blob::BlobRef; 7 + use jacquard::types::ident::AtIdentifier; 6 8 use weaver_api::sh_weaver::embed::images::Image; 7 9 use weaver_common::WeaverExt; 8 10 ··· 11 13 use crate::fetch::Fetcher; 12 14 13 15 use super::document::{CompositionState, EditorDocument}; 14 - use super::dom_sync::{sync_cursor_from_dom, sync_cursor_from_dom_with_direction, update_paragraph_dom}; 15 - use super::offset_map::SnapDirection; 16 + use super::dom_sync::{ 17 + sync_cursor_from_dom, sync_cursor_from_dom_with_direction, update_paragraph_dom, 18 + }; 16 19 use super::formatting; 17 20 use super::input::{ 18 21 get_char_at, handle_copy, handle_cut, handle_keydown, handle_paste, should_intercept_key, 19 22 }; 23 + use super::offset_map::SnapDirection; 20 24 use super::paragraph::ParagraphRender; 21 25 use super::platform; 22 - use super::publish::PublishButton; 26 + use super::publish::{LoadedEntry, PublishButton, load_entry_for_editing}; 23 27 use super::render; 24 28 use super::storage; 25 29 use super::toolbar::EditorToolbar; 26 30 use super::visibility::update_syntax_visibility; 27 31 use super::writer::{EditorImageResolver, SyntaxSpanInfo}; 28 32 29 - /// Main markdown editor component. 33 + /// Result of loading an entry - either loaded, failed, or not needed. 34 + #[derive(Clone, PartialEq)] 35 + enum LoadResult { 36 + Loaded(LoadedEntry), 37 + Failed, 38 + NotNeeded, 39 + } 40 + 41 + /// Wrapper component that handles loading an existing entry before rendering the editor. 30 42 /// 31 43 /// # Props 32 - /// - `initial_content`: Optional initial markdown content 44 + /// - `initial_content`: Optional initial markdown content (for new entries) 45 + /// - `entry_uri`: Optional AT-URI of an existing entry to edit 46 + #[component] 47 + pub fn MarkdownEditor(initial_content: Option<String>, entry_uri: Option<String>) -> Element { 48 + let fetcher = use_context::<Fetcher>(); 49 + 50 + // Determine draft key - use entry URI if editing existing, otherwise "current" 51 + let draft_key = entry_uri.clone().unwrap_or_else(|| "current".to_string()); 52 + 53 + // Check if we have a local draft first 54 + let has_local_draft = use_hook(|| storage::load_from_storage(&draft_key).is_some()); 55 + 56 + // If we have an entry_uri but no local draft, we need to fetch from PDS 57 + let needs_fetch = entry_uri.is_some() && !has_local_draft; 58 + 59 + // Resource returns the load result 60 + let entry_resource = use_resource(move || { 61 + let fetcher = fetcher.clone(); 62 + let uri_str = entry_uri.clone(); 63 + async move { 64 + if !needs_fetch { 65 + return LoadResult::NotNeeded; 66 + } 67 + if let Some(uri_str) = uri_str { 68 + if let Ok(uri) = jacquard::types::string::AtUri::new(&uri_str) { 69 + match load_entry_for_editing(&fetcher, &uri).await { 70 + Ok(loaded) => return LoadResult::Loaded(loaded), 71 + Err(e) => { 72 + tracing::error!("Failed to load entry: {}", e); 73 + return LoadResult::Failed; 74 + } 75 + } 76 + } 77 + } 78 + LoadResult::Failed 79 + } 80 + }); 81 + 82 + // Render based on resource state 83 + match &*entry_resource.read() { 84 + Some(LoadResult::Loaded(loaded)) => { 85 + rsx! { 86 + MarkdownEditorInner { 87 + key: "{draft_key}", 88 + draft_key: draft_key.clone(), 89 + loaded_entry: Some(loaded.clone()), 90 + initial_content: None, 91 + } 92 + } 93 + } 94 + Some(LoadResult::Failed) => { 95 + rsx! { 96 + div { class: "editor-error", 97 + "Failed to load entry. It may not exist or you may not have access." 98 + } 99 + } 100 + } 101 + Some(LoadResult::NotNeeded) => { 102 + rsx! { 103 + MarkdownEditorInner { 104 + key: "{draft_key}", 105 + draft_key: draft_key.clone(), 106 + loaded_entry: None, 107 + initial_content: initial_content.clone(), 108 + } 109 + } 110 + } 111 + None => { 112 + // Still loading 113 + rsx! { 114 + div { class: "editor-loading", 115 + "Loading entry..." 116 + } 117 + } 118 + } 119 + } 120 + } 121 + 122 + /// Inner markdown editor component (actual editor implementation). 33 123 /// 34 124 /// # Features 35 125 /// - Loro CRDT-based text storage with undo/redo support ··· 38 128 /// - LocalStorage auto-save with debouncing 39 129 /// - Keyboard shortcuts (Ctrl+B for bold, Ctrl+I for italic) 40 130 #[component] 41 - pub fn MarkdownEditor(initial_content: Option<String>) -> Element { 131 + fn MarkdownEditorInner( 132 + draft_key: String, 133 + loaded_entry: Option<LoadedEntry>, 134 + initial_content: Option<String>, 135 + ) -> Element { 42 136 // Context for authenticated API calls 43 137 let fetcher = use_context::<Fetcher>(); 44 138 let auth_state = use_context::<Signal<AuthState>>(); 45 139 46 - // Try to restore from localStorage (includes CRDT state for undo history) 47 - // Use "current" as the default draft key for now 48 - let draft_key = "current"; 49 - // Document is NOT in a signal - its fields are individually reactive 50 140 let mut document = use_hook(|| { 51 - storage::load_from_storage(draft_key) 52 - .unwrap_or_else(|| EditorDocument::new(initial_content.clone().unwrap_or_default())) 141 + // Priority: loaded_entry > local storage > new with initial_content 142 + if let Some(ref loaded) = loaded_entry { 143 + let doc = EditorDocument::from_entry(&loaded.entry, loaded.entry_ref.clone()); 144 + // Save to local storage so future visits use the draft 145 + storage::save_to_storage(&doc, &draft_key).ok(); 146 + doc 147 + } else { 148 + storage::load_from_storage(&draft_key) 149 + .unwrap_or_else(|| EditorDocument::new(initial_content.clone().unwrap_or_default())) 150 + } 53 151 }); 54 152 let editor_id = "markdown-editor"; 55 153 ··· 149 247 // Use requestAnimationFrame to wait for browser paint 150 248 if let Some(window) = web_sys::window() { 151 249 let closure = Closure::once(move || { 152 - if let Err(e) = 153 - super::cursor::restore_cursor_position(cursor_offset, &map, editor_id, snap_direction) 154 - { 250 + if let Err(e) = super::cursor::restore_cursor_position( 251 + cursor_offset, 252 + &map, 253 + editor_id, 254 + snap_direction, 255 + ) { 155 256 tracing::warn!("Cursor restoration failed: {:?}", e); 156 257 } 157 258 }); ··· 175 276 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 176 277 let doc_for_autosave = document.clone(); 177 278 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 279 + let draft_key_for_autosave = draft_key.clone(); 280 + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 178 281 use_effect(move || { 179 282 // Check every 500ms if there are unsaved changes 180 283 let mut doc = doc_for_autosave.clone(); 284 + let draft_key = draft_key_for_autosave.clone(); 181 285 let interval = gloo_timers::callback::Interval::new(500, move || { 182 286 let current_frontiers = doc.state_frontiers(); 183 287 ··· 192 296 193 297 if needs_save { 194 298 doc.sync_loro_cursor(); 195 - let _ = storage::save_to_storage(&doc, draft_key); 299 + let _ = storage::save_to_storage(&doc, &draft_key); 196 300 197 301 // Update last saved frontiers 198 302 last_saved_frontiers.set(Some(current_frontiers)); ··· 783 887 // Upload blob and create temporary PublishedBlob record 784 888 match client.publish_blob(data, &name_for_upload, None).await { 785 889 Ok((strong_ref, published_blob)) => { 786 - // Extract the blob from PublishedBlob 787 - let blob = match published_blob.upload { 788 - BlobRef::Blob(b) => b, 789 - _ => { 790 - tracing::warn!("Unexpected BlobRef variant"); 791 - return; 792 - } 793 - }; 794 - 795 - // Get format from mime type 796 - let format = blob 797 - .mime_type 798 - .0 799 - .strip_prefix("image/") 800 - .unwrap_or("jpeg") 801 - .to_string(); 802 - 803 890 // Get DID from fetcher 804 891 let did = match fetcher.current_did().await { 805 - Some(d) => d.to_string(), 892 + Some(d) => d, 806 893 None => { 807 894 tracing::warn!("No DID available"); 808 895 return; 809 896 } 810 897 }; 811 898 812 - let cid = blob.cid().to_string(); 899 + // Extract rkey from the AT-URI 900 + let blob_rkey = match strong_ref.uri.rkey() { 901 + Some(rkey) => rkey.0.clone().into_static(), 902 + None => { 903 + tracing::warn!("No rkey in PublishedBlob URI"); 904 + return; 905 + } 906 + }; 813 907 814 908 // Build Image using the builder API 815 909 let name_for_resolver = name_for_upload.clone(); 816 910 let image = Image::new() 817 911 .alt(alt_for_upload.to_cowstr()) 818 - .image(BlobRef::Blob(blob)) 912 + .image(published_blob.upload) 819 913 .name(name_for_upload.to_cowstr()) 820 914 .build(); 821 915 ··· 823 917 doc_for_spawn.add_image(&image, Some(&strong_ref.uri)); 824 918 825 919 // Promote from pending to uploaded in resolver 920 + let ident = AtIdentifier::Did(did); 826 921 image_resolver.with_mut(|resolver| { 827 922 resolver.promote_to_uploaded( 828 923 &name_for_resolver, 829 - cid, 830 - did, 831 - format, 924 + blob_rkey, 925 + ident, 832 926 ); 833 927 }); 834 928
+166 -14
crates/weaver-app/src/components/editor/document.rs
··· 10 10 //! - Content changes (via `last_edit`) trigger paragraph memo re-evaluation 11 11 //! - The document struct itself is NOT wrapped in a Signal - use `use_hook` 12 12 13 + use std::borrow::Cow; 13 14 use std::cell::RefCell; 14 15 use std::rc::Rc; 15 16 16 17 use dioxus::prelude::*; 17 18 use loro::{ 18 - ExportMode, LoroDoc, LoroList, LoroMap, LoroResult, LoroText, LoroValue, ToJson, UndoManager, 19 + ExportMode, Frontiers, LoroDoc, LoroList, LoroMap, LoroResult, LoroText, LoroValue, ToJson, 20 + UndoManager, VersionVector, 19 21 cursor::{Cursor, Side}, 20 22 }; 21 23 22 24 use jacquard::IntoStatic; 23 25 use jacquard::from_json_value; 24 26 use jacquard::types::string::AtUri; 27 + use weaver_api::com_atproto::repo::strong_ref::StrongRef; 25 28 use weaver_api::sh_weaver::embed::images::Image; 29 + use weaver_api::sh_weaver::notebook::entry::Entry; 26 30 27 31 /// Helper for working with editor images. 28 32 /// Constructed from LoroMap data, NOT serialized directly. ··· 80 84 /// Contains nested containers: images (LoroList), externals (LoroList), etc. 81 85 embeds: LoroMap, 82 86 83 - // --- Entry tracking --- 84 - /// AT-URI of the entry if editing an existing record. 87 + // --- Entry tracking (reactive) --- 88 + /// StrongRef to the entry if editing an existing record. 85 89 /// None for new entries that haven't been published yet. 86 - entry_uri: Option<AtUri<'static>>, 90 + /// Signal so cloned docs share the same state after publish. 91 + pub entry_ref: Signal<Option<StrongRef<'static>>>, 92 + 93 + // --- Edit sync state (for PDS sync) --- 94 + /// StrongRef to the sh.weaver.edit.root record for this edit session. 95 + /// None if we haven't synced to PDS yet. 96 + pub edit_root: Signal<Option<StrongRef<'static>>>, 97 + 98 + /// StrongRef to the most recent sh.weaver.edit.diff record. 99 + /// Used for the `prev` field when creating new diffs. 100 + /// None if no diffs have been created yet (only root exists). 101 + pub last_diff: Signal<Option<StrongRef<'static>>>, 102 + 103 + /// Version vector at the time of last sync to PDS. 104 + /// Used to export only changes since last sync. 105 + /// None if never synced. 106 + last_synced_version: Option<VersionVector>, 87 107 88 108 // --- Editor state (non-reactive) --- 89 109 /// Undo manager for the document. ··· 244 264 created_at, 245 265 tags, 246 266 embeds, 247 - entry_uri: None, 267 + entry_ref: Signal::new(None), 268 + edit_root: Signal::new(None), 269 + last_diff: Signal::new(None), 270 + last_synced_version: None, 248 271 undo_mgr: Rc::new(RefCell::new(undo_mgr)), 249 272 loro_cursor, 250 273 // Reactive editor state - wrapped in Signals ··· 260 283 } 261 284 } 262 285 286 + /// Create an EditorDocument from a fetched Entry. 287 + /// 288 + /// MUST be called from within a reactive context (e.g., `use_hook`) to 289 + /// properly initialize Dioxus Signals. 290 + /// 291 + /// # Arguments 292 + /// * `entry` - The entry record fetched from PDS 293 + /// * `entry_ref` - StrongRef to the entry (URI + CID) 294 + pub fn from_entry(entry: &Entry<'_>, entry_ref: StrongRef<'static>) -> Self { 295 + let mut doc = Self::new(entry.content.to_string()); 296 + 297 + // Set metadata 298 + doc.set_title(&entry.title); 299 + doc.set_path(&entry.path); 300 + doc.set_created_at(&entry.created_at.to_string()); 301 + 302 + // Add tags 303 + if let Some(ref tags) = entry.tags { 304 + for tag in tags.iter() { 305 + doc.add_tag(tag.as_ref()); 306 + } 307 + } 308 + 309 + // Add existing images (no published_blob_uri needed - they're already in the entry) 310 + if let Some(ref embeds) = entry.embeds { 311 + if let Some(ref images) = embeds.images { 312 + for img in &images.images { 313 + doc.add_image(&img.clone().into_static(), None); 314 + } 315 + } 316 + } 317 + 318 + // Set the entry_ref so subsequent publishes update this record 319 + doc.set_entry_ref(Some(entry_ref)); 320 + 321 + doc 322 + } 323 + 263 324 /// Generate current datetime as ISO 8601 string. 264 325 #[cfg(target_family = "wasm")] 265 326 fn current_datetime_string() -> String { ··· 359 420 self.created_at.insert(0, datetime).ok(); 360 421 } 361 422 362 - // --- Entry URI accessors --- 423 + // --- Entry ref accessors --- 363 424 364 - /// Get the AT-URI of the entry if editing an existing record. 365 - pub fn entry_uri(&self) -> Option<&AtUri<'static>> { 366 - self.entry_uri.as_ref() 425 + /// Get the StrongRef to the entry if editing an existing record. 426 + pub fn entry_ref(&self) -> Option<StrongRef<'static>> { 427 + self.entry_ref.read().clone() 367 428 } 368 429 369 - /// Set the AT-URI when editing an existing entry. 370 - pub fn set_entry_uri(&mut self, uri: Option<AtUri<'static>>) { 371 - self.entry_uri = uri; 430 + /// Set the StrongRef when editing an existing entry. 431 + pub fn set_entry_ref(&mut self, entry: Option<StrongRef<'static>>) { 432 + self.entry_ref.set(entry); 372 433 } 373 434 374 435 // --- Tags accessors --- ··· 689 750 690 751 /// Get the current state frontiers for change detection. 691 752 /// Frontiers represent the "version" of the document state. 692 - pub fn state_frontiers(&self) -> loro::Frontiers { 753 + pub fn state_frontiers(&self) -> Frontiers { 693 754 self.doc.state_frontiers() 694 755 } 695 756 757 + /// Get the current version vector. 758 + pub fn version_vector(&self) -> VersionVector { 759 + self.doc.oplog_vv() 760 + } 761 + 696 762 /// Get the last edit info for incremental rendering. 697 763 /// Reading this creates a reactive dependency on content changes. 698 764 pub fn last_edit(&self) -> Option<EditInfo> { 699 765 self.last_edit.read().clone() 700 766 } 701 767 768 + // --- Edit sync methods --- 769 + 770 + /// Get the edit root StrongRef if set. 771 + pub fn edit_root(&self) -> Option<StrongRef<'static>> { 772 + self.edit_root.read().clone() 773 + } 774 + 775 + /// Set the edit root after creating or finding the root record. 776 + pub fn set_edit_root(&mut self, root: Option<StrongRef<'static>>) { 777 + self.edit_root.set(root); 778 + } 779 + 780 + /// Get the last diff StrongRef if set. 781 + pub fn last_diff(&self) -> Option<StrongRef<'static>> { 782 + self.last_diff.read().clone() 783 + } 784 + 785 + /// Set the last diff after creating a new diff record. 786 + pub fn set_last_diff(&mut self, diff: Option<StrongRef<'static>>) { 787 + self.last_diff.set(diff); 788 + } 789 + 790 + /// Check if there are unsynchronized changes since the last sync. 791 + pub fn has_unsync_changes(&self) -> bool { 792 + match &self.last_synced_version { 793 + Some(synced_vv) => self.doc.oplog_vv() != *synced_vv, 794 + None => true, // Never synced, so there are changes 795 + } 796 + } 797 + 798 + /// Export updates since the last sync. 799 + /// Returns None if there are no changes to export. 800 + /// After successful upload, call `mark_synced()` to update the sync marker. 801 + pub fn export_updates_since_sync(&self) -> Option<Vec<u8>> { 802 + let from_vv = self.last_synced_version.clone().unwrap_or_default(); 803 + let current_vv = self.doc.oplog_vv(); 804 + 805 + // No changes since last sync 806 + if from_vv == current_vv { 807 + return None; 808 + } 809 + 810 + let updates = self 811 + .doc 812 + .export(ExportMode::Updates { 813 + from: Cow::Owned(from_vv), 814 + }) 815 + .ok()?; 816 + 817 + // Don't return empty updates 818 + if updates.is_empty() { 819 + return None; 820 + } 821 + 822 + Some(updates) 823 + } 824 + 825 + /// Mark the current state as synced. 826 + /// Call this after successfully uploading a diff to the PDS. 827 + pub fn mark_synced(&mut self) { 828 + self.last_synced_version = Some(self.doc.oplog_vv()); 829 + } 830 + 831 + /// Import updates from a PDS diff blob. 832 + /// Used when loading edit history from the PDS. 833 + pub fn import_updates(&mut self, updates: &[u8]) -> LoroResult<()> { 834 + self.doc.import(updates)?; 835 + Ok(()) 836 + } 837 + 838 + /// Set the sync state when loading from PDS. 839 + /// This sets the version marker to the current state so we don't 840 + /// re-upload what we just downloaded. 841 + pub fn set_synced_from_pds( 842 + &mut self, 843 + edit_root: StrongRef<'static>, 844 + last_diff: Option<StrongRef<'static>>, 845 + ) { 846 + self.edit_root.set(Some(edit_root)); 847 + self.last_diff.set(last_diff); 848 + self.last_synced_version = Some(self.doc.oplog_vv()); 849 + } 850 + 702 851 /// Create a new EditorDocument from a binary snapshot. 703 852 /// Falls back to empty document if import fails. 704 853 /// ··· 765 914 created_at, 766 915 tags, 767 916 embeds, 768 - entry_uri: None, 917 + entry_ref: Signal::new(None), 918 + edit_root: Signal::new(None), 919 + last_diff: Signal::new(None), 920 + last_synced_version: None, 769 921 undo_mgr: Rc::new(RefCell::new(undo_mgr)), 770 922 loro_cursor, 771 923 // Reactive editor state - wrapped in Signals
+27 -1
crates/weaver-app/src/components/editor/input.rs
··· 26 26 _ => {} 27 27 } 28 28 } 29 + // Intercept Cmd+Backspace (delete to start of line) and Cmd+Delete (delete to end) 30 + if matches!(key, Key::Backspace | Key::Delete) { 31 + return true; 32 + } 29 33 // Let browser handle other Ctrl/Cmd shortcuts (paste, copy, cut, etc.) 30 34 return false; 31 35 } ··· 151 155 doc.cursor.write().offset = start; 152 156 } else if doc.cursor.read().offset > 0 { 153 157 let cursor_offset = doc.cursor.read().offset; 158 + 159 + // Cmd+Backspace: delete to start of line 160 + if mods.meta() || mods.ctrl() { 161 + let line_start = find_line_start(doc.loro_text(), cursor_offset); 162 + if line_start < cursor_offset { 163 + let _ = doc.remove_tracked(line_start, cursor_offset - line_start); 164 + doc.cursor.write().offset = line_start; 165 + } 166 + return; 167 + } 168 + 154 169 // Check if we're about to delete a newline 155 170 let prev_char = get_char_at(doc.loro_text(), cursor_offset - 1); 156 171 ··· 213 228 doc.cursor.write().offset = start; 214 229 } else { 215 230 let cursor_offset = doc.cursor.read().offset; 216 - if cursor_offset < doc.len_chars() { 231 + let doc_len = doc.len_chars(); 232 + 233 + // Cmd+Delete: delete to end of line 234 + if mods.meta() || mods.ctrl() { 235 + let line_end = find_line_end(doc.loro_text(), cursor_offset); 236 + if cursor_offset < line_end { 237 + let _ = doc.remove_tracked(cursor_offset, line_end - cursor_offset); 238 + } 239 + return; 240 + } 241 + 242 + if cursor_offset < doc_len { 217 243 // Delete next char 218 244 let _ = doc.remove_tracked(cursor_offset, 1); 219 245 }
+1
crates/weaver-app/src/components/editor/mod.rs
··· 19 19 mod render; 20 20 mod report; 21 21 mod storage; 22 + mod sync; 22 23 mod toolbar; 23 24 mod visibility; 24 25 mod writer;
+202 -27
crates/weaver-app/src/components/editor/publish.rs
··· 1 - //! Entry publishing functionality for the markdown editor. 1 + //! Entry publishing and loading functionality for the markdown editor. 2 2 //! 3 - //! Handles creating/updating AT Protocol notebook entries from editor state. 3 + //! Handles creating/updating/loading AT Protocol notebook entries. 4 4 5 5 use dioxus::prelude::*; 6 + use jacquard::types::collection::Collection; 6 7 use jacquard::types::ident::AtIdentifier; 7 - use jacquard::types::string::{AtUri, Datetime, Nsid}; 8 - use jacquard::{IntoStatic, prelude::*, to_data}; 8 + use jacquard::types::recordkey::RecordKey; 9 + use jacquard::types::string::{AtUri, Datetime, Nsid, Rkey}; 10 + use jacquard::types::tid::Ticker; 11 + use jacquard::{IntoStatic, from_data, prelude::*, to_data}; 12 + use regex_lite::Regex; 13 + use std::sync::LazyLock; 14 + use weaver_api::com_atproto::repo::get_record::GetRecord; 15 + use weaver_api::com_atproto::repo::strong_ref::StrongRef; 9 16 use weaver_api::com_atproto::repo::{create_record::CreateRecord, put_record::PutRecord}; 10 17 use weaver_api::sh_weaver::embed::images::Images; 11 18 use weaver_api::sh_weaver::notebook::entry::{Entry, EntryEmbeds}; ··· 13 20 14 21 const ENTRY_NSID: &str = "sh.weaver.notebook.entry"; 15 22 23 + /// Regex to match draft image paths: /image/{did}/draft/{blob_rkey}/{name} 24 + /// Captures: 1=did, 2=blob_rkey, 3=name 25 + static DRAFT_IMAGE_PATH_REGEX: LazyLock<Regex> = 26 + LazyLock::new(|| Regex::new(r"/image/([^/]+)/draft/([^/]+)/([^)\s]+)").unwrap()); 27 + 28 + /// Rewrite draft image paths to published paths. 29 + /// 30 + /// Converts `/image/{did}/draft/{blob_rkey}/{name}` to `/image/{did}/{entry_rkey}/{name}` 31 + fn rewrite_draft_paths(content: &str, entry_rkey: &str) -> String { 32 + DRAFT_IMAGE_PATH_REGEX 33 + .replace_all(content, |caps: &regex_lite::Captures| { 34 + let did = &caps[1]; 35 + let name = &caps[3]; 36 + format!("/image/{}/{}/{}", did, entry_rkey, name) 37 + }) 38 + .into_owned() 39 + } 40 + 16 41 use crate::auth::AuthState; 17 42 use crate::fetch::Fetcher; 18 43 19 44 use super::document::EditorDocument; 20 - use super::storage::delete_draft; 45 + use super::storage::{delete_draft, save_to_storage}; 21 46 22 47 /// Result of a publish operation. 23 48 #[derive(Clone, Debug)] ··· 36 61 } 37 62 } 38 63 64 + /// Result of fetching an entry for editing. 65 + /// Contains the entry data and URI, but NOT an EditorDocument. 66 + /// The document must be created in a reactive context (use_hook) to properly initialize Signals. 67 + #[derive(Clone, PartialEq)] 68 + pub struct LoadedEntry { 69 + pub entry: Entry<'static>, 70 + pub entry_ref: StrongRef<'static>, 71 + } 72 + 73 + /// Fetch an existing entry from the PDS for editing. 74 + /// 75 + /// Returns the entry data and URI. The caller should create an `EditorDocument` 76 + /// from this data using `EditorDocument::from_entry()` inside a reactive context. 77 + /// 78 + /// # Arguments 79 + /// * `fetcher` - The fetcher for making API calls 80 + /// * `uri` - The AT-URI of the entry to load (e.g., `at://did:plc:xxx/sh.weaver.notebook.entry/rkey`) 81 + /// 82 + /// # Returns 83 + /// The entry and its URI, or an error. 84 + pub async fn load_entry_for_editing( 85 + fetcher: &Fetcher, 86 + uri: &AtUri<'_>, 87 + ) -> Result<LoadedEntry, WeaverError> { 88 + // Parse the AT-URI components 89 + let ident = uri.authority(); 90 + let rkey = uri 91 + .rkey() 92 + .ok_or_else(|| WeaverError::InvalidNotebook("Entry URI missing rkey".into()))?; 93 + 94 + // Resolve DID and PDS 95 + let (did, pds_url) = match ident { 96 + AtIdentifier::Did(d) => { 97 + let pds = fetcher 98 + .client 99 + .pds_for_did(d) 100 + .await 101 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to resolve DID: {}", e)))?; 102 + (d.clone(), pds) 103 + } 104 + AtIdentifier::Handle(h) => { 105 + let (did, pds) = fetcher 106 + .client 107 + .pds_for_handle(h) 108 + .await 109 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to resolve handle: {}", e)))?; 110 + (did, pds) 111 + } 112 + }; 113 + 114 + // Fetch the entry record 115 + let request = GetRecord::new() 116 + .repo(AtIdentifier::Did(did)) 117 + .collection(Nsid::raw(<Entry as Collection>::NSID)) 118 + .rkey(rkey.clone()) 119 + .build(); 120 + 121 + let response = fetcher 122 + .client 123 + .xrpc(pds_url) 124 + .send(&request) 125 + .await 126 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to fetch entry: {}", e)))?; 127 + 128 + let record = response 129 + .into_output() 130 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to parse response: {}", e)))?; 131 + 132 + // Deserialize the entry 133 + let entry: Entry = from_data(&record.value) 134 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to deserialize entry: {}", e)))?; 135 + 136 + // Build StrongRef from URI and CID 137 + let entry_ref = StrongRef::new() 138 + .uri(uri.clone().into_static()) 139 + .cid(record.cid.ok_or_else(|| { 140 + WeaverError::InvalidNotebook("Entry response missing CID".into()) 141 + })?.into_static()) 142 + .build(); 143 + 144 + Ok(LoadedEntry { 145 + entry: entry.into_static(), 146 + entry_ref, 147 + }) 148 + } 149 + 39 150 /// Publish an entry to the AT Protocol. 40 151 /// 41 152 /// Supports three modes: ··· 43 154 /// - Without notebook but with entry_uri in doc: uses `put_record` to update existing 44 155 /// - Without notebook and no entry_uri: uses `create_record` for free-floating entry 45 156 /// 157 + /// Draft image paths (`/image/{did}/draft/{blob_rkey}/{name}`) are rewritten to 158 + /// published paths (`/image/{did}/{entry_rkey}/{name}`) before publishing. 159 + /// 160 + /// On successful create, sets `doc.entry_uri` so subsequent publishes update the same record. 161 + /// 46 162 /// # Arguments 47 163 /// * `fetcher` - The authenticated fetcher/client 48 - /// * `doc` - The editor document containing entry data 164 + /// * `doc` - The editor document containing entry data (mutable to update entry_uri) 49 165 /// * `notebook_title` - Optional title of the notebook to publish to 50 166 /// * `draft_key` - Storage key for the draft (for cleanup) 51 167 /// ··· 53 169 /// The AT-URI of the created/updated entry, or an error. 54 170 pub async fn publish_entry( 55 171 fetcher: &Fetcher, 56 - doc: &EditorDocument, 172 + doc: &mut EditorDocument, 57 173 notebook_title: Option<&str>, 58 174 draft_key: &str, 59 175 ) -> Result<PublishResult, WeaverError> { ··· 96 212 } 97 213 }; 98 214 99 - // Build the entry 100 - let entry = Entry::new() 101 - .content(doc.content()) 102 - .title(doc.title()) 103 - .path(path) 104 - .created_at(Datetime::now()) 105 - .maybe_tags(tags) 106 - .maybe_embeds(entry_embeds) 107 - .build(); 108 - let entry_data = to_data(&entry).unwrap(); 109 - 110 215 let client = fetcher.get_client(); 111 216 let result = if let Some(notebook) = notebook_title { 112 217 // Publish to a notebook via upsert_entry 113 - let (uri, was_created) = client.upsert_entry(notebook, &doc.title(), entry).await?; 218 + // TODO: Need to handle path rewriting for notebook case 219 + // For now, use content as-is (notebook entries use different path scheme anyway) 220 + let entry = Entry::new() 221 + .content(doc.content()) 222 + .title(doc.title()) 223 + .path(path) 224 + .created_at(Datetime::now()) 225 + .maybe_tags(tags) 226 + .maybe_embeds(entry_embeds) 227 + .build(); 228 + 229 + let (entry_ref, was_created) = client.upsert_entry(notebook, &doc.title(), entry).await?; 230 + let uri = entry_ref.uri.clone(); 231 + 232 + // Set entry_ref so subsequent publishes update this record 233 + doc.set_entry_ref(Some(entry_ref)); 114 234 115 235 if was_created { 116 236 PublishResult::Created(uri) 117 237 } else { 118 238 PublishResult::Updated(uri) 119 239 } 120 - } else if let Some(existing_uri) = doc.entry_uri() { 121 - // Update existing free-floating entry 240 + } else if let Some(existing_ref) = doc.entry_ref() { 241 + // Update existing free-floating entry - use existing rkey for path rewriting 122 242 let did = fetcher 123 243 .current_did() 124 244 .await 125 245 .ok_or_else(|| WeaverError::InvalidNotebook("Not authenticated".into()))?; 126 246 127 - let rkey = existing_uri 247 + let rkey = existing_ref 248 + .uri 128 249 .rkey() 129 250 .ok_or_else(|| WeaverError::InvalidNotebook("Entry URI missing rkey".into()))?; 130 251 252 + // Rewrite draft image paths to published paths 253 + let content = rewrite_draft_paths(&doc.content(), rkey.0.as_str()); 254 + 255 + let entry = Entry::new() 256 + .content(content) 257 + .title(doc.title()) 258 + .path(path) 259 + .created_at(Datetime::now()) 260 + .maybe_tags(tags) 261 + .maybe_embeds(entry_embeds) 262 + .build(); 263 + let entry_data = to_data(&entry).unwrap(); 264 + 131 265 let collection = Nsid::new(ENTRY_NSID).map_err(|e| WeaverError::AtprotoString(e))?; 132 266 133 267 let request = PutRecord::new() ··· 145 279 .into_output() 146 280 .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 147 281 282 + // Update entry_ref with new CID 283 + let updated_ref = StrongRef::new() 284 + .uri(output.uri.clone().into_static()) 285 + .cid(output.cid.into_static()) 286 + .build(); 287 + doc.set_entry_ref(Some(updated_ref)); 288 + 148 289 PublishResult::Updated(output.uri.into_static()) 149 290 } else { 150 - // Create new free-floating entry 291 + // Create new free-floating entry - pre-generate rkey for path rewriting 151 292 let did = fetcher 152 293 .current_did() 153 294 .await 154 295 .ok_or_else(|| WeaverError::InvalidNotebook("Not authenticated".into()))?; 155 296 297 + // Pre-generate TID for the entry rkey 298 + let entry_tid = Ticker::new().next(None); 299 + let entry_rkey_str = entry_tid.as_str(); 300 + 301 + // Rewrite draft image paths to published paths 302 + let content = rewrite_draft_paths(&doc.content(), entry_rkey_str); 303 + 304 + let entry = Entry::new() 305 + .content(content) 306 + .title(doc.title()) 307 + .path(path) 308 + .created_at(Datetime::now()) 309 + .maybe_tags(tags) 310 + .maybe_embeds(entry_embeds) 311 + .build(); 312 + let entry_data = to_data(&entry).unwrap(); 313 + 156 314 let collection = Nsid::new(ENTRY_NSID).map_err(|e| WeaverError::AtprotoString(e))?; 315 + let rkey = RecordKey::any(entry_rkey_str) 316 + .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 157 317 158 318 let request = CreateRecord::new() 159 319 .repo(AtIdentifier::Did(did)) 160 320 .collection(collection) 321 + .rkey(rkey) 161 322 .record(entry_data) 162 323 .build(); 163 324 ··· 169 330 .into_output() 170 331 .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 171 332 172 - PublishResult::Created(output.uri.into_static()) 333 + let uri = output.uri.into_static(); 334 + // Set entry_ref so subsequent publishes update this record 335 + let entry_ref = StrongRef::new() 336 + .uri(uri.clone()) 337 + .cid(output.cid.into_static()) 338 + .build(); 339 + doc.set_entry_ref(Some(entry_ref)); 340 + PublishResult::Created(uri) 173 341 }; 174 342 175 343 // Cleanup: delete PublishedBlob records (entry's embed refs now keep blobs alive) ··· 180 348 // } 181 349 // } 182 350 183 - // Clear local draft 351 + // Delete the old draft key 184 352 delete_draft(draft_key); 353 + 354 + // Save with the new uri-based key so continued editing is tracked by entry URI 355 + let new_key = result.uri().to_string(); 356 + if let Err(e) = save_to_storage(doc, &new_key) { 357 + tracing::warn!("Failed to save draft after publish: {e}"); 358 + } 185 359 186 360 Ok(result) 187 361 } ··· 237 411 let draft_key = props.draft_key.clone(); 238 412 239 413 // Check if we're editing an existing entry 240 - let is_editing_existing = doc.entry_uri().is_some(); 414 + let is_editing_existing = doc.entry_ref().is_some(); 241 415 242 416 // Validate that we have required fields 243 417 let can_publish = !doc.title().trim().is_empty() && !doc.content().trim().is_empty(); ··· 268 442 is_publishing.set(true); 269 443 error_message.set(None); 270 444 271 - match publish_entry(&fetcher, &doc_snapshot, notebook.as_deref(), &draft_key).await { 445 + let mut doc_snapshot = doc_snapshot; 446 + match publish_entry(&fetcher, &mut doc_snapshot, notebook.as_deref(), &draft_key).await { 272 447 Ok(result) => { 273 448 success_uri.set(Some(result.uri().clone())); 274 449 }
+18 -8
crates/weaver-app/src/components/editor/storage.rs
··· 13 13 #[cfg(all(target_family = "wasm", target_os = "unknown"))] 14 14 use gloo_storage::{LocalStorage, Storage}; 15 15 use jacquard::IntoStatic; 16 - use jacquard::types::string::AtUri; 16 + use jacquard::types::string::{AtUri, Cid}; 17 + use weaver_api::com_atproto::repo::strong_ref::StrongRef; 17 18 use loro::cursor::Cursor; 18 19 use serde::{Deserialize, Serialize}; 19 20 ··· 58 59 /// AT-URI if editing an existing entry (None for new entries) 59 60 #[serde(default, skip_serializing_if = "Option::is_none")] 60 61 pub editing_uri: Option<String>, 62 + 63 + /// CID of the entry if editing an existing entry 64 + #[serde(default, skip_serializing_if = "Option::is_none")] 65 + pub editing_cid: Option<String>, 61 66 } 62 67 63 68 /// Build the full storage key from a draft key. ··· 88 93 snapshot: snapshot_b64, 89 94 cursor: doc.loro_cursor().cloned(), 90 95 cursor_offset: doc.cursor.read().offset, 91 - editing_uri: doc.entry_uri().map(|u| u.to_string()), 96 + editing_uri: doc.entry_ref().map(|r| r.uri.to_string()), 97 + editing_cid: doc.entry_ref().map(|r| r.cid.to_string()), 92 98 }; 93 99 LocalStorage::set(storage_key(key), &snapshot) 94 100 } ··· 104 110 pub fn load_from_storage(key: &str) -> Option<EditorDocument> { 105 111 let snapshot: EditorSnapshot = LocalStorage::get(storage_key(key)).ok()?; 106 112 107 - // Parse entry_uri from the snapshot 108 - let entry_uri = snapshot 113 + // Parse entry_ref from the snapshot (requires both URI and CID) 114 + let entry_ref = snapshot 109 115 .editing_uri 110 116 .as_ref() 111 - .and_then(|s| AtUri::new(s).ok()) 112 - .map(|u| u.into_static()); 117 + .zip(snapshot.editing_cid.as_ref()) 118 + .and_then(|(uri_str, cid_str)| { 119 + let uri = AtUri::new(uri_str).ok()?.into_static(); 120 + let cid = Cid::new(cid_str.as_bytes()).ok()?.into_static(); 121 + Some(StrongRef::new().uri(uri).cid(cid).build()) 122 + }); 113 123 114 124 // Try to restore from CRDT snapshot first 115 125 if let Some(ref snapshot_b64) = snapshot.snapshot { ··· 121 131 ); 122 132 // Verify the content matches (sanity check) 123 133 if doc.content() == snapshot.content { 124 - doc.set_entry_uri(entry_uri); 134 + doc.set_entry_ref(entry_ref.clone()); 125 135 return Some(doc); 126 136 } 127 137 tracing::warn!("Snapshot content mismatch, falling back to text content"); ··· 132 142 let mut doc = EditorDocument::new(snapshot.content); 133 143 doc.cursor.write().offset = snapshot.cursor_offset.min(doc.len_chars()); 134 144 doc.sync_loro_cursor(); 135 - doc.set_entry_uri(entry_uri); 145 + doc.set_entry_ref(entry_ref); 136 146 Some(doc) 137 147 } 138 148
+691
crates/weaver-app/src/components/editor/sync.rs
··· 1 + //! PDS synchronization for editor edit state. 2 + //! 3 + //! This module handles syncing the editor's Loro CRDT document to AT Protocol 4 + //! edit records (`sh.weaver.edit.root` and `sh.weaver.edit.diff`). 5 + //! 6 + //! ## Edit State Structure 7 + //! 8 + //! - `sh.weaver.edit.root`: The starting point for an edit session, containing 9 + //! a full Loro snapshot and a reference to the entry being edited. 10 + //! - `sh.weaver.edit.diff`: Incremental updates since the root (or previous diff), 11 + //! containing only the Loro delta bytes. 12 + //! 13 + //! ## Sync Flow 14 + //! 15 + //! 1. **First sync**: Create a root record with a full snapshot 16 + //! 2. **Subsequent syncs**: Create diff records with deltas since last sync 17 + //! 3. **Loading**: Find root via constellation backlinks, fetch all diffs, apply in order 18 + 19 + use std::collections::BTreeMap; 20 + 21 + use jacquard::cowstr::ToCowStr; 22 + use jacquard::prelude::*; 23 + use jacquard::types::blob::MimeType; 24 + use jacquard::types::collection::Collection; 25 + use jacquard::types::ident::AtIdentifier; 26 + use jacquard::types::recordkey::RecordKey; 27 + use jacquard::types::string::{AtUri, Cid, Did, Nsid}; 28 + use jacquard::types::tid::Ticker; 29 + use jacquard::types::uri::Uri; 30 + use jacquard::url::Url; 31 + use jacquard::{CowStr, IntoStatic, to_data}; 32 + use weaver_api::com_atproto::repo::create_record::CreateRecord; 33 + use weaver_api::com_atproto::repo::strong_ref::StrongRef; 34 + use weaver_api::com_atproto::sync::get_blob::GetBlob; 35 + use weaver_api::sh_weaver::edit::diff::Diff; 36 + use weaver_api::sh_weaver::edit::root::Root; 37 + use weaver_api::sh_weaver::edit::{DocRef, DocRefValue, DraftRef, EntryRef}; 38 + use weaver_common::constellation::{GetBacklinksQuery, RecordId}; 39 + use weaver_common::{WeaverError, WeaverExt}; 40 + 41 + use crate::fetch::Fetcher; 42 + 43 + use super::document::EditorDocument; 44 + 45 + const ROOT_NSID: &str = "sh.weaver.edit.root"; 46 + const DIFF_NSID: &str = "sh.weaver.edit.diff"; 47 + const CONSTELLATION_URL: &str = "https://constellation.microcosm.blue"; 48 + 49 + /// Build a DocRef for either a published entry or an unpublished draft. 50 + /// 51 + /// If entry_uri and entry_cid are provided, creates an EntryRef. 52 + /// Otherwise, creates a DraftRef with the given draft key. 53 + fn build_doc_ref( 54 + draft_key: &str, 55 + entry_uri: Option<&AtUri<'_>>, 56 + entry_cid: Option<&Cid<'_>>, 57 + ) -> DocRef<'static> { 58 + match (entry_uri, entry_cid) { 59 + (Some(uri), Some(cid)) => DocRef { 60 + value: DocRefValue::EntryRef(Box::new(EntryRef { 61 + entry: StrongRef::new() 62 + .uri(uri.clone().into_static()) 63 + .cid(cid.clone().into_static()) 64 + .build(), 65 + extra_data: None, 66 + })), 67 + extra_data: None, 68 + }, 69 + _ => DocRef { 70 + value: DocRefValue::DraftRef(Box::new(DraftRef { 71 + draft_key: CowStr::from(draft_key.to_string()), 72 + extra_data: None, 73 + })), 74 + extra_data: None, 75 + }, 76 + } 77 + } 78 + 79 + /// Result of a sync operation. 80 + #[derive(Clone, Debug)] 81 + pub enum SyncResult { 82 + /// Created a new root record (first sync) 83 + CreatedRoot { 84 + uri: AtUri<'static>, 85 + cid: Cid<'static>, 86 + }, 87 + /// Created a new diff record 88 + CreatedDiff { 89 + uri: AtUri<'static>, 90 + cid: Cid<'static>, 91 + }, 92 + /// No changes to sync 93 + NoChanges, 94 + } 95 + 96 + /// Find the edit root for an entry using constellation backlinks. 97 + /// 98 + /// Queries constellation for `sh.weaver.edit.root` records that reference 99 + /// the given entry URI via the `.doc.value.entry.uri` path. 100 + #[allow(dead_code)] 101 + pub async fn find_edit_root_for_entry( 102 + fetcher: &Fetcher, 103 + entry_uri: &AtUri<'_>, 104 + ) -> Result<Option<RecordId<'static>>, WeaverError> { 105 + let constellation_url = Url::parse(CONSTELLATION_URL) 106 + .map_err(|e| WeaverError::InvalidNotebook(format!("Invalid constellation URL: {}", e)))?; 107 + 108 + let query = GetBacklinksQuery { 109 + subject: Uri::At(entry_uri.clone().into_static()), 110 + source: format!("{}:.doc.value.entry.uri", ROOT_NSID).into(), 111 + cursor: None, 112 + did: vec![], 113 + limit: 1, 114 + }; 115 + 116 + let response = fetcher 117 + .client 118 + .xrpc(constellation_url) 119 + .send(&query) 120 + .await 121 + .map_err(|e| WeaverError::InvalidNotebook(format!("Constellation query failed: {}", e)))?; 122 + 123 + let output = response.into_output().map_err(|e| { 124 + WeaverError::InvalidNotebook(format!("Failed to parse constellation response: {}", e)) 125 + })?; 126 + 127 + Ok(output.records.into_iter().next().map(|r| r.into_static())) 128 + } 129 + 130 + /// Find all diffs for a root record using constellation backlinks. 131 + #[allow(dead_code)] 132 + pub async fn find_diffs_for_root( 133 + fetcher: &Fetcher, 134 + root_uri: &AtUri<'_>, 135 + ) -> Result<Vec<RecordId<'static>>, WeaverError> { 136 + let constellation_url = Url::parse(CONSTELLATION_URL) 137 + .map_err(|e| WeaverError::InvalidNotebook(format!("Invalid constellation URL: {}", e)))?; 138 + 139 + let mut all_diffs = Vec::new(); 140 + let mut cursor: Option<String> = None; 141 + 142 + loop { 143 + let query = GetBacklinksQuery { 144 + subject: Uri::At(root_uri.clone().into_static()), 145 + source: format!("{}:.root.uri", DIFF_NSID).into(), 146 + cursor: cursor.map(Into::into), 147 + did: vec![], 148 + limit: 100, 149 + }; 150 + 151 + let response = fetcher 152 + .client 153 + .xrpc(constellation_url.clone()) 154 + .send(&query) 155 + .await 156 + .map_err(|e| { 157 + WeaverError::InvalidNotebook(format!("Constellation query failed: {}", e)) 158 + })?; 159 + 160 + let output = response.into_output().map_err(|e| { 161 + WeaverError::InvalidNotebook(format!("Failed to parse constellation response: {}", e)) 162 + })?; 163 + 164 + all_diffs.extend(output.records.into_iter().map(|r| r.into_static())); 165 + 166 + match output.cursor { 167 + Some(c) => cursor = Some(c.to_string()), 168 + None => break, 169 + } 170 + } 171 + 172 + Ok(all_diffs) 173 + } 174 + 175 + /// Create the edit root record for an entry. 176 + /// 177 + /// Uploads the current Loro snapshot as a blob and creates an `sh.weaver.edit.root` 178 + /// record referencing the entry (or draft key if unpublished). 179 + /// 180 + /// # Arguments 181 + /// * `fetcher` - The authenticated fetcher 182 + /// * `doc` - The editor document 183 + /// * `draft_key` - The draft key (used for unpublished entries) 184 + /// * `entry_uri` - Optional AT-URI of the published entry 185 + /// * `entry_cid` - Optional CID of the published entry 186 + pub async fn create_edit_root( 187 + fetcher: &Fetcher, 188 + doc: &EditorDocument, 189 + draft_key: &str, 190 + entry_uri: Option<&AtUri<'_>>, 191 + entry_cid: Option<&Cid<'_>>, 192 + ) -> Result<(AtUri<'static>, Cid<'static>), WeaverError> { 193 + let client = fetcher.get_client(); 194 + let did = fetcher 195 + .current_did() 196 + .await 197 + .ok_or_else(|| WeaverError::InvalidNotebook("Not authenticated".into()))?; 198 + 199 + // Export full snapshot 200 + let snapshot = doc.export_snapshot(); 201 + 202 + // Upload snapshot blob 203 + let mime_type = MimeType::new_static("application/octet-stream"); 204 + let blob_ref = client 205 + .upload_blob(snapshot, mime_type) 206 + .await 207 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to upload snapshot: {}", e)))?; 208 + 209 + // Build DocRef - use EntryRef if published, DraftRef if not 210 + let doc_ref = build_doc_ref(draft_key, entry_uri, entry_cid); 211 + 212 + // Build root record 213 + let root = Root::new().doc(doc_ref).snapshot(blob_ref).build(); 214 + 215 + let root_data = to_data(&root) 216 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to serialize root: {}", e)))?; 217 + 218 + // Generate TID for the root rkey 219 + let root_tid = Ticker::new().next(None); 220 + let rkey = RecordKey::any(root_tid.as_str()) 221 + .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 222 + 223 + let collection = Nsid::new(ROOT_NSID).map_err(|e| WeaverError::AtprotoString(e))?; 224 + 225 + let request = CreateRecord::new() 226 + .repo(AtIdentifier::Did(did)) 227 + .collection(collection) 228 + .rkey(rkey) 229 + .record(root_data) 230 + .build(); 231 + 232 + let response = fetcher 233 + .send(request) 234 + .await 235 + .map_err(jacquard::client::AgentError::from)?; 236 + 237 + let output = response 238 + .into_output() 239 + .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 240 + 241 + Ok((output.uri.into_static(), output.cid.into_static())) 242 + } 243 + 244 + /// Create a diff record with updates since the last sync. 245 + /// 246 + /// # Arguments 247 + /// * `fetcher` - The authenticated fetcher 248 + /// * `doc` - The editor document 249 + /// * `root_uri` - URI of the edit root 250 + /// * `root_cid` - CID of the edit root 251 + /// * `prev_diff` - Optional reference to the previous diff 252 + /// * `draft_key` - The draft key (used for doc reference) 253 + /// * `entry_uri` - Optional AT-URI of the published entry 254 + /// * `entry_cid` - Optional CID of the published entry 255 + pub async fn create_diff( 256 + fetcher: &Fetcher, 257 + doc: &EditorDocument, 258 + root_uri: &AtUri<'_>, 259 + root_cid: &Cid<'_>, 260 + prev_diff: Option<(&AtUri<'_>, &Cid<'_>)>, 261 + draft_key: &str, 262 + entry_uri: Option<&AtUri<'_>>, 263 + entry_cid: Option<&Cid<'_>>, 264 + ) -> Result<Option<(AtUri<'static>, Cid<'static>)>, WeaverError> { 265 + // Export updates since last sync 266 + let updates = match doc.export_updates_since_sync() { 267 + Some(u) => u, 268 + None => return Ok(None), // No changes 269 + }; 270 + 271 + let client = fetcher.get_client(); 272 + let did = fetcher 273 + .current_did() 274 + .await 275 + .ok_or_else(|| WeaverError::InvalidNotebook("Not authenticated".into()))?; 276 + 277 + // Upload updates blob 278 + let mime_type = MimeType::new_static("application/octet-stream"); 279 + let blob_ref = client 280 + .upload_blob(updates, mime_type) 281 + .await 282 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to upload diff: {}", e)))?; 283 + 284 + // Build DocRef - use EntryRef if published, DraftRef if not 285 + let doc_ref = build_doc_ref(draft_key, entry_uri, entry_cid); 286 + 287 + // Build root reference 288 + let root_ref = StrongRef::new() 289 + .uri(root_uri.clone().into_static()) 290 + .cid(root_cid.clone().into_static()) 291 + .build(); 292 + 293 + // Build prev reference if we have a previous diff 294 + let prev_ref = prev_diff.map(|(uri, cid)| { 295 + StrongRef::new() 296 + .uri(uri.clone().into_static()) 297 + .cid(cid.clone().into_static()) 298 + .build() 299 + }); 300 + 301 + // Build diff record 302 + let diff = Diff::new() 303 + .doc(doc_ref) 304 + .root(root_ref) 305 + .snapshot(blob_ref) 306 + .maybe_prev(prev_ref) 307 + .build(); 308 + 309 + let diff_data = to_data(&diff) 310 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to serialize diff: {}", e)))?; 311 + 312 + // Generate TID for the diff rkey 313 + let diff_tid = Ticker::new().next(None); 314 + let rkey = RecordKey::any(diff_tid.as_str()) 315 + .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 316 + 317 + let collection = Nsid::new(DIFF_NSID).map_err(|e| WeaverError::AtprotoString(e))?; 318 + 319 + let request = CreateRecord::new() 320 + .repo(AtIdentifier::Did(did)) 321 + .collection(collection) 322 + .rkey(rkey) 323 + .record(diff_data) 324 + .build(); 325 + 326 + let response = fetcher 327 + .send(request) 328 + .await 329 + .map_err(jacquard::client::AgentError::from)?; 330 + 331 + let output = response 332 + .into_output() 333 + .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 334 + 335 + Ok(Some((output.uri.into_static(), output.cid.into_static()))) 336 + } 337 + 338 + /// Sync the document to the PDS. 339 + /// 340 + /// If no edit root exists, creates one with a full snapshot. 341 + /// If a root exists, creates a diff with updates since last sync. 342 + /// 343 + /// Updates the document's sync state on success. 344 + /// 345 + /// # Arguments 346 + /// * `fetcher` - The authenticated fetcher 347 + /// * `doc` - The editor document (mutable to update sync state) 348 + /// * `draft_key` - The draft key for this document 349 + /// 350 + /// # Returns 351 + /// The sync result indicating what was created. 352 + pub async fn sync_to_pds( 353 + fetcher: &Fetcher, 354 + doc: &mut EditorDocument, 355 + draft_key: &str, 356 + ) -> Result<SyncResult, WeaverError> { 357 + // Check if we have changes to sync 358 + if !doc.has_unsync_changes() { 359 + return Ok(SyncResult::NoChanges); 360 + } 361 + 362 + // Get entry info if published 363 + let entry_ref = doc.entry_ref(); 364 + 365 + if doc.edit_root().is_none() { 366 + // First sync - create root 367 + let (root_uri, root_cid) = create_edit_root( 368 + fetcher, 369 + doc, 370 + draft_key, 371 + entry_ref.as_ref().map(|r| &r.uri), 372 + entry_ref.as_ref().map(|r| &r.cid), 373 + ) 374 + .await?; 375 + 376 + // Build StrongRef for the root 377 + let root_ref = StrongRef::new() 378 + .uri(root_uri.clone()) 379 + .cid(root_cid.clone()) 380 + .build(); 381 + 382 + // Update document state 383 + doc.set_edit_root(Some(root_ref)); 384 + doc.set_last_diff(None); 385 + doc.mark_synced(); 386 + 387 + Ok(SyncResult::CreatedRoot { 388 + uri: root_uri, 389 + cid: root_cid, 390 + }) 391 + } else { 392 + // Subsequent sync - create diff 393 + let root_ref = doc.edit_root().unwrap(); 394 + let prev_diff = doc.last_diff(); 395 + 396 + let result = create_diff( 397 + fetcher, 398 + doc, 399 + &root_ref.uri, 400 + &root_ref.cid, 401 + prev_diff.as_ref().map(|d| (&d.uri, &d.cid)), 402 + draft_key, 403 + entry_ref.as_ref().map(|r| &r.uri), 404 + entry_ref.as_ref().map(|r| &r.cid), 405 + ) 406 + .await?; 407 + 408 + match result { 409 + Some((diff_uri, diff_cid)) => { 410 + // Build StrongRef for the diff 411 + let diff_ref = StrongRef::new() 412 + .uri(diff_uri.clone()) 413 + .cid(diff_cid.clone()) 414 + .build(); 415 + 416 + doc.set_last_diff(Some(diff_ref)); 417 + doc.mark_synced(); 418 + 419 + Ok(SyncResult::CreatedDiff { 420 + uri: diff_uri, 421 + cid: diff_cid, 422 + }) 423 + } 424 + None => Ok(SyncResult::NoChanges), 425 + } 426 + } 427 + } 428 + 429 + /// Result of loading edit state from PDS. 430 + #[derive(Clone, Debug)] 431 + pub struct PdsEditState { 432 + /// The root record reference 433 + pub root_ref: StrongRef<'static>, 434 + /// The latest diff reference (if any diffs exist) 435 + pub last_diff_ref: Option<StrongRef<'static>>, 436 + /// The Loro snapshot bytes from the root 437 + pub root_snapshot: Vec<u8>, 438 + /// All diff update bytes in order (oldest first, by TID) 439 + pub diff_updates: Vec<Vec<u8>>, 440 + } 441 + 442 + /// Fetch a blob from the PDS. 443 + async fn fetch_blob( 444 + fetcher: &Fetcher, 445 + did: &Did<'_>, 446 + cid: &Cid<'_>, 447 + ) -> Result<Vec<u8>, WeaverError> { 448 + let pds_url = fetcher 449 + .client 450 + .pds_for_did(did) 451 + .await 452 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to resolve DID: {}", e)))?; 453 + 454 + let request = GetBlob::new().did(did.clone()).cid(cid.clone()).build(); 455 + 456 + let response = fetcher 457 + .client 458 + .xrpc(pds_url) 459 + .send(&request) 460 + .await 461 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to fetch blob: {}", e)))?; 462 + 463 + let output = response.into_output().map_err(|e| { 464 + WeaverError::InvalidNotebook(format!("Failed to parse blob response: {}", e)) 465 + })?; 466 + 467 + Ok(output.body.to_vec()) 468 + } 469 + 470 + /// Load edit state from the PDS for an entry. 471 + /// 472 + /// Finds the edit root via constellation backlinks, fetches all diffs, 473 + /// and returns the snapshot + updates needed to reconstruct the document. 474 + /// 475 + /// # Arguments 476 + /// * `fetcher` - The authenticated fetcher 477 + /// * `entry_uri` - The AT-URI of the entry to load edit state for 478 + /// 479 + /// # Returns 480 + /// The edit state if found, or None if no edit root exists for this entry. 481 + pub async fn load_edit_state_from_pds( 482 + fetcher: &Fetcher, 483 + entry_uri: &AtUri<'_>, 484 + ) -> Result<Option<PdsEditState>, WeaverError> { 485 + // Find the edit root for this entry 486 + let root_id = match find_edit_root_for_entry(fetcher, entry_uri).await? { 487 + Some(id) => id, 488 + None => return Ok(None), 489 + }; 490 + 491 + // Build root URI 492 + let root_uri = AtUri::new(&format!( 493 + "at://{}/{}/{}", 494 + root_id.did(), 495 + ROOT_NSID, 496 + root_id.rkey().as_ref() 497 + )) 498 + .map_err(|e| WeaverError::InvalidNotebook(format!("Invalid root URI: {}", e)))? 499 + .into_static(); 500 + 501 + // Fetch the root record using get_record helper 502 + let root_response = fetcher 503 + .client 504 + .get_record::<Root>(&root_uri) 505 + .await 506 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to fetch root: {}", e)))?; 507 + 508 + let root_output = root_response 509 + .into_output() 510 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to parse root: {}", e)))?; 511 + 512 + let root_cid = root_output 513 + .cid 514 + .ok_or_else(|| WeaverError::InvalidNotebook("Root response missing CID".into()))?; 515 + 516 + let root_ref = StrongRef::new() 517 + .uri(root_uri.clone()) 518 + .cid(root_cid.into_static()) 519 + .build(); 520 + 521 + // Fetch the root snapshot blob 522 + let root_snapshot = fetch_blob( 523 + fetcher, 524 + &root_id.did(), 525 + root_output.value.snapshot.blob().cid(), 526 + ) 527 + .await?; 528 + 529 + // Find all diffs for this root 530 + let diff_ids = find_diffs_for_root(fetcher, &root_uri).await?; 531 + 532 + if diff_ids.is_empty() { 533 + return Ok(Some(PdsEditState { 534 + root_ref, 535 + last_diff_ref: None, 536 + root_snapshot, 537 + diff_updates: vec![], 538 + })); 539 + } 540 + 541 + // Fetch all diffs and store in BTreeMap keyed by rkey (TID) for sorted order 542 + // TIDs are lexicographically sortable timestamps 543 + let mut diffs_by_rkey: BTreeMap< 544 + CowStr<'static>, 545 + (Diff<'static>, Cid<'static>, AtUri<'static>), 546 + > = BTreeMap::new(); 547 + 548 + for diff_id in &diff_ids { 549 + let rkey = diff_id.rkey(); 550 + let rkey_str: &str = rkey.as_ref(); 551 + let diff_uri = AtUri::new(&format!( 552 + "at://{}/{}/{}", 553 + diff_id.did(), 554 + DIFF_NSID, 555 + rkey_str 556 + )) 557 + .map_err(|e| WeaverError::InvalidNotebook(format!("Invalid diff URI: {}", e)))? 558 + .into_static(); 559 + 560 + let diff_response = fetcher 561 + .client 562 + .get_record::<Diff>(&diff_uri) 563 + .await 564 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to fetch diff: {}", e)))?; 565 + 566 + let diff_output = diff_response 567 + .into_output() 568 + .map_err(|e| WeaverError::InvalidNotebook(format!("Failed to parse diff: {}", e)))?; 569 + 570 + let diff_cid = diff_output 571 + .cid 572 + .ok_or_else(|| WeaverError::InvalidNotebook("Diff response missing CID".into()))?; 573 + 574 + diffs_by_rkey.insert( 575 + rkey_str.to_cowstr().into_static(), 576 + ( 577 + diff_output.value.into_static(), 578 + diff_cid.into_static(), 579 + diff_uri, 580 + ), 581 + ); 582 + } 583 + 584 + // Fetch all diff blobs in TID order (BTreeMap iterates in sorted order) 585 + let mut diff_updates = Vec::new(); 586 + let mut last_diff_ref = None; 587 + 588 + for (_rkey, (diff, cid, uri)) in &diffs_by_rkey { 589 + let blob_bytes = fetch_blob(fetcher, &root_id.did(), diff.snapshot.blob().cid()).await?; 590 + diff_updates.push(blob_bytes); 591 + 592 + // Track the last diff (will be the one with highest TID after iteration) 593 + last_diff_ref = Some(StrongRef::new().uri(uri.clone()).cid(cid.clone()).build()); 594 + } 595 + 596 + Ok(Some(PdsEditState { 597 + root_ref, 598 + last_diff_ref, 599 + root_snapshot, 600 + diff_updates, 601 + })) 602 + } 603 + 604 + /// Load an EditorDocument by merging local storage and PDS state. 605 + /// 606 + /// This is the main entry point for loading a document with full sync support. 607 + /// It: 608 + /// 1. Loads from localStorage (if available) 609 + /// 2. Loads from PDS (if available) 610 + /// 3. Merges both using Loro's CRDT merge 611 + /// 612 + /// The result is a document with all changes from both sources. 613 + /// 614 + /// # Arguments 615 + /// * `fetcher` - The authenticated fetcher 616 + /// * `draft_key` - The localStorage key for this draft 617 + /// * `entry_uri` - Optional AT-URI if editing an existing entry 618 + /// 619 + /// # Returns 620 + /// A merged EditorDocument, or None if no state exists anywhere. 621 + pub async fn load_and_merge_document( 622 + fetcher: &Fetcher, 623 + draft_key: &str, 624 + entry_uri: Option<&AtUri<'_>>, 625 + ) -> Result<Option<EditorDocument>, WeaverError> { 626 + use super::storage::load_from_storage; 627 + 628 + // Load from localStorage 629 + let local_doc = load_from_storage(draft_key); 630 + 631 + // Load from PDS (only if we have an entry URI) 632 + let pds_state = if let Some(uri) = entry_uri { 633 + load_edit_state_from_pds(fetcher, uri).await? 634 + } else { 635 + None 636 + }; 637 + 638 + match (local_doc, pds_state) { 639 + (None, None) => Ok(None), 640 + 641 + (Some(doc), None) => { 642 + // Only local state exists 643 + tracing::debug!("Loaded document from localStorage only"); 644 + Ok(Some(doc)) 645 + } 646 + 647 + (None, Some(pds)) => { 648 + // Only PDS state exists - reconstruct from snapshot + diffs 649 + tracing::debug!("Loaded document from PDS only"); 650 + let mut doc = EditorDocument::from_snapshot(&pds.root_snapshot, None, 0); 651 + 652 + // Apply all diffs in order 653 + for updates in &pds.diff_updates { 654 + if let Err(e) = doc.import_updates(updates) { 655 + tracing::warn!("Failed to apply diff update: {:?}", e); 656 + } 657 + } 658 + 659 + // Set sync state so we don't re-upload what we just downloaded 660 + doc.set_synced_from_pds(pds.root_ref, pds.last_diff_ref); 661 + 662 + Ok(Some(doc)) 663 + } 664 + 665 + (Some(mut local_doc), Some(pds)) => { 666 + // Both exist - merge using CRDT 667 + tracing::debug!("Merging document from localStorage and PDS"); 668 + 669 + // Import PDS root snapshot into local doc 670 + // Loro will automatically merge concurrent changes 671 + if let Err(e) = local_doc.import_updates(&pds.root_snapshot) { 672 + tracing::warn!("Failed to merge PDS root snapshot: {:?}", e); 673 + } 674 + 675 + // Import all diffs 676 + for updates in &pds.diff_updates { 677 + if let Err(e) = local_doc.import_updates(updates) { 678 + tracing::warn!("Failed to merge PDS diff: {:?}", e); 679 + } 680 + } 681 + 682 + // Update sync state 683 + // We keep the PDS root/diff refs since that's where we'll push updates 684 + local_doc.set_edit_root(Some(pds.root_ref)); 685 + local_doc.set_last_diff(pds.last_diff_ref); 686 + // Don't call set_synced_from_pds - local changes still need syncing 687 + 688 + Ok(Some(local_doc)) 689 + } 690 + } 691 + }
+109 -42
crates/weaver-app/src/components/editor/writer.rs
··· 7 7 //! represent consumed formatting characters. 8 8 9 9 use super::offset_map::{OffsetMapping, RenderResult}; 10 + use jacquard::types::{ident::AtIdentifier, string::Rkey}; 10 11 use loro::LoroText; 11 12 use markdown_weaver::{ 12 13 Alignment, BlockQuoteKind, CodeBlockKind, CowStr, EmbedType, Event, LinkType, Tag, ··· 138 139 139 140 /// Concrete image resolver that maps image names to URLs. 140 141 /// 141 - /// Supports two states for images: 142 + /// Resolved image path type 143 + #[derive(Clone, Debug)] 144 + enum ResolvedImage { 145 + /// Data URL for immediate preview (still uploading) 146 + Pending(String), 147 + /// Draft image: `/image/{ident}/draft/{blob_rkey}/{name}` 148 + Draft { 149 + blob_rkey: Rkey<'static>, 150 + ident: AtIdentifier<'static>, 151 + }, 152 + /// Published image: `/image/{ident}/{entry_rkey}/{name}` 153 + Published { 154 + entry_rkey: Rkey<'static>, 155 + ident: AtIdentifier<'static>, 156 + }, 157 + } 158 + 159 + /// Resolves image paths in the editor. 160 + /// 161 + /// Supports three states for images: 142 162 /// - Pending: uses data URL for immediate preview while upload is in progress 143 - /// - Uploaded: uses CDN URL format `https://cdn.bsky.app/img/feed_fullsize/plain/{did}/{cid}@{format}` 163 + /// - Draft: uses path format `/image/{did}/draft/{blob_rkey}/{name}` 164 + /// - Published: uses path format `/image/{did}/{entry_rkey}/{name}` 144 165 /// 145 166 /// Image URLs in markdown use the format `/image/{name}`. 146 167 #[derive(Clone, Default)] 147 168 pub struct EditorImageResolver { 148 - /// Pending images: name -> data URL (still uploading) 149 - pending: std::collections::HashMap<String, String>, 150 - /// Uploaded images: name -> (CID string, DID string, format) 151 - uploaded: std::collections::HashMap<String, (String, String, String)>, 169 + /// All resolved images: name -> resolved path info 170 + images: std::collections::HashMap<String, ResolvedImage>, 152 171 } 153 172 154 173 impl EditorImageResolver { ··· 162 181 /// * `name` - The image name used in markdown (e.g., "photo.jpg") 163 182 /// * `data_url` - The base64 data URL for preview 164 183 pub fn add_pending(&mut self, name: String, data_url: String) { 165 - self.pending.insert(name, data_url); 184 + self.images.insert(name, ResolvedImage::Pending(data_url)); 185 + } 186 + 187 + /// Promote a pending image to uploaded (draft) status. 188 + /// 189 + /// # Arguments 190 + /// * `name` - The image name used in markdown 191 + /// * `blob_rkey` - The rkey of the PublishedBlob record 192 + /// * `ident` - The AT identifier (DID or handle) of the blob owner 193 + pub fn promote_to_uploaded( 194 + &mut self, 195 + name: &str, 196 + blob_rkey: Rkey<'static>, 197 + ident: AtIdentifier<'static>, 198 + ) { 199 + self.images.insert( 200 + name.to_string(), 201 + ResolvedImage::Draft { blob_rkey, ident }, 202 + ); 166 203 } 167 204 168 - /// Promote a pending image to uploaded status. 205 + /// Add an already-uploaded draft image. 169 206 /// 170 - /// Removes from pending and adds to uploaded with CDN info. 171 - pub fn promote_to_uploaded(&mut self, name: &str, cid: String, did: String, format: String) { 172 - self.pending.remove(name); 173 - self.uploaded.insert(name.to_string(), (cid, did, format)); 207 + /// # Arguments 208 + /// * `name` - The name/URL used in markdown (e.g., "photo.jpg") 209 + /// * `blob_rkey` - The rkey of the PublishedBlob record 210 + /// * `ident` - The AT identifier (DID or handle) of the blob owner 211 + pub fn add_uploaded( 212 + &mut self, 213 + name: String, 214 + blob_rkey: Rkey<'static>, 215 + ident: AtIdentifier<'static>, 216 + ) { 217 + self.images 218 + .insert(name, ResolvedImage::Draft { blob_rkey, ident }); 174 219 } 175 220 176 - /// Add an already-uploaded image. 221 + /// Add a published image. 177 222 /// 178 223 /// # Arguments 179 224 /// * `name` - The name/URL used in markdown (e.g., "photo.jpg") 180 - /// * `cid` - The blob CID 181 - /// * `did` - The DID of the blob owner 182 - /// * `format` - The image format (e.g., "jpeg", "png") 183 - pub fn add_uploaded(&mut self, name: String, cid: String, did: String, format: String) { 184 - self.uploaded.insert(name, (cid, did, format)); 225 + /// * `entry_rkey` - The rkey of the entry record containing this image 226 + /// * `ident` - The AT identifier (DID or handle) of the entry owner 227 + pub fn add_published( 228 + &mut self, 229 + name: String, 230 + entry_rkey: Rkey<'static>, 231 + ident: AtIdentifier<'static>, 232 + ) { 233 + self.images 234 + .insert(name, ResolvedImage::Published { entry_rkey, ident }); 185 235 } 186 236 187 237 /// Check if an image is pending upload. 188 238 pub fn is_pending(&self, name: &str) -> bool { 189 - self.pending.contains_key(name) 239 + matches!(self.images.get(name), Some(ResolvedImage::Pending(_))) 190 240 } 191 241 192 - /// Build a resolver from editor images and user DID. 242 + /// Build a resolver from editor images and user identifier. 243 + /// 244 + /// # Arguments 245 + /// * `images` - Iterator of editor images 246 + /// * `ident` - The AT identifier (DID or handle) of the user 247 + /// * `entry_rkey` - If Some, images are resolved as published (`/image/{ident}/{entry_rkey}/{name}`). 248 + /// If None, images are resolved as drafts using their `published_blob_uri`. 249 + /// 250 + /// For draft mode (entry_rkey=None), only images with a `published_blob_uri` are included. 251 + /// For published mode (entry_rkey=Some), all images are included. 193 252 pub fn from_images<'a>( 194 253 images: impl IntoIterator<Item = &'a super::document::EditorImage>, 195 - user_did: &str, 254 + ident: AtIdentifier<'static>, 255 + entry_rkey: Option<Rkey<'static>>, 196 256 ) -> Self { 257 + use jacquard::IntoStatic; 258 + 197 259 let mut resolver = Self::new(); 198 260 for editor_image in images { 199 261 // Get the name from the Image (use alt text as fallback if name is empty) ··· 208 270 continue; 209 271 } 210 272 211 - // Get CID and format from the blob ref 212 - let blob = editor_image.image.image.blob(); 213 - let cid = blob.cid().to_string(); 214 - let format = blob 215 - .mime_type 216 - .0 217 - .strip_prefix("image/") 218 - .unwrap_or("jpeg") 219 - .to_string(); 220 - 221 - resolver.add_uploaded(name, cid, user_did.to_string(), format); 273 + match &entry_rkey { 274 + // Published mode: use entry rkey for all images 275 + Some(rkey) => { 276 + resolver.add_published(name, rkey.clone(), ident.clone()); 277 + } 278 + // Draft mode: use published_blob_uri rkey 279 + None => { 280 + let blob_rkey = match &editor_image.published_blob_uri { 281 + Some(uri) => match uri.rkey() { 282 + Some(rkey) => rkey.0.clone().into_static(), 283 + None => continue, 284 + }, 285 + None => continue, 286 + }; 287 + resolver.add_uploaded(name, blob_rkey, ident.clone()); 288 + } 289 + } 222 290 } 223 291 resolver 224 292 } ··· 229 297 // Extract image name from /image/{name} format 230 298 let name = url.strip_prefix("/image/").unwrap_or(url); 231 299 232 - // Check pending first (data URL for immediate preview) 233 - if let Some(data_url) = self.pending.get(name) { 234 - return Some(data_url.clone()); 300 + let resolved = self.images.get(name)?; 301 + match resolved { 302 + ResolvedImage::Pending(data_url) => Some(data_url.clone()), 303 + ResolvedImage::Draft { blob_rkey, ident } => { 304 + Some(format!("/image/{}/draft/{}/{}", ident, blob_rkey, name)) 305 + } 306 + ResolvedImage::Published { entry_rkey, ident } => { 307 + Some(format!("/image/{}/{}/{}", ident, entry_rkey, name)) 308 + } 235 309 } 236 - 237 - // Then check uploaded (CDN URL) 238 - let (cid, did, format) = self.uploaded.get(name)?; 239 - Some(format!( 240 - "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@{}", 241 - did, cid, format 242 - )) 243 310 } 244 311 } 245 312
+162 -6
crates/weaver-app/src/main.rs
··· 54 54 #[layout(Navbar)] 55 55 #[route("/")] 56 56 Home {}, 57 - #[route("/editor")] 58 - Editor {}, 57 + #[route("/editor?:entry")] 58 + Editor { entry: Option<String> }, 59 59 #[layout(ErrorLayout)] 60 60 #[nest("/record")] 61 61 #[layout(RecordIndex)] ··· 304 304 #[cfg(all(feature = "fullstack-server", feature = "server"))] 305 305 #[get("/{notebook}/image/{name}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 306 306 pub async fn image_named(notebook: SmolStr, name: SmolStr) -> Result<axum::response::Response> { 307 - use axum::{http::header::CONTENT_TYPE, response::IntoResponse}; 307 + use axum::{ 308 + http::header::{CACHE_CONTROL, CONTENT_TYPE}, 309 + response::IntoResponse, 310 + }; 308 311 use mime_sniffer::MimeTypeSniffer; 309 312 if let Some(bytes) = blob_cache.get_named(&name) { 310 313 let blob = bytes.clone(); 311 314 let mime = blob.sniff_mime_type().unwrap_or("image/jpg"); 312 - Ok(([(CONTENT_TYPE, mime)], bytes).into_response()) 315 + // Blobs are immutable by CID - cache aggressively 316 + Ok(( 317 + [ 318 + (CONTENT_TYPE, mime), 319 + (CACHE_CONTROL, "public, max-age=31536000, immutable"), 320 + ], 321 + bytes, 322 + ) 323 + .into_response()) 313 324 } else { 314 325 Err(CapturedError::from_display("no image")) 315 326 } ··· 318 329 #[cfg(all(feature = "fullstack-server", feature = "server"))] 319 330 #[get("/{notebook}/blob/{cid}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 320 331 pub async fn blob(notebook: SmolStr, cid: SmolStr) -> Result<axum::response::Response> { 321 - use axum::{http::header::CONTENT_TYPE, response::IntoResponse}; 332 + use axum::{ 333 + http::header::{CACHE_CONTROL, CONTENT_TYPE}, 334 + response::IntoResponse, 335 + }; 322 336 use mime_sniffer::MimeTypeSniffer; 323 337 if let Some(bytes) = blob_cache.get_cid(&Cid::new_owned(cid.as_bytes())?) { 324 338 let blob = bytes.clone(); 325 339 let mime = blob.sniff_mime_type().unwrap_or("application/octet-stream"); 326 - Ok(([(CONTENT_TYPE, mime)], bytes).into_response()) 340 + // Blobs are immutable by CID - cache aggressively 341 + Ok(( 342 + [ 343 + (CONTENT_TYPE, mime), 344 + (CACHE_CONTROL, "public, max-age=31536000, immutable"), 345 + ], 346 + bytes, 347 + ) 348 + .into_response()) 327 349 } else { 328 350 Err(CapturedError::from_display("no blob")) 351 + } 352 + } 353 + 354 + // New image routes with unified /image/ prefix 355 + // Route: /image/{notebook}/{name} - notebook entry image by name 356 + #[cfg(all(feature = "fullstack-server", feature = "server"))] 357 + #[get("/image/{notebook}/{name}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 358 + pub async fn image_notebook(notebook: SmolStr, name: SmolStr) -> Result<axum::response::Response> { 359 + use axum::{ 360 + http::header::{CACHE_CONTROL, CONTENT_TYPE}, 361 + response::IntoResponse, 362 + }; 363 + use mime_sniffer::MimeTypeSniffer; 364 + 365 + // Try name-based lookup first (backwards compat with cached entries) 366 + if let Some(bytes) = blob_cache.get_named(&name) { 367 + let blob = bytes.clone(); 368 + let mime = blob.sniff_mime_type().unwrap_or("image/jpg"); 369 + return Ok(( 370 + [ 371 + (CONTENT_TYPE, mime), 372 + (CACHE_CONTROL, "public, max-age=31536000, immutable"), 373 + ], 374 + bytes, 375 + ) 376 + .into_response()); 377 + } 378 + 379 + // Try to resolve from notebook 380 + match blob_cache.resolve_from_notebook(&notebook, &name).await { 381 + Ok(bytes) => { 382 + let blob = bytes.clone(); 383 + let mime = blob.sniff_mime_type().unwrap_or("image/jpg"); 384 + Ok(( 385 + [ 386 + (CONTENT_TYPE, mime), 387 + (CACHE_CONTROL, "public, max-age=31536000, immutable"), 388 + ], 389 + bytes, 390 + ) 391 + .into_response()) 392 + } 393 + Err(e) => Err(e), 394 + } 395 + } 396 + 397 + // Route: /image/{ident}/draft/{blob_rkey} - draft image (unpublished) 398 + // Route: /image/{ident}/draft/{blob_rkey}/{name} - draft image with name 399 + #[cfg(all(feature = "fullstack-server", feature = "server"))] 400 + #[get("/image/{ident}/draft/{blob_rkey}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 401 + pub async fn image_draft(ident: SmolStr, blob_rkey: SmolStr) -> Result<axum::response::Response> { 402 + use axum::{ 403 + http::header::{CACHE_CONTROL, CONTENT_TYPE}, 404 + response::IntoResponse, 405 + }; 406 + use mime_sniffer::MimeTypeSniffer; 407 + 408 + let at_ident = AtIdentifier::new_owned(ident.clone()) 409 + .map_err(|e| CapturedError::from_display(format!("Invalid identifier '{}': {}", ident, e)))?; 410 + 411 + match blob_cache.resolve_from_draft(&at_ident, &blob_rkey).await { 412 + Ok(bytes) => { 413 + let blob = bytes.clone(); 414 + let mime = blob.sniff_mime_type().unwrap_or("image/jpg"); 415 + Ok(( 416 + [ 417 + (CONTENT_TYPE, mime), 418 + (CACHE_CONTROL, "public, max-age=31536000, immutable"), 419 + ], 420 + bytes, 421 + ) 422 + .into_response()) 423 + } 424 + Err(e) => Err(e), 425 + } 426 + } 427 + 428 + #[cfg(all(feature = "fullstack-server", feature = "server"))] 429 + #[get("/image/{ident}/draft/{blob_rkey}/{name}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 430 + pub async fn image_draft_named(ident: SmolStr, blob_rkey: SmolStr, name: SmolStr) -> Result<axum::response::Response> { 431 + use axum::{ 432 + http::header::{CACHE_CONTROL, CONTENT_TYPE}, 433 + response::IntoResponse, 434 + }; 435 + use mime_sniffer::MimeTypeSniffer; 436 + 437 + // Name is optional/decorative for drafts, just use the blob_rkey 438 + let at_ident = AtIdentifier::new_owned(ident.clone()) 439 + .map_err(|e| CapturedError::from_display(format!("Invalid identifier '{}': {}", ident, e)))?; 440 + 441 + match blob_cache.resolve_from_draft(&at_ident, &blob_rkey).await { 442 + Ok(bytes) => { 443 + let blob = bytes.clone(); 444 + let mime = blob.sniff_mime_type().unwrap_or("image/jpg"); 445 + Ok(( 446 + [ 447 + (CONTENT_TYPE, mime), 448 + (CACHE_CONTROL, "public, max-age=31536000, immutable"), 449 + ], 450 + bytes, 451 + ) 452 + .into_response()) 453 + } 454 + Err(e) => Err(e), 455 + } 456 + } 457 + 458 + // Route: /image/{ident}/{rkey}/{name} - published entry image 459 + #[cfg(all(feature = "fullstack-server", feature = "server"))] 460 + #[get("/image/{ident}/{rkey}/{name}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 461 + pub async fn image_entry(ident: SmolStr, rkey: SmolStr, name: SmolStr) -> Result<axum::response::Response> { 462 + use axum::{ 463 + http::header::{CACHE_CONTROL, CONTENT_TYPE}, 464 + response::IntoResponse, 465 + }; 466 + use mime_sniffer::MimeTypeSniffer; 467 + 468 + let at_ident = AtIdentifier::new_owned(ident.clone()) 469 + .map_err(|e| CapturedError::from_display(format!("Invalid identifier '{}': {}", ident, e)))?; 470 + 471 + match blob_cache.resolve_from_entry(&at_ident, &rkey, &name).await { 472 + Ok(bytes) => { 473 + let blob = bytes.clone(); 474 + let mime = blob.sniff_mime_type().unwrap_or("image/jpg"); 475 + Ok(( 476 + [ 477 + (CONTENT_TYPE, mime), 478 + (CACHE_CONTROL, "public, max-age=31536000, immutable"), 479 + ], 480 + bytes, 481 + ) 482 + .into_response()) 483 + } 484 + Err(e) => Err(e), 329 485 } 330 486 } 331 487
+4 -4
crates/weaver-app/src/views/editor.rs
··· 5 5 6 6 /// Editor page view. 7 7 /// 8 - /// Displays the markdown editor at the /editor route for testing during development. 9 - /// Eventually this will be integrated into the notebook editing workflow. 8 + /// Displays the markdown editor at the /editor route. 9 + /// Optionally loads an existing entry for editing via `?entry={at-uri}`. 10 10 #[component] 11 - pub fn Editor() -> Element { 11 + pub fn Editor(entry: Option<String>) -> Element { 12 12 rsx! { 13 13 EditorCss {} 14 14 div { class: "editor-page", 15 - MarkdownEditor { initial_content: None } 15 + MarkdownEditor { entry_uri: entry } 16 16 } 17 17 } 18 18 }
+24 -13
crates/weaver-common/src/agent.rs
··· 216 216 /// 3. If found: update the entry with new content 217 217 /// 4. If not found: create new entry and append to notebook's entry_list 218 218 /// 219 - /// Returns (entry_uri, was_created) 219 + /// Returns (entry_ref, was_created) 220 220 fn upsert_entry( 221 221 &self, 222 222 notebook_title: &str, 223 223 entry_title: &str, 224 224 entry: entry::Entry<'_>, 225 - ) -> impl Future<Output = Result<(AtUri<'static>, bool), WeaverError>> 225 + ) -> impl Future<Output = Result<(StrongRef<'static>, bool), WeaverError>> 226 226 where 227 227 Self: Sized, 228 228 { ··· 244 244 if let Ok(existing_entry) = existing.parse() { 245 245 if existing_entry.value.title == entry_title { 246 246 // Update existing entry 247 - self.update_record::<entry::Entry>(&entry_ref.uri, |e| { 248 - e.content = entry.content.clone(); 249 - e.embeds = entry.embeds.clone(); 250 - e.tags = entry.tags.clone(); 251 - }) 252 - .await?; 253 - return Ok((entry_ref.uri.clone().into_static(), false)); 247 + let output = self 248 + .update_record::<entry::Entry>(&entry_ref.uri, |e| { 249 + e.content = entry.content.clone(); 250 + e.embeds = entry.embeds.clone(); 251 + e.tags = entry.tags.clone(); 252 + }) 253 + .await?; 254 + let updated_ref = StrongRef::new() 255 + .uri(output.uri.into_static()) 256 + .cid(output.cid.into_static()) 257 + .build(); 258 + return Ok((updated_ref, false)); 254 259 } 255 260 } 256 261 } 257 262 258 263 // Entry doesn't exist, create it 259 264 let response = self.create_record(entry, None).await?; 260 - let entry_uri = response.uri.clone(); 265 + let new_ref = StrongRef::new() 266 + .uri(response.uri.clone().into_static()) 267 + .cid(response.cid.clone().into_static()) 268 + .build(); 261 269 262 270 // Add to notebook's entry_list 263 271 use weaver_api::sh_weaver::notebook::book::Book; 264 - let new_ref = StrongRef::new().uri(response.uri).cid(response.cid).build(); 272 + let notebook_entry_ref = StrongRef::new() 273 + .uri(response.uri.into_static()) 274 + .cid(response.cid.into_static()) 275 + .build(); 265 276 266 277 self.update_record::<Book>(&notebook_uri, |book| { 267 - book.entry_list.push(new_ref); 278 + book.entry_list.push(notebook_entry_ref); 268 279 }) 269 280 .await?; 270 281 271 - Ok((entry_uri, true)) 282 + Ok((new_ref, true)) 272 283 } 273 284 } 274 285
+8 -8
crates/weaver-common/src/constellation.rs
··· 26 26 /// The link target 27 27 /// 28 28 /// can be an AT-URI, plain DID, or regular URI 29 - subject: jacquard::types::uri::Uri<'a>, 29 + pub subject: jacquard::types::uri::Uri<'a>, 30 30 /// Filter links only from this link source 31 31 /// 32 32 /// eg.: `app.bsky.feed.like:subject.uri` 33 - source: CowStr<'a>, 33 + pub source: CowStr<'a>, 34 34 #[serde(borrow)] 35 - cursor: Option<CowStr<'a>>, 35 + pub cursor: Option<CowStr<'a>>, 36 36 /// Filter links only from these DIDs 37 37 /// 38 38 /// include multiple times to filter by multiple source DIDs 39 39 #[serde(default)] 40 - did: Vec<Did<'a>>, 40 + pub did: Vec<Did<'a>>, 41 41 /// Set the max number of links to return per page of results 42 42 #[serde(default = "get_default_cursor_limit")] 43 - limit: u64, 43 + pub limit: u64, 44 44 // TODO: allow reverse (er, forward) order as well 45 45 } 46 46 #[derive(Deserialize, Serialize, IntoStatic)] 47 47 pub struct GetBacklinksResponse<'a> { 48 - total: u64, 48 + pub total: u64, 49 49 #[serde(borrow)] 50 - records: Vec<RecordId<'a>>, 51 - cursor: Option<CowStr<'a>>, 50 + pub records: Vec<RecordId<'a>>, 51 + pub cursor: Option<CowStr<'a>>, 52 52 } 53 53 54 54 #[derive(Debug, PartialEq, Serialize, Deserialize, IntoStatic)]
+10 -3
lexicons.kdl
··· 5 5 } 6 6 7 7 8 - source "bluesky" type="git" priority=101 { 9 - repo "https://github.com/bluesky-social/atproto" 10 - pattern "**/*.json" 8 + // source "bluesky" type="git" priority=101 { 9 + // repo "https://github.com/bluesky-social/atproto" 10 + // pattern "**/*.json" 11 + // } 12 + 13 + source "weaver" type="local" priority=100 { 14 + //path "./lexicons" 15 + path "/home/orual/Projects/weaver.sh/crates/weaver-api/lexicons" 16 + 11 17 } 18 + 12 19 13 20 source "weaver" type="local" priority=200 { 14 21 path "./lexicons"
+14 -3
lexicons/edit/defs.json
··· 8 8 "properties": { 9 9 "value": { 10 10 "type": "union", 11 - "refs": ["#notebookRef", "#entryRef"] 11 + "refs": ["#notebookRef", "#entryRef", "#draftRef"] 12 12 } 13 13 } 14 14 }, ··· 24 24 }, 25 25 "entryRef": { 26 26 "type": "object", 27 - "required": ["notebook"], 27 + "required": ["entry"], 28 28 "properties": { 29 - "notebook": { 29 + "entry": { 30 30 "type": "ref", 31 31 "ref": "com.atproto.repo.strongRef" 32 + } 33 + } 34 + }, 35 + 36 + "draftRef": { 37 + "type": "object", 38 + "required": ["draft_key"], 39 + "properties": { 40 + "draft_key": { 41 + "type": "string", 42 + "maxLength": 200 32 43 } 33 44 } 34 45 }
+4
lexicons/edit/diff.json
··· 19 19 "type": "ref", 20 20 "ref": "com.atproto.repo.strongRef" 21 21 }, 22 + "prev": { 23 + "type": "ref", 24 + "ref": "com.atproto.repo.strongRef" 25 + }, 22 26 "doc": { 23 27 "type": "ref", 24 28 "ref": "sh.weaver.edit.defs#docRef"