Skip to main content

aws_smithy_http_server/schema/
settings.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Typed access to global and protocol settings.
7
8use aws_smithy_types::{Document, Number};
9
10use crate::schema::routing::RouterBuildError;
11
12/// Converts a settings value into a type, optionally borrowing from the document.
13///
14/// Protocols can implement this trait for their own settings types. Return `None`
15/// when the value is invalid; [`get`] reports it as a configuration error.
16pub trait FromSetting<'a>: Sized {
17    /// The expected type or value, used in configuration error diagnostics.
18    const EXPECTED: &'static str;
19
20    /// Converts a value, returning `None` if it does not match this type.
21    fn from_setting(value: &'a Document) -> Option<Self>;
22}
23
24impl FromSetting<'_> for bool {
25    const EXPECTED: &'static str = "a boolean";
26
27    fn from_setting(value: &Document) -> Option<Self> {
28        value.as_bool()
29    }
30}
31
32impl FromSetting<'_> for u64 {
33    const EXPECTED: &'static str = "a non-negative integer";
34
35    fn from_setting(value: &Document) -> Option<Self> {
36        match value {
37            Document::Number(Number::PosInt(value)) => Some(*value),
38            _ => None,
39        }
40    }
41}
42
43impl<'a> FromSetting<'a> for &'a str {
44    const EXPECTED: &'static str = "a string";
45
46    fn from_setting(value: &'a Document) -> Option<Self> {
47        value.as_string()
48    }
49}
50
51impl FromSetting<'_> for String {
52    const EXPECTED: &'static str = "a string";
53
54    fn from_setting(value: &Document) -> Option<Self> {
55        value.as_string().map(str::to_owned)
56    }
57}
58
59/// Reads a typed value from a settings object.
60///
61/// An absent section or key returns `Ok(None)`, leaving defaults to callers.
62/// A non-object section or an invalid value (including explicit `null`) returns
63/// [`RouterBuildError::Configuration`].
64pub fn get<'a, T: FromSetting<'a>>(settings: Option<&'a Document>, key: &str) -> Result<Option<T>, RouterBuildError> {
65    let Some(settings) = settings else {
66        return Ok(None);
67    };
68    let Document::Object(object) = settings else {
69        return Err(RouterBuildError::Configuration(format!(
70            "settings section for `{key}` (expected {}) must be a JSON object, got {settings:?}",
71            T::EXPECTED,
72        )));
73    };
74    object
75        .get(key)
76        .map(|value| {
77            T::from_setting(value).ok_or_else(|| {
78                RouterBuildError::Configuration(format!("setting `{key}` must be {}, got {value:?}", T::EXPECTED,))
79            })
80        })
81        .transpose()
82}
83
84/// Parses a JSON object emitted by codegen into a settings [`Document`].
85///
86/// Generated `routing_options()` functions embed each protocol's section of
87/// `customizationConfig.protocols` as a JSON byte-string and call this once at
88/// service build time. The input is printed by codegen from a validated node,
89/// so malformed JSON is a codegen bug: this panics rather than returning an
90/// error.
91///
92/// [`Document`]: aws_smithy_types::Document
93pub fn parse_settings_json(json: &[u8]) -> aws_smithy_types::Document {
94    let mut tokens = aws_smithy_json::deserialize::json_token_iter(json).peekable();
95    let document = aws_smithy_json::deserialize::token::expect_document(&mut tokens)
96        .expect("codegen emits well-formed settings JSON");
97    assert!(tokens.next().is_none(), "codegen emits a single settings JSON document");
98    document
99}