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

Orual 29177f7f 157cb538

+6501 -296
+1
Cargo.lock
··· 9955 "mini-moka 0.11.0", 9956 "n0-future", 9957 "regex", 9958 "reqwest", 9959 "serde", 9960 "serde_html_form",
··· 9955 "mini-moka 0.11.0", 9956 "n0-future", 9957 "regex", 9958 + "regex-lite", 9959 "reqwest", 9960 "serde", 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 "type": "union", 13 "refs": [ 14 "#notebookRef", 15 - "#entryRef" 16 ] 17 } 18 } 19 }, 20 "entryRef": { 21 "type": "object", 22 "required": [ 23 - "notebook" 24 ], 25 "properties": { 26 - "notebook": { 27 "type": "ref", 28 "ref": "com.atproto.repo.strongRef" 29 }
··· 12 "type": "union", 13 "refs": [ 14 "#notebookRef", 15 + "#entryRef", 16 + "#draftRef" 17 ] 18 } 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 + }, 33 "entryRef": { 34 "type": "object", 35 "required": [ 36 + "entry" 37 ], 38 "properties": { 39 + "entry": { 40 "type": "ref", 41 "ref": "com.atproto.repo.strongRef" 42 }
+4
crates/weaver-api/lexicons/sh_weaver_edit_diff.json
··· 18 "type": "ref", 19 "ref": "sh.weaver.edit.defs#docRef" 20 }, 21 "root": { 22 "type": "ref", 23 "ref": "com.atproto.repo.strongRef"
··· 18 "type": "ref", 19 "ref": "sh.weaver.edit.defs#docRef" 20 }, 21 + "prev": { 22 + "type": "ref", 23 + "ref": "com.atproto.repo.strongRef" 24 + }, 25 "root": { 26 "type": "ref", 27 "ref": "com.atproto.repo.strongRef"
+21 -5
crates/weaver-api/lexicons/tools_ozone_moderation_defs.json
··· 131 "attemptId" 132 ], 133 "properties": { 134 "attemptId": { 135 "type": "string", 136 "description": "The unique identifier for this instance of the age assurance flow, in UUID format." ··· 143 "type": "string", 144 "description": "The user agent used when completing the AA flow." 145 }, 146 "createdAt": { 147 "type": "string", 148 "description": "The date and time of this write operation.", ··· 156 "type": "string", 157 "description": "The user agent used when initiating the AA flow." 158 }, 159 "status": { 160 "type": "string", 161 - "description": "The status of the age assurance process.", 162 "knownValues": [ 163 "unknown", 164 "pending", ··· 175 "status" 176 ], 177 "properties": { 178 "comment": { 179 "type": "string", 180 "description": "Comment describing the reason for the override." ··· 1321 "subjectReviewState": { 1322 "type": "string", 1323 "knownValues": [ 1324 - "#reviewOpen", 1325 - "#reviewEscalated", 1326 - "#reviewClosed", 1327 - "#reviewNone" 1328 ] 1329 }, 1330 "subjectStatusView": {
··· 131 "attemptId" 132 ], 133 "properties": { 134 + "access": { 135 + "type": "ref", 136 + "ref": "app.bsky.ageassurance.defs#access" 137 + }, 138 "attemptId": { 139 "type": "string", 140 "description": "The unique identifier for this instance of the age assurance flow, in UUID format." ··· 147 "type": "string", 148 "description": "The user agent used when completing the AA flow." 149 }, 150 + "countryCode": { 151 + "type": "string", 152 + "description": "The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow." 153 + }, 154 "createdAt": { 155 "type": "string", 156 "description": "The date and time of this write operation.", ··· 164 "type": "string", 165 "description": "The user agent used when initiating the AA flow." 166 }, 167 + "regionCode": { 168 + "type": "string", 169 + "description": "The ISO 3166-2 region code provided when beginning the Age Assurance flow." 170 + }, 171 "status": { 172 "type": "string", 173 + "description": "The status of the Age Assurance process.", 174 "knownValues": [ 175 "unknown", 176 "pending", ··· 187 "status" 188 ], 189 "properties": { 190 + "access": { 191 + "type": "ref", 192 + "ref": "app.bsky.ageassurance.defs#access" 193 + }, 194 "comment": { 195 "type": "string", 196 "description": "Comment describing the reason for the override." ··· 1337 "subjectReviewState": { 1338 "type": "string", 1339 "knownValues": [ 1340 + "tools.ozone.moderation.defs#reviewOpen", 1341 + "tools.ozone.moderation.defs#reviewEscalated", 1342 + "tools.ozone.moderation.defs#reviewClosed", 1343 + "tools.ozone.moderation.defs#reviewNone" 1344 ] 1345 }, 1346 "subjectStatusView": {
+4 -4
crates/weaver-api/lexicons/tools_ozone_team_defs.json
··· 30 "role": { 31 "type": "string", 32 "knownValues": [ 33 - "#roleAdmin", 34 - "#roleModerator", 35 - "#roleTriage", 36 - "#roleVerifier" 37 ] 38 }, 39 "updatedAt": {
··· 30 "role": { 31 "type": "string", 32 "knownValues": [ 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 ] 38 }, 39 "updatedAt": {
+1
crates/weaver-api/src/app_bsky.rs
··· 4 // Any manual changes will be overwritten on the next regeneration. 5 6 pub mod actor; 7 pub mod bookmark; 8 pub mod embed; 9 pub mod feed;
··· 4 // Any manual changes will be overwritten on the next regeneration. 5 6 pub mod actor; 7 + pub mod ageassurance; 8 pub mod bookmark; 9 pub mod embed; 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 NotebookRef(Box<crate::sh_weaver::edit::NotebookRef<'a>>), 146 #[serde(rename = "sh.weaver.edit.defs#entryRef")] 147 EntryRef(Box<crate::sh_weaver::edit::EntryRef<'a>>), 148 } 149 150 fn lexicon_doc_sh_weaver_edit_defs() -> ::jacquard_lexicon::lexicon::LexiconDoc< ··· 174 description: None, 175 refs: vec![ 176 ::jacquard_common::CowStr::new_static("#notebookRef"), 177 - ::jacquard_common::CowStr::new_static("#entryRef") 178 ], 179 closed: None, 180 }), ··· 184 }), 185 ); 186 map.insert( 187 - ::jacquard_common::smol_str::SmolStr::new_static("entryRef"), 188 ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 189 description: None, 190 required: Some( 191 vec![ 192 - ::jacquard_common::smol_str::SmolStr::new_static("notebook") 193 ], 194 ), 195 nullable: None, ··· 197 #[allow(unused_mut)] 198 let mut map = ::std::collections::BTreeMap::new(); 199 map.insert( 200 - ::jacquard_common::smol_str::SmolStr::new_static("notebook"), 201 ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 202 description: None, 203 r#ref: ::jacquard_common::CowStr::new_static( ··· 265 Clone, 266 PartialEq, 267 Eq, 268 jacquard_derive::IntoStatic 269 )] 270 #[serde(rename_all = "camelCase")] 271 pub struct EntryRef<'a> { 272 #[serde(borrow)] 273 - pub notebook: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 274 } 275 276 pub mod entry_ref_state { ··· 283 } 284 /// State trait tracking which required fields have been set 285 pub trait State: sealed::Sealed { 286 - type Notebook; 287 } 288 /// Empty state - all required fields are unset 289 pub struct Empty(()); 290 impl sealed::Sealed for Empty {} 291 impl State for Empty { 292 - type Notebook = Unset; 293 } 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>; 299 } 300 /// Marker types for field names 301 #[allow(non_camel_case_types)] 302 pub mod members { 303 - ///Marker type for the `notebook` field 304 - pub struct notebook(()); 305 } 306 } 307 ··· 335 impl<'a, S> EntryRefBuilder<'a, S> 336 where 337 S: entry_ref_state::State, 338 - S::Notebook: entry_ref_state::IsUnset, 339 { 340 - /// Set the `notebook` field (required) 341 - pub fn notebook( 342 mut self, 343 value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 344 - ) -> EntryRefBuilder<'a, entry_ref_state::SetNotebook<S>> { 345 self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 346 EntryRefBuilder { 347 _phantom_state: ::core::marker::PhantomData, ··· 354 impl<'a, S> EntryRefBuilder<'a, S> 355 where 356 S: entry_ref_state::State, 357 - S::Notebook: entry_ref_state::IsSet, 358 { 359 /// Build the final struct 360 pub fn build(self) -> EntryRef<'a> { 361 EntryRef { 362 - notebook: self.__unsafe_private_named.0.unwrap(), 363 extra_data: Default::default(), 364 } 365 } ··· 372 >, 373 ) -> EntryRef<'a> { 374 EntryRef { 375 - notebook: self.__unsafe_private_named.0.unwrap(), 376 extra_data: Some(extra_data), 377 } 378 }
··· 145 NotebookRef(Box<crate::sh_weaver::edit::NotebookRef<'a>>), 146 #[serde(rename = "sh.weaver.edit.defs#entryRef")] 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>>), 150 } 151 152 fn lexicon_doc_sh_weaver_edit_defs() -> ::jacquard_lexicon::lexicon::LexiconDoc< ··· 176 description: None, 177 refs: vec![ 178 ::jacquard_common::CowStr::new_static("#notebookRef"), 179 + ::jacquard_common::CowStr::new_static("#entryRef"), 180 + ::jacquard_common::CowStr::new_static("#draftRef") 181 ], 182 closed: None, 183 }), ··· 187 }), 188 ); 189 map.insert( 190 + ::jacquard_common::smol_str::SmolStr::new_static("draftRef"), 191 ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 192 description: None, 193 required: Some( 194 vec![ 195 + ::jacquard_common::smol_str::SmolStr::new_static("draft_key") 196 ], 197 ), 198 nullable: None, ··· 200 #[allow(unused_mut)] 201 let mut map = ::std::collections::BTreeMap::new(); 202 map.insert( 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"), 236 ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 237 description: None, 238 r#ref: ::jacquard_common::CowStr::new_static( ··· 300 Clone, 301 PartialEq, 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, 350 jacquard_derive::IntoStatic 351 )] 352 #[serde(rename_all = "camelCase")] 353 pub struct EntryRef<'a> { 354 #[serde(borrow)] 355 + pub entry: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 356 } 357 358 pub mod entry_ref_state { ··· 365 } 366 /// State trait tracking which required fields have been set 367 pub trait State: sealed::Sealed { 368 + type Entry; 369 } 370 /// Empty state - all required fields are unset 371 pub struct Empty(()); 372 impl sealed::Sealed for Empty {} 373 impl State for Empty { 374 + type Entry = Unset; 375 } 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>; 381 } 382 /// Marker types for field names 383 #[allow(non_camel_case_types)] 384 pub mod members { 385 + ///Marker type for the `entry` field 386 + pub struct entry(()); 387 } 388 } 389 ··· 417 impl<'a, S> EntryRefBuilder<'a, S> 418 where 419 S: entry_ref_state::State, 420 + S::Entry: entry_ref_state::IsUnset, 421 { 422 + /// Set the `entry` field (required) 423 + pub fn entry( 424 mut self, 425 value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 426 + ) -> EntryRefBuilder<'a, entry_ref_state::SetEntry<S>> { 427 self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 428 EntryRefBuilder { 429 _phantom_state: ::core::marker::PhantomData, ··· 436 impl<'a, S> EntryRefBuilder<'a, S> 437 where 438 S: entry_ref_state::State, 439 + S::Entry: entry_ref_state::IsSet, 440 { 441 /// Build the final struct 442 pub fn build(self) -> EntryRef<'a> { 443 EntryRef { 444 + entry: self.__unsafe_private_named.0.unwrap(), 445 extra_data: Default::default(), 446 } 447 } ··· 454 >, 455 ) -> EntryRef<'a> { 456 EntryRef { 457 + entry: self.__unsafe_private_named.0.unwrap(), 458 extra_data: Some(extra_data), 459 } 460 }
+41 -7
crates/weaver-api/src/sh_weaver/edit/diff.rs
··· 20 pub struct Diff<'a> { 21 #[serde(borrow)] 22 pub doc: crate::sh_weaver::edit::DocRef<'a>, 23 #[serde(borrow)] 24 pub root: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 25 #[serde(borrow)] ··· 90 __unsafe_private_named: ( 91 ::core::option::Option<crate::sh_weaver::edit::DocRef<'a>>, 92 ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 93 ::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 94 ), 95 _phantom: ::core::marker::PhantomData<&'a ()>, ··· 107 pub fn new() -> Self { 108 DiffBuilder { 109 _phantom_state: ::core::marker::PhantomData, 110 - __unsafe_private_named: (None, None, None), 111 _phantom: ::core::marker::PhantomData, 112 } 113 } ··· 132 } 133 } 134 135 impl<'a, S> DiffBuilder<'a, S> 136 where 137 S: diff_state::State, ··· 142 mut self, 143 value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 144 ) -> DiffBuilder<'a, diff_state::SetRoot<S>> { 145 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 146 DiffBuilder { 147 _phantom_state: ::core::marker::PhantomData, 148 __unsafe_private_named: self.__unsafe_private_named, ··· 161 mut self, 162 value: impl Into<jacquard_common::types::blob::BlobRef<'a>>, 163 ) -> DiffBuilder<'a, diff_state::SetSnapshot<S>> { 164 - self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 165 DiffBuilder { 166 _phantom_state: ::core::marker::PhantomData, 167 __unsafe_private_named: self.__unsafe_private_named, ··· 181 pub fn build(self) -> Diff<'a> { 182 Diff { 183 doc: self.__unsafe_private_named.0.unwrap(), 184 - root: self.__unsafe_private_named.1.unwrap(), 185 - snapshot: self.__unsafe_private_named.2.unwrap(), 186 extra_data: Default::default(), 187 } 188 } ··· 196 ) -> Diff<'a> { 197 Diff { 198 doc: self.__unsafe_private_named.0.unwrap(), 199 - root: self.__unsafe_private_named.1.unwrap(), 200 - snapshot: self.__unsafe_private_named.2.unwrap(), 201 extra_data: Some(extra_data), 202 } 203 } ··· 319 description: None, 320 r#ref: ::jacquard_common::CowStr::new_static( 321 "sh.weaver.edit.defs#docRef", 322 ), 323 }), 324 );
··· 20 pub struct Diff<'a> { 21 #[serde(borrow)] 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>>, 26 #[serde(borrow)] 27 pub root: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 28 #[serde(borrow)] ··· 93 __unsafe_private_named: ( 94 ::core::option::Option<crate::sh_weaver::edit::DocRef<'a>>, 95 ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 96 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 97 ::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 98 ), 99 _phantom: ::core::marker::PhantomData<&'a ()>, ··· 111 pub fn new() -> Self { 112 DiffBuilder { 113 _phantom_state: ::core::marker::PhantomData, 114 + __unsafe_private_named: (None, None, None, None), 115 _phantom: ::core::marker::PhantomData, 116 } 117 } ··· 136 } 137 } 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 + 158 impl<'a, S> DiffBuilder<'a, S> 159 where 160 S: diff_state::State, ··· 165 mut self, 166 value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 167 ) -> DiffBuilder<'a, diff_state::SetRoot<S>> { 168 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 169 DiffBuilder { 170 _phantom_state: ::core::marker::PhantomData, 171 __unsafe_private_named: self.__unsafe_private_named, ··· 184 mut self, 185 value: impl Into<jacquard_common::types::blob::BlobRef<'a>>, 186 ) -> DiffBuilder<'a, diff_state::SetSnapshot<S>> { 187 + self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 188 DiffBuilder { 189 _phantom_state: ::core::marker::PhantomData, 190 __unsafe_private_named: self.__unsafe_private_named, ··· 204 pub fn build(self) -> Diff<'a> { 205 Diff { 206 doc: self.__unsafe_private_named.0.unwrap(), 207 + prev: self.__unsafe_private_named.1, 208 + root: self.__unsafe_private_named.2.unwrap(), 209 + snapshot: self.__unsafe_private_named.3.unwrap(), 210 extra_data: Default::default(), 211 } 212 } ··· 220 ) -> Diff<'a> { 221 Diff { 222 doc: self.__unsafe_private_named.0.unwrap(), 223 + prev: self.__unsafe_private_named.1, 224 + root: self.__unsafe_private_named.2.unwrap(), 225 + snapshot: self.__unsafe_private_named.3.unwrap(), 226 extra_data: Some(extra_data), 227 } 228 } ··· 344 description: None, 345 r#ref: ::jacquard_common::CowStr::new_static( 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", 356 ), 357 }), 358 );
+231 -48
crates/weaver-api/src/tools_ozone/moderation.rs
··· 628 #[allow(unused_mut)] 629 let mut map = ::std::collections::BTreeMap::new(); 630 map.insert( 631 ::jacquard_common::smol_str::SmolStr::new_static( 632 "attemptId", 633 ), ··· 692 ); 693 map.insert( 694 ::jacquard_common::smol_str::SmolStr::new_static( 695 "createdAt", 696 ), 697 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { ··· 752 }), 753 ); 754 map.insert( 755 ::jacquard_common::smol_str::SmolStr::new_static("status"), 756 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 757 description: Some( 758 ::jacquard_common::CowStr::new_static( 759 - "The status of the age assurance process.", 760 ), 761 ), 762 format: None, ··· 794 properties: { 795 #[allow(unused_mut)] 796 let mut map = ::std::collections::BTreeMap::new(); 797 map.insert( 798 ::jacquard_common::smol_str::SmolStr::new_static("comment"), 799 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { ··· 4990 )] 4991 #[serde(rename_all = "camelCase")] 4992 pub struct AgeAssuranceEvent<'a> { 4993 /// The unique identifier for this instance of the age assurance flow, in UUID format. 4994 #[serde(borrow)] 4995 pub attempt_id: jacquard_common::CowStr<'a>, ··· 5001 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5002 #[serde(borrow)] 5003 pub complete_ua: std::option::Option<jacquard_common::CowStr<'a>>, 5004 /// The date and time of this write operation. 5005 pub created_at: jacquard_common::types::string::Datetime, 5006 /// The IP address used when initiating the AA flow. ··· 5011 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5012 #[serde(borrow)] 5013 pub init_ua: std::option::Option<jacquard_common::CowStr<'a>>, 5014 - /// The status of the age assurance process. 5015 #[serde(borrow)] 5016 pub status: jacquard_common::CowStr<'a>, 5017 } ··· 5078 pub struct AgeAssuranceEventBuilder<'a, S: age_assurance_event_state::State> { 5079 _phantom_state: ::core::marker::PhantomData<fn() -> S>, 5080 __unsafe_private_named: ( 5081 ::core::option::Option<jacquard_common::CowStr<'a>>, 5082 ::core::option::Option<jacquard_common::CowStr<'a>>, 5083 ::core::option::Option<jacquard_common::CowStr<'a>>, 5084 ::core::option::Option<jacquard_common::types::string::Datetime>, 5085 ::core::option::Option<jacquard_common::CowStr<'a>>, 5086 ::core::option::Option<jacquard_common::CowStr<'a>>, 5087 ::core::option::Option<jacquard_common::CowStr<'a>>, ··· 5101 pub fn new() -> Self { 5102 AgeAssuranceEventBuilder { 5103 _phantom_state: ::core::marker::PhantomData, 5104 - __unsafe_private_named: (None, None, None, None, None, None, None), 5105 _phantom: ::core::marker::PhantomData, 5106 } 5107 } 5108 } 5109 5110 impl<'a, S> AgeAssuranceEventBuilder<'a, S> 5111 where 5112 S: age_assurance_event_state::State, ··· 5117 mut self, 5118 value: impl Into<jacquard_common::CowStr<'a>>, 5119 ) -> AgeAssuranceEventBuilder<'a, age_assurance_event_state::SetAttemptId<S>> { 5120 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 5121 AgeAssuranceEventBuilder { 5122 _phantom_state: ::core::marker::PhantomData, 5123 __unsafe_private_named: self.__unsafe_private_named, ··· 5132 mut self, 5133 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5134 ) -> Self { 5135 - self.__unsafe_private_named.1 = value.into(); 5136 self 5137 } 5138 /// Set the `completeIp` field to an Option value (optional) ··· 5140 mut self, 5141 value: Option<jacquard_common::CowStr<'a>>, 5142 ) -> Self { 5143 - self.__unsafe_private_named.1 = value; 5144 self 5145 } 5146 } ··· 5151 mut self, 5152 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5153 ) -> Self { 5154 - self.__unsafe_private_named.2 = value.into(); 5155 self 5156 } 5157 /// Set the `completeUa` field to an Option value (optional) ··· 5159 mut self, 5160 value: Option<jacquard_common::CowStr<'a>>, 5161 ) -> Self { 5162 - self.__unsafe_private_named.2 = value; 5163 self 5164 } 5165 } ··· 5174 mut self, 5175 value: impl Into<jacquard_common::types::string::Datetime>, 5176 ) -> AgeAssuranceEventBuilder<'a, age_assurance_event_state::SetCreatedAt<S>> { 5177 - self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 5178 AgeAssuranceEventBuilder { 5179 _phantom_state: ::core::marker::PhantomData, 5180 __unsafe_private_named: self.__unsafe_private_named, ··· 5189 mut self, 5190 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5191 ) -> Self { 5192 - self.__unsafe_private_named.4 = value.into(); 5193 self 5194 } 5195 /// Set the `initIp` field to an Option value (optional) 5196 pub fn maybe_init_ip(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 5197 - self.__unsafe_private_named.4 = value; 5198 self 5199 } 5200 } ··· 5205 mut self, 5206 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5207 ) -> Self { 5208 - self.__unsafe_private_named.5 = value.into(); 5209 self 5210 } 5211 /// Set the `initUa` field to an Option value (optional) 5212 pub fn maybe_init_ua(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 5213 - self.__unsafe_private_named.5 = value; 5214 self 5215 } 5216 } ··· 5225 mut self, 5226 value: impl Into<jacquard_common::CowStr<'a>>, 5227 ) -> AgeAssuranceEventBuilder<'a, age_assurance_event_state::SetStatus<S>> { 5228 - self.__unsafe_private_named.6 = ::core::option::Option::Some(value.into()); 5229 AgeAssuranceEventBuilder { 5230 _phantom_state: ::core::marker::PhantomData, 5231 __unsafe_private_named: self.__unsafe_private_named, ··· 5244 /// Build the final struct 5245 pub fn build(self) -> AgeAssuranceEvent<'a> { 5246 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(), 5254 extra_data: Default::default(), 5255 } 5256 } ··· 5263 >, 5264 ) -> AgeAssuranceEvent<'a> { 5265 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(), 5273 extra_data: Some(extra_data), 5274 } 5275 } ··· 5306 )] 5307 #[serde(rename_all = "camelCase")] 5308 pub struct AgeAssuranceOverrideEvent<'a> { 5309 /// Comment describing the reason for the override. 5310 #[serde(borrow)] 5311 pub comment: jacquard_common::CowStr<'a>, ··· 12247 12248 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 12249 pub enum SubjectReviewState<'a> { 12250 - ReviewOpen, 12251 - ReviewEscalated, 12252 - ReviewClosed, 12253 - ReviewNone, 12254 Other(jacquard_common::CowStr<'a>), 12255 } 12256 12257 impl<'a> SubjectReviewState<'a> { 12258 pub fn as_str(&self) -> &str { 12259 match self { 12260 - Self::ReviewOpen => "#reviewOpen", 12261 - Self::ReviewEscalated => "#reviewEscalated", 12262 - Self::ReviewClosed => "#reviewClosed", 12263 - Self::ReviewNone => "#reviewNone", 12264 Self::Other(s) => s.as_ref(), 12265 } 12266 } ··· 12269 impl<'a> From<&'a str> for SubjectReviewState<'a> { 12270 fn from(s: &'a str) -> Self { 12271 match s { 12272 - "#reviewOpen" => Self::ReviewOpen, 12273 - "#reviewEscalated" => Self::ReviewEscalated, 12274 - "#reviewClosed" => Self::ReviewClosed, 12275 - "#reviewNone" => Self::ReviewNone, 12276 _ => Self::Other(jacquard_common::CowStr::from(s)), 12277 } 12278 } ··· 12281 impl<'a> From<String> for SubjectReviewState<'a> { 12282 fn from(s: String) -> Self { 12283 match s.as_str() { 12284 - "#reviewOpen" => Self::ReviewOpen, 12285 - "#reviewEscalated" => Self::ReviewEscalated, 12286 - "#reviewClosed" => Self::ReviewClosed, 12287 - "#reviewNone" => Self::ReviewNone, 12288 _ => Self::Other(jacquard_common::CowStr::from(s)), 12289 } 12290 } ··· 12322 type Output = SubjectReviewState<'static>; 12323 fn into_static(self) -> Self::Output { 12324 match self { 12325 - SubjectReviewState::ReviewOpen => SubjectReviewState::ReviewOpen, 12326 - SubjectReviewState::ReviewEscalated => SubjectReviewState::ReviewEscalated, 12327 - SubjectReviewState::ReviewClosed => SubjectReviewState::ReviewClosed, 12328 - SubjectReviewState::ReviewNone => SubjectReviewState::ReviewNone, 12329 SubjectReviewState::Other(v) => SubjectReviewState::Other(v.into_static()), 12330 } 12331 }
··· 628 #[allow(unused_mut)] 629 let mut map = ::std::collections::BTreeMap::new(); 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( 640 ::jacquard_common::smol_str::SmolStr::new_static( 641 "attemptId", 642 ), ··· 701 ); 702 map.insert( 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( 725 "createdAt", 726 ), 727 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { ··· 782 }), 783 ); 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( 806 ::jacquard_common::smol_str::SmolStr::new_static("status"), 807 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 808 description: Some( 809 ::jacquard_common::CowStr::new_static( 810 + "The status of the Age Assurance process.", 811 ), 812 ), 813 format: None, ··· 845 properties: { 846 #[allow(unused_mut)] 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 + ); 857 map.insert( 858 ::jacquard_common::smol_str::SmolStr::new_static("comment"), 859 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { ··· 5050 )] 5051 #[serde(rename_all = "camelCase")] 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>>, 5056 /// The unique identifier for this instance of the age assurance flow, in UUID format. 5057 #[serde(borrow)] 5058 pub attempt_id: jacquard_common::CowStr<'a>, ··· 5064 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5065 #[serde(borrow)] 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>>, 5071 /// The date and time of this write operation. 5072 pub created_at: jacquard_common::types::string::Datetime, 5073 /// The IP address used when initiating the AA flow. ··· 5078 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5079 #[serde(borrow)] 5080 pub init_ua: std::option::Option<jacquard_common::CowStr<'a>>, 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. 5086 #[serde(borrow)] 5087 pub status: jacquard_common::CowStr<'a>, 5088 } ··· 5149 pub struct AgeAssuranceEventBuilder<'a, S: age_assurance_event_state::State> { 5150 _phantom_state: ::core::marker::PhantomData<fn() -> S>, 5151 __unsafe_private_named: ( 5152 + ::core::option::Option<crate::app_bsky::ageassurance::Access<'a>>, 5153 + ::core::option::Option<jacquard_common::CowStr<'a>>, 5154 ::core::option::Option<jacquard_common::CowStr<'a>>, 5155 ::core::option::Option<jacquard_common::CowStr<'a>>, 5156 ::core::option::Option<jacquard_common::CowStr<'a>>, 5157 ::core::option::Option<jacquard_common::types::string::Datetime>, 5158 + ::core::option::Option<jacquard_common::CowStr<'a>>, 5159 ::core::option::Option<jacquard_common::CowStr<'a>>, 5160 ::core::option::Option<jacquard_common::CowStr<'a>>, 5161 ::core::option::Option<jacquard_common::CowStr<'a>>, ··· 5175 pub fn new() -> Self { 5176 AgeAssuranceEventBuilder { 5177 _phantom_state: ::core::marker::PhantomData, 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 + ), 5190 _phantom: ::core::marker::PhantomData, 5191 } 5192 } 5193 } 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 + 5214 impl<'a, S> AgeAssuranceEventBuilder<'a, S> 5215 where 5216 S: age_assurance_event_state::State, ··· 5221 mut self, 5222 value: impl Into<jacquard_common::CowStr<'a>>, 5223 ) -> AgeAssuranceEventBuilder<'a, age_assurance_event_state::SetAttemptId<S>> { 5224 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 5225 AgeAssuranceEventBuilder { 5226 _phantom_state: ::core::marker::PhantomData, 5227 __unsafe_private_named: self.__unsafe_private_named, ··· 5236 mut self, 5237 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5238 ) -> Self { 5239 + self.__unsafe_private_named.2 = value.into(); 5240 self 5241 } 5242 /// Set the `completeIp` field to an Option value (optional) ··· 5244 mut self, 5245 value: Option<jacquard_common::CowStr<'a>>, 5246 ) -> Self { 5247 + self.__unsafe_private_named.2 = value; 5248 self 5249 } 5250 } ··· 5255 mut self, 5256 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5257 ) -> Self { 5258 + self.__unsafe_private_named.3 = value.into(); 5259 self 5260 } 5261 /// Set the `completeUa` field to an Option value (optional) ··· 5263 mut self, 5264 value: Option<jacquard_common::CowStr<'a>>, 5265 ) -> Self { 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; 5286 self 5287 } 5288 } ··· 5297 mut self, 5298 value: impl Into<jacquard_common::types::string::Datetime>, 5299 ) -> AgeAssuranceEventBuilder<'a, age_assurance_event_state::SetCreatedAt<S>> { 5300 + self.__unsafe_private_named.5 = ::core::option::Option::Some(value.into()); 5301 AgeAssuranceEventBuilder { 5302 _phantom_state: ::core::marker::PhantomData, 5303 __unsafe_private_named: self.__unsafe_private_named, ··· 5312 mut self, 5313 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5314 ) -> Self { 5315 + self.__unsafe_private_named.6 = value.into(); 5316 self 5317 } 5318 /// Set the `initIp` field to an Option value (optional) 5319 pub fn maybe_init_ip(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 5320 + self.__unsafe_private_named.6 = value; 5321 self 5322 } 5323 } ··· 5328 mut self, 5329 value: impl Into<Option<jacquard_common::CowStr<'a>>>, 5330 ) -> Self { 5331 + self.__unsafe_private_named.7 = value.into(); 5332 self 5333 } 5334 /// Set the `initUa` field to an Option value (optional) 5335 pub fn maybe_init_ua(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 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; 5356 self 5357 } 5358 } ··· 5367 mut self, 5368 value: impl Into<jacquard_common::CowStr<'a>>, 5369 ) -> AgeAssuranceEventBuilder<'a, age_assurance_event_state::SetStatus<S>> { 5370 + self.__unsafe_private_named.9 = ::core::option::Option::Some(value.into()); 5371 AgeAssuranceEventBuilder { 5372 _phantom_state: ::core::marker::PhantomData, 5373 __unsafe_private_named: self.__unsafe_private_named, ··· 5386 /// Build the final struct 5387 pub fn build(self) -> AgeAssuranceEvent<'a> { 5388 AgeAssuranceEvent { 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(), 5399 extra_data: Default::default(), 5400 } 5401 } ··· 5408 >, 5409 ) -> AgeAssuranceEvent<'a> { 5410 AgeAssuranceEvent { 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(), 5421 extra_data: Some(extra_data), 5422 } 5423 } ··· 5454 )] 5455 #[serde(rename_all = "camelCase")] 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>>, 5460 /// Comment describing the reason for the override. 5461 #[serde(borrow)] 5462 pub comment: jacquard_common::CowStr<'a>, ··· 12398 12399 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 12400 pub enum SubjectReviewState<'a> { 12401 + ToolsOzoneModerationDefsReviewOpen, 12402 + ToolsOzoneModerationDefsReviewEscalated, 12403 + ToolsOzoneModerationDefsReviewClosed, 12404 + ToolsOzoneModerationDefsReviewNone, 12405 Other(jacquard_common::CowStr<'a>), 12406 } 12407 12408 impl<'a> SubjectReviewState<'a> { 12409 pub fn as_str(&self) -> &str { 12410 match self { 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 + } 12423 Self::Other(s) => s.as_ref(), 12424 } 12425 } ··· 12428 impl<'a> From<&'a str> for SubjectReviewState<'a> { 12429 fn from(s: &'a str) -> Self { 12430 match s { 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 + } 12443 _ => Self::Other(jacquard_common::CowStr::from(s)), 12444 } 12445 } ··· 12448 impl<'a> From<String> for SubjectReviewState<'a> { 12449 fn from(s: String) -> Self { 12450 match s.as_str() { 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 + } 12463 _ => Self::Other(jacquard_common::CowStr::from(s)), 12464 } 12465 } ··· 12497 type Output = SubjectReviewState<'static>; 12498 fn into_static(self) -> Self::Output { 12499 match self { 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 + } 12512 SubjectReviewState::Other(v) => SubjectReviewState::Other(v.into_static()), 12513 } 12514 }
+1
crates/weaver-app/Cargo.toml
··· 47 # diesel_migrations = { version = "2.3", features = ["sqlite"] } 48 tokio = { version = "1.28", features = ["sync"] } 49 serde_html_form = "0.2.8" 50 tracing.workspace = true 51 serde_ipld_dagcbor = { version = "0.6" } 52 loro = "1.9.1"
··· 47 # diesel_migrations = { version = "2.3", features = ["sqlite"] } 48 tokio = { version = "1.28", features = ["sync"] } 49 serde_html_form = "0.2.8" 50 + regex-lite = "0.1" 51 tracing.workspace = true 52 serde_ipld_dagcbor = { version = "0.6" } 53 loro = "1.9.1"
+94 -18
crates/weaver-app/public/sw.js
··· 1 // Weaver Service Worker 2 - // Handles blob/image requests by intercepting fetch and serving from PDS 3 4 - const CACHE_NAME = "weaver-blobs-v1"; 5 6 - // Map of notebook/path -> real URL 7 // e.g., "notebook/image/foo.jpg" -> "https://pds.example.com/xrpc/com.atproto.sync.getBlob?..." 8 const urlMappings = new Map(); 9 ··· 13 }); 14 15 self.addEventListener("activate", (event) => { 16 - event.waitUntil(clients.claim()); 17 }); 18 19 - // Receive mappings from main thread 20 self.addEventListener("message", (event) => { 21 if (event.data.type === "register_mappings") { 22 const notebook = event.data.notebook; ··· 28 } 29 }); 30 31 // Intercept fetch requests 32 self.addEventListener("fetch", (event) => { 33 const url = new URL(event.request.url); 34 35 - // Extract key from path (e.g., "/notebook/image/foo.jpg" -> "notebook/image/foo.jpg") 36 const pathParts = url.pathname.split("/").filter((p) => p); 37 38 - // Check if this looks like an image request (format: /:notebook/image/:name) 39 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); 44 if (mapping) { 45 - event.respondWith(handleBlobRequest(mapping, key)); 46 return; 47 } 48 } 49 50 - // Let other requests pass through 51 }); 52 53 - async function handleBlobRequest(url, key) { 54 try { 55 - // Check cache first 56 const cache = await caches.open(CACHE_NAME); 57 - let response = await cache.match(key); 58 59 if (response) { 60 return response; ··· 68 return new Response("Blob not found", { status: 404 }); 69 } 70 71 - // Cache the response 72 - await cache.put(key, response.clone()); 73 74 return response; 75 } catch (error) { ··· 77 return new Response("Error fetching blob", { status: 500 }); 78 } 79 }
··· 1 // Weaver Service Worker 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) 9 10 + const CACHE_NAME = "weaver-blobs-v2"; 11 12 + // Map of notebook/path -> real URL for legacy blob mappings 13 // e.g., "notebook/image/foo.jpg" -> "https://pds.example.com/xrpc/com.atproto.sync.getBlob?..." 14 const urlMappings = new Map(); 15 ··· 19 }); 20 21 self.addEventListener("activate", (event) => { 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 + ); 35 }); 36 37 + // Receive mappings from main thread (for legacy notebook images) 38 self.addEventListener("message", (event) => { 39 if (event.data.type === "register_mappings") { 40 const notebook = event.data.notebook; ··· 46 } 47 }); 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 + 63 // Intercept fetch requests 64 self.addEventListener("fetch", (event) => { 65 const url = new URL(event.request.url); 66 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 81 const pathParts = url.pathname.split("/").filter((p) => p); 82 83 + // Check legacy mappings (for /{notebook}/image/{name} format) 84 if (pathParts.length >= 3 && pathParts[pathParts.length - 2] === "image") { 85 + const legacyKey = pathParts.join("/"); 86 + const mapping = urlMappings.get(legacyKey); 87 if (mapping) { 88 + event.respondWith(handleBlobRequest(mapping, cacheKey)); 89 return; 90 } 91 } 92 93 + // For new /image/... routes, cache the response from our server 94 + event.respondWith(handleImageRequest(event.request, cacheKey)); 95 }); 96 97 + // Handle requests that have a direct URL mapping (legacy) 98 + async function handleBlobRequest(url, cacheKey) { 99 try { 100 const cache = await caches.open(CACHE_NAME); 101 + let response = await cache.match(cacheKey); 102 103 if (response) { 104 return response; ··· 112 return new Response("Blob not found", { status: 404 }); 113 } 114 115 + // Cache the response (blobs are immutable by CID) 116 + await cache.put(cacheKey, response.clone()); 117 118 return response; 119 } catch (error) { ··· 121 return new Response("Error fetching blob", { status: 500 }); 122 } 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 identity::JacquardResolver, 7 prelude::*, 8 smol_str::SmolStr, 9 - types::{cid::Cid, ident::AtIdentifier}, 10 }; 11 use std::{ 12 - sync::{Arc, Mutex}, 13 time::Duration, 14 }; 15 use weaver_api::com_atproto::sync::get_blob::GetBlob; 16 17 #[derive(Clone)] 18 pub struct BlobCache { ··· 29 Self { client, cache, map } 30 } 31 32 - pub async fn cache( 33 &self, 34 - ident: AtIdentifier<'static>, 35 - cid: Cid<'static>, 36 - name: Option<SmolStr>, 37 - ) -> Result<()> { 38 - let (repo_did, pds_url) = match ident { 39 AtIdentifier::Did(did) => { 40 - let pds = self.client.pds_for_did(&did).await?; 41 - (did.clone(), pds) 42 } 43 - AtIdentifier::Handle(handle) => self.client.pds_for_handle(&handle).await?, 44 - }; 45 - if self.get_cid(&cid).is_some() { 46 - return Ok(()); 47 } 48 - let blob = if let Ok(blob_stream) = self 49 .client 50 .xrpc(pds_url) 51 .send( 52 &GetBlob::new() 53 .cid(cid.clone()) 54 - .did(repo_did.clone()) 55 .build(), 56 ) 57 .await 58 { 59 - blob_stream.buffer().clone() 60 } else { 61 - reqwest::get(format!( 62 "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@jpeg", 63 - repo_did, cid 64 )) 65 .await? 66 .bytes() 67 - .await? 68 - .clone() 69 - }; 70 71 self.cache.insert(cid.clone(), blob); 72 if let Some(name) = name { ··· 74 } 75 76 Ok(()) 77 } 78 79 pub fn get_cid(&self, cid: &Cid<'static>) -> Option<Bytes> {
··· 6 identity::JacquardResolver, 7 prelude::*, 8 smol_str::SmolStr, 9 + types::{cid::Cid, collection::Collection, ident::AtIdentifier, nsid::Nsid, string::Rkey}, 10 + xrpc::XrpcExt, 11 + IntoStatic, 12 }; 13 use std::{ 14 + sync::Arc, 15 time::Duration, 16 }; 17 + use weaver_api::com_atproto::repo::get_record::GetRecord; 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; 21 22 #[derive(Clone)] 23 pub struct BlobCache { ··· 34 Self { client, cache, map } 35 } 36 37 + /// Resolve DID and PDS URL from an identifier 38 + async fn resolve_ident( 39 &self, 40 + ident: &AtIdentifier<'_>, 41 + ) -> Result<(jacquard::types::string::Did<'static>, jacquard::url::Url)> { 42 + match ident { 43 AtIdentifier::Did(did) => { 44 + let pds = self.client.pds_for_did(did).await?; 45 + Ok((did.clone().into_static(), pds)) 46 } 47 + AtIdentifier::Handle(handle) => { 48 + let (did, pds) = self.client.pds_for_handle(handle).await?; 49 + Ok((did, pds)) 50 + } 51 } 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 62 .client 63 .xrpc(pds_url) 64 .send( 65 &GetBlob::new() 66 .cid(cid.clone()) 67 + .did(did.clone()) 68 .build(), 69 ) 70 .await 71 { 72 + Ok(blob_stream.buffer().clone()) 73 } else { 74 + // Fallback to Bluesky CDN (works for blobs stored on bsky PDSes) 75 + let bytes = reqwest::get(format!( 76 "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@jpeg", 77 + did, cid 78 )) 79 .await? 80 .bytes() 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?; 99 100 self.cache.insert(cid.clone(), blob); 101 if let Some(name) = name { ··· 103 } 104 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 + ))) 233 } 234 235 pub fn get_cid(&self, cid: &Cid<'static>) -> Option<Bytes> {
+133 -39
crates/weaver-app/src/components/editor/component.rs
··· 1 //! The main MarkdownEditor component. 2 3 use dioxus::prelude::*; 4 use jacquard::cowstr::ToCowStr; 5 use jacquard::types::blob::BlobRef; 6 use weaver_api::sh_weaver::embed::images::Image; 7 use weaver_common::WeaverExt; 8 ··· 11 use crate::fetch::Fetcher; 12 13 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::formatting; 17 use super::input::{ 18 get_char_at, handle_copy, handle_cut, handle_keydown, handle_paste, should_intercept_key, 19 }; 20 use super::paragraph::ParagraphRender; 21 use super::platform; 22 - use super::publish::PublishButton; 23 use super::render; 24 use super::storage; 25 use super::toolbar::EditorToolbar; 26 use super::visibility::update_syntax_visibility; 27 use super::writer::{EditorImageResolver, SyntaxSpanInfo}; 28 29 - /// Main markdown editor component. 30 /// 31 /// # Props 32 - /// - `initial_content`: Optional initial markdown content 33 /// 34 /// # Features 35 /// - Loro CRDT-based text storage with undo/redo support ··· 38 /// - LocalStorage auto-save with debouncing 39 /// - Keyboard shortcuts (Ctrl+B for bold, Ctrl+I for italic) 40 #[component] 41 - pub fn MarkdownEditor(initial_content: Option<String>) -> Element { 42 // Context for authenticated API calls 43 let fetcher = use_context::<Fetcher>(); 44 let auth_state = use_context::<Signal<AuthState>>(); 45 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 let mut document = use_hook(|| { 51 - storage::load_from_storage(draft_key) 52 - .unwrap_or_else(|| EditorDocument::new(initial_content.clone().unwrap_or_default())) 53 }); 54 let editor_id = "markdown-editor"; 55 ··· 149 // Use requestAnimationFrame to wait for browser paint 150 if let Some(window) = web_sys::window() { 151 let closure = Closure::once(move || { 152 - if let Err(e) = 153 - super::cursor::restore_cursor_position(cursor_offset, &map, editor_id, snap_direction) 154 - { 155 tracing::warn!("Cursor restoration failed: {:?}", e); 156 } 157 }); ··· 175 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 176 let doc_for_autosave = document.clone(); 177 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 178 use_effect(move || { 179 // Check every 500ms if there are unsaved changes 180 let mut doc = doc_for_autosave.clone(); 181 let interval = gloo_timers::callback::Interval::new(500, move || { 182 let current_frontiers = doc.state_frontiers(); 183 ··· 192 193 if needs_save { 194 doc.sync_loro_cursor(); 195 - let _ = storage::save_to_storage(&doc, draft_key); 196 197 // Update last saved frontiers 198 last_saved_frontiers.set(Some(current_frontiers)); ··· 783 // Upload blob and create temporary PublishedBlob record 784 match client.publish_blob(data, &name_for_upload, None).await { 785 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 // Get DID from fetcher 804 let did = match fetcher.current_did().await { 805 - Some(d) => d.to_string(), 806 None => { 807 tracing::warn!("No DID available"); 808 return; 809 } 810 }; 811 812 - let cid = blob.cid().to_string(); 813 814 // Build Image using the builder API 815 let name_for_resolver = name_for_upload.clone(); 816 let image = Image::new() 817 .alt(alt_for_upload.to_cowstr()) 818 - .image(BlobRef::Blob(blob)) 819 .name(name_for_upload.to_cowstr()) 820 .build(); 821 ··· 823 doc_for_spawn.add_image(&image, Some(&strong_ref.uri)); 824 825 // Promote from pending to uploaded in resolver 826 image_resolver.with_mut(|resolver| { 827 resolver.promote_to_uploaded( 828 &name_for_resolver, 829 - cid, 830 - did, 831 - format, 832 ); 833 }); 834
··· 1 //! The main MarkdownEditor component. 2 3 use dioxus::prelude::*; 4 + use jacquard::IntoStatic; 5 use jacquard::cowstr::ToCowStr; 6 use jacquard::types::blob::BlobRef; 7 + use jacquard::types::ident::AtIdentifier; 8 use weaver_api::sh_weaver::embed::images::Image; 9 use weaver_common::WeaverExt; 10 ··· 13 use crate::fetch::Fetcher; 14 15 use super::document::{CompositionState, EditorDocument}; 16 + use super::dom_sync::{ 17 + sync_cursor_from_dom, sync_cursor_from_dom_with_direction, update_paragraph_dom, 18 + }; 19 use super::formatting; 20 use super::input::{ 21 get_char_at, handle_copy, handle_cut, handle_keydown, handle_paste, should_intercept_key, 22 }; 23 + use super::offset_map::SnapDirection; 24 use super::paragraph::ParagraphRender; 25 use super::platform; 26 + use super::publish::{LoadedEntry, PublishButton, load_entry_for_editing}; 27 use super::render; 28 use super::storage; 29 use super::toolbar::EditorToolbar; 30 use super::visibility::update_syntax_visibility; 31 use super::writer::{EditorImageResolver, SyntaxSpanInfo}; 32 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. 42 /// 43 /// # Props 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). 123 /// 124 /// # Features 125 /// - Loro CRDT-based text storage with undo/redo support ··· 128 /// - LocalStorage auto-save with debouncing 129 /// - Keyboard shortcuts (Ctrl+B for bold, Ctrl+I for italic) 130 #[component] 131 + fn MarkdownEditorInner( 132 + draft_key: String, 133 + loaded_entry: Option<LoadedEntry>, 134 + initial_content: Option<String>, 135 + ) -> Element { 136 // Context for authenticated API calls 137 let fetcher = use_context::<Fetcher>(); 138 let auth_state = use_context::<Signal<AuthState>>(); 139 140 let mut document = use_hook(|| { 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 + } 151 }); 152 let editor_id = "markdown-editor"; 153 ··· 247 // Use requestAnimationFrame to wait for browser paint 248 if let Some(window) = web_sys::window() { 249 let closure = Closure::once(move || { 250 + if let Err(e) = super::cursor::restore_cursor_position( 251 + cursor_offset, 252 + &map, 253 + editor_id, 254 + snap_direction, 255 + ) { 256 tracing::warn!("Cursor restoration failed: {:?}", e); 257 } 258 }); ··· 276 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 277 let doc_for_autosave = document.clone(); 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"))] 281 use_effect(move || { 282 // Check every 500ms if there are unsaved changes 283 let mut doc = doc_for_autosave.clone(); 284 + let draft_key = draft_key_for_autosave.clone(); 285 let interval = gloo_timers::callback::Interval::new(500, move || { 286 let current_frontiers = doc.state_frontiers(); 287 ··· 296 297 if needs_save { 298 doc.sync_loro_cursor(); 299 + let _ = storage::save_to_storage(&doc, &draft_key); 300 301 // Update last saved frontiers 302 last_saved_frontiers.set(Some(current_frontiers)); ··· 887 // Upload blob and create temporary PublishedBlob record 888 match client.publish_blob(data, &name_for_upload, None).await { 889 Ok((strong_ref, published_blob)) => { 890 // Get DID from fetcher 891 let did = match fetcher.current_did().await { 892 + Some(d) => d, 893 None => { 894 tracing::warn!("No DID available"); 895 return; 896 } 897 }; 898 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 + }; 907 908 // Build Image using the builder API 909 let name_for_resolver = name_for_upload.clone(); 910 let image = Image::new() 911 .alt(alt_for_upload.to_cowstr()) 912 + .image(published_blob.upload) 913 .name(name_for_upload.to_cowstr()) 914 .build(); 915 ··· 917 doc_for_spawn.add_image(&image, Some(&strong_ref.uri)); 918 919 // Promote from pending to uploaded in resolver 920 + let ident = AtIdentifier::Did(did); 921 image_resolver.with_mut(|resolver| { 922 resolver.promote_to_uploaded( 923 &name_for_resolver, 924 + blob_rkey, 925 + ident, 926 ); 927 }); 928
+166 -14
crates/weaver-app/src/components/editor/document.rs
··· 10 //! - Content changes (via `last_edit`) trigger paragraph memo re-evaluation 11 //! - The document struct itself is NOT wrapped in a Signal - use `use_hook` 12 13 use std::cell::RefCell; 14 use std::rc::Rc; 15 16 use dioxus::prelude::*; 17 use loro::{ 18 - ExportMode, LoroDoc, LoroList, LoroMap, LoroResult, LoroText, LoroValue, ToJson, UndoManager, 19 cursor::{Cursor, Side}, 20 }; 21 22 use jacquard::IntoStatic; 23 use jacquard::from_json_value; 24 use jacquard::types::string::AtUri; 25 use weaver_api::sh_weaver::embed::images::Image; 26 27 /// Helper for working with editor images. 28 /// Constructed from LoroMap data, NOT serialized directly. ··· 80 /// Contains nested containers: images (LoroList), externals (LoroList), etc. 81 embeds: LoroMap, 82 83 - // --- Entry tracking --- 84 - /// AT-URI of the entry if editing an existing record. 85 /// None for new entries that haven't been published yet. 86 - entry_uri: Option<AtUri<'static>>, 87 88 // --- Editor state (non-reactive) --- 89 /// Undo manager for the document. ··· 244 created_at, 245 tags, 246 embeds, 247 - entry_uri: None, 248 undo_mgr: Rc::new(RefCell::new(undo_mgr)), 249 loro_cursor, 250 // Reactive editor state - wrapped in Signals ··· 260 } 261 } 262 263 /// Generate current datetime as ISO 8601 string. 264 #[cfg(target_family = "wasm")] 265 fn current_datetime_string() -> String { ··· 359 self.created_at.insert(0, datetime).ok(); 360 } 361 362 - // --- Entry URI accessors --- 363 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() 367 } 368 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; 372 } 373 374 // --- Tags accessors --- ··· 689 690 /// Get the current state frontiers for change detection. 691 /// Frontiers represent the "version" of the document state. 692 - pub fn state_frontiers(&self) -> loro::Frontiers { 693 self.doc.state_frontiers() 694 } 695 696 /// Get the last edit info for incremental rendering. 697 /// Reading this creates a reactive dependency on content changes. 698 pub fn last_edit(&self) -> Option<EditInfo> { 699 self.last_edit.read().clone() 700 } 701 702 /// Create a new EditorDocument from a binary snapshot. 703 /// Falls back to empty document if import fails. 704 /// ··· 765 created_at, 766 tags, 767 embeds, 768 - entry_uri: None, 769 undo_mgr: Rc::new(RefCell::new(undo_mgr)), 770 loro_cursor, 771 // Reactive editor state - wrapped in Signals
··· 10 //! - Content changes (via `last_edit`) trigger paragraph memo re-evaluation 11 //! - The document struct itself is NOT wrapped in a Signal - use `use_hook` 12 13 + use std::borrow::Cow; 14 use std::cell::RefCell; 15 use std::rc::Rc; 16 17 use dioxus::prelude::*; 18 use loro::{ 19 + ExportMode, Frontiers, LoroDoc, LoroList, LoroMap, LoroResult, LoroText, LoroValue, ToJson, 20 + UndoManager, VersionVector, 21 cursor::{Cursor, Side}, 22 }; 23 24 use jacquard::IntoStatic; 25 use jacquard::from_json_value; 26 use jacquard::types::string::AtUri; 27 + use weaver_api::com_atproto::repo::strong_ref::StrongRef; 28 use weaver_api::sh_weaver::embed::images::Image; 29 + use weaver_api::sh_weaver::notebook::entry::Entry; 30 31 /// Helper for working with editor images. 32 /// Constructed from LoroMap data, NOT serialized directly. ··· 84 /// Contains nested containers: images (LoroList), externals (LoroList), etc. 85 embeds: LoroMap, 86 87 + // --- Entry tracking (reactive) --- 88 + /// StrongRef to the entry if editing an existing record. 89 /// None for new entries that haven't been published yet. 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>, 107 108 // --- Editor state (non-reactive) --- 109 /// Undo manager for the document. ··· 264 created_at, 265 tags, 266 embeds, 267 + entry_ref: Signal::new(None), 268 + edit_root: Signal::new(None), 269 + last_diff: Signal::new(None), 270 + last_synced_version: None, 271 undo_mgr: Rc::new(RefCell::new(undo_mgr)), 272 loro_cursor, 273 // Reactive editor state - wrapped in Signals ··· 283 } 284 } 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 + 324 /// Generate current datetime as ISO 8601 string. 325 #[cfg(target_family = "wasm")] 326 fn current_datetime_string() -> String { ··· 420 self.created_at.insert(0, datetime).ok(); 421 } 422 423 + // --- Entry ref accessors --- 424 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() 428 } 429 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); 433 } 434 435 // --- Tags accessors --- ··· 750 751 /// Get the current state frontiers for change detection. 752 /// Frontiers represent the "version" of the document state. 753 + pub fn state_frontiers(&self) -> Frontiers { 754 self.doc.state_frontiers() 755 } 756 757 + /// Get the current version vector. 758 + pub fn version_vector(&self) -> VersionVector { 759 + self.doc.oplog_vv() 760 + } 761 + 762 /// Get the last edit info for incremental rendering. 763 /// Reading this creates a reactive dependency on content changes. 764 pub fn last_edit(&self) -> Option<EditInfo> { 765 self.last_edit.read().clone() 766 } 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 + 851 /// Create a new EditorDocument from a binary snapshot. 852 /// Falls back to empty document if import fails. 853 /// ··· 914 created_at, 915 tags, 916 embeds, 917 + entry_ref: Signal::new(None), 918 + edit_root: Signal::new(None), 919 + last_diff: Signal::new(None), 920 + last_synced_version: None, 921 undo_mgr: Rc::new(RefCell::new(undo_mgr)), 922 loro_cursor, 923 // Reactive editor state - wrapped in Signals
+27 -1
crates/weaver-app/src/components/editor/input.rs
··· 26 _ => {} 27 } 28 } 29 // Let browser handle other Ctrl/Cmd shortcuts (paste, copy, cut, etc.) 30 return false; 31 } ··· 151 doc.cursor.write().offset = start; 152 } else if doc.cursor.read().offset > 0 { 153 let cursor_offset = doc.cursor.read().offset; 154 // Check if we're about to delete a newline 155 let prev_char = get_char_at(doc.loro_text(), cursor_offset - 1); 156 ··· 213 doc.cursor.write().offset = start; 214 } else { 215 let cursor_offset = doc.cursor.read().offset; 216 - if cursor_offset < doc.len_chars() { 217 // Delete next char 218 let _ = doc.remove_tracked(cursor_offset, 1); 219 }
··· 26 _ => {} 27 } 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 + } 33 // Let browser handle other Ctrl/Cmd shortcuts (paste, copy, cut, etc.) 34 return false; 35 } ··· 155 doc.cursor.write().offset = start; 156 } else if doc.cursor.read().offset > 0 { 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 + 169 // Check if we're about to delete a newline 170 let prev_char = get_char_at(doc.loro_text(), cursor_offset - 1); 171 ··· 228 doc.cursor.write().offset = start; 229 } else { 230 let cursor_offset = doc.cursor.read().offset; 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 { 243 // Delete next char 244 let _ = doc.remove_tracked(cursor_offset, 1); 245 }
+1
crates/weaver-app/src/components/editor/mod.rs
··· 19 mod render; 20 mod report; 21 mod storage; 22 mod toolbar; 23 mod visibility; 24 mod writer;
··· 19 mod render; 20 mod report; 21 mod storage; 22 + mod sync; 23 mod toolbar; 24 mod visibility; 25 mod writer;
+202 -27
crates/weaver-app/src/components/editor/publish.rs
··· 1 - //! Entry publishing functionality for the markdown editor. 2 //! 3 - //! Handles creating/updating AT Protocol notebook entries from editor state. 4 5 use dioxus::prelude::*; 6 use jacquard::types::ident::AtIdentifier; 7 - use jacquard::types::string::{AtUri, Datetime, Nsid}; 8 - use jacquard::{IntoStatic, prelude::*, to_data}; 9 use weaver_api::com_atproto::repo::{create_record::CreateRecord, put_record::PutRecord}; 10 use weaver_api::sh_weaver::embed::images::Images; 11 use weaver_api::sh_weaver::notebook::entry::{Entry, EntryEmbeds}; ··· 13 14 const ENTRY_NSID: &str = "sh.weaver.notebook.entry"; 15 16 use crate::auth::AuthState; 17 use crate::fetch::Fetcher; 18 19 use super::document::EditorDocument; 20 - use super::storage::delete_draft; 21 22 /// Result of a publish operation. 23 #[derive(Clone, Debug)] ··· 36 } 37 } 38 39 /// Publish an entry to the AT Protocol. 40 /// 41 /// Supports three modes: ··· 43 /// - Without notebook but with entry_uri in doc: uses `put_record` to update existing 44 /// - Without notebook and no entry_uri: uses `create_record` for free-floating entry 45 /// 46 /// # Arguments 47 /// * `fetcher` - The authenticated fetcher/client 48 - /// * `doc` - The editor document containing entry data 49 /// * `notebook_title` - Optional title of the notebook to publish to 50 /// * `draft_key` - Storage key for the draft (for cleanup) 51 /// ··· 53 /// The AT-URI of the created/updated entry, or an error. 54 pub async fn publish_entry( 55 fetcher: &Fetcher, 56 - doc: &EditorDocument, 57 notebook_title: Option<&str>, 58 draft_key: &str, 59 ) -> Result<PublishResult, WeaverError> { ··· 96 } 97 }; 98 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 let client = fetcher.get_client(); 111 let result = if let Some(notebook) = notebook_title { 112 // Publish to a notebook via upsert_entry 113 - let (uri, was_created) = client.upsert_entry(notebook, &doc.title(), entry).await?; 114 115 if was_created { 116 PublishResult::Created(uri) 117 } else { 118 PublishResult::Updated(uri) 119 } 120 - } else if let Some(existing_uri) = doc.entry_uri() { 121 - // Update existing free-floating entry 122 let did = fetcher 123 .current_did() 124 .await 125 .ok_or_else(|| WeaverError::InvalidNotebook("Not authenticated".into()))?; 126 127 - let rkey = existing_uri 128 .rkey() 129 .ok_or_else(|| WeaverError::InvalidNotebook("Entry URI missing rkey".into()))?; 130 131 let collection = Nsid::new(ENTRY_NSID).map_err(|e| WeaverError::AtprotoString(e))?; 132 133 let request = PutRecord::new() ··· 145 .into_output() 146 .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 147 148 PublishResult::Updated(output.uri.into_static()) 149 } else { 150 - // Create new free-floating entry 151 let did = fetcher 152 .current_did() 153 .await 154 .ok_or_else(|| WeaverError::InvalidNotebook("Not authenticated".into()))?; 155 156 let collection = Nsid::new(ENTRY_NSID).map_err(|e| WeaverError::AtprotoString(e))?; 157 158 let request = CreateRecord::new() 159 .repo(AtIdentifier::Did(did)) 160 .collection(collection) 161 .record(entry_data) 162 .build(); 163 ··· 169 .into_output() 170 .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 171 172 - PublishResult::Created(output.uri.into_static()) 173 }; 174 175 // Cleanup: delete PublishedBlob records (entry's embed refs now keep blobs alive) ··· 180 // } 181 // } 182 183 - // Clear local draft 184 delete_draft(draft_key); 185 186 Ok(result) 187 } ··· 237 let draft_key = props.draft_key.clone(); 238 239 // Check if we're editing an existing entry 240 - let is_editing_existing = doc.entry_uri().is_some(); 241 242 // Validate that we have required fields 243 let can_publish = !doc.title().trim().is_empty() && !doc.content().trim().is_empty(); ··· 268 is_publishing.set(true); 269 error_message.set(None); 270 271 - match publish_entry(&fetcher, &doc_snapshot, notebook.as_deref(), &draft_key).await { 272 Ok(result) => { 273 success_uri.set(Some(result.uri().clone())); 274 }
··· 1 + //! Entry publishing and loading functionality for the markdown editor. 2 //! 3 + //! Handles creating/updating/loading AT Protocol notebook entries. 4 5 use dioxus::prelude::*; 6 + use jacquard::types::collection::Collection; 7 use jacquard::types::ident::AtIdentifier; 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; 16 use weaver_api::com_atproto::repo::{create_record::CreateRecord, put_record::PutRecord}; 17 use weaver_api::sh_weaver::embed::images::Images; 18 use weaver_api::sh_weaver::notebook::entry::{Entry, EntryEmbeds}; ··· 20 21 const ENTRY_NSID: &str = "sh.weaver.notebook.entry"; 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 + 41 use crate::auth::AuthState; 42 use crate::fetch::Fetcher; 43 44 use super::document::EditorDocument; 45 + use super::storage::{delete_draft, save_to_storage}; 46 47 /// Result of a publish operation. 48 #[derive(Clone, Debug)] ··· 61 } 62 } 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 + 150 /// Publish an entry to the AT Protocol. 151 /// 152 /// Supports three modes: ··· 154 /// - Without notebook but with entry_uri in doc: uses `put_record` to update existing 155 /// - Without notebook and no entry_uri: uses `create_record` for free-floating entry 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 + /// 162 /// # Arguments 163 /// * `fetcher` - The authenticated fetcher/client 164 + /// * `doc` - The editor document containing entry data (mutable to update entry_uri) 165 /// * `notebook_title` - Optional title of the notebook to publish to 166 /// * `draft_key` - Storage key for the draft (for cleanup) 167 /// ··· 169 /// The AT-URI of the created/updated entry, or an error. 170 pub async fn publish_entry( 171 fetcher: &Fetcher, 172 + doc: &mut EditorDocument, 173 notebook_title: Option<&str>, 174 draft_key: &str, 175 ) -> Result<PublishResult, WeaverError> { ··· 212 } 213 }; 214 215 let client = fetcher.get_client(); 216 let result = if let Some(notebook) = notebook_title { 217 // Publish to a notebook via upsert_entry 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)); 234 235 if was_created { 236 PublishResult::Created(uri) 237 } else { 238 PublishResult::Updated(uri) 239 } 240 + } else if let Some(existing_ref) = doc.entry_ref() { 241 + // Update existing free-floating entry - use existing rkey for path rewriting 242 let did = fetcher 243 .current_did() 244 .await 245 .ok_or_else(|| WeaverError::InvalidNotebook("Not authenticated".into()))?; 246 247 + let rkey = existing_ref 248 + .uri 249 .rkey() 250 .ok_or_else(|| WeaverError::InvalidNotebook("Entry URI missing rkey".into()))?; 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 + 265 let collection = Nsid::new(ENTRY_NSID).map_err(|e| WeaverError::AtprotoString(e))?; 266 267 let request = PutRecord::new() ··· 279 .into_output() 280 .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 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 + 289 PublishResult::Updated(output.uri.into_static()) 290 } else { 291 + // Create new free-floating entry - pre-generate rkey for path rewriting 292 let did = fetcher 293 .current_did() 294 .await 295 .ok_or_else(|| WeaverError::InvalidNotebook("Not authenticated".into()))?; 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 + 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()))?; 317 318 let request = CreateRecord::new() 319 .repo(AtIdentifier::Did(did)) 320 .collection(collection) 321 + .rkey(rkey) 322 .record(entry_data) 323 .build(); 324 ··· 330 .into_output() 331 .map_err(|e| WeaverError::InvalidNotebook(e.to_string()))?; 332 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) 341 }; 342 343 // Cleanup: delete PublishedBlob records (entry's embed refs now keep blobs alive) ··· 348 // } 349 // } 350 351 + // Delete the old draft key 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 + } 359 360 Ok(result) 361 } ··· 411 let draft_key = props.draft_key.clone(); 412 413 // Check if we're editing an existing entry 414 + let is_editing_existing = doc.entry_ref().is_some(); 415 416 // Validate that we have required fields 417 let can_publish = !doc.title().trim().is_empty() && !doc.content().trim().is_empty(); ··· 442 is_publishing.set(true); 443 error_message.set(None); 444 445 + let mut doc_snapshot = doc_snapshot; 446 + match publish_entry(&fetcher, &mut doc_snapshot, notebook.as_deref(), &draft_key).await { 447 Ok(result) => { 448 success_uri.set(Some(result.uri().clone())); 449 }
+18 -8
crates/weaver-app/src/components/editor/storage.rs
··· 13 #[cfg(all(target_family = "wasm", target_os = "unknown"))] 14 use gloo_storage::{LocalStorage, Storage}; 15 use jacquard::IntoStatic; 16 - use jacquard::types::string::AtUri; 17 use loro::cursor::Cursor; 18 use serde::{Deserialize, Serialize}; 19 ··· 58 /// AT-URI if editing an existing entry (None for new entries) 59 #[serde(default, skip_serializing_if = "Option::is_none")] 60 pub editing_uri: Option<String>, 61 } 62 63 /// Build the full storage key from a draft key. ··· 88 snapshot: snapshot_b64, 89 cursor: doc.loro_cursor().cloned(), 90 cursor_offset: doc.cursor.read().offset, 91 - editing_uri: doc.entry_uri().map(|u| u.to_string()), 92 }; 93 LocalStorage::set(storage_key(key), &snapshot) 94 } ··· 104 pub fn load_from_storage(key: &str) -> Option<EditorDocument> { 105 let snapshot: EditorSnapshot = LocalStorage::get(storage_key(key)).ok()?; 106 107 - // Parse entry_uri from the snapshot 108 - let entry_uri = snapshot 109 .editing_uri 110 .as_ref() 111 - .and_then(|s| AtUri::new(s).ok()) 112 - .map(|u| u.into_static()); 113 114 // Try to restore from CRDT snapshot first 115 if let Some(ref snapshot_b64) = snapshot.snapshot { ··· 121 ); 122 // Verify the content matches (sanity check) 123 if doc.content() == snapshot.content { 124 - doc.set_entry_uri(entry_uri); 125 return Some(doc); 126 } 127 tracing::warn!("Snapshot content mismatch, falling back to text content"); ··· 132 let mut doc = EditorDocument::new(snapshot.content); 133 doc.cursor.write().offset = snapshot.cursor_offset.min(doc.len_chars()); 134 doc.sync_loro_cursor(); 135 - doc.set_entry_uri(entry_uri); 136 Some(doc) 137 } 138
··· 13 #[cfg(all(target_family = "wasm", target_os = "unknown"))] 14 use gloo_storage::{LocalStorage, Storage}; 15 use jacquard::IntoStatic; 16 + use jacquard::types::string::{AtUri, Cid}; 17 + use weaver_api::com_atproto::repo::strong_ref::StrongRef; 18 use loro::cursor::Cursor; 19 use serde::{Deserialize, Serialize}; 20 ··· 59 /// AT-URI if editing an existing entry (None for new entries) 60 #[serde(default, skip_serializing_if = "Option::is_none")] 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>, 66 } 67 68 /// Build the full storage key from a draft key. ··· 93 snapshot: snapshot_b64, 94 cursor: doc.loro_cursor().cloned(), 95 cursor_offset: doc.cursor.read().offset, 96 + editing_uri: doc.entry_ref().map(|r| r.uri.to_string()), 97 + editing_cid: doc.entry_ref().map(|r| r.cid.to_string()), 98 }; 99 LocalStorage::set(storage_key(key), &snapshot) 100 } ··· 110 pub fn load_from_storage(key: &str) -> Option<EditorDocument> { 111 let snapshot: EditorSnapshot = LocalStorage::get(storage_key(key)).ok()?; 112 113 + // Parse entry_ref from the snapshot (requires both URI and CID) 114 + let entry_ref = snapshot 115 .editing_uri 116 .as_ref() 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 + }); 123 124 // Try to restore from CRDT snapshot first 125 if let Some(ref snapshot_b64) = snapshot.snapshot { ··· 131 ); 132 // Verify the content matches (sanity check) 133 if doc.content() == snapshot.content { 134 + doc.set_entry_ref(entry_ref.clone()); 135 return Some(doc); 136 } 137 tracing::warn!("Snapshot content mismatch, falling back to text content"); ··· 142 let mut doc = EditorDocument::new(snapshot.content); 143 doc.cursor.write().offset = snapshot.cursor_offset.min(doc.len_chars()); 144 doc.sync_loro_cursor(); 145 + doc.set_entry_ref(entry_ref); 146 Some(doc) 147 } 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 //! represent consumed formatting characters. 8 9 use super::offset_map::{OffsetMapping, RenderResult}; 10 use loro::LoroText; 11 use markdown_weaver::{ 12 Alignment, BlockQuoteKind, CodeBlockKind, CowStr, EmbedType, Event, LinkType, Tag, ··· 138 139 /// Concrete image resolver that maps image names to URLs. 140 /// 141 - /// Supports two states for images: 142 /// - 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}` 144 /// 145 /// Image URLs in markdown use the format `/image/{name}`. 146 #[derive(Clone, Default)] 147 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)>, 152 } 153 154 impl EditorImageResolver { ··· 162 /// * `name` - The image name used in markdown (e.g., "photo.jpg") 163 /// * `data_url` - The base64 data URL for preview 164 pub fn add_pending(&mut self, name: String, data_url: String) { 165 - self.pending.insert(name, data_url); 166 } 167 168 - /// Promote a pending image to uploaded status. 169 /// 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)); 174 } 175 176 - /// Add an already-uploaded image. 177 /// 178 /// # Arguments 179 /// * `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)); 185 } 186 187 /// Check if an image is pending upload. 188 pub fn is_pending(&self, name: &str) -> bool { 189 - self.pending.contains_key(name) 190 } 191 192 - /// Build a resolver from editor images and user DID. 193 pub fn from_images<'a>( 194 images: impl IntoIterator<Item = &'a super::document::EditorImage>, 195 - user_did: &str, 196 ) -> Self { 197 let mut resolver = Self::new(); 198 for editor_image in images { 199 // Get the name from the Image (use alt text as fallback if name is empty) ··· 208 continue; 209 } 210 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); 222 } 223 resolver 224 } ··· 229 // Extract image name from /image/{name} format 230 let name = url.strip_prefix("/image/").unwrap_or(url); 231 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()); 235 } 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 } 244 } 245
··· 7 //! represent consumed formatting characters. 8 9 use super::offset_map::{OffsetMapping, RenderResult}; 10 + use jacquard::types::{ident::AtIdentifier, string::Rkey}; 11 use loro::LoroText; 12 use markdown_weaver::{ 13 Alignment, BlockQuoteKind, CodeBlockKind, CowStr, EmbedType, Event, LinkType, Tag, ··· 139 140 /// Concrete image resolver that maps image names to URLs. 141 /// 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: 162 /// - Pending: uses data URL for immediate preview while upload is in progress 163 + /// - Draft: uses path format `/image/{did}/draft/{blob_rkey}/{name}` 164 + /// - Published: uses path format `/image/{did}/{entry_rkey}/{name}` 165 /// 166 /// Image URLs in markdown use the format `/image/{name}`. 167 #[derive(Clone, Default)] 168 pub struct EditorImageResolver { 169 + /// All resolved images: name -> resolved path info 170 + images: std::collections::HashMap<String, ResolvedImage>, 171 } 172 173 impl EditorImageResolver { ··· 181 /// * `name` - The image name used in markdown (e.g., "photo.jpg") 182 /// * `data_url` - The base64 data URL for preview 183 pub fn add_pending(&mut self, name: String, data_url: String) { 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 + ); 203 } 204 205 + /// Add an already-uploaded draft image. 206 /// 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 }); 219 } 220 221 + /// Add a published image. 222 /// 223 /// # Arguments 224 /// * `name` - The name/URL used in markdown (e.g., "photo.jpg") 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 }); 235 } 236 237 /// Check if an image is pending upload. 238 pub fn is_pending(&self, name: &str) -> bool { 239 + matches!(self.images.get(name), Some(ResolvedImage::Pending(_))) 240 } 241 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. 252 pub fn from_images<'a>( 253 images: impl IntoIterator<Item = &'a super::document::EditorImage>, 254 + ident: AtIdentifier<'static>, 255 + entry_rkey: Option<Rkey<'static>>, 256 ) -> Self { 257 + use jacquard::IntoStatic; 258 + 259 let mut resolver = Self::new(); 260 for editor_image in images { 261 // Get the name from the Image (use alt text as fallback if name is empty) ··· 270 continue; 271 } 272 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 + } 290 } 291 resolver 292 } ··· 297 // Extract image name from /image/{name} format 298 let name = url.strip_prefix("/image/").unwrap_or(url); 299 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 + } 309 } 310 } 311 } 312
+162 -6
crates/weaver-app/src/main.rs
··· 54 #[layout(Navbar)] 55 #[route("/")] 56 Home {}, 57 - #[route("/editor")] 58 - Editor {}, 59 #[layout(ErrorLayout)] 60 #[nest("/record")] 61 #[layout(RecordIndex)] ··· 304 #[cfg(all(feature = "fullstack-server", feature = "server"))] 305 #[get("/{notebook}/image/{name}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 306 pub async fn image_named(notebook: SmolStr, name: SmolStr) -> Result<axum::response::Response> { 307 - use axum::{http::header::CONTENT_TYPE, response::IntoResponse}; 308 use mime_sniffer::MimeTypeSniffer; 309 if let Some(bytes) = blob_cache.get_named(&name) { 310 let blob = bytes.clone(); 311 let mime = blob.sniff_mime_type().unwrap_or("image/jpg"); 312 - Ok(([(CONTENT_TYPE, mime)], bytes).into_response()) 313 } else { 314 Err(CapturedError::from_display("no image")) 315 } ··· 318 #[cfg(all(feature = "fullstack-server", feature = "server"))] 319 #[get("/{notebook}/blob/{cid}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 320 pub async fn blob(notebook: SmolStr, cid: SmolStr) -> Result<axum::response::Response> { 321 - use axum::{http::header::CONTENT_TYPE, response::IntoResponse}; 322 use mime_sniffer::MimeTypeSniffer; 323 if let Some(bytes) = blob_cache.get_cid(&Cid::new_owned(cid.as_bytes())?) { 324 let blob = bytes.clone(); 325 let mime = blob.sniff_mime_type().unwrap_or("application/octet-stream"); 326 - Ok(([(CONTENT_TYPE, mime)], bytes).into_response()) 327 } else { 328 Err(CapturedError::from_display("no blob")) 329 } 330 } 331
··· 54 #[layout(Navbar)] 55 #[route("/")] 56 Home {}, 57 + #[route("/editor?:entry")] 58 + Editor { entry: Option<String> }, 59 #[layout(ErrorLayout)] 60 #[nest("/record")] 61 #[layout(RecordIndex)] ··· 304 #[cfg(all(feature = "fullstack-server", feature = "server"))] 305 #[get("/{notebook}/image/{name}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 306 pub async fn image_named(notebook: SmolStr, name: SmolStr) -> Result<axum::response::Response> { 307 + use axum::{ 308 + http::header::{CACHE_CONTROL, CONTENT_TYPE}, 309 + response::IntoResponse, 310 + }; 311 use mime_sniffer::MimeTypeSniffer; 312 if let Some(bytes) = blob_cache.get_named(&name) { 313 let blob = bytes.clone(); 314 let mime = blob.sniff_mime_type().unwrap_or("image/jpg"); 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()) 324 } else { 325 Err(CapturedError::from_display("no image")) 326 } ··· 329 #[cfg(all(feature = "fullstack-server", feature = "server"))] 330 #[get("/{notebook}/blob/{cid}", blob_cache: Extension<Arc<crate::blobcache::BlobCache>>)] 331 pub async fn blob(notebook: SmolStr, cid: SmolStr) -> Result<axum::response::Response> { 332 + use axum::{ 333 + http::header::{CACHE_CONTROL, CONTENT_TYPE}, 334 + response::IntoResponse, 335 + }; 336 use mime_sniffer::MimeTypeSniffer; 337 if let Some(bytes) = blob_cache.get_cid(&Cid::new_owned(cid.as_bytes())?) { 338 let blob = bytes.clone(); 339 let mime = blob.sniff_mime_type().unwrap_or("application/octet-stream"); 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()) 349 } else { 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), 485 } 486 } 487
+4 -4
crates/weaver-app/src/views/editor.rs
··· 5 6 /// Editor page view. 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. 10 #[component] 11 - pub fn Editor() -> Element { 12 rsx! { 13 EditorCss {} 14 div { class: "editor-page", 15 - MarkdownEditor { initial_content: None } 16 } 17 } 18 }
··· 5 6 /// Editor page view. 7 /// 8 + /// Displays the markdown editor at the /editor route. 9 + /// Optionally loads an existing entry for editing via `?entry={at-uri}`. 10 #[component] 11 + pub fn Editor(entry: Option<String>) -> Element { 12 rsx! { 13 EditorCss {} 14 div { class: "editor-page", 15 + MarkdownEditor { entry_uri: entry } 16 } 17 } 18 }
+24 -13
crates/weaver-common/src/agent.rs
··· 216 /// 3. If found: update the entry with new content 217 /// 4. If not found: create new entry and append to notebook's entry_list 218 /// 219 - /// Returns (entry_uri, was_created) 220 fn upsert_entry( 221 &self, 222 notebook_title: &str, 223 entry_title: &str, 224 entry: entry::Entry<'_>, 225 - ) -> impl Future<Output = Result<(AtUri<'static>, bool), WeaverError>> 226 where 227 Self: Sized, 228 { ··· 244 if let Ok(existing_entry) = existing.parse() { 245 if existing_entry.value.title == entry_title { 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)); 254 } 255 } 256 } 257 258 // Entry doesn't exist, create it 259 let response = self.create_record(entry, None).await?; 260 - let entry_uri = response.uri.clone(); 261 262 // Add to notebook's entry_list 263 use weaver_api::sh_weaver::notebook::book::Book; 264 - let new_ref = StrongRef::new().uri(response.uri).cid(response.cid).build(); 265 266 self.update_record::<Book>(&notebook_uri, |book| { 267 - book.entry_list.push(new_ref); 268 }) 269 .await?; 270 271 - Ok((entry_uri, true)) 272 } 273 } 274
··· 216 /// 3. If found: update the entry with new content 217 /// 4. If not found: create new entry and append to notebook's entry_list 218 /// 219 + /// Returns (entry_ref, was_created) 220 fn upsert_entry( 221 &self, 222 notebook_title: &str, 223 entry_title: &str, 224 entry: entry::Entry<'_>, 225 + ) -> impl Future<Output = Result<(StrongRef<'static>, bool), WeaverError>> 226 where 227 Self: Sized, 228 { ··· 244 if let Ok(existing_entry) = existing.parse() { 245 if existing_entry.value.title == entry_title { 246 // Update existing entry 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)); 259 } 260 } 261 } 262 263 // Entry doesn't exist, create it 264 let response = self.create_record(entry, None).await?; 265 + let new_ref = StrongRef::new() 266 + .uri(response.uri.clone().into_static()) 267 + .cid(response.cid.clone().into_static()) 268 + .build(); 269 270 // Add to notebook's entry_list 271 use weaver_api::sh_weaver::notebook::book::Book; 272 + let notebook_entry_ref = StrongRef::new() 273 + .uri(response.uri.into_static()) 274 + .cid(response.cid.into_static()) 275 + .build(); 276 277 self.update_record::<Book>(&notebook_uri, |book| { 278 + book.entry_list.push(notebook_entry_ref); 279 }) 280 .await?; 281 282 + Ok((new_ref, true)) 283 } 284 } 285
+8 -8
crates/weaver-common/src/constellation.rs
··· 26 /// The link target 27 /// 28 /// can be an AT-URI, plain DID, or regular URI 29 - subject: jacquard::types::uri::Uri<'a>, 30 /// Filter links only from this link source 31 /// 32 /// eg.: `app.bsky.feed.like:subject.uri` 33 - source: CowStr<'a>, 34 #[serde(borrow)] 35 - cursor: Option<CowStr<'a>>, 36 /// Filter links only from these DIDs 37 /// 38 /// include multiple times to filter by multiple source DIDs 39 #[serde(default)] 40 - did: Vec<Did<'a>>, 41 /// Set the max number of links to return per page of results 42 #[serde(default = "get_default_cursor_limit")] 43 - limit: u64, 44 // TODO: allow reverse (er, forward) order as well 45 } 46 #[derive(Deserialize, Serialize, IntoStatic)] 47 pub struct GetBacklinksResponse<'a> { 48 - total: u64, 49 #[serde(borrow)] 50 - records: Vec<RecordId<'a>>, 51 - cursor: Option<CowStr<'a>>, 52 } 53 54 #[derive(Debug, PartialEq, Serialize, Deserialize, IntoStatic)]
··· 26 /// The link target 27 /// 28 /// can be an AT-URI, plain DID, or regular URI 29 + pub subject: jacquard::types::uri::Uri<'a>, 30 /// Filter links only from this link source 31 /// 32 /// eg.: `app.bsky.feed.like:subject.uri` 33 + pub source: CowStr<'a>, 34 #[serde(borrow)] 35 + pub cursor: Option<CowStr<'a>>, 36 /// Filter links only from these DIDs 37 /// 38 /// include multiple times to filter by multiple source DIDs 39 #[serde(default)] 40 + pub did: Vec<Did<'a>>, 41 /// Set the max number of links to return per page of results 42 #[serde(default = "get_default_cursor_limit")] 43 + pub limit: u64, 44 // TODO: allow reverse (er, forward) order as well 45 } 46 #[derive(Deserialize, Serialize, IntoStatic)] 47 pub struct GetBacklinksResponse<'a> { 48 + pub total: u64, 49 #[serde(borrow)] 50 + pub records: Vec<RecordId<'a>>, 51 + pub cursor: Option<CowStr<'a>>, 52 } 53 54 #[derive(Debug, PartialEq, Serialize, Deserialize, IntoStatic)]
+10 -3
lexicons.kdl
··· 5 } 6 7 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=200 { 14 path "./lexicons"
··· 5 } 6 7 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 + 17 } 18 + 19 20 source "weaver" type="local" priority=200 { 21 path "./lexicons"
+14 -3
lexicons/edit/defs.json
··· 8 "properties": { 9 "value": { 10 "type": "union", 11 - "refs": ["#notebookRef", "#entryRef"] 12 } 13 } 14 }, ··· 24 }, 25 "entryRef": { 26 "type": "object", 27 - "required": ["notebook"], 28 "properties": { 29 - "notebook": { 30 "type": "ref", 31 "ref": "com.atproto.repo.strongRef" 32 } 33 } 34 }
··· 8 "properties": { 9 "value": { 10 "type": "union", 11 + "refs": ["#notebookRef", "#entryRef", "#draftRef"] 12 } 13 } 14 }, ··· 24 }, 25 "entryRef": { 26 "type": "object", 27 + "required": ["entry"], 28 "properties": { 29 + "entry": { 30 "type": "ref", 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 43 } 44 } 45 }
+4
lexicons/edit/diff.json
··· 19 "type": "ref", 20 "ref": "com.atproto.repo.strongRef" 21 }, 22 "doc": { 23 "type": "ref", 24 "ref": "sh.weaver.edit.defs#docRef"
··· 19 "type": "ref", 20 "ref": "com.atproto.repo.strongRef" 21 }, 22 + "prev": { 23 + "type": "ref", 24 + "ref": "com.atproto.repo.strongRef" 25 + }, 26 "doc": { 27 "type": "ref", 28 "ref": "sh.weaver.edit.defs#docRef"