jspg progress
This commit is contained in:
243
old_code/lib.rs
Normal file
243
old_code/lib.rs
Normal file
@ -0,0 +1,243 @@
|
||||
use pgrx::*;
|
||||
|
||||
pg_module_magic!();
|
||||
|
||||
// mod schema;
|
||||
mod registry;
|
||||
mod validator;
|
||||
mod util;
|
||||
|
||||
use crate::registry::REGISTRY;
|
||||
// use crate::schema::Schema;
|
||||
use crate::validator::{Validator, ValidationOptions};
|
||||
use lazy_static::lazy_static;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum SchemaType {
|
||||
Enum,
|
||||
Type,
|
||||
Family,
|
||||
PublicPunc,
|
||||
PrivatePunc,
|
||||
}
|
||||
|
||||
struct CachedSchema {
|
||||
t: SchemaType,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SCHEMA_META: std::sync::RwLock<HashMap<String, CachedSchema>> = std::sync::RwLock::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[pg_extern(strict)]
|
||||
fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB {
|
||||
let mut meta = SCHEMA_META.write().unwrap();
|
||||
let enums_value: Value = enums.0;
|
||||
let types_value: Value = types.0;
|
||||
let puncs_value: Value = puncs.0;
|
||||
|
||||
let mut schemas_to_register = Vec::new();
|
||||
|
||||
// Phase 1: Enums
|
||||
if let Some(enums_array) = enums_value.as_array() {
|
||||
for enum_row in enums_array {
|
||||
if let Some(schemas_raw) = enum_row.get("schemas") {
|
||||
if let Some(schemas_array) = schemas_raw.as_array() {
|
||||
for schema_def in schemas_array {
|
||||
if let Some(schema_id) = schema_def.get("$id").and_then(|v| v.as_str()) {
|
||||
schemas_to_register.push((schema_id.to_string(), schema_def.clone(), SchemaType::Enum));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Types & Hierarchy
|
||||
let mut hierarchy_map: HashMap<String, HashSet<String>> = HashMap::new();
|
||||
if let Some(types_array) = types_value.as_array() {
|
||||
for type_row in types_array {
|
||||
if let Some(schemas_raw) = type_row.get("schemas") {
|
||||
if let Some(schemas_array) = schemas_raw.as_array() {
|
||||
for schema_def in schemas_array {
|
||||
if let Some(schema_id) = schema_def.get("$id").and_then(|v| v.as_str()) {
|
||||
schemas_to_register.push((schema_id.to_string(), schema_def.clone(), SchemaType::Type));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(type_name) = type_row.get("name").and_then(|v| v.as_str()) {
|
||||
if let Some(hierarchy_raw) = type_row.get("hierarchy") {
|
||||
if let Some(hierarchy_array) = hierarchy_raw.as_array() {
|
||||
for ancestor_val in hierarchy_array {
|
||||
if let Some(ancestor_name) = ancestor_val.as_str() {
|
||||
hierarchy_map.entry(ancestor_name.to_string()).or_default().insert(type_name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (base_type, descendant_types) in hierarchy_map {
|
||||
let family_id = format!("{}.family", base_type);
|
||||
let values: Vec<String> = descendant_types.into_iter().collect();
|
||||
let family_schema = json!({ "$id": family_id, "type": "string", "enum": values });
|
||||
schemas_to_register.push((family_id, family_schema, SchemaType::Family));
|
||||
}
|
||||
|
||||
// Phase 3: Puncs
|
||||
if let Some(puncs_array) = puncs_value.as_array() {
|
||||
for punc_row in puncs_array {
|
||||
if let Some(punc_obj) = punc_row.as_object() {
|
||||
if let Some(punc_name) = punc_obj.get("name").and_then(|v| v.as_str()) {
|
||||
let is_public = punc_obj.get("public").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
let punc_type = if is_public { SchemaType::PublicPunc } else { SchemaType::PrivatePunc };
|
||||
if let Some(schemas_raw) = punc_obj.get("schemas") {
|
||||
if let Some(schemas_array) = schemas_raw.as_array() {
|
||||
for schema_def in schemas_array {
|
||||
if let Some(schema_id) = schema_def.get("$id").and_then(|v| v.as_str()) {
|
||||
let req_id = format!("{}.request", punc_name);
|
||||
let resp_id = format!("{}.response", punc_name);
|
||||
let st = if schema_id == req_id || schema_id == resp_id { punc_type } else { SchemaType::Type };
|
||||
schemas_to_register.push((schema_id.to_string(), schema_def.clone(), st));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut all_errors = Vec::new();
|
||||
for (id, value, st) in schemas_to_register {
|
||||
// Meta-validation: Check 'type' enum if present
|
||||
if let Some(type_val) = value.get("type") {
|
||||
let types = match type_val {
|
||||
Value::String(s) => vec![s.as_str()],
|
||||
Value::Array(a) => a.iter().filter_map(|v| v.as_str()).collect(),
|
||||
_ => vec![],
|
||||
};
|
||||
let valid_primitives = ["string", "number", "integer", "boolean", "array", "object", "null"];
|
||||
for t in types {
|
||||
if !valid_primitives.contains(&t) {
|
||||
all_errors.push(json!({ "code": "ENUM_VIOLATED", "message": format!("Invalid type: {}", t) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone value for insertion since it might be consumed/moved if we were doing other things
|
||||
let value_for_registry = value.clone();
|
||||
|
||||
// Validation: just ensure it is an object or boolean
|
||||
if value.is_object() || value.is_boolean() {
|
||||
REGISTRY.insert(id.clone(), value_for_registry);
|
||||
meta.insert(id, CachedSchema { t: st });
|
||||
} else {
|
||||
all_errors.push(json!({ "code": "INVALID_SCHEMA_TYPE", "message": format!("Schema {} must be an object or boolean", id) }));
|
||||
}
|
||||
}
|
||||
|
||||
if !all_errors.is_empty() {
|
||||
return JsonB(json!({ "errors": all_errors }));
|
||||
}
|
||||
|
||||
JsonB(json!({ "response": "success" }))
|
||||
}
|
||||
|
||||
#[pg_extern(strict, parallel_safe)]
|
||||
fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||
let schema = match REGISTRY.get(schema_id) {
|
||||
Some(s) => s,
|
||||
None => return JsonB(json!({
|
||||
"errors": [{
|
||||
"code": "SCHEMA_NOT_FOUND",
|
||||
"message": format!("Schema '{}' not found", schema_id),
|
||||
"details": { "schema": schema_id }
|
||||
}]
|
||||
})),
|
||||
};
|
||||
|
||||
let meta = SCHEMA_META.read().unwrap();
|
||||
let st = meta.get(schema_id).map(|m| m.t).unwrap_or(SchemaType::Type);
|
||||
|
||||
let be_strict = match st {
|
||||
SchemaType::PublicPunc => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let options = ValidationOptions {
|
||||
be_strict,
|
||||
};
|
||||
|
||||
let mut validator = Validator::new(options, schema_id);
|
||||
match validator.validate(&schema, &instance.0) {
|
||||
Ok(_) => JsonB(json!({ "response": "success" })),
|
||||
Err(errors) => {
|
||||
let drop_errors: Vec<Value> = errors.into_iter().map(|e| json!({
|
||||
"code": e.code,
|
||||
"message": e.message,
|
||||
"details": {
|
||||
"path": e.path,
|
||||
"context": e.context,
|
||||
"cause": e.cause,
|
||||
"schema": e.schema_id
|
||||
}
|
||||
})).collect();
|
||||
|
||||
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/debug_jspg_errors.log") {
|
||||
use std::io::Write;
|
||||
let _ = writeln!(f, "VALIDATION FAILED for {}: {:?}", schema_id, drop_errors);
|
||||
}
|
||||
|
||||
JsonB(json!({ "errors": drop_errors }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[pg_extern(strict, parallel_safe)]
|
||||
fn json_schema_cached(schema_id: &str) -> bool {
|
||||
REGISTRY.get(schema_id).is_some()
|
||||
}
|
||||
|
||||
#[pg_extern(strict)]
|
||||
fn clear_json_schemas() -> JsonB {
|
||||
REGISTRY.reset();
|
||||
let mut meta = SCHEMA_META.write().unwrap();
|
||||
meta.clear();
|
||||
JsonB(json!({ "response": "success" }))
|
||||
}
|
||||
|
||||
#[pg_extern(strict, parallel_safe)]
|
||||
fn show_json_schemas() -> JsonB {
|
||||
let meta = SCHEMA_META.read().unwrap();
|
||||
let ids: Vec<String> = meta.keys().cloned().collect();
|
||||
JsonB(json!({ "response": ids }))
|
||||
}
|
||||
|
||||
/// This module is required by `cargo pgrx test` invocations.
|
||||
/// It must be visible at the root of your extension crate.
|
||||
#[cfg(test)]
|
||||
pub mod pg_test {
|
||||
pub fn setup(_options: Vec<&str>) {
|
||||
// perform one-off initialization when the pg_test framework starts
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn postgresql_conf_options() -> Vec<&'static str> {
|
||||
// return any postgresql.conf settings that are required for your tests
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "pg_test"))]
|
||||
#[pg_schema]
|
||||
mod tests {
|
||||
use pgrx::pg_test;
|
||||
include!("suite.rs");
|
||||
}
|
||||
Reference in New Issue
Block a user