Something went wrong. Try again.
A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118//! What a method's lexicon says its input and output are encoded as.//!//! `com.atproto.sync.getBlob` returns bytes because its lexicon declares//! `"output": { "encoding": "*/*" }`, and `com.atproto.repo.uploadBlob` takes//! them because it declares the same on its input. The declaration is the//! contract both sides read, so it is what decides here too — never the shape//! of whatever a script happened to return, or of whatever a caller happened//! to send.
use serde_json::Value;
use crate::lexicon::ParsedLexicon;
/// A declared encoding, on either side of a method.#[derive(Debug, Clone, PartialEq, Eq)]pub enum Encoding { /// `application/json`, or no declaration at all. Json, /// A media type the lexicon pins, so neither side has to name one and /// neither can contradict it. Fixed(String), /// `*/*` — the concrete type is named by whoever supplies the bytes. Any,}
impl Encoding { /// Whether this side of the method carries bytes rather than JSON. pub fn is_bytes(&self) -> bool { !matches!(self, Encoding::Json) }}
fn read(declaration: Option<&Value>) -> Encoding { let declared = declaration .and_then(|side| side.get("encoding")) .and_then(Value::as_str) .map(str::trim) .unwrap_or("");
match declared { "" | "application/json" => Encoding::Json, "*/*" => Encoding::Any, concrete => Encoding::Fixed(concrete.to_string()), }}
/// What `defs.main.output.encoding` says.pub fn of_output(lexicon: &ParsedLexicon) -> Encoding { read(lexicon.output.as_ref())}
/// What `defs.main.input.encoding` says.pub fn of_input(lexicon: &ParsedLexicon) -> Encoding { read(lexicon.input.as_ref())}
#[cfg(test)]mod tests { use super::*; use crate::lexicon::ProcedureAction; use serde_json::json;
/// A lexicon declaring whichever encodings the caller names. fn lexicon(input: Option<&str>, output: Option<&str>) -> ParsedLexicon { let side = |e: Option<&str>| match e { Some(e) => json!({ "encoding": e }), None => json!({}), }; ParsedLexicon::parse( json!({ "lexicon": 1, "id": "at.example.thing", "defs": { "main": { "type": "procedure", "input": side(input), "output": side(output), } } }), 1, None, ProcedureAction::Create, None, ) .expect("the fixture lexicon should parse") }
#[test] fn a_declaration_is_read_as_json_bytes_or_delegated() { for (declared, expected) in [ (None, Encoding::Json), (Some("application/json"), Encoding::Json), // Whitespace should not make a declaration into a media type of // its own. (Some(" application/json "), Encoding::Json), (Some("*/*"), Encoding::Any), ( Some("application/wasm"), Encoding::Fixed("application/wasm".into()), ), ] { let lex = lexicon(declared, declared); assert_eq!(of_input(&lex), expected, "input {declared:?}"); assert_eq!(of_output(&lex), expected, "output {declared:?}"); } }
/// The two sides are read independently, which is the shape `uploadBlob` /// has: bytes in, JSON out. #[test] fn the_two_sides_are_independent() { let lex = lexicon(Some("*/*"), Some("application/json")); assert_eq!(of_input(&lex), Encoding::Any); assert_eq!(of_output(&lex), Encoding::Json); assert!(of_input(&lex).is_bytes()); assert!(!of_output(&lex).is_bytes()); }}