nexgen 0.2.1

Generate language-specific Nexus operation bindings from WIT
Documentation
use crate::spec::{ExternalTypeSpec, JsonModelSpec};

use super::ApiPlanner;

impl ApiPlanner<'_> {
    pub(super) fn mark_json_model_used(&mut self, json_type: &JsonModelSpec<crate::spec::Symbol>) {
        if !self
            .used_json_models
            .insert(json_type.name.as_str().to_string())
        {
            return;
        }
        self.mark_json_schema_refs_used(&json_type.schema);
    }

    fn mark_json_schema_refs_used(&mut self, schema: &serde_json::Value) {
        let Some(object) = schema.as_object() else {
            return;
        };

        if let Some(reference) = object.get("$ref").and_then(serde_json::Value::as_str)
            && let Some(model_name) = json_ref_model_name(reference)
            && let Some(nested) = self.json_model_for_ref(&model_name)
        {
            self.mark_json_model_used(&nested);
        }

        for value in object.values() {
            match value {
                serde_json::Value::Array(values) => {
                    for value in values {
                        self.mark_json_schema_refs_used(value);
                    }
                }
                serde_json::Value::Object(_) => self.mark_json_schema_refs_used(value),
                _ => {}
            }
        }
    }

    fn json_model_for_ref(&self, model_name: &str) -> Option<JsonModelSpec<crate::spec::Symbol>> {
        self.spec
            .external_type_binding(model_name)
            .and_then(|binding| match &binding.external_type {
                ExternalTypeSpec::Json(json_type) => Some(json_type.clone()),
                _ => None,
            })
            .or_else(|| {
                self.spec
                    .external_types()
                    .map(|(_, binding)| binding)
                    .find_map(|binding| match &binding.external_type {
                        ExternalTypeSpec::Json(json_type)
                            if json_type.model_name == model_name
                                || json_type.name.local_name() == model_name =>
                        {
                            Some(json_type.clone())
                        }
                        _ => None,
                    })
            })
    }
}

fn json_ref_model_name(reference: &str) -> Option<String> {
    let fragment = reference
        .split_once('#')
        .map(|(_, fragment)| fragment)
        .unwrap_or(reference);
    let name = fragment
        .strip_prefix("/$defs/")
        .or_else(|| fragment.trim_start_matches('/').rsplit('/').next())?;
    let name = name.replace("~1", "/").replace("~0", "~");
    (!name.is_empty()).then_some(name)
}